From patchwork Wed Apr 20 08:02:03 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 12819894 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 019C4C433F5 for ; Wed, 20 Apr 2022 08:02:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1376601AbiDTIFB (ORCPT ); Wed, 20 Apr 2022 04:05:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59630 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1376597AbiDTIE7 (ORCPT ); Wed, 20 Apr 2022 04:04:59 -0400 Received: from mail-ed1-x533.google.com (mail-ed1-x533.google.com [IPv6:2a00:1450:4864:20::533]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 77B073C4B3 for ; Wed, 20 Apr 2022 01:02:12 -0700 (PDT) Received: by mail-ed1-x533.google.com with SMTP id z12so1233318edl.2 for ; Wed, 20 Apr 2022 01:02:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=VpqNi1y5mpiSP7Rit4dlX5EsZAV9UDyOlmn+95jhGBE=; b=QeV037j8K6iiZ5l3zDBGx4GD/2bWVWsFK1kFCeOlZm+W6eTqvUwuWB5HDF6jTgxz0Z pNtRdKYM+4h1SD92Vg1QYb92QIcsli02RTWFJS4cITYOSQHExfKPaW44gcuW78mfig7o fuC38DwePooCtJkBLGRKwdgQnKJCx9rIaLgpnfi0ObjzsIEEvu1XuaeVatF8P9tsEq0c zfSMyRtQFMITW4Nj9sqwF7udmsxSb4segL7P014PGhUvmqYRqOImmdk2GmqDbjiJn3V8 u/anP5PETHnZUN0ampuHFFVmrN5R94YsK32KwM7o3j3TkEjSA7Th4czSttyqiNIO3aNp fYSg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=VpqNi1y5mpiSP7Rit4dlX5EsZAV9UDyOlmn+95jhGBE=; b=BykcMgpvqy8iyQA0w51WoJ2hcp01MEkEbZ4l6+81GaJ1ke9Z6qojXvWYdHCIYZ+UUN fiyc9Sf2m6ywBg2Q5hhJ1spCUUDxFiaqtXbalMLOkNGBVNE9RtgAzEKPnAz5tUQxTnxw 79dd1eZkIUrOwduuXClA8cjVBKg61qoV9gZo+V91lFrNLUiirucGosKHiZTKsLQ0aaTz 3w/cbXiiDrNOcX6OImOLy2eBkrjLC1eJzNfB4B8OViWMfHZd6Rd1qvMeUiFB+tV7tefU osG/yiQztOYUEP39HBdFPzaSZ31wmIvBHZUxgfcrRWO6V8+xO5fFjr0WoijFYHcuq2z9 nUZg== X-Gm-Message-State: AOAM530uDWJ5p0OFJ6YodGuOk7KQ9kA/3t2Nz1CpflNDBPG2phKu0D8y +/ZDMoucrxWjXPCkfT07cwj536u9oU9YiQ== X-Google-Smtp-Source: ABdhPJxuL42MJATRt5ui6IP+pUqIjY46kKJ9jJeUdbzwfyJwX3uB0tv0wZb6CjeNgQzD/l4iVviAkQ== X-Received: by 2002:aa7:d8d5:0:b0:41d:836d:858 with SMTP id k21-20020aa7d8d5000000b0041d836d0858mr21467960eds.214.1650441730512; Wed, 20 Apr 2022 01:02:10 -0700 (PDT) Received: from oberon.zico.biz.com ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id t12-20020a1709067c0c00b006e86db76851sm6393763ejo.193.2022.04.20.01.02.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Apr 2022 01:02:09 -0700 (PDT) From: "Tzvetomir Stoyanov (VMware)" To: y.karadz@gmail.com Cc: rostedt@goodmis.org, linux-trace-devel@vger.kernel.org Subject: [RFC PATCH v3 1/4] trace-cruncher: Logic for resolving address to function name Date: Wed, 20 Apr 2022 11:02:03 +0300 Message-Id: <20220420080206.252356-2-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220420080206.252356-1-tz.stoyanov@gmail.com> References: <20220420080206.252356-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Resolving virtual address to function name and vise versa is useful functionality for a trace application. Trace-cruncher can use it in two use cases: - Resolving VMA to function name, when collecting user application performance traces with perf. - Resolving function name to VMA, when using ftarce uprobe dynamic events. Proposed implementation uses the bfd library to parse the binary files and read the symbol table. This information is available only if the files are not stripped. Signed-off-by: Tzvetomir Stoyanov (VMware) --- src/trace-obj-debug.c | 988 ++++++++++++++++++++++++++++++++++++++++++ src/trace-obj-debug.h | 54 +++ 2 files changed, 1042 insertions(+) create mode 100644 src/trace-obj-debug.c create mode 100644 src/trace-obj-debug.h diff --git a/src/trace-obj-debug.c b/src/trace-obj-debug.c new file mode 100644 index 0000000..0e0e293 --- /dev/null +++ b/src/trace-obj-debug.c @@ -0,0 +1,988 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2020, VMware, Tzvetomir Stoyanov + * + */ + +#ifndef _GNU_SOURCE +/** Use GNU C Library. */ +#define _GNU_SOURCE +#endif // _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "trace-obj-debug.h" + +//#define DEBUG_INTERNALS + +/* Got from demangle.h */ +#define DMGL_AUTO (1 << 8) + +struct debug_bfd_handle { + bfd *bfd; + unsigned long long addr_offset; +}; + +enum match_type { + MATCH_EXACT = 0, + MATH_WILDCARD = 1, +}; + +struct debug_symbols { + struct debug_symbols *next; + struct dbg_trace_symbols symbol; + enum match_type match; +}; + +struct debug_file { + struct debug_file *next; + char *file_name; + + /* Start and end address, where this file is mapped into the memory of the process. */ + unsigned long long vmem_start; + unsigned long long vmem_end; + + /* bfd library context for this file. */ + struct debug_bfd_handle *dbg; + + /* Symbols to resolve, search in this file only. */ + int sym_count; + struct debug_symbols *sym; +}; + +struct dbg_trace_context { + /* PID or name of the process. */ + int pid; + char *fname; + + /* List of all libraries and object files, mapped in the memory of the process. */ + struct dbg_trace_pid_maps *fmaps; + + /* Symbols to resolve, search in all files. */ + int sym_count; + struct debug_symbols *sym; + + /* List of all libraries and object files, opened from bfd library for processing. */ + struct debug_file *files; +}; + +#define RESOLVE_NAME (1 << 0) +#define RESOLVE_VMA (1 << 1) +#define RESOLVE_FOFFSET (1 << 2) + +struct debug_obj_job { + unsigned int flags; + unsigned long long addr_offset; + struct debug_symbols *symbols; +}; + +struct debug_dwarf_bfd_context { + asymbol **table; + struct debug_obj_job *job; +}; + +/* + * A hook, called from bfd library for each section of the file. + * The logic is used for symbol name and file offset resolving from given symbol VMA. + */ +static void process_bfd_section(bfd *abfd, asection *section, void *param) +{ + struct debug_dwarf_bfd_context *context = (struct debug_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; + + /* Skip sections that have no code. */ + if (!(section->flags & SEC_CODE)) + return; + + /* Loop through all symbols, that have to be resolved. */ + 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; + + /* Adjust the symbol's VMA, if this section is dynamically loaded. */ + if (abfd->flags & DYNAMIC) + vma -= context->job->addr_offset; + + /* Check if requested symbol's vma is within the current section. */ + if (vma && section->vma <= vma && + (section->vma + section->size) > vma) { + + /* Found the file, where the symbol is defined. */ + if (!s->symbol.fname) + s->symbol.fname = strdup(abfd->filename); + + /* Found the offset of the symbol within the file. */ + if (context->job->flags & RESOLVE_FOFFSET) + s->symbol.foffset = section->filepos + (vma - section->vma); + + /* Look for the nearest function. */ + 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); +#ifdef DEBUG_INTERNALS + printf("Find addr near 0x%X, offset 0x%X - > vma - 0x%X in %s, found %s\n\r", + s->symbol.vma_near, context->job->addr_offset, vma, abfd->filename, + found ? functionname : "NA"); +#endif + if (found) { + /* Demangle the name of the function. */ + s->symbol.name = bfd_demangle(abfd, functionname, DMGL_AUTO); + /* Found the name of the symbol. */ + if (!s->symbol.name) + s->symbol.name = strdup(functionname); + } + } + } + } +} + +/* Load the symbol table from the file. */ +static asymbol **get_sym_table(bfd *handle) +{ + long size, ssize, dsize; + asymbol **symtable; + long count; + + if ((bfd_get_file_flags(handle) & HAS_SYMS) == 0) + return NULL; + + dsize = bfd_get_dynamic_symtab_upper_bound(handle); + size = dsize > 0 ? dsize : 0; + + ssize = bfd_get_symtab_upper_bound(handle); + size += ssize > 0 ? ssize : 0; + + if (size <= 0) + return NULL; + + symtable = (asymbol **) calloc(1, size); + if (!symtable) + return NULL; + + count = bfd_canonicalize_symtab(handle, symtable); + count += bfd_canonicalize_dynamic_symtab(handle, symtable + count); + if (count <= 0) { + free(symtable); + return NULL; + } + + return symtable; +} + +/* Match the requested name to the name of the symbol. Handle a wildcard match. */ +static bool symbol_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; +} + +/* Lookup in the file's symbol table. + * The logic is used for symbol VMA resolving from given symbol name. + */ +static int lookup_bfd_sym(struct debug_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++) { + /* Skip the symbol, if it is not a function. */ + if (!((*sp)->flags & BSF_FUNCTION)) + continue; + /* Loop through all symbols that should be resolved. */ + for (s = context->job->symbols; s; s = s->next) { + if (!s->symbol.name) + continue; + last = s; + if (!symbol_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; + /* Adjust the VMA, if the section is dynamically loaded. */ + if ((*sp)->the_bfd->flags & DYNAMIC) + vma += context->job->addr_offset; + if (s->match == MATCH_EXACT) { + /* Exact match, update the VMA. */ + s->symbol.vma_start = vma; + } else if (s->match == MATH_WILDCARD) { + /* Wildcard pattern match, create a new symbol. */ + 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; + new->symbol.cookie = s->symbol.cookie; + 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; +} + +/* Process a bfd object from the file. */ +static int process_bfd_object(bfd *abfd, struct debug_obj_job *job) +{ + struct debug_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; + + /* Resolve VMA from the symbol table. */ + if (job->flags & RESOLVE_VMA) + lookup_bfd_sym(&context); + + /* Resolve symbol name and file offset from file's sections. */ + 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; +} + +/* Open a bfd archive file and read all objects. */ +static int read_all_bfd(bfd *abfd, struct debug_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 debug_bfd_handle *obj, + struct debug_symbols *symbols) +{ + struct debug_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 debug_bfd_handle *obj, + struct debug_symbols *symbols) +{ + struct debug_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 debug_bfd_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 debug_bfd_handle *debug_handle_create(char *file) +{ + struct debug_bfd_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; +} + +/* Get the full path of process's executable, using the /proc fs. */ +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); +} + +/* Get or create a bfd debug context for an object file. */ +static struct debug_file *get_mapped_file(struct dbg_trace_context *dbg, + char *fname, + unsigned long long vmem_start) +{ + struct debug_file *file = dbg->files; + + /* Search if the file is already added. */ + while (file) { + if (!strcmp(fname, file->file_name) && + vmem_start && file->vmem_end == vmem_start) + return file; + file = file->next; + } + + file = calloc(1, sizeof(*file)); + if (!file) + return NULL; + file->file_name = strdup(fname); + if (!file->file_name) + goto error; + file->dbg = debug_handle_create(fname); + file->next = dbg->files; + dbg->files = file; + return file; + +error: + free(file->file_name); + debug_handle_destroy(file->dbg); + free(file); + return NULL; +} + +/* Destroy a bfd debug context. */ +void dbg_trace_context_destroy(struct dbg_trace_context *dbg) +{ + struct 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); + } + free(fdel); + } + + free(dbg->fname); + dbg_trace_free_filemap(dbg->fmaps); + free(dbg); +} + +/* Add an object file, mapped to specific memory of the process. */ +int dbg_trace_context_add_file(struct dbg_trace_context *dbg, char *file_name, + unsigned long long vmem_start, + unsigned long long vmem_end, + unsigned long long pgoff) +{ + struct debug_file *file; + + file = get_mapped_file(dbg, file_name, vmem_start); + if (!file) + return -1; + if (file->vmem_end == vmem_start) { + file->vmem_end = vmem_end; + } else { + file->vmem_start = vmem_start; + file->vmem_end = vmem_end; + if (file->dbg) + file->dbg->addr_offset = vmem_start - pgoff; + } + + return 0; +} + +/** + * dbg_trace_context_create_pid - create debug context for given PID + * @pid - ID of running process + * @libs - if true: inspect also all libraries, uased by the given process. + * + * Returns a pointer to allocated debug context, or NULL in case of an error. + */ +struct dbg_trace_context *dbg_trace_context_create_pid(int pid, bool libs) +{ + struct dbg_trace_context *dbg; + unsigned int i; + + dbg = calloc(1, sizeof(*dbg)); + if (!dbg) + return NULL; + + dbg->pid = pid; + /* Get the full path of the process executable. */ + dbg->fname = get_full_name(pid); + if (!dbg->fname) { + free(dbg); + return NULL; + } + + /* Get the memory map of all libraries, linked to the process. */ + dbg_trace_get_filemap(&dbg->fmaps, pid); + + for (i = 0; i < dbg->fmaps->nr_lib_maps; i++) { + if (!libs && strcmp(dbg->fname, dbg->fmaps->lib_maps[i].lib_name)) + continue; + /* Create a bfd debug object for each file. */ + dbg_trace_context_add_file(dbg, dbg->fmaps->lib_maps[i].lib_name, + dbg->fmaps->lib_maps[i].start, + dbg->fmaps->lib_maps[i].end, 0); + } + + return dbg; +} + +/* Get the full path of a library. */ +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 || asprintf(&fname, "%s/%s", dldir, libname) <= 0) + return NULL; + + return fname; +} + +/* Get the memory map of all libraries, linked to an executable file. */ +static int debug_obj_file_add_libs(struct dbg_trace_context *dbg, + struct debug_file *file) +{ + char line[PATH_MAX]; + char *libname; + char *trimmed; + char *fullname; + FILE *fp = NULL; + int ret = -1; + + setenv("LD_TRACE_LOADED_OBJECTS", "1", 1); + fp = popen(file->file_name, "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, 0); + free(fullname); + } + } else { + get_mapped_file(dbg, trimmed, 0); + } + } + } + +out: + unsetenv("LD_TRACE_LOADED_OBJECTS"); + if (fp) + pclose(fp); + return ret; +} + +/** + * dbg_trace_context_create_file - create debug context for given executable file + * @fname - full path to an executable file + * @libs - if true: inspect also all libraries, used by the given file. + * + * Returns a pointer to allocated debug context, or NULL in case of an error. + */ +struct dbg_trace_context *dbg_trace_context_create_file(char *fname, bool libs) +{ + struct dbg_trace_context *dbg; + struct debug_file *file; + + dbg = calloc(1, sizeof(*dbg)); + if (!dbg) + return NULL; + + dbg->fname = strdup(fname); + file = get_mapped_file(dbg, fname, 0); + 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: + dbg_trace_context_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; + } +} + +/* Perform the requested symbols resolving, using the bfd library. */ +int dbg_trace_resolve_symbols(struct dbg_trace_context *obj) +{ + struct debug_file *file; + + for (file = obj->files; file; file = file->next) { + if (!file->dbg) { + set_unknown(file->sym, file->file_name); + continue; + } + /* Resolve near VMA -> name. */ + resolve_symbol_name(file->dbg, file->sym); + /* Resolve name -> exact VMA. */ + resolve_symbol_vma(file->dbg, file->sym); + resolve_symbol_vma(file->dbg, obj->sym); + } + + return 0; +} + +/* Add VMA -> name resolving request. */ +static int add_resolve_vma2name(struct dbg_trace_context *obj, + unsigned long long vma, int cookie) +{ + struct debug_symbols *s = NULL; + struct debug_file *file; + + file = obj->files; + while (file) { + /* Find the file, where the requested VMA is. */ + if (vma >= file->vmem_start && vma <= file->vmem_end) + break; + file = file->next; + } + + if (file) { + s = file->sym; + while (s) { + /* Check if the given VMA is already added for resolving. */ + if (s->symbol.vma_near == vma) + break; + s = s->next; + } + if (!s) { + s = calloc(1, sizeof(*s)); + if (!s) + return -1; + s->symbol.cookie = cookie; + s->symbol.vma_near = vma; + s->symbol.fname = strdup(file->file_name); + if (!s->symbol.fname) + goto error; + s->next = file->sym; + file->sym = s; + file->sym_count++; + } + } + + if (s) + return 0; +error: + if (s) { + free(s->symbol.fname); + free(s); + } + return -1; +} + +/* Add name - VMA resolving request, The @name can have wildcards. */ +static int add_resolve_name2vma(struct dbg_trace_context *obj, char *name, int cookie) +{ + struct debug_symbols *s = NULL; + + s = obj->sym; + while (s) { + /* Check if the given name is already added for resolving. */ + 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.cookie = cookie; + 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; +} + +/** + * dbg_trace_add_resolve_symbol - add new resolving request + * @obj - debug object context + * @vma - VMA->name resolving, if @vma is not 0 + * @name - name-VMA resolving, if @name is not NULL + * @cookie - a cookie, attached to each successful resolving from this request + * + * Returns 0 if the request is added successfully, or -1 in case of an error. + */ +int dbg_trace_add_resolve_symbol(struct dbg_trace_context *obj, + unsigned long long vma, char *name, int cookie) +{ + int ret = -1; + + if (!obj) + return -1; + + if (!name && vma) /* vma -> name resolving */ + ret = add_resolve_vma2name(obj, vma, cookie); + else if (name) /* name -> vma resolving */ + ret = add_resolve_name2vma(obj, name, cookie); + + return ret; +} + +static int walk_symbols(struct debug_symbols *sym, + int (*callback)(struct dbg_trace_symbols *, void *), + void *context) +{ + while (sym) { + if (callback(&sym->symbol, context)) + return -1; + sym = sym->next; + } + + return 0; +} + +/** + * dbg_trace_walk_resolved_symbols - walk through all resolved symbols + * @obj - debug object context + * @callback - a callback hook, called for each resolved symbol. + * If the callback returns non-zero, the walk stops. + * @context - a user specified context, passed to the callback + */ +void dbg_trace_walk_resolved_symbols(struct dbg_trace_context *obj, + int (*callback)(struct dbg_trace_symbols *, void *), + void *context) +{ + struct debug_file *file; + + walk_symbols(obj->sym, callback, context); + file = obj->files; + while (file) { + walk_symbols(file->sym, callback, context); + file = file->next; + } +} + +/** + * trace_debug_free_symbols - free array of debug symbols + * @symbols - array with debug symbols + * @count - count of the @symbols array + */ +void trace_debug_free_symbols(struct dbg_trace_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); + +} + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) +/** + * dbg_trace_get_filemap - get a memory map of a process, using /proc fs + * @pid_maps - return: list of files, mapped into the process memory + * @pid - id of a process + * + * Returns 0 on success, -1 in case of an error. + */ +int dbg_trace_get_filemap(struct dbg_trace_pid_maps **pid_maps, int pid) +{ + struct dbg_trace_pid_maps *maps = *pid_maps; + struct dbg_trace_proc_addr_map *map; + unsigned long long begin, end; + struct dbg_trace_pid_maps *m; + char mapname[PATH_MAX+1]; + char fname[PATH_MAX+1]; + char buf[PATH_MAX+100]; + unsigned int i; + FILE *f; + int ret; + int res; + + 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_fail; + + 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++; + } + } + + 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 dbg_trace_pid_maps *maps) +{ + unsigned 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); +} + +/** + * dbg_trace_free_filemap - Free list of files, associated with given process + * @maps - list of files, returned by dbg_trace_get_filemap() + */ +void dbg_trace_free_filemap(struct dbg_trace_pid_maps *maps) +{ + struct dbg_trace_pid_maps *del; + + while (maps) { + del = maps; + maps = maps->next; + procmap_free(del); + } +} diff --git a/src/trace-obj-debug.h b/src/trace-obj-debug.h new file mode 100644 index 0000000..19091ad --- /dev/null +++ b/src/trace-obj-debug.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright 2022 VMware Inc, Tzvetomir Stoyanov (VMware) + */ + +#ifndef _TC_TRACE_DEBUG_UTILS_ +#define _TC_TRACE_DEBUG_UTILS_ + +/* --- Debug symbols--- */ +struct dbg_trace_pid_maps { + struct dbg_trace_pid_maps *next; + struct dbg_trace_proc_addr_map *lib_maps; + unsigned int nr_lib_maps; + char *proc_name; + int pid; +}; +int dbg_trace_get_filemap(struct dbg_trace_pid_maps **file_maps, int pid); +void dbg_trace_free_filemap(struct dbg_trace_pid_maps *maps); + +struct dbg_trace_symbols { + char *name; /* symbol's name */ + char *fname; /* symbol's file */ + int cookie; + 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 dbg_trace_proc_addr_map { + unsigned long long start; + unsigned long long end; + char *lib_name; +}; + +struct dbg_trace_context; +struct dbg_trace_context *dbg_trace_context_create_file(char *file, bool libs); +struct dbg_trace_context *dbg_trace_context_create_pid(int pid, bool libs); + +void dbg_trace_context_destroy(struct dbg_trace_context *debug); +int dbg_trace_context_add_file(struct dbg_trace_context *dbg, char *file_name, + unsigned long long vmem_start, + unsigned long long vmem_end, + unsigned long long pgoff); + +int dbg_trace_resolve_symbols(struct dbg_trace_context *obj); +int dbg_trace_add_resolve_symbol(struct dbg_trace_context *obj, + unsigned long long vma, char *name, int cookie); + +void dbg_trace_walk_resolved_symbols(struct dbg_trace_context *obj, + int (*callback)(struct dbg_trace_symbols *, void *), + void *context); + +#endif /* _TC_TRACE_DEBUG_UTILS_ */ From patchwork Wed Apr 20 08:02:04 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 12819893 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 38F31C433EF for ; Wed, 20 Apr 2022 08:02:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1376596AbiDTIFC (ORCPT ); Wed, 20 Apr 2022 04:05:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59636 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1376598AbiDTIE7 (ORCPT ); Wed, 20 Apr 2022 04:04:59 -0400 Received: from mail-ed1-x533.google.com (mail-ed1-x533.google.com [IPv6:2a00:1450:4864:20::533]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5D5D53C4BE for ; Wed, 20 Apr 2022 01:02:13 -0700 (PDT) Received: by mail-ed1-x533.google.com with SMTP id 11so1268232edw.0 for ; Wed, 20 Apr 2022 01:02:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=SgQiod47sFEUxczZH3PKKs147/zpWToDx6tta0OrLyc=; b=WGtpIN0TVxIiLIPJePlHBgEG3+YINfpSd6Ru0sRX4z+MfgfPTWaxp3Kepi07f/9CyX uQIhNcaNoRTssJ6TgNOSP2VFv++I053kJuinN1z3SsVWwTkHRBJ+jWSgZoDDGZwKsLeb auHzFRiDdoEg5BaFEABGAi0/BrV5n0eggMgDxKOZ5XGOP4EJ8nbulAfX9d6Noli3IyHu uZjkBr7oRnPosEwLB5dUQx6+aI6cA1TbAY54IXTKHOf1OVfPS6MGZJs3JRhrwfNZ6J7Q WCfmwYalx/jpIKKQjBXyg26YHXR8IuEl5EyBlolfBYGxjK4vLvz8iY47Oqh+dEZBfBvO Awlg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=SgQiod47sFEUxczZH3PKKs147/zpWToDx6tta0OrLyc=; b=P8vetJRq3tqcsDbDhd2jJAsWm6McKzc58iZQDVA2/jB7g5YLZ55WpxKE1M84iFFvGH otOKmAlT9Mbz31sRXNDUN6Sxv9UKcQCJQpXRU505uL9OEO1CTfH4eRqS1HVrqieRwHwX UsBwqdXCDopPo0xYv26RSKocASznQ3ZxuAri2hv4tvvmg0utSu9u5bjinTeupyfrvzDS o4rr01YGbLp6efUhHzVZAAYET0CNhHquiQGVPTkIxav10SxZGYjg66FUBuYnBdT0FGfv sb+RZg5mxr8nnxQBUaAnBE/6ulTfAumi0/WqnMfcsEPutSUSS8d2TdomlKfM5WEJhMIM zj0A== X-Gm-Message-State: AOAM533+6XMMv99RJA8nQ/Lj7PX5s3Er3nnQJJM50AT6YyjTLd54o9uN JWS3l7qajmBazb6u11t03g5fKmBLJiZC8A== X-Google-Smtp-Source: ABdhPJzy030LSZ/boR5ywfCt9mDKhDVVvQzaeIESDNCST/GK+BXnukm4nElV+VuZ3gNSetHjSOfUsA== X-Received: by 2002:a05:6402:14d0:b0:41d:946b:7494 with SMTP id f16-20020a05640214d000b0041d946b7494mr21337738edx.190.1650441731863; Wed, 20 Apr 2022 01:02:11 -0700 (PDT) Received: from oberon.zico.biz.com ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id t12-20020a1709067c0c00b006e86db76851sm6393763ejo.193.2022.04.20.01.02.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Apr 2022 01:02:11 -0700 (PDT) From: "Tzvetomir Stoyanov (VMware)" To: y.karadz@gmail.com Cc: rostedt@goodmis.org, linux-trace-devel@vger.kernel.org Subject: [RFC PATCH v3 2/4] trace-cruncher: ftrace uprobe raw API Date: Wed, 20 Apr 2022 11:02:04 +0300 Message-Id: <20220420080206.252356-3-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220420080206.252356-1-tz.stoyanov@gmail.com> References: <20220420080206.252356-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Trace-cruncher low level wrappers for tracefs library APIs for uprobe and uretprobe allocation. Signed-off-by: Tzvetomir Stoyanov (VMware) --- src/ftracepy-utils.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ src/ftracepy-utils.h | 4 ++++ src/ftracepy.c | 10 +++++++++ 3 files changed, 62 insertions(+) diff --git a/src/ftracepy-utils.c b/src/ftracepy-utils.c index e8411ae..b39459b 100644 --- a/src/ftracepy-utils.c +++ b/src/ftracepy-utils.c @@ -2449,6 +2449,54 @@ PyObject *PyFtrace_eprobe(PyObject *self, PyObject *args, PyObject *kwargs) return py_dyn; } +static PyObject *alloc_uprobe(PyObject *self, PyObject *args, PyObject *kwargs, bool pret) +{ + static char *kwlist[] = {"event", "file", "offset", "fetch_args", NULL}; + const char *event, *file, *fetchargs = NULL; + unsigned long long offset; + struct tracefs_dynevent *uprobe; + PyObject *py_dyn; + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "ssK|s", + kwlist, + &event, + &file, + &offset, + &fetchargs)) { + return NULL; + } + + if (pret) + uprobe = tracefs_uretprobe_alloc(TC_SYS, event, file, offset, fetchargs); + else + uprobe = tracefs_uprobe_alloc(TC_SYS, event, file, offset, fetchargs); + if (!uprobe) { + MEM_ERROR; + return NULL; + } + + py_dyn = PyDynevent_New(uprobe); + /* + * Here we only allocated and initializes a dynamic event object. + * However, no dynamic event is added to the system yet. Hence, + * there is no need to 'destroy' this event at exit. + */ + set_destroy_flag(py_dyn, false); + return py_dyn; +} + +PyObject *PyFtrace_uprobe(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return alloc_uprobe(self, args, kwargs, false); +} + +PyObject *PyFtrace_uretprobe(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return alloc_uprobe(self, args, kwargs, true); +} + static PyObject *set_filter(PyObject *args, PyObject *kwargs, struct tep_handle *tep, struct tep_event *event) diff --git a/src/ftracepy-utils.h b/src/ftracepy-utils.h index 9491b18..e6fab69 100644 --- a/src/ftracepy-utils.h +++ b/src/ftracepy-utils.h @@ -257,6 +257,10 @@ PyObject *PyFtrace_kretprobe(PyObject *self, PyObject *args, PyObject *kwargs); PyObject *PyFtrace_eprobe(PyObject *self, PyObject *args, PyObject *kwargs); +PyObject *PyFtrace_uprobe(PyObject *self, PyObject *args, PyObject *kwargs); + +PyObject *PyFtrace_uretprobe(PyObject *self, PyObject *args, PyObject *kwargs); + PyObject *PyFtrace_hist(PyObject *self, PyObject *args, PyObject *kwargs); diff --git a/src/ftracepy.c b/src/ftracepy.c index 574bf38..681d641 100644 --- a/src/ftracepy.c +++ b/src/ftracepy.c @@ -481,6 +481,16 @@ static PyMethodDef ftracepy_methods[] = { METH_VARARGS | METH_KEYWORDS, "Define an eprobe." }, + {"uprobe", + (PyCFunction) PyFtrace_uprobe, + METH_VARARGS | METH_KEYWORDS, + "Define a uprobe." + }, + {"uretprobe", + (PyCFunction) PyFtrace_uretprobe, + METH_VARARGS | METH_KEYWORDS, + "Define a uretprobe." + }, {"hist", (PyCFunction) PyFtrace_hist, METH_VARARGS | METH_KEYWORDS, From patchwork Wed Apr 20 08:02:05 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 12819896 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id CB963C433FE for ; Wed, 20 Apr 2022 08:02:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1376597AbiDTIFC (ORCPT ); Wed, 20 Apr 2022 04:05:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59660 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1376599AbiDTIFB (ORCPT ); Wed, 20 Apr 2022 04:05:01 -0400 Received: from mail-ej1-x633.google.com (mail-ej1-x633.google.com [IPv6:2a00:1450:4864:20::633]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 905CA3C4B4 for ; Wed, 20 Apr 2022 01:02:14 -0700 (PDT) Received: by mail-ej1-x633.google.com with SMTP id ks6so1887576ejb.1 for ; Wed, 20 Apr 2022 01:02:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=zwHwx0qQ/dip6b3JdqSp301udOyKQhlK2finJcZkHEI=; b=JGbfxF7/bjD9dUNtUshL4LO+/ha462vv42MdCLWAiHvk6aD1uItPfbDdbIaTQpnzuM +6zDvvDERgCBNi83nwaqdEGRfarpGZmEDjY0I7Yp/RmI+jn0jYhBA3UvNre9QfNlXF3h WofzDWM0LmbUMEDbJjqbpLfqoHCFN3t4l6MYatsD8nKmsRq7SK7144wP//G4r8ffWYMN aFhEXmCPW9kzF9LwGw9rI9woPJ3Iymb1NhkF9IJfQqSDWd3vS+Vg/a7Z8Zq5H0JTA6rr 3+JG2bM5O8JmSA6uryEAA9OprwOCGJPUAefeZIuEg3ksRQdnIfg/NY4sr/kAsqCDWTIC Jcwg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=zwHwx0qQ/dip6b3JdqSp301udOyKQhlK2finJcZkHEI=; b=0pd6b/L1LPo8En6gA1MaIEXcCaZzNwUXD5WJ2JiP9DHGtME7/NIYUrRVibZThrsFMO 5gaYAswOCnkUsBK/bhkCJXcYvoBzsTbqbYXsyNqprIIKVYi8WghoPnPTQewkbtvKaQxM 4YpJ7+V2UTQp5nElyDZzuKBq51vTgsOhSTBAKI3V3P1L/+jPg5jgqJcfLeLeHZ5mOeOn ofmLIVvzezM2AOu/s0LemFCo4Aapv+Mv8VU14Gl9krE+wdCzZmWXuVBNPey+CsB/qV5y olSUq45DJv0fcd1O2tBjxEEZ2m/zITwTiWWIpOchKaoARcaPzKiL0K/zY1N4vIxZIGYC jRMw== X-Gm-Message-State: AOAM53191c14PA/JxeNjYihWcIujF9xN9KFHbHCEmM8xJqfdJePptpVp awPe2Kkxj9XjWdsAPdBRgAo= X-Google-Smtp-Source: ABdhPJxY0rdKxZcy1JyswXxUOn0ydnOrjHCE67+EVmXXnS89T755fmUtbAccpSuTJP0YPhUvUCJCHA== X-Received: by 2002:a17:906:c0d6:b0:6ca:457e:f1b7 with SMTP id bn22-20020a170906c0d600b006ca457ef1b7mr16708177ejb.399.1650441732953; Wed, 20 Apr 2022 01:02:12 -0700 (PDT) Received: from oberon.zico.biz.com ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id t12-20020a1709067c0c00b006e86db76851sm6393763ejo.193.2022.04.20.01.02.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Apr 2022 01:02:12 -0700 (PDT) From: "Tzvetomir Stoyanov (VMware)" To: y.karadz@gmail.com Cc: rostedt@goodmis.org, linux-trace-devel@vger.kernel.org Subject: [RFC PATCH v3 3/4] trace-cruncher: High level wrappers for ftrace uprobes Date: Wed, 20 Apr 2022 11:02:05 +0300 Message-Id: <20220420080206.252356-4-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220420080206.252356-1-tz.stoyanov@gmail.com> References: <20220420080206.252356-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Using uprobes requires finding the offset of a user function within the binary file, where this functions is compiled. This is not a trivial task, especially in the cases when a bunch of uprobes to user functions should be added. A high level trace-cruncher API allows adding multiple user functions as uprobes or uretprobes. It supports wildcards for function names and adding uprobes for library functions, used by the applications. Signed-off-by: Tzvetomir Stoyanov (VMware) --- setup.py | 4 +- src/ftracepy-utils.c | 698 +++++++++++++++++++++++++++++++++++++++++++ src/ftracepy-utils.h | 17 ++ src/ftracepy.c | 35 +++ 4 files changed, 752 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 58561cf..464d3d2 100644 --- a/setup.py +++ b/setup.py @@ -71,8 +71,8 @@ def extension(name, sources, libraries): def main(): module_ft = extension(name='tracecruncher.ftracepy', - sources=['src/ftracepy.c', 'src/ftracepy-utils.c'], - libraries=['traceevent', 'tracefs']) + sources=['src/ftracepy.c', 'src/ftracepy-utils.c', 'src/trace-obj-debug.c'], + libraries=['traceevent', 'tracefs', 'bfd']) cythonize('src/npdatawrapper.pyx', language_level = 3) module_data = extension(name='tracecruncher.npdatawrapper', diff --git a/src/ftracepy-utils.c b/src/ftracepy-utils.c index b39459b..9d9fa5d 100644 --- a/src/ftracepy-utils.c +++ b/src/ftracepy-utils.c @@ -10,20 +10,51 @@ #endif // _GNU_SOURCE // C +#include #include #include #include #include #include +#include #include // trace-cruncher #include "ftracepy-utils.h" +#include "trace-obj-debug.h" PyObject *TFS_ERROR; PyObject *TEP_ERROR; PyObject *TRACECRUNCHER_ERROR; +#define UPROBES_SYSTEM "tc_uprobes" + +#define FTRACE_UPROBE 0x1 +#define FTRACE_URETPROBE 0x2 + +struct fprobes_list { + int size; + int count; + void **data; +}; + +struct utrace_func { + int type; + char *func_name; + char *func_args; +}; + +struct py_utrace_context { + pid_t pid; + char **cmd_argv; + char *usystem; + uint32_t trace_time; /* in msec */ + struct fprobes_list fretprobes; + struct fprobes_list ufuncs; + struct fprobes_list uevents; + struct dbg_trace_context *dbg; +}; + static char *kernel_version() { struct utsname uts; @@ -3508,3 +3539,670 @@ void PyFtrace_at_exit(void) if (seq.buffer) trace_seq_destroy(&seq); } + +#define UTRACE_LIST_INIT_SIZE 100 +static int utrace_list_add(struct fprobes_list *list, void *data) +{ + void **tmp; + int size; + + if (list->size <= list->count) { + if (!list->size) + size = UTRACE_LIST_INIT_SIZE; + else + size = 2 * list->size; + tmp = realloc(list->data, size * sizeof(void *)); + if (!tmp) + return -1; + list->data = tmp; + list->size = size; + } + + list->data[list->count] = data; + list->count++; + return list->count - 1; +} + +void py_utrace_free(struct py_utrace_context *utrace) +{ + struct utrace_func *f; + int i; + + if (!utrace) + return; + if (utrace->dbg) + dbg_trace_context_destroy(utrace->dbg); + + for (i = 0; i < utrace->ufuncs.count; i++) { + f = utrace->ufuncs.data[i]; + free(f->func_name); + free(f); + } + free(utrace->ufuncs.data); + if (utrace->cmd_argv) { + i = 0; + while (utrace->cmd_argv[i]) + free(utrace->cmd_argv[i++]); + free(utrace->cmd_argv); + } + + for (i = 0; i < utrace->uevents.count; i++) + tracefs_dynevent_free(utrace->uevents.data[i]); + free(utrace->uevents.data); + + free(utrace->usystem); + free(utrace); +} + +/* + * All strings, used as ftrace system or event name must contain only + * alphabetic characters, digits or underscores. + */ +static void fname_unify(char *fname) +{ + int i; + + for (i = 0; fname[i]; i++) + if (!isalnum(fname[i]) && fname[i] != '_') + fname[i] = '_'; +} + +int py_utrace_destroy(struct py_utrace_context *utrace) +{ + int i; + + for (i = 0; i < utrace->uevents.count; i++) + tracefs_dynevent_destroy(utrace->uevents.data[i], true); + + return 0; +} + +static unsigned long long str_hash(char *s) +{ + unsigned long long sum = 0, add; + int i, len = strlen(s); + + for (i = 0; i < len; i++) { + if (i + 8 < len) + add = *(long long *)(s+i); + else if (i + 4 < len) + add = *(long *)(s+i); + else + add = s[i]; + + sum += add; + } + + return sum; +} + +static struct py_utrace_context *utrace_new(pid_t pid, char **argv, bool libs) +{ + struct py_utrace_context *utrace; + + utrace = calloc(1, sizeof(*utrace)); + if (!utrace) + return NULL; + + if (argv) { + utrace->cmd_argv = argv; + utrace->dbg = dbg_trace_context_create_file(argv[0], libs); + if (!utrace->dbg) + goto error; + if (asprintf(&utrace->usystem, "%s_%llX", UPROBES_SYSTEM, str_hash(argv[0])) <= 0) + goto error; + } else { + utrace->pid = pid; + utrace->dbg = dbg_trace_context_create_pid(pid, libs); + if (!utrace->dbg) + goto error; + if (asprintf(&utrace->usystem, "%s_%d", UPROBES_SYSTEM, pid) <= 0) + goto error; + } + + fname_unify(utrace->usystem); + return utrace; + +error: + py_utrace_free(utrace); + return NULL; +} + +static int py_utrace_add_func(struct py_utrace_context *utrace, char *func, int type) +{ + struct utrace_func *p; + int ret; + int i; + + for (i = 0; i < utrace->ufuncs.count; i++) { + p = utrace->ufuncs.data[i]; + if (!strcmp(p->func_name, func)) { + p->type |= type; + return 0; + } + } + + p = calloc(1, sizeof(*p)); + if (!p) + return -1; + p->func_name = strdup(func); + if (!p->func_name) + goto error; + p->type = type; + + ret = utrace_list_add(&utrace->ufuncs, p); + if (ret < 0) + goto error; + + if (dbg_trace_add_resolve_symbol(utrace->dbg, 0, func, ret)) + goto error; + + return 0; + +error: + free(p->func_name); + free(p); + return -1; +} + +PyObject *PyUserTrace_add_function(PyUserTrace *self, PyObject *args, + PyObject *kwargs) +{ + struct py_utrace_context *utrace = self->ptrObj; + static char *kwlist[] = {"fname", NULL}; + char *fname; + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "s", + kwlist, + &fname)) { + return NULL; + } + + if (py_utrace_add_func(utrace, fname, FTRACE_UPROBE) < 0) { + MEM_ERROR + return NULL; + } + + Py_RETURN_NONE; +} + +PyObject *PyUserTrace_add_ret_function(PyUserTrace *self, PyObject *args, + PyObject *kwargs) +{ + struct py_utrace_context *utrace = self->ptrObj; + static char *kwlist[] = {"fname", NULL}; + char *fname; + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "s", + kwlist, + &fname)) { + return NULL; + } + + if (py_utrace_add_func(utrace, fname, FTRACE_URETPROBE) < 0) { + MEM_ERROR + return NULL; + } + + Py_RETURN_NONE; +} + +/* + * Max event name is 64 bytes, hard coded in the kernel. + * It can consist only of alphabetic characters, digits or underscores. + */ +#define FILENAME_TRUNCATE 10 +#define FUNCAME_TRUNCATE 50 +static char *uprobe_event_name(char *file, char *func, int type) +{ + char *event = NULL; + char *fname; + + fname = strrchr(file, '/'); + if (fname) + fname++; + if (!fname || *fname == '\0') + fname = file; + + asprintf(&event, "%s%.*s_%.*s", + type == FTRACE_URETPROBE ? "r_":"", + FILENAME_TRUNCATE, fname, FUNCAME_TRUNCATE, func); + if (event) + fname_unify(event); + + return event; +} + +/* + * Create uprobe based on function name, + * file name and function offset within the file. + */ +static int utrace_event_create(struct py_utrace_context *utrace, + struct dbg_trace_symbols *sym, char *fecthargs, + int type) +{ + struct tracefs_dynevent *uevent = NULL; + char *rname; + + /* Generate uprobe event name, according to ftrace name requirements. */ + rname = uprobe_event_name(sym->fname, sym->name, type); + if (!rname) + return -1; + + if (type == FTRACE_URETPROBE) + uevent = tracefs_uretprobe_alloc(utrace->usystem, rname, + sym->fname, sym->foffset, fecthargs); + else + uevent = tracefs_uprobe_alloc(utrace->usystem, rname, + sym->fname, sym->foffset, fecthargs); + + free(rname); + if (!uevent) + return -1; + + if (tracefs_dynevent_create(uevent)) { + tracefs_dynevent_free(uevent); + return -1; + } + + utrace_list_add(&utrace->uevents, uevent); + return 0; +} + +/* A callback, called on each resolved function. */ +static int symblos_walk(struct dbg_trace_symbols *sym, void *context) +{ + struct py_utrace_context *utrace = context; + struct utrace_func *ufunc; + + if (!sym->name || !sym->fname || !sym->foffset || + sym->cookie < 0 || sym->cookie >= utrace->ufuncs.count) + return 0; + + ufunc = utrace->ufuncs.data[sym->cookie]; + + if (ufunc->type & FTRACE_UPROBE) + utrace_event_create(utrace, sym, ufunc->func_args, FTRACE_UPROBE); + + if (ufunc->type & FTRACE_URETPROBE) + utrace_event_create(utrace, sym, ufunc->func_args, FTRACE_URETPROBE); + + return 0; +} + +static void py_utrace_generate_uprobes(struct py_utrace_context *utrace) +{ + /* Find the exact name and file offset of each user function that should be traced. */ + dbg_trace_resolve_symbols(utrace->dbg); + dbg_trace_walk_resolved_symbols(utrace->dbg, symblos_walk, utrace); +} + +static int uprobe_start_trace(struct py_utrace_context *utrace, struct tracefs_instance *instance) +{ + PyObject *pPid = PyLong_FromLong(utrace->pid); + bool ret; + + if (!pPid) + return -1; + + /* Filter the trace only on desired pid(s). */ + ret = hook2pid(instance, PyLong_FromLong(utrace->pid), true); + Py_DECREF(pPid); + if (!ret) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to set trace filter"); + return -1; + } + + /* Enable uprobes in the system. */ + if (tracefs_event_enable(instance, utrace->usystem, NULL)) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to enable trace events"); + return -1; + } + + return 0; +} + +#define PERF_EXEC_SYNC "/TC_PERF_SYNC_XXXXXX" +static int uprobe_exec_cmd(struct py_utrace_context *utrace, struct tracefs_instance *instance) +{ + char *envp[] = {NULL}; + char sname[strlen(PERF_EXEC_SYNC) + 1]; + sem_t *sem; + pid_t pid; + int ret; + + strcpy(sname, PERF_EXEC_SYNC); + mktemp(sname); + sem = sem_open(sname, O_CREAT | O_EXCL, 0644, 0); + sem_unlink(sname); + + pid = fork(); + if (pid < 0) { + PyErr_SetString(TRACECRUNCHER_ERROR, "Failed to fork"); + return -1; + } + if (pid == 0) { + sem_wait(sem); + execvpe(utrace->cmd_argv[0], utrace->cmd_argv, envp); + } else { + utrace->pid = pid; + uprobe_start_trace(utrace, instance); + sem_post(sem); + return ret; + } + + return 0; +} + +static int py_utrace_enable(struct py_utrace_context *utrace, struct tracefs_instance *instance) +{ + /* If uprobes on desired user functions are not yet generated, do it now. */ + if (!utrace->uevents.count) + py_utrace_generate_uprobes(utrace); + + /* No functions are found in the given program / pid. */ + if (!utrace->uevents.count) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Cannot find requested user functions"); + return -1; + } + + if (utrace->cmd_argv) + uprobe_exec_cmd(utrace, instance); + else + uprobe_start_trace(utrace, instance); + + return 0; +} + +static int py_utrace_disable(struct py_utrace_context *utrace, struct tracefs_instance *instance) +{ + /* Disable uprobes in the system. */ + if (tracefs_event_disable(instance, utrace->usystem, NULL)) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to disable trace events"); + return -1; + } + + return 0; +} + +static bool tracing_run; + +static void tracing_stop(int sig) +{ + tracing_run = false; +} + +static void tracing_timer(int sig, siginfo_t *si, void *uc) +{ + tracing_run = false; +} + +#define PID_WAIT_CHECK_USEC 500000 +#define TIMER_SEC_NANO 1000000000LL +static int utrace_wait_pid(struct py_utrace_context *utrace) +{ + struct itimerspec tperiod = {0}; + struct sigaction saction = {0}; + struct sigevent stime = {0}; + timer_t timer_id; + + if (utrace->pid == 0) + return -1; + + tracing_run = true; + signal(SIGINT, tracing_stop); + + if (utrace->trace_time) { + stime.sigev_notify = SIGEV_SIGNAL; + stime.sigev_signo = SIGRTMIN; + if (timer_create(CLOCK_MONOTONIC, &stime, &timer_id)) + return -1; + saction.sa_flags = SA_SIGINFO; + saction.sa_sigaction = tracing_timer; + sigemptyset(&saction.sa_mask); + if (sigaction(SIGRTMIN, &saction, NULL)) { + timer_delete(timer_id); + return -1; + } + /* Convert trace_time from msec to sec, nsec. */ + tperiod.it_value.tv_nsec = ((unsigned long long)utrace->trace_time * 1000000LL); + if (tperiod.it_value.tv_nsec >= TIMER_SEC_NANO) { + tperiod.it_value.tv_sec = tperiod.it_value.tv_nsec / TIMER_SEC_NANO; + tperiod.it_value.tv_nsec %= TIMER_SEC_NANO; + } + if (timer_settime(timer_id, 0, &tperiod, NULL)) + return -1; + } + + do { + if (utrace->cmd_argv) { /* Wait for a child. */ + if (waitpid(utrace->pid, NULL, WNOHANG) == (int)utrace->pid) { + utrace->pid = 0; + tracing_run = false; + } + } else { /* Not a child, check if still exist. */ + if (kill(utrace->pid, 0) == -1 && errno == ESRCH) { + utrace->pid = 0; + tracing_run = false; + } + } + usleep(PID_WAIT_CHECK_USEC); + } while (tracing_run); + + if (utrace->trace_time) + timer_delete(timer_id); + + signal(SIGINT, SIG_DFL); + + return 0; +} + +PyObject *PyUserTrace_enable(PyUserTrace *self, PyObject *args, PyObject *kwargs) +{ + struct py_utrace_context *utrace = self->ptrObj; + static char *kwlist[] = {"instance", "wait", "time", NULL}; + struct tracefs_instance *instance = NULL; + PyObject *py_inst = NULL; + int wait = false; + int ret; + + if (!utrace) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to get utrace context"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "|Ops", + kwlist, + &py_inst, + &wait, + &utrace->trace_time)) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to parse input arguments"); + return NULL; + } + + if (!get_optional_instance(py_inst, &instance)) + return NULL; + + ret = py_utrace_enable(utrace, instance); + if (ret) + return NULL; + + if (wait) { + utrace_wait_pid(utrace); + py_utrace_disable(utrace, instance); + } + + Py_RETURN_NONE; +} + +PyObject *PyUserTrace_disable(PyUserTrace *self, PyObject *args, PyObject *kwargs) +{ + struct py_utrace_context *utrace = self->ptrObj; + static char *kwlist[] = {"instance", NULL}; + struct tracefs_instance *instance = NULL; + PyObject *py_inst = NULL; + int ret; + + if (!utrace) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to get utrace context"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "|O", + kwlist, + &py_inst)) { + PyErr_SetString(TRACECRUNCHER_ERROR, + "Failed to parse input arguments"); + return NULL; + } + + if (!get_optional_instance(py_inst, &instance)) + return NULL; + + ret = py_utrace_disable(utrace, instance); + + if (ret) + return NULL; + + Py_RETURN_NONE; +} + +static char *find_in_path(const char *name) +{ + char *paths = strdup(getenv("PATH")); + char fullpath[PATH_MAX]; + bool found = false; + char *tmp = paths; + const char *item; + + if (!paths) + return NULL; + + while ((item = strsep(&paths, ":")) != NULL) { + snprintf(fullpath, PATH_MAX, "%s/%s", item, name); + if (access(fullpath, F_OK|X_OK) == 0) { + found = true; + break; + } + } + + free(tmp); + + if (found) + return strdup(fullpath); + + return NULL; +} + +static char *get_full_name(char *name) +{ + bool resolve = false; + char *fname, *tmp; + + if (!strchr(name, '/')) { + tmp = find_in_path(name); + if (!tmp) + return NULL; + resolve = true; + } else { + tmp = name; + } + + fname = realpath(tmp, NULL); + if (resolve) + free(tmp); + + return fname; +} + +PyObject *PyFtrace_user_trace(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"pid", "argv", "follow_libs", NULL}; + PyObject *py_utrace, *py_arg, *py_args = NULL; + struct py_utrace_context *utrace; + char **argv = NULL; + long long pid = 0; + int libs = 0; + int i, argc; + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "|KOp", + kwlist, + &pid, + &py_args, + &libs)) { + return NULL; + } + + if (pid <= 0 && !py_args) { + PyErr_Format(TFS_ERROR, + "Process ID or program name should be specified"); + return NULL; + } + if (pid > 0 && py_args) { + PyErr_Format(TFS_ERROR, + "Only one of Process ID or program name should be specified"); + return NULL; + } + + if (py_args) { + if (!PyList_CheckExact(py_args)) { + PyErr_Format(TFS_ERROR, "Failed to parse argv list"); + return NULL; + } + argc = PyList_Size(py_args); + argv = calloc(argc + 1, sizeof(char *)); + for (i = 0; i < argc; i++) { + py_arg = PyList_GetItem(py_args, i); + if (!PyUnicode_Check(py_arg)) + continue; + + if (i == 0) + argv[i] = get_full_name(PyUnicode_DATA(py_arg)); + else + argv[i] = strdup(PyUnicode_DATA(py_arg)); + + if (!argv[i]) { + if (i == 0) + PyErr_Format(TFS_ERROR, + "Failed to find program with name %s", + PyUnicode_DATA(py_arg)); + goto error; + } + } + argv[i] = NULL; + } + + utrace = utrace_new(pid, argv, libs); + if (!utrace) { + MEM_ERROR; + goto error; + } + + py_utrace = PyUserTrace_New(utrace); + return py_utrace; + +error: + if (argv) { + for (i = 0; i < argc; i++) + free(argv[i]); + free(argv); + } + return NULL; +} diff --git a/src/ftracepy-utils.h b/src/ftracepy-utils.h index e6fab69..3f9c906 100644 --- a/src/ftracepy-utils.h +++ b/src/ftracepy-utils.h @@ -34,6 +34,21 @@ C_OBJECT_WRAPPER_DECLARE(tracefs_synth, PySynthEvent) PyObject *PyTepRecord_time(PyTepRecord* self); +struct py_utrace_context; +void py_utrace_free(struct py_utrace_context *utrace); +int py_utrace_destroy(struct py_utrace_context *utrace); +C_OBJECT_WRAPPER_DECLARE(py_utrace_context, PyUserTrace); + +PyObject *PyUserTrace_add_function(PyUserTrace *self, PyObject *args, + PyObject *kwargs); + +PyObject *PyUserTrace_add_ret_function(PyUserTrace *self, PyObject *args, + PyObject *kwargs); + +PyObject *PyUserTrace_enable(PyUserTrace *self, PyObject *args, PyObject *kwargs); + +PyObject *PyUserTrace_disable(PyUserTrace *self, PyObject *args, PyObject *kwargs); + PyObject *PyTepRecord_cpu(PyTepRecord* self); PyObject *PyTepEvent_name(PyTepEvent* self); @@ -270,6 +285,8 @@ PyObject *PyFtrace_synth(PyObject *self, PyObject *args, PyObject *PyFtrace_set_ftrace_loglevel(PyObject *self, PyObject *args, PyObject *kwargs); +PyObject *PyFtrace_user_trace(PyObject *self, PyObject *args, PyObject *kwargs); + PyObject *PyFtrace_trace_process(PyObject *self, PyObject *args, PyObject *kwargs); diff --git a/src/ftracepy.c b/src/ftracepy.c index 681d641..73f9245 100644 --- a/src/ftracepy.c +++ b/src/ftracepy.c @@ -315,6 +315,32 @@ C_OBJECT_WRAPPER(tracefs_synth, PySynthEvent, tracefs_synth_destroy, tracefs_synth_free) +static PyMethodDef PyUserTrace_methods[] = { + {"add_function", + (PyCFunction) PyUserTrace_add_function, + METH_VARARGS | METH_KEYWORDS, + "Add tracepoint on user function." + }, + {"add_ret_function", + (PyCFunction) PyUserTrace_add_ret_function, + METH_VARARGS | METH_KEYWORDS, + "Add tracepoint on user function return." + }, + {"enable", + (PyCFunction) PyUserTrace_enable, + METH_VARARGS | METH_KEYWORDS, + "Enable tracing of the configured user tracepoints." + }, + {"disable", + (PyCFunction) PyUserTrace_disable, + METH_VARARGS | METH_KEYWORDS, + "Disable tracing of the configured user tracepoints." + }, + {NULL, NULL, 0, NULL} +}; +C_OBJECT_WRAPPER(py_utrace_context, PyUserTrace, + py_utrace_destroy, py_utrace_free) + static PyMethodDef ftracepy_methods[] = { {"dir", (PyCFunction) PyFtrace_dir, @@ -501,6 +527,11 @@ static PyMethodDef ftracepy_methods[] = { METH_VARARGS | METH_KEYWORDS, "Define a synthetic event." }, + {"user_trace", + (PyCFunction) PyFtrace_user_trace, + METH_VARARGS | METH_KEYWORDS, + "Create a context for tracing a user process using uprobes" + }, {"set_ftrace_loglevel", (PyCFunction) PyFtrace_set_ftrace_loglevel, METH_VARARGS | METH_KEYWORDS, @@ -575,6 +606,9 @@ PyMODINIT_FUNC PyInit_ftracepy(void) if (!PySynthEventTypeInit()) return NULL; + if (!PyUserTraceTypeInit()) + return NULL; + TFS_ERROR = PyErr_NewException("tracecruncher.ftracepy.tfs_error", NULL, NULL); @@ -593,6 +627,7 @@ PyMODINIT_FUNC PyInit_ftracepy(void) PyModule_AddObject(module, "tracefs_dynevent", (PyObject *) &PyDyneventType); PyModule_AddObject(module, "tracefs_hist", (PyObject *) &PyTraceHistType); PyModule_AddObject(module, "tracefs_synth", (PyObject *) &PySynthEventType); + PyModule_AddObject(module, "py_utrace_context", (PyObject *) &PyUserTraceType); PyModule_AddObject(module, "tfs_error", TFS_ERROR); PyModule_AddObject(module, "tep_error", TEP_ERROR); From patchwork Wed Apr 20 08:02:06 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Tzvetomir Stoyanov (VMware)" X-Patchwork-Id: 12819895 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2C356C4332F for ; Wed, 20 Apr 2022 08:02:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1376598AbiDTIFD (ORCPT ); Wed, 20 Apr 2022 04:05:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59652 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1376433AbiDTIFA (ORCPT ); Wed, 20 Apr 2022 04:05:00 -0400 Received: from mail-ej1-x62a.google.com (mail-ej1-x62a.google.com [IPv6:2a00:1450:4864:20::62a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 929F93C4B5 for ; Wed, 20 Apr 2022 01:02:15 -0700 (PDT) Received: by mail-ej1-x62a.google.com with SMTP id g18so1795199ejc.10 for ; Wed, 20 Apr 2022 01:02:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=BIRv3Z1Yu8sQXl4ViSztyM9UBTpvjZTiPqvH6s98QZw=; b=A7/T81ezZzYwNiFeHP95a46KAsJScCCacvw/hRNdZJ8yRe2XdWf1+HunNuJQn4WoPI jqu/gPc7YR3tf9FUJ5HuoxdXHIkzbNSpwrfzEhNLS+uzPYuhFRSov7cl3Gk/7tkO4A2A lwT/snZPRjobKYpTaWptNBLFtZ1GwVFbH6IavDPhkxMeH45XTHurJmdZ7iqGNLleKnMm nlydQn4HG28sTgWLKSxW8+seALrBiKi3FIH2CCuwQNnqXRhw8KMDTK7uobwktfR2atvL 6hMbjmK3K91Ip+GXGo8isgZsdeHb4a+kEJaHtgD2ZdIsw+wPqcOEtHYQc9YUIqkCrnWB P1cA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=BIRv3Z1Yu8sQXl4ViSztyM9UBTpvjZTiPqvH6s98QZw=; b=hsQuUMDXNMEZ0tMsqWyid61VhmRvgS3lu1+6WzUOCmui6zbsowyHWEiiAiNTugZRHx YtajghYzaUSolW8Ehb3gTRXWJHopM+1PF/ie49+lmmR+Xp/ALBD3dLKXwwddIq4YNNdy py/Nw6i6PaHEgxOOmOyT5G+uPqmklGkoX9zQAdB/Hhdb1ib/LPRmMv9snovn4Usok6Yn +dTeuL9DTdr2XVrgGuFt7oRugQ4NaMulcO+tSeFN57BxiuMsDGG3GdsL6YItMrhGCXql wyT1AayDAxh/j58LSskNo6YfOdShhgnAjSy+nUWHutgzIUh0D45bI/8EoKrn+RzXi+44 w8OA== X-Gm-Message-State: AOAM533oQNnX4UncUZreCE7qWy81CJ2zXHouxy7Uf3uH5jvjdC/BM/Mb 6sNc0VQTGwXfhQZwDKcSqu0= X-Google-Smtp-Source: ABdhPJy49wf3Qs7+TAjdsjtOe70qlRMZJ3SXwcDoktxMNTyDa/HCOadGpcZgJLbpu+HDUx8mI9A2kQ== X-Received: by 2002:a17:906:d9cf:b0:6ee:32ef:8da with SMTP id qk15-20020a170906d9cf00b006ee32ef08damr17538522ejb.750.1650441734087; Wed, 20 Apr 2022 01:02:14 -0700 (PDT) Received: from oberon.zico.biz.com ([83.222.187.186]) by smtp.gmail.com with ESMTPSA id t12-20020a1709067c0c00b006e86db76851sm6393763ejo.193.2022.04.20.01.02.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Apr 2022 01:02:13 -0700 (PDT) From: "Tzvetomir Stoyanov (VMware)" To: y.karadz@gmail.com Cc: rostedt@goodmis.org, linux-trace-devel@vger.kernel.org Subject: [RFC PATCH v3 4/4] trace-cruncher: Example script for uprobes high level API Date: Wed, 20 Apr 2022 11:02:06 +0300 Message-Id: <20220420080206.252356-5-tz.stoyanov@gmail.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220420080206.252356-1-tz.stoyanov@gmail.com> References: <20220420080206.252356-1-tz.stoyanov@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Proposed example illustrates how to use uprobes high level API to trace all functions for user program. It can be attached to already running program, or run the program and trace its execution. Signed-off-by: Tzvetomir Stoyanov (VMware) --- examples/user_trace.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 examples/user_trace.py diff --git a/examples/user_trace.py b/examples/user_trace.py new file mode 100755 index 0000000..3fdef26 --- /dev/null +++ b/examples/user_trace.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +""" +SPDX-License-Identifier: CC-BY-4.0 + +Copyright 2022 VMware Inc, Tzvetomir Stoyanov (VMware) +""" + +import sys +import shutil + +import tracecruncher.ftracepy as ft + +if __name__ == "__main__": + if len(sys.argv) < 2: + print('Usage: ', sys.argv[0], ' [PROCESS or PID]') + sys.exit(1) + + # Create new Ftrace instance to work in. The tracing in this new instance + # is not going to be enabled yet. + inst = ft.create_instance(tracing_on=False) + + # Create a user tracing context for given process, exclude the libraries + if sys.argv[1].isdigit(): + utrace = ft.user_trace(pid=int(sys.argv[1]), follow_libs=False) + else: + utrace = ft.user_trace(argv=sys.argv[1:], follow_libs=False) + + # Trace execution of all available functions in the given process + utrace.add_function(fname="*") + + # Add trace points on functions return as well + utrace.add_ret_function(fname="*") + + # Start tracing in an instance + utrace.enable(instance=inst, wait=False) + + # Read the trace buffer during the trace until ctrl-c is pressed + ft.read_trace(instance=inst)