From e245f0f1c34627bd99e81dcda4d7f147fe9d84cb Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 12:52:39 -0800 Subject: [PATCH 01/12] mcount: Factor out mcount_trigger_init() This is to properly initialzie triggers and filters. Now it returns a new trigger info which can be to the global variable. Later we will call it when dlopen() needs to update trigger settings and replace it. Signed-off-by: Namhyung Kim --- libmcount/internal.h | 6 ++++ libmcount/mcount.c | 68 +++++++++++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/libmcount/internal.h b/libmcount/internal.h index 899d36e22..c26238f1c 100644 --- a/libmcount/internal.h +++ b/libmcount/internal.h @@ -236,6 +236,11 @@ static inline void mcount_watch_setup(struct mcount_thread_data *mtdp) static inline void mcount_watch_release(struct mcount_thread_data *mtdp) { } +static inline struct uftrace_triggers_info * +mcount_trigger_init(struct uftrace_filter_setting *filter_setting) +{ + return NULL; +} #endif /* DISABLE_MCOUNT_FILTER */ static inline uint64_t mcount_gettime(void) @@ -434,6 +439,7 @@ extern void save_argument(struct mcount_thread_data *mtdp, struct mcount_ret_sta void save_retval(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval); void save_trigger_read(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, enum trigger_read_type type, bool diff); +struct uftrace_triggers_info *mcount_trigger_init(struct uftrace_filter_setting *filter_setting); #endif /* DISABLE_MCOUNT_FILTER */ bool check_mem_region(struct mcount_arg_context *ctx, unsigned long addr); diff --git a/libmcount/mcount.c b/libmcount/mcount.c index ba29a0baa..ca95bb118 100644 --- a/libmcount/mcount.c +++ b/libmcount/mcount.c @@ -391,24 +391,19 @@ static void mcount_signal_finish(void) } } -static void mcount_filter_init(struct uftrace_filter_setting *filter_setting, bool force) +struct uftrace_triggers_info *mcount_trigger_init(struct uftrace_filter_setting *filter_setting) { + struct uftrace_triggers_info *triggers; char *filter_str = getenv("UFTRACE_FILTER"); char *trigger_str = getenv("UFTRACE_TRIGGER"); char *argument_str = getenv("UFTRACE_ARGUMENT"); char *retval_str = getenv("UFTRACE_RETVAL"); char *autoargs_str = getenv("UFTRACE_AUTO_ARGS"); + char *patch_str = getenv("UFTRACE_PATCH"); char *caller_str = getenv("UFTRACE_CALLER"); char *loc_str = getenv("UFTRACE_LOCATION"); bool needs_debug_info = false; - filter_setting->lp64 = host_is_lp64(); - filter_setting->arch = host_cpu_arch(); - - load_module_symtabs(&mcount_sym_info); - - mcount_signal_init(getenv("UFTRACE_SIGNAL"), filter_setting); - /* setup auto-args only if argument/return value is used */ if (argument_str || retval_str || autoargs_str || (trigger_str && (strstr(trigger_str, "arg") || strstr(trigger_str, "retval")))) { @@ -422,27 +417,29 @@ static void mcount_filter_init(struct uftrace_filter_setting *filter_setting, bo /* use debug info if available */ if (needs_debug_info) { prepare_debug_info(&mcount_sym_info, filter_setting->ptype, argument_str, - retval_str, !!autoargs_str, force); + retval_str, !!autoargs_str, !!patch_str); save_debug_info(&mcount_sym_info, mcount_sym_info.dirname); } - mcount_triggers = xmalloc(sizeof(*mcount_triggers)); - memset(mcount_triggers, 0, sizeof(*mcount_triggers)); - mcount_triggers->root = RB_ROOT; - uftrace_setup_filter(filter_str, &mcount_sym_info, mcount_triggers, filter_setting); - uftrace_setup_trigger(trigger_str, &mcount_sym_info, mcount_triggers, filter_setting); - uftrace_setup_argument(argument_str, &mcount_sym_info, mcount_triggers, filter_setting); - uftrace_setup_retval(retval_str, &mcount_sym_info, mcount_triggers, filter_setting); + if (!filter_str && !trigger_str && !argument_str && !retval_str && !autoargs_str && + !caller_str && !loc_str) + return NULL; - if (needs_debug_info) { - uftrace_setup_loc_filter(loc_str, &mcount_sym_info, mcount_triggers, - filter_setting); - } + triggers = xzalloc(sizeof(*triggers)); + triggers->root = RB_ROOT; - if (caller_str) { - uftrace_setup_caller_filter(caller_str, &mcount_sym_info, mcount_triggers, - filter_setting); - } + filter_setting->auto_args = false; + + uftrace_setup_filter(filter_str, &mcount_sym_info, triggers, filter_setting); + uftrace_setup_trigger(trigger_str, &mcount_sym_info, triggers, filter_setting); + uftrace_setup_argument(argument_str, &mcount_sym_info, triggers, filter_setting); + uftrace_setup_retval(retval_str, &mcount_sym_info, triggers, filter_setting); + + if (needs_debug_info) + uftrace_setup_loc_filter(loc_str, &mcount_sym_info, triggers, filter_setting); + + if (caller_str) + uftrace_setup_caller_filter(caller_str, &mcount_sym_info, triggers, filter_setting); if (autoargs_str) { char *autoarg = "."; @@ -453,8 +450,27 @@ static void mcount_filter_init(struct uftrace_filter_setting *filter_setting, bo filter_setting->auto_args = true; - uftrace_setup_argument(autoarg, &mcount_sym_info, mcount_triggers, filter_setting); - uftrace_setup_retval(autoret, &mcount_sym_info, mcount_triggers, filter_setting); + uftrace_setup_argument(autoarg, &mcount_sym_info, triggers, filter_setting); + uftrace_setup_retval(autoret, &mcount_sym_info, triggers, filter_setting); + } + + return triggers; +} + +static void mcount_filter_init(struct uftrace_filter_setting *filter_setting, bool force) +{ + filter_setting->lp64 = host_is_lp64(); + filter_setting->arch = host_cpu_arch(); + + load_module_symtabs(&mcount_sym_info); + + mcount_signal_init(getenv("UFTRACE_SIGNAL"), filter_setting); + + mcount_triggers = mcount_trigger_init(filter_setting); + if (mcount_triggers == NULL) { + /* make sure it has the root of triggers */ + mcount_triggers = xzalloc(sizeof(*mcount_triggers)); + mcount_triggers->root = RB_ROOT; } if (getenv("UFTRACE_DEPTH")) From 4c28352cc87d7b1b915c7e6e6bf4fb6b9b67a5a5 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:11:13 -0800 Subject: [PATCH 02/12] mcount: Pass map to mcount_dynamic_patch() It needs to add a new for dlopen library regardless of dynamic tracing. Let's add it from outside and pass the pointer to the function. Signed-off-by: Namhyung Kim --- libmcount/dynamic.c | 16 +--------------- libmcount/dynamic.h | 3 ++- libmcount/wrap.c | 27 ++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/libmcount/dynamic.c b/libmcount/dynamic.c index 19dc6c680..bf0b35b07 100644 --- a/libmcount/dynamic.c +++ b/libmcount/dynamic.c @@ -692,30 +692,16 @@ int mcount_dynamic_update(struct uftrace_sym_info *sinfo, char *patch_funcs, } void mcount_dynamic_dlopen(struct uftrace_sym_info *sinfo, struct dl_phdr_info *info, - char *pathname) + char *pathname, struct uftrace_mmap *map) { struct mcount_dynamic_info *mdi; - struct uftrace_mmap *map; if (!match_pattern_module(pathname)) return; mdi = create_mdi(info); - - map = xmalloc(sizeof(*map) + strlen(pathname) + 1); - map->start = info->dlpi_addr; - map->end = map->start + mdi->text_size; - map->len = strlen(pathname); - - strcpy(map->libname, pathname); - mcount_memcpy1(map->prot, "r-xp", 4); - read_build_id(pathname, map->build_id, sizeof(map->build_id)); - - map->next = sinfo->maps; - sinfo->maps = map; mdi->map = map; - map->mod = load_module_symtab(sinfo, map->libname, map->build_id); mcount_arch_find_module(mdi, &map->mod->symtab); if (mcount_setup_trampoline(mdi) < 0) { diff --git a/libmcount/dynamic.h b/libmcount/dynamic.h index 395e7dc82..215b13bb2 100644 --- a/libmcount/dynamic.h +++ b/libmcount/dynamic.h @@ -81,7 +81,8 @@ struct mcount_disasm_engine { int mcount_dynamic_update(struct uftrace_sym_info *sinfo, char *patch_funcs, enum uftrace_pattern_type ptype); -void mcount_dynamic_dlopen(struct uftrace_sym_info *sinfo, struct dl_phdr_info *info, char *path); +void mcount_dynamic_dlopen(struct uftrace_sym_info *sinfo, struct dl_phdr_info *info, char *path, + struct uftrace_mmap *map); void mcount_dynamic_finish(void); struct mcount_orig_insn { diff --git a/libmcount/wrap.c b/libmcount/wrap.c index 174be8847..3d8a22f1b 100644 --- a/libmcount/wrap.c +++ b/libmcount/wrap.c @@ -71,6 +71,7 @@ static void send_dlopen_msg(struct mcount_thread_data *mtdp, const char *sess_id static int dlopen_base_callback(struct dl_phdr_info *info, size_t size, void *arg) { struct dlopen_base_data *data = arg; + struct uftrace_mmap *map; char buf[PATH_MAX]; char *p; @@ -92,7 +93,31 @@ static int dlopen_base_callback(struct dl_phdr_info *info, size_t size, void *ar send_dlopen_msg(data->mtdp, mcount_session_name(), data->timestamp, info->dlpi_addr, info->dlpi_name); - mcount_dynamic_dlopen(&mcount_sym_info, info, p); + map = xzalloc(sizeof(*map) + strlen(p) + 1); + map->len = strlen(p); + strcpy(map->libname, p); + mcount_memcpy1(map->prot, "r-xp", 4); + + for (int i = 0; i < info->dlpi_phnum; i++) { + if (info->dlpi_phdr[i].p_type != PT_LOAD) + continue; + + if (map->start == 0) + map->start = info->dlpi_phdr[i].p_vaddr + info->dlpi_addr; + + if (info->dlpi_phdr[i].p_flags & PF_X) { + map->end = info->dlpi_phdr[i].p_vaddr + info->dlpi_addr; + map->end += info->dlpi_phdr[i].p_memsz; + break; + } + } + + read_build_id(p, map->build_id, sizeof(map->build_id)); + map->mod = load_module_symtab(&mcount_sym_info, p, map->build_id); + + map->next = mcount_sym_info.maps; + write_memory_barrier(); + mcount_sym_info.maps = map; return 0; } From 9317132c1bb96228b6457452dc61067ce82a76a8 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:35:56 -0800 Subject: [PATCH 03/12] filter: Do not load auto-args again Now libmcount might call it multiple times. Check if auto args are loaded already. Signed-off-by: Namhyung Kim --- utils/auto-args.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/auto-args.c b/utils/auto-args.c index c01b90c25..4d36efa3a 100644 --- a/utils/auto-args.c +++ b/utils/auto-args.c @@ -227,6 +227,9 @@ char *get_auto_retspec_str(void) void setup_auto_args(struct uftrace_filter_setting *setting) { + if (!RB_EMPTY_ROOT(&auto_enum)) + return; + parse_enum_string(auto_enum_list, &auto_enum); build_auto_args(auto_args_list, &auto_argspec, TRIGGER_FL_ARGUMENT, setting); build_auto_args(auto_retvals_list, &auto_retspec, TRIGGER_FL_RETVAL, setting); @@ -235,6 +238,9 @@ void setup_auto_args(struct uftrace_filter_setting *setting) void setup_auto_args_str(char *args, char *rets, char *enums, struct uftrace_filter_setting *setting) { + if (!RB_EMPTY_ROOT(&auto_enum)) + return; + parse_enum_string(enums, &auto_enum); build_auto_args(args, &auto_argspec, TRIGGER_FL_ARGUMENT, setting); build_auto_args(rets, &auto_retspec, TRIGGER_FL_RETVAL, setting); From 8da53b903046513508c45394686e49a66ad0867f Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:15:15 -0800 Subject: [PATCH 04/12] mcount: Build a new trigger info from dlopen() To support filters, triggers and arguments on dynamically loaded libraries, it needs to setup a whole new trigger info. Then use swap_triggers() to replace it atomically. Signed-off-by: Namhyung Kim --- libmcount/agent.c | 2 +- libmcount/internal.h | 2 ++ libmcount/mcount.c | 10 +++++++++- libmcount/wrap.c | 11 ++++++++++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libmcount/agent.c b/libmcount/agent.c index 3c62f4b56..9df27e8bf 100644 --- a/libmcount/agent.c +++ b/libmcount/agent.c @@ -32,7 +32,7 @@ static volatile bool agent_run = false; * @old - pointer to the tree to deprecate * @new - new version of the tree to use */ -static void swap_triggers(struct uftrace_triggers_info **old, struct uftrace_triggers_info *new) +void swap_triggers(struct uftrace_triggers_info **old, struct uftrace_triggers_info *new) { struct uftrace_triggers_info *tmp; tmp = __sync_val_compare_and_swap(old, *old, new); diff --git a/libmcount/internal.h b/libmcount/internal.h index c26238f1c..91ac8ebaf 100644 --- a/libmcount/internal.h +++ b/libmcount/internal.h @@ -478,4 +478,6 @@ bool mcount_is_main_executable(const char *filename, const char *exename); int agent_spawn(void); int agent_kill(void); +void swap_triggers(struct uftrace_triggers_info **old, struct uftrace_triggers_info *new); + #endif /* UFTRACE_MCOUNT_INTERNAL_H */ diff --git a/libmcount/mcount.c b/libmcount/mcount.c index ca95bb118..1498753cd 100644 --- a/libmcount/mcount.c +++ b/libmcount/mcount.c @@ -1312,8 +1312,16 @@ void mcount_exit_filter_record(struct mcount_thread_data *mtdp, struct mcount_re if (!mcount_enabled) return; - if (!(rstack->flags & MCOUNT_FL_RETVAL)) + if (rstack->flags & MCOUNT_FL_RETVAL) { + struct uftrace_trigger tr; + + /* update args as trigger might be updated due to dlopen() */ + uftrace_match_filter(rstack->child_ip, &mcount_triggers->root, &tr); + rstack->pargs = tr.pargs; + } + else { retval = NULL; + } if (rstack->flags & MCOUNT_FL_READ) { struct uftrace_trigger tr; diff --git a/libmcount/wrap.c b/libmcount/wrap.c index 3d8a22f1b..c7b7b32b8 100644 --- a/libmcount/wrap.c +++ b/libmcount/wrap.c @@ -21,6 +21,7 @@ extern struct uftrace_sym_info mcount_sym_info; struct dlopen_base_data { const char *filename; struct mcount_thread_data *mtdp; + struct uftrace_triggers_info *triggers; uint64_t timestamp; }; @@ -116,8 +117,12 @@ static int dlopen_base_callback(struct dl_phdr_info *info, size_t size, void *ar map->mod = load_module_symtab(&mcount_sym_info, p, map->build_id); map->next = mcount_sym_info.maps; - write_memory_barrier(); mcount_sym_info.maps = map; + + mcount_dynamic_dlopen(&mcount_sym_info, info, p, map); + + data->triggers = mcount_trigger_init(&mcount_filter_setting); + return 0; } @@ -526,7 +531,11 @@ __visible_default void *dlopen(const char *filename, int flags) data.mtdp = mtdp; dl_iterate_phdr(dlopen_base_callback, &data); + if (data.triggers) + swap_triggers(&mcount_triggers, data.triggers); + mcount_unguard_recursion(mtdp); + return ret; } From abc24d63905bd3de165f4b380d3358b8257fa97c Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:36:48 -0800 Subject: [PATCH 05/12] symbol: Add memory barrier when reading maps As dlopen() may update maps concurrently, let's add a memory barrier to make sure the map contents are filled before use. Signed-off-by: Namhyung Kim --- libmcount/wrap.c | 1 + utils/symbol.h | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libmcount/wrap.c b/libmcount/wrap.c index c7b7b32b8..b3160b88b 100644 --- a/libmcount/wrap.c +++ b/libmcount/wrap.c @@ -117,6 +117,7 @@ static int dlopen_base_callback(struct dl_phdr_info *info, size_t size, void *ar map->mod = load_module_symtab(&mcount_sym_info, p, map->build_id); map->next = mcount_sym_info.maps; + write_memory_barrier(); mcount_sym_info.maps = map; mcount_dynamic_dlopen(&mcount_sym_info, info, p, map); diff --git a/utils/symbol.h b/utils/symbol.h index c282184ca..eaa9810ff 100644 --- a/utils/symbol.h +++ b/utils/symbol.h @@ -128,7 +128,12 @@ struct uftrace_sym_info { }; #define for_each_map(sym_info, map) \ - for ((map) = (sym_info)->maps; (map) != NULL; (map) = (map)->next) + for (({ \ + (map) = (sym_info)->maps; \ + read_memory_barrier(); \ + }); \ + \ + (map) != NULL; (map) = (map)->next) /* addr should be from fstack or something other than rstack (rec) */ static inline bool is_kernel_address(struct uftrace_sym_info *sinfo, uint64_t addr) From 51d608a89b3b933800aa1864422d720efac62d19 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:00:30 -0800 Subject: [PATCH 06/12] mcount: Wrap dlclose() to mark unloaded libraries This is needed to support arguments and other triggers for dynamically loaded libraries. Instead of deleting the map, just mark it with NULL module. Signed-off-by: Namhyung Kim --- libmcount/wrap.c | 41 +++++++++++++++++++++++++++++++++++++++++ utils/symbol.h | 1 + 2 files changed, 42 insertions(+) diff --git a/libmcount/wrap.c b/libmcount/wrap.c index b3160b88b..ebdd7f458 100644 --- a/libmcount/wrap.c +++ b/libmcount/wrap.c @@ -23,6 +23,7 @@ struct dlopen_base_data { struct mcount_thread_data *mtdp; struct uftrace_triggers_info *triggers; uint64_t timestamp; + void *handle; }; static void send_dlopen_msg(struct mcount_thread_data *mtdp, const char *sess_id, @@ -98,6 +99,7 @@ static int dlopen_base_callback(struct dl_phdr_info *info, size_t size, void *ar map->len = strlen(p); strcpy(map->libname, p); mcount_memcpy1(map->prot, "r-xp", 4); + map->handle = data->handle; for (int i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type != PT_LOAD) @@ -287,6 +289,7 @@ static void *(*real_cxa_begin_catch)(void *exc); static void (*real_cxa_end_catch)(void); static void (*real_cxa_guard_abort)(void *guard_obj); static void *(*real_dlopen)(const char *filename, int flags); +static int (*real_dlclose)(void *handle); static __noreturn void (*real_pthread_exit)(void *retval); static void (*real_unwind_resume)(void *exc); static int (*real_posix_spawn)(pid_t *pid, const char *path, @@ -312,6 +315,7 @@ void mcount_hook_functions(void) real_cxa_end_catch = dlsym(RTLD_NEXT, "__cxa_end_catch"); real_cxa_guard_abort = dlsym(RTLD_NEXT, "__cxa_guard_abort"); real_dlopen = dlsym(RTLD_NEXT, "dlopen"); + real_dlclose = dlsym(RTLD_NEXT, "dlclose"); real_pthread_exit = dlsym(RTLD_NEXT, "pthread_exit"); real_unwind_resume = dlsym(RTLD_NEXT, "_Unwind_Resume"); real_posix_spawn = dlsym(RTLD_NEXT, "posix_spawn"); @@ -530,11 +534,48 @@ __visible_default void *dlopen(const char *filename, int flags) } data.mtdp = mtdp; + data.handle = ret; dl_iterate_phdr(dlopen_base_callback, &data); if (data.triggers) swap_triggers(&mcount_triggers, data.triggers); + mcount_unguard_recursion(mtdp); + return ret; +} + +__visible_default int dlclose(void *handle) +{ + struct mcount_thread_data *mtdp; + struct uftrace_mmap *map; + int ret; + + if (unlikely(real_dlopen == NULL)) + mcount_hook_functions(); + + ret = real_dlclose(handle); + + mtdp = get_thread_data(); + if (unlikely(check_thread_data(mtdp))) { + mtdp = mcount_prepare(); + if (mtdp == NULL) + return ret; + } + else { + if (!mcount_guard_recursion(mtdp)) + return ret; + } + + for_each_map(&mcount_sym_info, map) { + if (map->mod == NULL) + continue; + + if (map->handle == handle) { + map->mod = NULL; + break; + } + } + mcount_unguard_recursion(mtdp); return ret; diff --git a/utils/symbol.h b/utils/symbol.h index eaa9810ff..68e71f370 100644 --- a/utils/symbol.h +++ b/utils/symbol.h @@ -93,6 +93,7 @@ struct uftrace_mmap { char prot[4]; uint32_t len; char build_id[BUILD_ID_STR_SIZE]; + void *handle; /* for dlopen() */ char libname[]; }; From 6a31b4ca92e6f92e9454cf2347b8e531702e7beb Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:21:26 -0800 Subject: [PATCH 07/12] session: Load debug information for dlopen libraries Add load_module_debug_info() and call it from session_add_dlopen(). Signed-off-by: Namhyung Kim --- misc/symbols.c | 2 +- uftrace.h | 2 +- utils/data-file.c | 3 ++- utils/dwarf.c | 12 ++++++++++++ utils/dwarf.h | 3 +++ utils/session.c | 7 +++++-- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/misc/symbols.c b/misc/symbols.c index 9f72b2bcc..5e043afe9 100644 --- a/misc/symbols.c +++ b/misc/symbols.c @@ -162,7 +162,7 @@ static int read_sessions(struct uftrace_session_link *link, char *dirname) dlop.namelen = strlen(exename); s = get_session_from_sid(link, dlop.sid); - session_add_dlopen(s, dlop.task.time, dlop.base_addr, exename); + session_add_dlopen(s, dlop.task.time, dlop.base_addr, exename, false); } } diff --git a/uftrace.h b/uftrace.h index 90b5bc878..f30f587c7 100644 --- a/uftrace.h +++ b/uftrace.h @@ -486,7 +486,7 @@ void delete_session_map(struct uftrace_sym_info *sinfo); void update_session_map(const char *filename); struct uftrace_session *get_session_from_sid(struct uftrace_session_link *sess, char sid[]); void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsigned long base_addr, - const char *libname); + const char *libname, bool needs_srcline); struct uftrace_symbol *session_find_dlsym(struct uftrace_session *sess, uint64_t timestamp, unsigned long addr); void delete_sessions(struct uftrace_session_link *sess); diff --git a/utils/data-file.c b/utils/data-file.c index 3afc27947..a66a4ce12 100644 --- a/utils/data-file.c +++ b/utils/data-file.c @@ -199,7 +199,8 @@ int read_task_txt_file(struct uftrace_session_link *sess, char *dirname, char *s s = get_session_from_sid(sess, dlop.sid); ASSERT(s); - session_add_dlopen(s, dlop.task.time, dlop.base_addr, exename); + session_add_dlopen(s, dlop.task.time, dlop.base_addr, exename, + needs_srcline); } } ret = 0; diff --git a/utils/dwarf.c b/utils/dwarf.c index e0922671e..9dbfdd115 100644 --- a/utils/dwarf.c +++ b/utils/dwarf.c @@ -2221,6 +2221,18 @@ static int load_debug_file(struct uftrace_dbg_info *dinfo, struct uftrace_symtab return ret; } +void load_module_debug_info(struct uftrace_module *mod, const char *dirname, bool needs_srcline) +{ + struct uftrace_dbg_info *dinfo; + + dinfo = &mod->dinfo; + + if (!debug_info_has_location(dinfo) && !debug_info_has_argspec(dinfo)) { + load_debug_file(dinfo, &mod->symtab, dirname, mod->name, mod->build_id, + needs_srcline); + } +} + void load_debug_info(struct uftrace_sym_info *sinfo, bool needs_srcline) { struct uftrace_mmap *map; diff --git a/utils/dwarf.h b/utils/dwarf.h index 772190579..35146b364 100644 --- a/utils/dwarf.h +++ b/utils/dwarf.h @@ -9,6 +9,7 @@ #include "utils/rbtree.h" struct uftrace_sym_info; +struct uftrace_module; #ifdef HAVE_LIBDW #include @@ -74,5 +75,7 @@ struct uftrace_dbg_loc *find_file_line(struct uftrace_sym_info *sinfo, uint64_t extern void save_debug_info(struct uftrace_sym_info *sinfo, const char *dirname); extern void load_debug_info(struct uftrace_sym_info *sinfo, bool needs_srcline); extern void save_debug_file(FILE *fp, char code, char *str, unsigned long val); +extern void load_module_debug_info(struct uftrace_module *mod, const char *dirname, + bool needs_srcline); #endif /* UFTRACE_DWARF_H */ diff --git a/utils/session.c b/utils/session.c index abfc5b441..996a27c26 100644 --- a/utils/session.c +++ b/utils/session.c @@ -340,13 +340,14 @@ struct uftrace_session *get_session_from_sid(struct uftrace_session_link *sessio * @timestamp: timestamp at the dlopen call * @base_addr: load address of text segment of the library * @libname: name of the library + * @needs_srcline: whether debug info loading is needed * * This functions adds the info of a library which was loaded by dlopen. * Instead of creating a new session, it just adds the library information * to the @sess. */ void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsigned long base_addr, - const char *libname) + const char *libname, bool needs_srcline) { struct uftrace_dlopen_list *udl, *pos; char build_id[BUILD_ID_STR_SIZE]; @@ -357,6 +358,8 @@ void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsign read_build_id(libname, build_id, sizeof(build_id)); udl->mod = load_module_symtab(&sess->sym_info, libname, build_id); + load_module_debug_info(udl->mod, sess->sym_info.symdir, needs_srcline); + list_for_each_entry(pos, &sess->dlopen_libs, list) { if (pos->time > timestamp) break; @@ -1165,7 +1168,7 @@ TEST_CASE(task_symbol_dlopen) TEST_EQ(test_sessions.first->pid, 1); pr_dbg("add dlopen info message\n"); - session_add_dlopen(test_sessions.first, 200, 0x7003000, "libuftrace-test.so.0"); + session_add_dlopen(test_sessions.first, 200, 0x7003000, "libuftrace-test.so.0", false); remove("libuftrace-test.so.0.sym"); TEST_EQ(list_empty(&test_sessions.first->dlopen_libs), false); From 4cf7ad05625f2f682471f354d9cf09cbb2ede4a0 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:23:25 -0800 Subject: [PATCH 08/12] session: Setup arguments for dlopen libraries When it reconstructs arguments and return values, do the same for the dlopen libraries. Signed-off-by: Namhyung Kim --- uftrace.h | 3 +++ utils/fstack.c | 2 ++ utils/session.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/uftrace.h b/uftrace.h index f30f587c7..b74ec1022 100644 --- a/uftrace.h +++ b/uftrace.h @@ -383,6 +383,7 @@ struct uftrace_dlopen_list { uint64_t time; unsigned long base; struct uftrace_module *mod; + struct rb_root filters; }; struct uftrace_task { @@ -487,6 +488,8 @@ void update_session_map(const char *filename); struct uftrace_session *get_session_from_sid(struct uftrace_session_link *sess, char sid[]); void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsigned long base_addr, const char *libname, bool needs_srcline); +void session_setup_dlopen_argspec(struct uftrace_session *sess, + struct uftrace_filter_setting *setting, bool is_retval); struct uftrace_symbol *session_find_dlsym(struct uftrace_session *sess, uint64_t timestamp, unsigned long addr); void delete_sessions(struct uftrace_session_link *sess); diff --git a/utils/fstack.c b/utils/fstack.c index f76974a7a..97621dccf 100644 --- a/utils/fstack.c +++ b/utils/fstack.c @@ -448,6 +448,7 @@ static int build_arg_spec(struct uftrace_session *s, void *arg) s->filters = triggers.root; } + session_setup_dlopen_argspec(s, setting, false); return 0; } @@ -463,6 +464,7 @@ static int build_ret_spec(struct uftrace_session *s, void *arg) s->filters = triggers.root; } + session_setup_dlopen_argspec(s, setting, true); return 0; } diff --git a/utils/session.c b/utils/session.c index 996a27c26..27e47bcd8 100644 --- a/utils/session.c +++ b/utils/session.c @@ -360,6 +360,8 @@ void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsign udl->mod = load_module_symtab(&sess->sym_info, libname, build_id); load_module_debug_info(udl->mod, sess->sym_info.symdir, needs_srcline); + udl->filters = RB_ROOT; + list_for_each_entry(pos, &sess->dlopen_libs, list) { if (pos->time > timestamp) break; @@ -404,6 +406,7 @@ void delete_session(struct uftrace_session *sess) list_for_each_entry_safe(udl, tmp, &sess->dlopen_libs, list) { list_del(&udl->list); + uftrace_cleanup_filter(&udl->filters); free(udl); } @@ -786,6 +789,33 @@ struct uftrace_dbg_loc *task_find_loc_addr(struct uftrace_session_link *sessions return NULL; } +void session_setup_dlopen_argspec(struct uftrace_session *sess, + struct uftrace_filter_setting *setting, bool is_retval) +{ + struct uftrace_dlopen_list *udl; + struct uftrace_triggers_info triggers = { + .root = RB_ROOT, + }; + + list_for_each_entry(udl, &sess->dlopen_libs, list) { + struct uftrace_sym_info dl_info; + struct uftrace_mmap dl_map = { + .start = udl->base, + .mod = udl->mod, + }; + + dl_info = sess->sym_info; + dl_info.maps = &dl_map; + + triggers.root = udl->filters; + if (is_retval) + uftrace_setup_retval(setting->info_str, &dl_info, &triggers, setting); + else + uftrace_setup_argument(setting->info_str, &dl_info, &triggers, setting); + udl->filters = triggers.root; + } +} + #ifdef UNIT_TEST static struct uftrace_session_link test_sessions; From f77ef405f555d2ebf8daca150002562a9bec7829 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Fri, 29 Nov 2024 13:34:37 -0800 Subject: [PATCH 09/12] session: Introduce session_find_filter() It first searches the main filter tree and check dlopen libraries. Also add a new unit test to verify it can find a automatic arguments in dlopen libraries. Signed-off-by: Namhyung Kim --- uftrace.h | 4 ++ utils/fstack.c | 2 +- utils/session.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/uftrace.h b/uftrace.h index b74ec1022..43389f9a2 100644 --- a/uftrace.h +++ b/uftrace.h @@ -490,8 +490,12 @@ void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsign const char *libname, bool needs_srcline); void session_setup_dlopen_argspec(struct uftrace_session *sess, struct uftrace_filter_setting *setting, bool is_retval); +struct uftrace_dlopen_list *session_find_dlopen(struct uftrace_session *sess, uint64_t timestamp, + unsigned long addr); struct uftrace_symbol *session_find_dlsym(struct uftrace_session *sess, uint64_t timestamp, unsigned long addr); +struct uftrace_filter *session_find_filter(struct uftrace_session *sess, struct uftrace_record *rec, + struct uftrace_trigger *tr); void delete_sessions(struct uftrace_session_link *sess); struct uftrace_record; diff --git a/utils/fstack.c b/utils/fstack.c index 97621dccf..fe47fd2f0 100644 --- a/utils/fstack.c +++ b/utils/fstack.c @@ -1347,7 +1347,7 @@ int read_task_args(struct uftrace_task_reader *task, struct uftrace_record *rsta return -1; } - fl = uftrace_match_filter(rstack->addr, &sess->filters, &tr); + fl = session_find_filter(sess, rstack, &tr); if (fl == NULL) { pr_dbg("cannot find filter: %lx\n", rstack->addr); return -1; diff --git a/utils/session.c b/utils/session.c index 27e47bcd8..47c6b4a83 100644 --- a/utils/session.c +++ b/utils/session.c @@ -400,6 +400,27 @@ struct uftrace_symbol *session_find_dlsym(struct uftrace_session *sess, uint64_t return NULL; } +struct uftrace_dlopen_list *session_find_dlopen(struct uftrace_session *sess, uint64_t timestamp, + unsigned long addr) +{ + struct uftrace_dlopen_list *pos; + struct uftrace_symbol *sym; + + list_for_each_entry_reverse(pos, &sess->dlopen_libs, list) { + if (pos->time > timestamp) + continue; + + if (pos->mod == NULL) + continue; + + sym = find_sym(&pos->mod->symtab, addr - pos->base); + if (sym) + return pos; + } + + return NULL; +} + void delete_session(struct uftrace_session *sess) { struct uftrace_dlopen_list *udl, *tmp; @@ -816,6 +837,23 @@ void session_setup_dlopen_argspec(struct uftrace_session *sess, } } +struct uftrace_filter *session_find_filter(struct uftrace_session *sess, struct uftrace_record *rec, + struct uftrace_trigger *tr) +{ + struct uftrace_filter *ret; + struct uftrace_dlopen_list *udl; + + ret = uftrace_match_filter(rec->addr, &sess->filters, tr); + if (ret) + return ret; + + udl = session_find_dlopen(sess, rec->time, rec->addr); + if (udl == NULL) + return NULL; + + return uftrace_match_filter(rec->addr, &udl->filters, tr); +} + #ifdef UNIT_TEST static struct uftrace_session_link test_sessions; @@ -1250,4 +1288,91 @@ TEST_CASE(session_map_build_id) return TEST_OK; } +TEST_CASE(session_autoarg_dlopen) +{ + struct uftrace_session *sess; + struct uftrace_filter *filter; + struct uftrace_trigger tr = {}; + struct uftrace_record rec = { + .time = 234, + .addr = 0x7003456, + }; + struct uftrace_msg_sess msg = { + .task = { + .pid = 1, + .tid = 1, + .time = 100, + }, + .sid = "test", + .namelen = 8, /* = strlen("unittest") */ + }; + struct uftrace_filter_setting setting = { + .ptype = PATT_SIMPLE, + .info_str = "foo@auto-args", + }; + struct uftrace_dlopen_list *udl; + FILE *fp; + + fp = fopen("sid-test.map", "w"); + TEST_NE(fp, NULL); + fprintf(fp, "%s", session_map); + fclose(fp); + + pr_dbg("creating symbol for the dlopen library\n"); + fp = fopen("libuftrace-test.so.0.sym", "w"); + TEST_NE(fp, NULL); + fprintf(fp, "0100 P __tls_get_addr\n"); + fprintf(fp, "0200 P __dynsym_end\n"); + fprintf(fp, "0300 T _start\n"); + fprintf(fp, "0400 T foo\n"); + fprintf(fp, "0500 T __sym_end\n"); + fclose(fp); + + pr_dbg("creating debug info for the dlopen library\n"); + fp = fopen("libuftrace-test.so.0.dbg", "w"); + TEST_NE(fp, NULL); + fprintf(fp, "# path name: libuftrace-test.so.0\n"); + fprintf(fp, "# build-id: \n"); + fprintf(fp, "F: 400 foo\n"); + fprintf(fp, "L: 5 s-uftrace-test.c\n"); + fprintf(fp, "A: @arg1,arg2/f32\n"); + fprintf(fp, "R: @retval\n"); + fclose(fp); + + create_session(&test_sessions, &msg, ".", ".", "unittest", false, true, true); + remove("sid-test.map"); + + sess = test_sessions.first; + TEST_NE(sess, NULL); + TEST_EQ(sess->pid, 1); + + pr_dbg("add dlopen info message\n"); + session_add_dlopen(sess, 200, 0x7003000, "libuftrace-test.so.0", false); + remove("libuftrace-test.so.0.sym"); + remove("libuftrace-test.so.0.dbg"); + + pr_dbg("set filters for dlopen library\n"); + udl = session_find_dlopen(sess, rec.time, rec.addr); + TEST_NE(udl, NULL); + TEST_NE(udl->mod, NULL); + + session_setup_dlopen_argspec(sess, &setting, false); + session_setup_dlopen_argspec(sess, &setting, true); + +#ifdef HAVE_LIBDW + pr_dbg("try to find a filter for the dlopen address\n"); + filter = session_find_filter(sess, &rec, &tr); + + TEST_NE(filter, NULL); + TEST_EQ(filter->trigger.flags, TRIGGER_FL_ARGUMENT | TRIGGER_FL_RETVAL); + TEST_NE(filter->trigger.pargs, NULL); + TEST_STREQ(filter->name, "foo"); +#endif /* HAVE_LIBDW */ + + delete_sessions(&test_sessions); + TEST_EQ(RB_EMPTY_ROOT(&test_sessions.root), true); + + return TEST_OK; +} + #endif /* UNIT_TEST */ From 32d9d8a0a50f63560a09652bdb5cafbcd958e1a7 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 2 Dec 2024 00:37:33 -0800 Subject: [PATCH 10/12] replay: Add module name support for dlopen library It can use the basename of the dlopen library as module field. $ uftrace -f +module t-dlopen # DURATION TID MODULE NAME FUNCTION 0.527 us [1500312] t-dlopen | __monstartup(); 0.264 us [1500312] t-dlopen | __cxa_atexit(); [1500312] t-dlopen | main() { 118.775 us [1500312] t-dlopen | dlopen(); 0.466 us [1500312] t-dlopen | dlsym(); [1500312] libabc_test_lib. | lib_a() { [1500312] libabc_test_lib. | lib_b() { 0.542 us [1500312] libabc_test_lib. | lib_c(); 0.715 us [1500312] libabc_test_lib. | } /* lib_b */ 0.812 us [1500312] libabc_test_lib. | } /* lib_a */ 7.189 us [1500312] t-dlopen | dlclose(); 102.428 us [1500312] t-dlopen | dlopen(); 0.274 us [1500312] t-dlopen | dlsym(); [1500312] libfoo.so | foo() { 0.047 us [1500312] libfoo.so | AAA::bar(); 0.515 us [1500312] libfoo.so | } /* foo */ 4.627 us [1500312] t-dlopen | dlclose(); 236.688 us [1500312] t-dlopen | } /* main */ Signed-off-by: Namhyung Kim --- cmds/replay.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmds/replay.c b/cmds/replay.c index 263f7b838..abc418e6c 100644 --- a/cmds/replay.c +++ b/cmds/replay.c @@ -98,6 +98,7 @@ static void print_module(struct field_data *fd) uint64_t timestamp = task->timestamp; struct uftrace_session *s; struct uftrace_mmap *map; + struct uftrace_dlopen_list *udl; const char *modname = "[unknown]"; /* for EVENT or LOST record */ @@ -115,6 +116,11 @@ static void print_module(struct field_data *fd) modname = uftrace_basename(map->libname); else if (is_sched_event(fstack->addr)) modname = "[event]"; + else { + udl = session_find_dlopen(s, timestamp, fstack->addr); + if (udl) + modname = uftrace_basename(udl->mod->name); + } } pr_out("%*.*s", 16, 16, modname); From 9d638e973fb029288ecd153ec0a3c126c5105611 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 2 Dec 2024 00:39:09 -0800 Subject: [PATCH 11/12] replay: Add srcline support for dlopen library $ uftrace -T main@filter,arg1 --srcline t-dlopen # DURATION TID FUNCTION [SOURCE] [1500942] | main(1) { /* s-dlopen.c:5 */ [1500942] | dlopen() { 1.000 s [1500942] | /* linux:schedule */ 1.000 s [1500942] | } /* dlopen */ 9.405 us [1500942] | dlsym(); [1500942] | lib_a() { /* s-lib.c:10 */ [1500942] | lib_b() { /* s-lib.c:15 */ 3.404 us [1500942] | lib_c(); /* s-lib.c:20 */ 4.205 us [1500942] | } /* lib_b */ 4.761 us [1500942] | } /* lib_a */ 67.927 us [1500942] | dlclose(); [1500942] | dlopen() { 1.000 s [1500942] | /* linux:schedule */ 1.001 s [1500942] | } /* dlopen */ 6.718 us [1500942] | dlsym(); [1500942] | foo() { /* s-libfoo.cpp:12 */ 0.171 us [1500942] | AAA::bar(); 4.119 us [1500942] | } /* foo */ 87.556 us [1500942] | dlclose(); 2.002 s [1500942] | } /* main */ Signed-off-by: Namhyung Kim --- utils/session.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/utils/session.c b/utils/session.c index 47c6b4a83..98a37b683 100644 --- a/utils/session.c +++ b/utils/session.c @@ -768,8 +768,9 @@ struct uftrace_dbg_loc *task_find_loc_addr(struct uftrace_session_link *sessions uint64_t addr) { struct uftrace_session *sess; - struct uftrace_symbol *sym = NULL; + struct uftrace_symbol *sym; struct uftrace_mmap *map; + struct uftrace_module *mod; struct uftrace_dbg_info *dinfo; struct uftrace_dbg_loc *loc; ptrdiff_t sym_idx; @@ -794,14 +795,25 @@ struct uftrace_dbg_loc *task_find_loc_addr(struct uftrace_session_link *sessions if (sym->type == ST_LOCAL_FUNC || sym->type == ST_GLOBAL_FUNC) { map = find_map(&sess->sym_info, addr); - if (map == NULL) - return NULL; + if (map) { + mod = map->mod; + dinfo = &mod->dinfo; + } + else { + struct uftrace_dlopen_list *udl; + + udl = session_find_dlopen(sess, time, addr); + if (udl == NULL) + return NULL; + + mod = udl->mod; + dinfo = &mod->dinfo; + } - dinfo = &(map->mod->dinfo); if (dinfo == NULL || dinfo->nr_locs_used == 0) return NULL; - sym_idx = sym - map->mod->symtab.sym; + sym_idx = sym - mod->symtab.sym; loc = &dinfo->locs[sym_idx]; if (loc->file != NULL) return loc; From 6ef641d1aa739fa14bc669a854062370e7728fbd Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Sat, 30 Nov 2024 11:23:59 -0800 Subject: [PATCH 12/12] test: Add a test for auto-args in dlopen library The Parent and Child classes are in a library which is loaded by dlopen. $ uftrace -a t-dlopen2 [1358278] | main(1, 0xADDR) { 1.004 s [1358278] | dlopen("./libbaz.so", RTLD_LAZY) = 0xADDR; 11.847 us [1358278] | dlsym(0xADDR, "create") = &create; [1358278] | create() { [1358278] | Child::Child(0xADDR) { 7.422 us [1358278] | Parent::Parent(0xADDR); 8.859 us [1358278] | } /* Child::Child */ 10.168 us [1358278] | } = 0xADDR; /* create */ 99.308 us [1358278] | Child::func(0xADDR, 1) = 100; 36.109 us [1358278] | dlclose(0xADDR) = 0; 2.012 s [1358278] | } = 0; /* main */ Signed-off-by: Namhyung Kim --- tests/t291_arg_dlopen.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/t291_arg_dlopen.py diff --git a/tests/t291_arg_dlopen.py b/tests/t291_arg_dlopen.py new file mode 100644 index 000000000..1504d26f5 --- /dev/null +++ b/tests/t291_arg_dlopen.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import os +import re + +from runtest import TestBase + +class TestCase(TestBase): + def __init__(self): + TestBase.__init__(self, 'dlopen2', lang="C++", cflags='-g', result=""" +# DURATION TID FUNCTION + [1358278] | main(1, 0xADDR) { + 1.004 s [1358278] | dlopen("./libbaz.so", RTLD_LAZY) = 0xADDR; + 11.847 us [1358278] | dlsym(0xADDR, "create") = &create; + [1358278] | create() { + [1358278] | Child::Child(0xADDR) { + 7.422 us [1358278] | Parent::Parent(0xADDR); + 8.859 us [1358278] | } /* Child::Child */ + 10.168 us [1358278] | } = 0xADDR; /* create */ + 99.308 us [1358278] | Child::func(0xADDR, 1) = 100; + 36.109 us [1358278] | dlclose(0xADDR) = 0; + 2.012 s [1358278] | } = 0; /* main */ +""") + os.environ['LD_LIBRARY_PATH'] = "." + + def build(self, name, cflags='', ldflags=''): + # cygprof doesn't support arguments now + if cflags.find('-finstrument-functions') >= 0: + return TestBase.TEST_SKIP + # we want to test auto-args from DWARF + if not 'dwarf' in self.feature: + return TestBase.TEST_SKIP + + if TestBase.build_libfoo(self, 'bar', cflags, ldflags) != 0: + return TestBase.TEST_BUILD_FAIL + if TestBase.build_libfoo(self, 'baz', cflags, ldflags + ' -L. -lbar') != 0: + return TestBase.TEST_BUILD_FAIL + return TestBase.build_libmain(self, name, 's-dlopen2.cpp', ['libdl.so'], + cflags, ldflags) + + def setup(self): + self.option = '-F main -a' + + def sort(self, output): + result = [] + for ln in output.split('\n'): + # ignore blank lines and comments + if ln.strip() == '' or ln.startswith('#'): + continue + line = ln.split('|', 1)[-1] + func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) + result.append(func) + + return '\n'.join(result) + + def fixup(self, cflags, result): + # GCC seems to optimize out the empty Parent::Parent(). + return result.replace(""" + [1358278] | Child::Child(0xADDR) { + 7.422 us [1358278] | Parent::Parent(0xADDR); + 8.859 us [1358278] | } /* Child::Child */""", """ + [1358278] | Child::Child(0xADDR);""")