From patchwork Wed Nov 25 10:44:20 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 11931159 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.9 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id ACA42C56202 for ; Wed, 25 Nov 2020 10:44:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 52F01206E0 for ; Wed, 25 Nov 2020 10:44:38 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lTRrbyLz" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729183AbgKYKoi (ORCPT ); Wed, 25 Nov 2020 05:44:38 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33102 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727724AbgKYKoh (ORCPT ); Wed, 25 Nov 2020 05:44:37 -0500 Received: from mail-wr1-x444.google.com (mail-wr1-x444.google.com [IPv6:2a00:1450:4864:20::444]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 91AFBC0613D4 for ; Wed, 25 Nov 2020 02:44:27 -0800 (PST) Received: by mail-wr1-x444.google.com with SMTP id l1so1397537wrb.9 for ; Wed, 25 Nov 2020 02:44:27 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=w6S0AIadwdAfGVn+FZLJAzqqX+8Na1Q9LnPBtvhAtYg=; b=lTRrbyLzusog4lC07VLGB5nMHn0XyhmAKQS2M1a7yckqMyCcLpD6ypV+UMMn6sRONk YlcIZPdCoMaK572Jcjeblrn4mjI3wtK4RtoqPmW6Yw8a+mOfQ2Ullx2LluqcgLGaETKv 5mlmCBF3uE4AlEZ/ti6Yy39GhYn5hMOtZ0953U1Nhi5Udgb3glLxfLiVYrqkhiZgXfOZ DEWX3lZvzQunPnL0Jx8ld+3mWE8tvL5DeXGo++pgfnQSCY5jMhzs14Zvsl0fa0eMCSLu WcutB2fQCEddmMf//fea7YDM2JksK1pYVJjOmczZwWU2CTs+kUnz9husb+4gxVJoIkBM 7X6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=w6S0AIadwdAfGVn+FZLJAzqqX+8Na1Q9LnPBtvhAtYg=; b=BGlMN5xN/UMmyffljjDZDx1YlTyBInObie72N1lm+Aq8I4x2ofWi5n1wqJydhsXOxe WAJwPFHunljJ6q4O5TVDLY2wk107lKS6Rw9uKgmBgE024nYkeEw7fQyErfkD2vrnYLRm anQRSsjWm9s3PNwUtkCDj9dfr7R53iPD+PI/B3GqSs9xloFf1vpgxR7QN+7hTXGrI5wg mDkD/yMOfR1dpnlf8Wvc8o1nVbuGy6P/DsUNU1oYzV5eiWMiElXf+Cwjm8mf8JsnLj4H BvumLuN+2oxPURrvcZIbhkx0rNQucWoBjnuEO/g+7Te5K1k0Rq/iAP23cw3zzCiCQUxr /m5w== X-Gm-Message-State: AOAM531qMw02D53yrC5uSM0lS+W7wirn4rvjO7VOT7hz+fVQLs3iPhLY t/a05o2xqAZjaEozLxs/cwI= X-Google-Smtp-Source: ABdhPJyP38kbC2fqy3EvwNlbm6aQjnYAvy0EY8us3TdDmph4xea3eJKbzF/EhrgZF2xiaFZP5qua1A== X-Received: by 2002:a5d:4084:: with SMTP id o4mr3231013wrp.278.1606301066273; Wed, 25 Nov 2020 02:44:26 -0800 (PST) Received: from oberon.zico.biz ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id s4sm4026243wro.10.2020.11.25.02.44.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Nov 2020 02:44:25 -0800 (PST) From: "Tzvetomir Stoyanov (VMware)" To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org Subject: [PATCH v3 1/3] trace-cmd: Internal refactoring of pid address map logic Date: Wed, 25 Nov 2020 12:44:20 +0200 Message-Id: <20201125104422.823216-2-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20201125104422.823216-1-tz.stoyanov@gmail.com> References: <20201125104422.823216-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Make functions for collecting file to memory map of a PID non static, so they can be reused in the trace-cmd application context. Created new file trace-obj-debug.c with these functions. Signed-off-by: Tzvetomir Stoyanov (VMware) --- tracecmd/Makefile | 1 + tracecmd/include/trace-local.h | 19 +++-- tracecmd/trace-obj-debug.c | 138 +++++++++++++++++++++++++++++++++ tracecmd/trace-record.c | 107 +------------------------ 4 files changed, 152 insertions(+), 113 deletions(-) create mode 100644 tracecmd/trace-obj-debug.c diff --git a/tracecmd/Makefile b/tracecmd/Makefile index 5e59adf8..f9435558 100644 --- a/tracecmd/Makefile +++ b/tracecmd/Makefile @@ -31,6 +31,7 @@ TRACE_CMD_OBJS += trace-show.o TRACE_CMD_OBJS += trace-list.o TRACE_CMD_OBJS += trace-usage.o TRACE_CMD_OBJS += trace-dump.o +TRACE_CMD_OBJS += trace-obj-debug.o ifeq ($(VSOCK_DEFINED), 1) TRACE_CMD_OBJS += trace-tsync.o endif diff --git a/tracecmd/include/trace-local.h b/tracecmd/include/trace-local.h index 28d1b4e0..6b469e63 100644 --- a/tracecmd/include/trace-local.h +++ b/tracecmd/include/trace-local.h @@ -178,14 +178,6 @@ struct func_list { const char *mod; }; -struct pid_addr_maps { - struct pid_addr_maps *next; - struct tracecmd_proc_addr_map *lib_maps; - unsigned int nr_lib_maps; - char *proc_name; - int pid; -}; - struct opt_list { struct opt_list *next; const char *option; @@ -314,4 +306,15 @@ void *malloc_or_die(unsigned int size); /* Can be overridden */ void __noreturn __die(const char *fmt, ...); void __noreturn _vdie(const char *fmt, va_list ap); +/* --- Debug symbols--- */ +struct pid_addr_maps { + struct pid_addr_maps *next; + struct tracecmd_proc_addr_map *lib_maps; + unsigned int nr_lib_maps; + char *proc_name; + int pid; +}; +int trace_debug_get_filemap(struct pid_addr_maps **file_maps, int pid); +void trace_debug_free_filemap(struct pid_addr_maps *maps); + #endif /* __TRACE_LOCAL_H */ diff --git a/tracecmd/trace-obj-debug.c b/tracecmd/trace-obj-debug.c new file mode 100644 index 00000000..9aa9baae --- /dev/null +++ b/tracecmd/trace-obj-debug.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2020, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include + +#include "trace-local.h" + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) +int trace_debug_get_filemap(struct pid_addr_maps **pid_maps, int pid) +{ + struct pid_addr_maps *maps = *pid_maps; + struct tracecmd_proc_addr_map *map; + unsigned long long begin, end; + struct pid_addr_maps *m; + char mapname[PATH_MAX+1]; + char fname[PATH_MAX+1]; + char buf[PATH_MAX+100]; + FILE *f; + int ret; + int res; + int i; + + sprintf(fname, "/proc/%d/exe", pid); + ret = readlink(fname, mapname, PATH_MAX); + if (ret >= PATH_MAX || ret < 0) + return -ENOENT; + mapname[ret] = 0; + + sprintf(fname, "/proc/%d/maps", pid); + f = fopen(fname, "r"); + if (!f) + return -ENOENT; + + while (maps) { + if (pid == maps->pid) + break; + maps = maps->next; + } + + ret = -ENOMEM; + if (!maps) { + maps = calloc(1, sizeof(*maps)); + if (!maps) + goto out_fail; + maps->pid = pid; + maps->next = *pid_maps; + *pid_maps = maps; + } else { + for (i = 0; i < maps->nr_lib_maps; i++) + free(maps->lib_maps[i].lib_name); + free(maps->lib_maps); + maps->lib_maps = NULL; + maps->nr_lib_maps = 0; + free(maps->proc_name); + } + + maps->proc_name = strdup(mapname); + if (!maps->proc_name) + goto out; + + while (fgets(buf, sizeof(buf), f)) { + mapname[0] = '\0'; + res = sscanf(buf, "%llx-%llx %*s %*x %*s %*d %"STRINGIFY(PATH_MAX)"s", + &begin, &end, mapname); + if (res == 3 && mapname[0] != '\0') { + map = realloc(maps->lib_maps, + (maps->nr_lib_maps + 1) * sizeof(*map)); + if (!map) + goto out_fail; + map[maps->nr_lib_maps].end = end; + map[maps->nr_lib_maps].start = begin; + map[maps->nr_lib_maps].lib_name = strdup(mapname); + if (!map[maps->nr_lib_maps].lib_name) + goto out_fail; + maps->lib_maps = map; + maps->nr_lib_maps++; + } + } +out: + fclose(f); + return 0; + +out_fail: + fclose(f); + if (maps) { + for (i = 0; i < maps->nr_lib_maps; i++) + free(maps->lib_maps[i].lib_name); + if (*pid_maps != maps) { + m = *pid_maps; + while (m) { + if (m->next == maps) { + m->next = maps->next; + break; + } + m = m->next; + } + } else + *pid_maps = maps->next; + free(maps->lib_maps); + maps->lib_maps = NULL; + maps->nr_lib_maps = 0; + free(maps->proc_name); + maps->proc_name = NULL; + free(maps); + } + return ret; +} + +static void procmap_free(struct pid_addr_maps *maps) +{ + int i; + + if (!maps) + return; + if (maps->lib_maps) { + for (i = 0; i < maps->nr_lib_maps; i++) + free(maps->lib_maps[i].lib_name); + free(maps->lib_maps); + } + free(maps->proc_name); + free(maps); +} + +void trace_debug_free_filemap(struct pid_addr_maps *maps) +{ + struct pid_addr_maps *del; + + while (maps) { + del = maps; + maps = maps->next; + procmap_free(del); + } +} diff --git a/tracecmd/trace-record.c b/tracecmd/trace-record.c index 908adb93..ea424339 100644 --- a/tracecmd/trace-record.c +++ b/tracecmd/trace-record.c @@ -1090,109 +1090,6 @@ static char *make_pid_filter(struct buffer_instance *instance, return filter; } -#define _STRINGIFY(x) #x -#define STRINGIFY(x) _STRINGIFY(x) - -static int get_pid_addr_maps(struct buffer_instance *instance, int pid) -{ - struct pid_addr_maps *maps = instance->pid_maps; - struct tracecmd_proc_addr_map *map; - unsigned long long begin, end; - struct pid_addr_maps *m; - char mapname[PATH_MAX+1]; - char fname[PATH_MAX+1]; - char buf[PATH_MAX+100]; - FILE *f; - int ret; - int res; - int i; - - sprintf(fname, "/proc/%d/exe", pid); - ret = readlink(fname, mapname, PATH_MAX); - if (ret >= PATH_MAX || ret < 0) - return -ENOENT; - mapname[ret] = 0; - - sprintf(fname, "/proc/%d/maps", pid); - f = fopen(fname, "r"); - if (!f) - return -ENOENT; - - while (maps) { - if (pid == maps->pid) - break; - maps = maps->next; - } - - ret = -ENOMEM; - if (!maps) { - maps = calloc(1, sizeof(*maps)); - if (!maps) - goto out_fail; - maps->pid = pid; - maps->next = instance->pid_maps; - instance->pid_maps = maps; - } else { - for (i = 0; i < maps->nr_lib_maps; i++) - free(maps->lib_maps[i].lib_name); - free(maps->lib_maps); - maps->lib_maps = NULL; - maps->nr_lib_maps = 0; - free(maps->proc_name); - } - - maps->proc_name = strdup(mapname); - if (!maps->proc_name) - goto out; - - while (fgets(buf, sizeof(buf), f)) { - mapname[0] = '\0'; - res = sscanf(buf, "%llx-%llx %*s %*x %*s %*d %"STRINGIFY(PATH_MAX)"s", - &begin, &end, mapname); - if (res == 3 && mapname[0] != '\0') { - map = realloc(maps->lib_maps, - (maps->nr_lib_maps + 1) * sizeof(*map)); - if (!map) - goto out_fail; - map[maps->nr_lib_maps].end = end; - map[maps->nr_lib_maps].start = begin; - map[maps->nr_lib_maps].lib_name = strdup(mapname); - if (!map[maps->nr_lib_maps].lib_name) - goto out_fail; - maps->lib_maps = map; - maps->nr_lib_maps++; - } - } -out: - fclose(f); - return 0; - -out_fail: - fclose(f); - if (maps) { - for (i = 0; i < maps->nr_lib_maps; i++) - free(maps->lib_maps[i].lib_name); - if (instance->pid_maps != maps) { - m = instance->pid_maps; - while (m) { - if (m->next == maps) { - m->next = maps->next; - break; - } - m = m->next; - } - } else - instance->pid_maps = maps->next; - free(maps->lib_maps); - maps->lib_maps = NULL; - maps->nr_lib_maps = 0; - free(maps->proc_name); - maps->proc_name = NULL; - free(maps); - } - return ret; -} - static void get_filter_pid_maps(void) { struct buffer_instance *instance; @@ -1204,7 +1101,7 @@ static void get_filter_pid_maps(void) for (p = instance->filter_pids; p; p = p->next) { if (p->exclude) continue; - get_pid_addr_maps(instance, p->pid); + trace_debug_get_filemap(&instance->pid_maps, p->pid); } } } @@ -1534,7 +1431,7 @@ static void ptrace_wait(enum trace_type type) case PTRACE_EVENT_EXIT: instance = get_intance_fpid(pid); if (instance && instance->get_procmap) - get_pid_addr_maps(instance, pid); + trace_debug_get_filemap(&instance->pid_maps, pid); ptrace(PTRACE_GETEVENTMSG, pid, NULL, &cstatus); ptrace(PTRACE_DETACH, pid, NULL, NULL); break; From patchwork Wed Nov 25 10:44:21 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 11931155 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8E10AC56202 for ; Wed, 25 Nov 2020 10:44:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 22487206E0 for ; Wed, 25 Nov 2020 10:44:30 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="NTIxkMZU" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729170AbgKYKo3 (ORCPT ); Wed, 25 Nov 2020 05:44:29 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33108 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727724AbgKYKo3 (ORCPT ); Wed, 25 Nov 2020 05:44:29 -0500 Received: from mail-wr1-x441.google.com (mail-wr1-x441.google.com [IPv6:2a00:1450:4864:20::441]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id ED904C0613D6 for ; Wed, 25 Nov 2020 02:44:28 -0800 (PST) Received: by mail-wr1-x441.google.com with SMTP id l1so1397616wrb.9 for ; Wed, 25 Nov 2020 02:44:28 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=dZlhJUwlEfFKiZo906d5C7AkZYzxXUiGrgcVLTFGAHw=; b=NTIxkMZU6ZDHhz0xjXSw1bWf/eNMpXpa/2Eb40L3U8GYVjF6fg9KSC5Qh353RUGENq q7K95dR8DlqoAuR2m0jfZeqlAqnUJ8FPuGPSfbmK7sFhXJfhSSSfN3y1u/uQDEVGmRqq Bhwz5nYwYBGvjNLSkGh4EjtWYF0ytVl0yr/UN8W3hfpzkl7Trn04Xsonh1EbJGQeH61N sCQ3vqhtznjyY1MSRguCLdvJG0t/CKn6jH8H8MWTsddauJ39hkYImBywMtVdGzjYghjY ZKztFPqet7CmMq3S+3XLZnxNAAdYpkHWUPNDBzVdT2dowZN9JfOhmUkmt4mQwMmextOa Pd4g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=dZlhJUwlEfFKiZo906d5C7AkZYzxXUiGrgcVLTFGAHw=; b=jRvzDcRgdZ07wzee3m0Ddm8mZSMp+NAvzY/5gznSkwynrc3GA5Cpi1pW7Gt1ab9Mz3 kRIoaCiP6jXQooK10Oe9oZcPIxacFAzXnMJWpozm3h5GOWOTjZ6jWf6lRlhIhw/52dWf YAWbo81zwEfLQ7cYXZnvFs7uHwocAcke7DFCTnob+uTjPAGUV/dQjq8vpzc8ykCsekeu fV7fueWl7r75dd3VXS9BLrWtt+sTBu9QX0IFTQgbIbAvB5n43NSvV+3fa0pLsTxGd+MW ujSXmUDgc2mM9/zYrdBD8vAazQV51ZC3+KBPX8W2ST6WMcYP+pefje+V+7yZlPuhqsWG Im2Q== X-Gm-Message-State: AOAM533QjgJvU+w3z4iBYVDgtcHi+A5c8l2eaERSvd228OZFu4I7VGGG os3p7Ckq9vvpnRpV1srMtoRvRN2ohUT4NK6O X-Google-Smtp-Source: ABdhPJwc1l1cgk6yplcOMLc9Rl/tg6SP0sKo/d2DrTae3pnlpnqGbodhlO4v5fEIr2Ivl02MaQqplw== X-Received: by 2002:adf:ee51:: with SMTP id w17mr3296523wro.373.1606301067505; Wed, 25 Nov 2020 02:44:27 -0800 (PST) Received: from oberon.zico.biz ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id s4sm4026243wro.10.2020.11.25.02.44.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Nov 2020 02:44:26 -0800 (PST) From: "Tzvetomir Stoyanov (VMware)" To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org Subject: [PATCH v3 2/3] trace-cmd: New internal APIs for reading ELF header Date: Wed, 25 Nov 2020 12:44:21 +0200 Message-Id: <20201125104422.823216-3-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20201125104422.823216-1-tz.stoyanov@gmail.com> References: <20201125104422.823216-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Implemented new trace-cmd internal APIs for parsing ELF header and resolving VMA to function name and function name to VMA and file offset. The bfd library is used to read and parse the binary file. The new APIs are part of trace-cmd application and are visible only inside its context. Signed-off-by: Tzvetomir Stoyanov (VMware) --- Makefile | 10 + tracecmd/include/trace-local.h | 19 + tracecmd/trace-obj-debug.c | 770 +++++++++++++++++++++++++++++++++ 3 files changed, 799 insertions(+) diff --git a/Makefile b/Makefile index 8626b91b..a59b586d 100644 --- a/Makefile +++ b/Makefile @@ -246,6 +246,16 @@ endif CUNIT_INSTALLED := $(shell if (printf "$(pound)include \n void main(){CU_initialize_registry();}" | $(CC) -x c - -lcunit >/dev/null 2>&1) ; then echo 1; else echo 0 ; fi) export CUNIT_INSTALLED +BFD_INSTALLED := $(shell if (echo -e "$(pound)include \n void main(){bfd_init();}" | $(CC) -xc - -lbfd >/dev/null 2>&1) ; then echo 1; else echo 0 ; fi) + +export BFD_INSTALLED +ifeq ($(BFD_INSTALLED), 1) +CFLAGS += -DBFD_INSTALLED +LIBS += -lbfd +else +$(warning libbfd is not installed) +endif + export CFLAGS export INCLUDES diff --git a/tracecmd/include/trace-local.h b/tracecmd/include/trace-local.h index 6b469e63..933360a5 100644 --- a/tracecmd/include/trace-local.h +++ b/tracecmd/include/trace-local.h @@ -317,4 +317,23 @@ struct pid_addr_maps { int trace_debug_get_filemap(struct pid_addr_maps **file_maps, int pid); void trace_debug_free_filemap(struct pid_addr_maps *maps); +struct tracecmd_debug_symbols { + char *name; /* symbol's name */ + char *fname; /* symbol's file */ + unsigned long long vma_start; /* symbol's start VMA */ + unsigned long long vma_near; /* symbol's requested VMA */ + unsigned long long foffset; /* symbol's offset in the binary file*/ +}; +struct trace_debug_object; +struct trace_debug_object *trace_debug_obj_create_file(char *file, bool libs); +struct trace_debug_object *trace_debug_obj_create_pid(int pid); +void trace_debug_obj_destroy(struct trace_debug_object *debug); + +int trace_debug_resolve_symbols(struct trace_debug_object *obj); +int trace_debug_add_resolve_symbol(struct trace_debug_object *obj, + unsigned long long vma, char *name); + +void trace_debug_walk_resolved_symbols(struct trace_debug_object *obj, + int (*callback)(struct tracecmd_debug_symbols *, void *), + void *context); #endif /* __TRACE_LOCAL_H */ diff --git a/tracecmd/trace-obj-debug.c b/tracecmd/trace-obj-debug.c index 9aa9baae..79809a38 100644 --- a/tracecmd/trace-obj-debug.c +++ b/tracecmd/trace-obj-debug.c @@ -4,10 +4,780 @@ * */ #include +#include +#include +#include #include +#include #include +#include +#include #include "trace-local.h" +#include "trace-cmd.h" + +#define DEBUG_INTERNALS + +#ifdef BFD_INSTALLED + +struct trace_debug_handle { + bfd *bfd; + unsigned long long addr_offset; +}; + +struct trace_debug_vmem_range { + struct trace_debug_vmem_range *next; + unsigned long long start; + unsigned long long end; +}; + +enum match_type { + MATCH_EXACT = 0, + MATH_WILDCARD = 1, +}; +struct debug_symbols { + struct debug_symbols *next; + struct tracecmd_debug_symbols symbol; + enum match_type match; +}; + +struct trace_debug_file { + struct trace_debug_file *next; + char *file_name; + struct trace_debug_vmem_range *vmem; + struct trace_debug_handle *dbg; + int sym_count; + struct debug_symbols *sym; /* symbols to resolve, + * look in this file only + */ +}; + +struct trace_debug_object { + int pid; + char *fname; + struct pid_addr_maps *fmaps; + int sym_count; + struct debug_symbols *sym; /* symbols to resolve, + * look into all files + */ + struct trace_debug_file *files; +}; + +#define RESOLVE_NAME (1 << 0) +#define RESOLVE_VMA (1 << 1) +#define RESOLVE_FOFFSET (1 << 2) +struct trace_obj_job { + unsigned int flags; + unsigned long long addr_offset; + struct debug_symbols *symbols; +}; + +struct dwarf_bfd_context { + asymbol **table; + struct trace_obj_job *job; +}; + +static void process_bfd_section(bfd *abfd, asection *section, void *param) +{ + struct dwarf_bfd_context *context = (struct dwarf_bfd_context *)param; + unsigned int discriminator; + const char *functionname; + struct debug_symbols *s; + unsigned long long vma; + const char *filename; + unsigned int line; + bfd_boolean found; + + if (!(section->flags & SEC_CODE)) + return; + + for (s = context->job->symbols; s; s = s->next) { + if (s->symbol.vma_near) + vma = s->symbol.vma_near; + else if (s->symbol.vma_start) + vma = s->symbol.vma_start; + else + continue; + + if (abfd->flags & DYNAMIC) + vma -= context->job->addr_offset; + if (vma && section->vma <= vma && + (section->vma + section->size) > vma) { + if (!s->symbol.fname) + s->symbol.fname = strdup(abfd->filename); + if (context->job->flags & RESOLVE_FOFFSET) + s->symbol.foffset = section->filepos + (vma - section->vma); + if (!s->symbol.name && (context->job->flags & RESOLVE_NAME)) { + found = bfd_find_nearest_line_discriminator(abfd, section, context->table, + vma - section->vma, &filename, + &functionname, &line, &discriminator); + if (found) + s->symbol.name = strdup(functionname); + } + } + } +} + +static asymbol **get_sym_table(bfd *handle) +{ + bfd_boolean dyn = FALSE; + asymbol **symtable; + long count; + long size; + + if ((bfd_get_file_flags(handle) & HAS_SYMS) == 0) + return NULL; + size = bfd_get_symtab_upper_bound(handle); + if (size == 0) { + size = bfd_get_dynamic_symtab_upper_bound(handle); + dyn = TRUE; + } + if (size <= 0) + return NULL; + + symtable = (asymbol **) calloc(1, size); + if (!symtable) + return NULL; + if (dyn) + count = bfd_canonicalize_dynamic_symtab(handle, symtable); + else + count = bfd_canonicalize_symtab(handle, symtable); + if (count <= 0) { + free(symtable); + return NULL; + } +/* + * alloc = bfd_demangle(cur_bfd, name, demangle_flags); + */ + + return symtable; +} + +static bool sym_match(char *pattern, enum match_type match, const char *symbol) +{ + bool ret = false; + + switch (match) { + case MATCH_EXACT: + if (strlen(pattern) == strlen(symbol) && + !strcmp(pattern, symbol)) + ret = true; + break; + case MATH_WILDCARD: + if (!fnmatch(pattern, symbol, 0)) + ret = true; + break; + } + + return ret; +} + +static int lookup_bfd_sym(struct dwarf_bfd_context *context) +{ + struct debug_symbols *s, *last = NULL; + struct debug_symbols *new, *new_list = NULL; + unsigned long long vma; + asymbol **sp; + int res = 0; + + for (sp = context->table; *sp != NULL; sp++) { + if (!((*sp)->flags & BSF_FUNCTION)) + continue; + for (s = context->job->symbols; s; s = s->next) { + last = s; + if (!sym_match(s->symbol.name, s->match, (*sp)->name)) + continue; +#ifdef DEBUG_INTERNALS + printf("Matched %s, pattern %s\n\r", (*sp)->name, s->symbol.name); +#endif + vma = (*sp)->value + (*sp)->section->vma; + if ((*sp)->the_bfd->flags & DYNAMIC) + vma += context->job->addr_offset; + if (s->match == MATCH_EXACT) { /* exact match */ + s->symbol.vma_start = vma; + } else if (s->match == MATH_WILDCARD) { /* wildcard pattern match */ + new = calloc(1, sizeof(struct debug_symbols)); + if (!new) + break; + new->symbol.name = strdup((*sp)->name); + new->symbol.vma_start = vma; + new->symbol.vma_near = s->symbol.vma_near; + new->symbol.foffset = s->symbol.foffset; + if (s->symbol.fname) + new->symbol.fname = strdup(s->symbol.fname); + new->next = new_list; + new_list = new; + } + res++; + } + } + if (last && !last->next) + last->next = new_list; + + return res; +} + +static int process_bfd_object(bfd *abfd, struct trace_obj_job *job) +{ + struct dwarf_bfd_context context; + int ret = 0; + + memset(&context, 0, sizeof(context)); + context.job = job; + + if (bfd_check_format_matches(abfd, bfd_object, NULL) || + bfd_check_format_matches(abfd, bfd_core, NULL)) { + context.table = get_sym_table(abfd); + if (!context.table) + return -1; + if (job->flags & RESOLVE_VMA) + lookup_bfd_sym(&context); + if ((job->flags & RESOLVE_NAME) || (job->flags & RESOLVE_FOFFSET)) + bfd_map_over_sections(abfd, process_bfd_section, &context); + free(context.table); + } else { + ret = -1; + } + + return ret; +} + +static int read_all_bfd(bfd *abfd, struct trace_obj_job *job) +{ + bfd *last_arfile = NULL; + bfd *arfile = NULL; + int ret = 0; + + if (bfd_check_format(abfd, bfd_archive)) { + for (;;) { + bfd_set_error(bfd_error_no_error); + arfile = bfd_openr_next_archived_file(abfd, arfile); + if (!arfile) { + if (bfd_get_error() != bfd_error_no_more_archived_files) + break; + } + ret = read_all_bfd(arfile, job); + if (last_arfile) + bfd_close(last_arfile); + last_arfile = arfile; + } + if (last_arfile) + bfd_close(last_arfile); + } else + ret = process_bfd_object(abfd, job); + + return ret; +} + +/** + * resolve_symbol_vma - name -> (vma, file offset) resolving + * @obj - pointer to object, returned by trace_obj_debug_create() + * @symbols - link list with desired symbols, with given name + * + * Get VMA and file offset of the symbols with given name + * Return 0 on success, -1 on error + */ +static int resolve_symbol_vma(struct trace_debug_handle *obj, + struct debug_symbols *symbols) +{ + struct trace_obj_job job; + int ret; + + memset(&job, 0, sizeof(job)); + job.flags |= RESOLVE_VMA; + job.flags |= RESOLVE_FOFFSET; + job.symbols = symbols; + job.addr_offset = obj->addr_offset; + ret = read_all_bfd(obj->bfd, &job); + + return ret; +} + +/** + * resolve_symbol_name - vma -> name resolving + * @obj - pointer to object, returned by trace_obj_debug_create() + * @symbols - link list with desired symbols, with given VMA + * + * Get names of the symbols with given VMA, look for nearest symbol to that VMA + * Return 0 on success, -1 on error + */ +static int resolve_symbol_name(struct trace_debug_handle *obj, + struct debug_symbols *symbols) +{ + struct trace_obj_job job; + + if (!obj || !obj->bfd) + return -1; + memset(&job, 0, sizeof(job)); + job.flags |= RESOLVE_NAME; + job.addr_offset = obj->addr_offset; + job.symbols = symbols; + return read_all_bfd(obj->bfd, &job); +} + +/** + * debug_handle_destroy - Close file opened with trace_obj_debug_create() + * @obj - pointer to object, returned by trace_obj_debug_create() + * + * Close the file and free any allocated resources, related to file's debug + * information + */ +static void debug_handle_destroy(struct trace_debug_handle *obj) +{ + if (obj && obj->bfd) + bfd_close(obj->bfd); + free(obj); +} + +/** + * debug_handle_create - Open binary file for parsing ELF and DWARF information + * @name: Name of the binary ELF file. + * + * Return pointer to trace_obj_debug structure, that can be passed to other APIs + * for extracting debug information from the file. NULL in case of an error. + */ +static struct trace_debug_handle *debug_handle_create(char *file) +{ + struct trace_debug_handle *obj = NULL; + + obj = calloc(1, sizeof(*obj)); + if (!obj) + return NULL; + + bfd_init(); + obj->bfd = bfd_openr(file, NULL); + if (!obj->bfd) + goto error; + obj->bfd->flags |= BFD_DECOMPRESS; + + return obj; + +error: + debug_handle_destroy(obj); + return NULL; +} + +static void set_vma_offset(struct trace_debug_handle *obj, + unsigned long long addr_offset) +{ + if (obj) + obj->addr_offset = addr_offset; +} + +static char *get_full_name(int pid) +{ + char mapname[PATH_MAX+1]; + char fname[PATH_MAX+1]; + int ret; + + sprintf(fname, "/proc/%d/exe", pid); + ret = readlink(fname, mapname, PATH_MAX); + if (ret >= PATH_MAX || ret < 0) + return NULL; + mapname[ret] = 0; + + return strdup(mapname); +} + +static struct trace_debug_file *get_mapped_file(struct trace_debug_object *dbg, char *fname) +{ + struct trace_debug_file *file = dbg->files; + + while (file) { + if (!strcmp(fname, file->file_name)) + break; + file = file->next; + } + if (file) + return file; + + file = calloc(1, sizeof(*file)); + if (!file) + return NULL; + file->file_name = strdup(fname); + file->dbg = debug_handle_create(fname); + file->next = dbg->files; + dbg->files = file; + return file; +} + +void trace_debug_obj_destroy(struct trace_debug_object *dbg) +{ + struct trace_debug_vmem_range *mdel; + struct trace_debug_file *fdel; + struct debug_symbols *sdel; + + while (dbg->sym) { + sdel = dbg->sym; + dbg->sym = dbg->sym->next; + free(sdel->symbol.name); + free(sdel->symbol.fname); + free(sdel); + } + while (dbg->files) { + fdel = dbg->files; + dbg->files = dbg->files->next; + debug_handle_destroy(fdel->dbg); + while (fdel->sym) { + sdel = fdel->sym; + fdel->sym = fdel->sym->next; + free(sdel->symbol.name); + free(sdel->symbol.fname); + free(sdel); + } + while (fdel->vmem) { + mdel = fdel->vmem; + fdel->vmem = fdel->vmem->next; + free(mdel); + } + free(fdel); + } + + free(dbg->fname); + trace_debug_free_filemap(dbg->fmaps); + free(dbg); +} + +struct trace_debug_object *trace_debug_obj_create_pid(int pid) +{ + struct trace_debug_vmem_range *mem; + struct trace_debug_object *dbg; + struct trace_debug_file *file; + int i; + + dbg = calloc(1, sizeof(*dbg)); + if (!dbg) + return NULL; + + dbg->pid = pid; + dbg->fname = get_full_name(pid); + trace_debug_get_filemap(&dbg->fmaps, pid); + + for (i = 0; i < dbg->fmaps->nr_lib_maps; i++) { + file = get_mapped_file(dbg, dbg->fmaps->lib_maps[i].lib_name); + if (!file) + goto error; + if (file->vmem && file->vmem->end == dbg->fmaps->lib_maps[i].start) { + file->vmem->end = dbg->fmaps->lib_maps[i].end; + } else { + mem = calloc(1, sizeof(*mem)); + if (!mem) + goto error; + mem->start = dbg->fmaps->lib_maps[i].start; + mem->end = dbg->fmaps->lib_maps[i].end; + mem->next = file->vmem; + file->vmem = mem; + set_vma_offset(file->dbg, mem->start); + } + } + return dbg; + +error: + trace_debug_obj_destroy(dbg); + return NULL; +} + +static char *get_lib_full_path(char *libname) +{ + void *h = dlmopen(LM_ID_NEWLM, libname, RTLD_LAZY); + char dldir[PATH_MAX+1]; + char *fname = NULL; + int ret; + + if (!h) + return NULL; + ret = dlinfo(h, RTLD_DI_ORIGIN, dldir); + dlclose(h); + + if (!ret) { + ret = asprintf(&fname, "%s/%s", dldir, libname); + if (ret > 0) + return fname; + } + + free(fname); + return NULL; + + +} + +static int debug_obj_file_add_libs(struct trace_debug_object *dbg, + struct trace_debug_file *file) +{ + char line[PATH_MAX]; + char *libname; + char *trimmed; + char *fullname; + char *cmd = NULL; + FILE *fp = NULL; + int ret = -1; + + if (asprintf(&cmd, "LD_TRACE_LOADED_OBJECTS=1 %s", file->file_name) < 0) + return -1; + fp = popen(cmd, "r"); + if (!fp) + goto out; + + while (fgets(line, sizeof(line), fp) != NULL) { + libname = strchr(line, ' '); + trimmed = line; + if (libname) { + *libname = '\0'; + while (isspace(*trimmed)) + trimmed++; + if (*trimmed != '/') { + fullname = get_lib_full_path(trimmed); + if (fullname) { + get_mapped_file(dbg, fullname); + free(fullname); + } + } else { + get_mapped_file(dbg, trimmed); + } + } + } + +out: + if (fp) + pclose(fp); + free(cmd); + return ret; +} + +struct trace_debug_object *trace_debug_obj_create_file(char *fname, bool libs) +{ + struct trace_debug_object *dbg; + struct trace_debug_file *file; + + dbg = calloc(1, sizeof(*dbg)); + if (!dbg) + return NULL; + + dbg->fname = strdup(fname); + file = get_mapped_file(dbg, fname); + if (!file) + goto error; + if (libs) + debug_obj_file_add_libs(dbg, file); + +#ifdef DEBUG_INTERNALS + printf("Created debug object for %s:\n\r", dbg->fname); + file = dbg->files; + while (file) { + printf("\t%s\n\r", file->file_name); + file = file->next; + } +#endif + return dbg; + +error: + trace_debug_obj_destroy(dbg); + return NULL; +} + +static void set_unknown(struct debug_symbols *sym, char *file) +{ + while (sym) { + if (!sym->symbol.fname) + sym->symbol.fname = strdup(file); + sym = sym->next; + } +} + +int trace_debug_resolve_symbols(struct trace_debug_object *obj) +{ + struct trace_debug_file *file; + + for (file = obj->files; file; file = file->next) { + if (!file->dbg) { + set_unknown(file->sym, file->file_name); + continue; + } + /* near VMA -> name resolving */ + resolve_symbol_name(file->dbg, file->sym); + /* name -> exact VMA resolving */ + resolve_symbol_vma(file->dbg, file->sym); + resolve_symbol_vma(file->dbg, obj->sym); + } + + return 0; +} + +static int add_resolve_vma2name(struct trace_debug_object *obj, + unsigned long long vma) +{ + struct trace_debug_vmem_range *vmem; + struct debug_symbols *s = NULL; + struct trace_debug_file *file; + + file = obj->files; + while (file) { + if (!file->vmem) + break; + vmem = file->vmem; + while (vmem) { + if (vma >= vmem->start && vma <= vmem->end) + break; + vmem = vmem->next; + } + if (vmem) + break; + file = file->next; + } + if (file) { + s = file->sym; + while (s) { + if (s->symbol.vma_near == vma) + break; + s = s->next; + } + if (!s) { + s = calloc(1, sizeof(*s)); + if (!s) + return -1; + s->symbol.vma_near = vma; + s->next = file->sym; + file->sym = s; + file->sym_count++; + } + } + + if (s) + return 0; + return -1; +} + +static int add_resolve_name2vma(struct trace_debug_object *obj, char *name) +{ + struct debug_symbols *s = NULL; + + s = obj->sym; + while (s) { + if (s->symbol.name && !strcmp(name, s->symbol.name)) + break; + s = s->next; + } + if (!s) { + s = calloc(1, sizeof(*s)); + if (!s) + return -1; + s->symbol.name = strdup(name); + if (!s->symbol.name) + goto error; + if (strchr(name, '*') || strchr(name, '?')) + s->match = MATH_WILDCARD; + + s->next = obj->sym; + obj->sym = s; + obj->sym_count++; + } + + return 0; + +error: + if (s) { + free(s->symbol.name); + free(s); + } + return -1; +} + +int trace_debug_add_resolve_symbol(struct trace_debug_object *obj, + unsigned long long vma, char *name) +{ + int ret = -1; + + if (!obj) + return -1; + + if (!name && vma) /* vma -> name resolving */ + ret = add_resolve_vma2name(obj, vma); + else if (name) /* name -> vma resolving */ + ret = add_resolve_name2vma(obj, name); + return ret; +} + +static int walk_symbols(struct debug_symbols *sym, + int (*callback)(struct tracecmd_debug_symbols *, void *), + void *context) +{ + while (sym) { + if (callback(&sym->symbol, context)) + return -1; + sym = sym->next; + } + + return 0; +} + +void trace_debug_walk_resolved_symbols(struct trace_debug_object *obj, + int (*callback)(struct tracecmd_debug_symbols *, void *), + void *context) +{ + struct trace_debug_file *file; + + walk_symbols(obj->sym, callback, context); + file = obj->files; + while (file) { + walk_symbols(file->sym, callback, context); + file = file->next; + } +} + + +void trace_debug_free_symbols(struct tracecmd_debug_symbols *symbols, int count) +{ + int i; + + if (!symbols) + return; + + for (i = 0; i < count; i++) { + free(symbols[i].name); + free(symbols[i].fname); + } + free(symbols); + +} +#else +int trace_debug_resolve_symbols(struct trace_debug_object *obj) +{ + return -1; +} + +int trace_debug_add_resolve_symbol(struct trace_debug_object *obj, + unsigned long long vma, char *name) +{ + return -1; +} + +void trace_debug_walk_resolved_symbols(struct trace_debug_object *obj, + int (*callback)(struct tracecmd_debug_symbols *, void *), + void *context) +{ + +} + +void trace_debug_free_symbols(struct tracecmd_debug_symbols *symbols, int count) +{ + +} + +void trace_debug_obj_destroy(struct trace_debug_object *debug) +{ + +} + +struct trace_debug_object *trace_debug_obj_create_file(char *file, bool libs) +{ + return NULL; +} +struct trace_debug_object *trace_debug_obj_create_pid(int pid) +{ + return NULL; +} + +#endif #define _STRINGIFY(x) #x #define STRINGIFY(x) _STRINGIFY(x) From patchwork Wed Nov 25 10:44:22 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 11931157 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9B887C5519F for ; Wed, 25 Nov 2020 10:44:31 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2529E206E0 for ; Wed, 25 Nov 2020 10:44:31 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="f1CvM6//" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729180AbgKYKoa (ORCPT ); Wed, 25 Nov 2020 05:44:30 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33114 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727724AbgKYKoa (ORCPT ); Wed, 25 Nov 2020 05:44:30 -0500 Received: from mail-wr1-x436.google.com (mail-wr1-x436.google.com [IPv6:2a00:1450:4864:20::436]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 18D5DC0613D6 for ; Wed, 25 Nov 2020 02:44:30 -0800 (PST) Received: by mail-wr1-x436.google.com with SMTP id t4so1390100wrr.12 for ; Wed, 25 Nov 2020 02:44:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=PCY7JCgMO/J9mvY26jb/iuUqLrIOsCoFGR/P9r95hWE=; b=f1CvM6//qNaaQO6SeUpAzL6GcOAPgSzgwva9f/TOBUbf5nMajUa0DSyvRb7+xJXyz8 7oUUwnFTt+wuOJT9tFIlPdaN1n4n0GgLKoKk1m9qac2XFS3YMLgob4tU5rOM1+jWn9Zq ac46ocyiTWK/gV4MGJ6FW+tmHLz0uiSUw/ITfjKfkFJzVt4J01WHVzvjJimG1HTbKZcC SPbWqL6SYVq7XtZosjwXJo/5DAKOY02CTV6tjhY3QUvAJwhQ6M7w0G6R64s1pkdNs9D/ OQBRg/g8ijdagw/zNTflSI3USivtQlHE3hrWmg50pipPjmgORq7OEdIaUC7a7OWiBARQ 77qQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=PCY7JCgMO/J9mvY26jb/iuUqLrIOsCoFGR/P9r95hWE=; b=EI7OCGT/qVcJLBd8ZctBFQz0rK+qOk7NWnlcFNvqJfUbQ80qAq+6V6wCLvAF6Ytvcd FqF8iELJ3zEsb6a5JtPwqH1e3xWv8y877PIIR03738s1Ae8EQlQiZs6rKNOW4y8irjTm 9I1ID39Y1sntXCnuEHpWqRm3/RoZjgKATpMaOE5ztZkWO2b0Txsav792vQ5Je4QdisDu 7OddzQvB5TY8iQ0yQY4ohagkK/eKLbDl+FxLwgfxwb6It/AH1iAp6HQF0j/sM+v+2LAY xuSHXxs0OIeBDJpe9MZQwoii04653fBoOxVbhVi/dXBt0N0b3ZDBUYqb+OPK58DXanZs y2BA== X-Gm-Message-State: AOAM533I/+OPthzjPdAEZ+bZbWRSPYasv7JV6kf/hu37/WbElH47JVRQ oaOPF++lHEJXe+nPqGHCDJtoNK+Cb6gshlZm X-Google-Smtp-Source: ABdhPJyUtqP23GEKHHqQ+8X/rXMnpPaAf/PtGYWcEMwUbVNrSpBra2PsTsHOz30CSW85pQ0HpI7tpg== X-Received: by 2002:adf:82cc:: with SMTP id 70mr3399651wrc.74.1606301068659; Wed, 25 Nov 2020 02:44:28 -0800 (PST) Received: from oberon.zico.biz ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id s4sm4026243wro.10.2020.11.25.02.44.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Nov 2020 02:44:28 -0800 (PST) From: "Tzvetomir Stoyanov (VMware)" To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org Subject: [PATCH v3 3/3] trace-cmd: [POC] Add support for uprobes Date: Wed, 25 Nov 2020 12:44:22 +0200 Message-Id: <20201125104422.823216-4-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20201125104422.823216-1-tz.stoyanov@gmail.com> References: <20201125104422.823216-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Initial implementaton of trace-cmd support for ftrace uprobes. New trace-cmd record / set arguments are introduced: --uprobe file:function --uprobe-ret file:function The ftrace (return) probe is set on the given function from the file. Wildcards '*' and '?' are supported in the function name: --uprobe file:* will set uprobes on all functions from the given file. Set uprobes on related libraries: --libs --no-libs When set before --uprobe/--uprobe-ret argument, specifies whether to search or not for the given function in the libraries, that the given executable file depends on. By default, uprobes are set only for the matched functions in the given executable, no search in related libraries. The --libs/--no-libs argument affects all --uprobe/--uprobe-ret argumenst, specified after it. Signed-off-by: Tzvetomir Stoyanov (VMware) --- tracecmd/Makefile | 1 + tracecmd/include/trace-local.h | 18 +++ tracecmd/trace-record.c | 97 +++++++++++++- tracecmd/trace-uprobes.c | 223 +++++++++++++++++++++++++++++++++ tracecmd/trace-usage.c | 8 ++ 5 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 tracecmd/trace-uprobes.c diff --git a/tracecmd/Makefile b/tracecmd/Makefile index f9435558..37a5c42b 100644 --- a/tracecmd/Makefile +++ b/tracecmd/Makefile @@ -32,6 +32,7 @@ TRACE_CMD_OBJS += trace-list.o TRACE_CMD_OBJS += trace-usage.o TRACE_CMD_OBJS += trace-dump.o TRACE_CMD_OBJS += trace-obj-debug.o +TRACE_CMD_OBJS += trace-uprobes.o ifeq ($(VSOCK_DEFINED), 1) TRACE_CMD_OBJS += trace-tsync.o endif diff --git a/tracecmd/include/trace-local.h b/tracecmd/include/trace-local.h index 933360a5..d3b767cf 100644 --- a/tracecmd/include/trace-local.h +++ b/tracecmd/include/trace-local.h @@ -189,6 +189,21 @@ struct filter_pids { int exclude; }; +struct tracecmd_uprobe { + struct tracecmd_uprobe *next; + + char *file; + int pcount; + struct trace_debug_object *debug; + char **events; + int ecount; + int esize; +}; +int tracecmd_uprobe_new(struct tracecmd_uprobe **list, char *file, char *func, bool libs); +int tracecmd_uprobe_remove(struct tracecmd_uprobe *probes); +void tracecmd_uprobe_free(struct tracecmd_uprobe *probes); +int tracecmd_uprobe_create(struct tracecmd_uprobe *probes, bool uret); + struct buffer_instance { struct buffer_instance *next; char *name; @@ -209,6 +224,9 @@ struct buffer_instance { struct func_list *filter_funcs; struct func_list *notrace_funcs; + struct tracecmd_uprobe *uprobes; + struct tracecmd_uprobe *uretprobes; + struct opt_list *options; struct filter_pids *filter_pids; struct filter_pids *process_pids; diff --git a/tracecmd/trace-record.c b/tracecmd/trace-record.c index ea424339..b255a27e 100644 --- a/tracecmd/trace-record.c +++ b/tracecmd/trace-record.c @@ -5016,7 +5016,8 @@ static void check_function_plugin(void) static int __check_doing_something(struct buffer_instance *instance) { return is_guest(instance) || (instance->flags & BUFFER_FL_PROFILE) || - instance->plugin || instance->events || instance->get_procmap; + instance->plugin || instance->events || instance->get_procmap || + instance->uprobes || instance->uretprobes; } static void check_doing_something(void) @@ -5431,6 +5432,10 @@ void init_top_instance(void) } enum { + OPT_nolibs = 237, + OPT_libs = 238, + OPT_retuprobe = 239, + OPT_uprobe = 240, OPT_fork = 241, OPT_tsyncinterval = 242, OPT_user = 243, @@ -5620,6 +5625,25 @@ void trace_reset(int argc, char **argv) exit(0); } +static int +uprobe_param(struct buffer_instance *instance, char *param, bool pret, bool libs) +{ + char *str, *file, *func; + + if (!param) + return -1; + + file = strtok_r(param, ":", &str); + func = strtok_r(NULL, ":", &str); + + if (!file || !func) + return -1; + if (pret) + return tracecmd_uprobe_new(&instance->uretprobes, file, func, libs); + else + return tracecmd_uprobe_new(&instance->uprobes, file, func, libs); +} + static void init_common_record_context(struct common_record_context *ctx, enum trace_cmd curr_cmd) { @@ -5744,6 +5768,7 @@ static void parse_record_options(int argc, bool guest_sync_set = false; int do_children = 0; int fpids_count = 0; + bool libs = false; init_common_record_context(ctx, curr_cmd); @@ -5775,6 +5800,10 @@ static void parse_record_options(int argc, {"module", required_argument, NULL, OPT_module}, {"tsync-interval", required_argument, NULL, OPT_tsyncinterval}, {"fork", no_argument, NULL, OPT_fork}, + {"uprobe", required_argument, NULL, OPT_uprobe}, + {"uprobe-ret", required_argument, NULL, OPT_retuprobe}, + {"libs", no_argument, NULL, OPT_libs}, + {"no-libs", no_argument, NULL, OPT_nolibs}, {NULL, 0, NULL, 0} }; @@ -6174,6 +6203,20 @@ static void parse_record_options(int argc, die("--fork option used for 'start' command only"); fork_process = true; break; + case OPT_libs: + libs = true; + break; + case OPT_nolibs: + libs = false; + break; + case OPT_uprobe: + check_instance_die(ctx->instance, "--uprobe"); + uprobe_param(ctx->instance, optarg, false, libs); + break; + case OPT_retuprobe: + check_instance_die(ctx->instance, "--uprobe-ret"); + uprobe_param(ctx->instance, optarg, true, libs); + break; case OPT_quiet: case 'q': quiet = true; @@ -6285,6 +6328,17 @@ static void finalize_record_trace(struct common_record_context *ctx) set_plugin("nop"); + for_all_instances(instance) { + if (instance->uprobes) { + tracecmd_uprobe_remove(instance->uprobes); + tracecmd_uprobe_free(instance->uprobes); + } + if (instance->uretprobes) { + tracecmd_uprobe_remove(instance->uretprobes); + tracecmd_uprobe_free(instance->uretprobes); + } + } + tracecmd_remove_instances(); /* If tracing_on was enabled before we started, set it on now */ @@ -6316,6 +6370,37 @@ static bool has_local_instances(void) return false; } +static int uprobes_set(struct buffer_instance *instance, bool ret_probe) +{ + struct tracecmd_uprobe *list; + struct event_list *event; + int i, ret; + + if (ret_probe) + list = instance->uretprobes; + else + list = instance->uprobes; + + ret = tracecmd_uprobe_create(list, ret_probe); + if (ret < 0) + return ret; + for (i = 0; i < list->ecount; i++) { + event = calloc(1, sizeof(*event)); + if (!event) + break; + + event->event = strdup(list->events[i]); + add_event(instance, event); + + if (!recording_all_events()) + list_event(event->event); + } + + if (i < list->ecount) + return -1; + return 0; +} + /* * This function contains common code for the following commands: * record, start, stream, profile. @@ -6359,6 +6444,16 @@ static void record_trace(int argc, char **argv, /* Some instances may not be created yet */ if (instance->tracing_on_init_val < 0) instance->tracing_on_init_val = 1; + if (instance->uprobes) { + ctx->events = 1; + if (uprobes_set(instance, false) < 0) + die("Failed to set uprobes"); + } + if (instance->uretprobes) { + ctx->events = 1; + if (uprobes_set(instance, true) < 0) + die("Failed to set return uprobes"); + } } if (ctx->events) diff --git a/tracecmd/trace-uprobes.c b/tracecmd/trace-uprobes.c new file mode 100644 index 00000000..b0641100 --- /dev/null +++ b/tracecmd/trace-uprobes.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "trace-local.h" +#include "trace-cmd.h" + +#define UPROBE_FILE "uprobe_events" + +static char *uprobe_event_name(char *file, char *func, bool pret) +{ + char *event = NULL; + char *fname; + int i; + + fname = strrchr(file, '/'); + if (fname) + fname++; + if (!fname || *fname == '\0') + fname = file; + + asprintf(&event, "%c_%.*s_%.10s", pret ? 'r':'p', 10, fname, func); + if (event) { + for (i = 0; event[i]; i++) { + if (!isalpha(event[i])) + event[i] = '_'; + } + } + return event; +} + +/** + * tracecmd_uprobe_new - Add new uprobe in the uprobe list + * @list - list with uprobes, the new one will be added in this list + * @file - executable file that will be traced + * @func - function from @file + * @pret - indicate if this is return probe (true for return uprobe) + * @libs - indicate if to load the libraries that the given executable @file + * depends on, match the given @func and set uprobes on them too. + * + * Returns 0 on success or -1 on failure + */ +int tracecmd_uprobe_new(struct tracecmd_uprobe **list, char *file, char *func, bool libs) +{ + struct tracecmd_uprobe *probe = *list; + bool new_file = false; + + while (probe) { + if (!strcmp(probe->file, file)) + break; + probe = probe->next; + } + + if (!probe) { + probe = calloc(1, sizeof(*probe)); + if (!probe) + return -1; + + probe->file = strdup(file); + probe->next = *list; + probe->debug = trace_debug_obj_create_file(file, libs); + if (!probe->debug) + goto error; + new_file = true; + } + + if (trace_debug_add_resolve_symbol(probe->debug, 0, func) < 0) + goto error; + probe->pcount++; + if (new_file) + *list = probe; + + return 0; + +error: + if (new_file) { + if (probe && probe->debug) + trace_debug_obj_destroy(probe->debug); + free(probe); + } + + return -1; +} + +/** + * tracecmd_uprobe_free - Free uprobe list + * @list - list with uprobes, that wil be freed + * + */ +void tracecmd_uprobe_free(struct tracecmd_uprobe *probes) +{ + struct tracecmd_uprobe *del; + + while (probes) { + del = probes; + probes = probes->next; + trace_debug_obj_destroy(del->debug); + free(del->events); + free(del->file); + free(del); + } +} + +struct uprobe_walk { + struct tracecmd_uprobe *probe; + bool uret; + int fd; +}; + +static int uprobe_write(struct tracecmd_debug_symbols *symbol, void *data) +{ + struct uprobe_walk *context = (struct uprobe_walk *)data; + char **events; + char probe_str[BUFSIZ]; + + if (!symbol->foffset || !symbol->name) + return 0; + + if (context->probe->ecount == context->probe->esize) { + events = realloc(context->probe->events, + (context->probe->esize + 1) * sizeof(char *)); + if (events) { + context->probe->esize++; + context->probe->events = events; + } + } + if (!context->probe->events) + return -1; + + context->probe->events[context->probe->ecount] = uprobe_event_name(symbol->fname, symbol->name, context->uret); + if (!context->probe->events[context->probe->ecount]) + return -1; + snprintf(probe_str, BUFSIZ, + "%c:%s %s:0x%llx", context->uret?'r':'p', + context->probe->events[context->probe->ecount], + symbol->fname, symbol->foffset); + write(context->fd, probe_str, strlen(probe_str)); + context->probe->ecount++; + + return 0; +} + +/** + * tracecmd_uprobe_create - Create uprobes in ftrace + * @list - list with uprobes, that will be created + * + * Returns 0 on success or -1 on failure + */ +int tracecmd_uprobe_create(struct tracecmd_uprobe *probes, bool uret) +{ + char *ufile = tracefs_instance_get_file(NULL, UPROBE_FILE); + struct tracecmd_uprobe *probe = probes; + struct uprobe_walk context; + + if (!ufile) + return -1; + context.uret = uret; + context.fd = open(ufile, O_WRONLY | O_APPEND); + tracefs_put_tracing_file(ufile); + + while (probe) { + context.probe = probe; + if (trace_debug_resolve_symbols(probe->debug) == 0) { + if (!probe->events) { + probe->esize = probe->pcount; + probe->events = calloc(probe->esize, sizeof(char *)); + } + trace_debug_walk_resolved_symbols(probe->debug, + uprobe_write, + (void *)&context); + } + probe = probe->next; + } + close(context.fd); + + return 0; +} + +/** + * tracecmd_uprobe_remove - Remove uprobes from ftrace + * @list - list with uprobes, that will be removed + * + * Returns 0 on success or -1 on failure + */ +int tracecmd_uprobe_remove(struct tracecmd_uprobe *probes) +{ + char *ufile = tracefs_instance_get_file(NULL, UPROBE_FILE); + struct tracecmd_uprobe *probe = probes; + char probe_str[BUFSIZ]; + int fd; + int i; + + if (!ufile) + return -1; + fd = open(ufile, O_WRONLY | O_APPEND); + tracefs_put_tracing_file(ufile); + if (fd < 0) + return -1; + + while (probe) { + if (probe->events) { + for (i = 0; i < probe->ecount; i++) { + snprintf(probe_str, BUFSIZ, + "-:%s", probe->events[i]); + write(fd, probe_str, strlen(probe_str)); + } + } + + probe = probe->next; + } + + close(fd); + return 0; +} diff --git a/tracecmd/trace-usage.c b/tracecmd/trace-usage.c index 3f0b2d07..49628d6b 100644 --- a/tracecmd/trace-usage.c +++ b/tracecmd/trace-usage.c @@ -65,6 +65,10 @@ static struct usage_help usage_help[] = { " If a negative number is specified, timestamps synchronization is disabled" " If 0 is specified, no loop is performed - timestamps offset is calculated only twice," " at the beginnig and at the end of the trace\n" + " --uprobe set the specified [file:function] as uprobe\n" + " --uprobe-ret set the specified [file:function] as return uprobe\n" + " --libs affects all --uprobe/--uprobe-ret after it: set uprobes on the libraries, that the application depends on\n" + " --no-libs affects all --uprobe/--uprobe-ret after it: do not set uprobes on the libraries, that the application depends on\n" }, { "set", @@ -101,6 +105,10 @@ static struct usage_help usage_help[] = { " --cmdlines-size change kernel saved_cmdlines_size\n" " --user execute the specified [command ...] as given user\n" " --fork return immediately if a command is specified\n" + " --uprobe set the specified [file:function] as uprobe\n" + " --uprobe-ret set the specified [file:function] as return uprobe\n" + " --libs affects all --uprobe/--uprobe-ret after it: set uprobes on the libraries, that the application depends on\n" + " --no-libs affects all --uprobe/--uprobe-ret after it: do not set uprobes on the libraries, that the application depends on\n" }, { "start",