From patchwork Thu Apr 24 21:47:14 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Serhei Makarov X-Patchwork-Id: 110973 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 5B1973858D39 for ; Thu, 24 Apr 2025 21:54:11 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 5B1973858D39 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=serhei.io header.i=@serhei.io header.a=rsa-sha256 header.s=fm3 header.b=W9krbEoH; dkim=pass (2048-bit key, unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.a=rsa-sha256 header.s=fm3 header.b=ON4VfV9N X-Original-To: elfutils-devel@sourceware.org Delivered-To: elfutils-devel@sourceware.org Received: from fhigh-b5-smtp.messagingengine.com (fhigh-b5-smtp.messagingengine.com [202.12.124.156]) by sourceware.org (Postfix) with ESMTPS id 51AF83858D21 for ; Thu, 24 Apr 2025 21:48:59 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 51AF83858D21 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=serhei.io Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=serhei.io ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 51AF83858D21 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=202.12.124.156 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1745531339; cv=none; b=ScHJIu5S/Hm54XmbxxznNOXwVxLKC51gSAPkyCiaYzj5CQ5sVt4eC2UmtbUu0N2CNlVN0EREaeRAp7KPxINNMSPZNTOUk7tEUNmbTs1FuV9dmBwyCknVs5tcdCd+pGZXo2ODqw9HUmfWu1VZ33kPUhXgNBz5d16y/JBi0XKdrrc= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1745531339; c=relaxed/simple; bh=6+LjSbE0T0kmABEd+/FH9XY/tBKWZ8Qrr6v3mToqAFU=; h=DKIM-Signature:DKIM-Signature:From:To:Subject:Date:Message-ID: MIME-Version; b=BRAxSPHYHazbdDFYe0sVlCSFZoDRMT0wSFDyc2hmIP958GPgPWx2VOptbnqhRF9cifNBNTFq3liVMAqYw5KY1XlaNy1+UlGRS9WkgmgUnU5bE9oB7GcdvlT88rT8q55QfTLsHYPtYt5ES/ExzLmrlvu0GhlxUR1lgDOci+n59po= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 51AF83858D21 Received: from phl-compute-01.internal (phl-compute-01.phl.internal [10.202.2.41]) by mailfhigh.stl.internal (Postfix) with ESMTP id EBF15254023F; Thu, 24 Apr 2025 17:48:58 -0400 (EDT) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-01.internal (MEProxy); Thu, 24 Apr 2025 17:48:59 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=serhei.io; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm3; t=1745531338; x= 1745617738; bh=NSeHL6Dw5ztXgO8OoodrB0RQjN1Q76gPaIeHYKV57T8=; b=W 9krbEoHL/4zFGDCjvF6pOyko/8PWH9kMRDB/cgDda6dD86nFtfqxffy17key8zcZ bAuEyuNfJP6wAhzwcBkOKrH41a1pINAZE6yqCZSacI7IyfSdFwb/WjWjejwNapi/ 7IZhztSJuDXdt3s8KtLZKL0/IOB/Y7MeTnaYnuFmIcyCtnP98ow5vMQv3XtxuScX aGA1XGH1I78CtBreOUuj7+20ItqAXQmHOsqdjDGCHulG1cTIJX1va2TRixPahEUT 0xBw3sJnV9AuuGyqFKVES5Mb1HhlW/+x/TfQHatYbAbX/+dC3QmN7VqP+Es/rpGj r6nO97KmqVCyHXMJCyR3g== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm3; t=1745531338; x=1745617738; bh=N SeHL6Dw5ztXgO8OoodrB0RQjN1Q76gPaIeHYKV57T8=; b=ON4VfV9NKYlKLCA3D erOj4Q5+aFFwkoR7tviELOCwGJ9fkWEUcbKN9767KGmW25uyJGfKGyVHcf7hjSFR AxGk7vL6wO9fAoGK5G7DOwaX7zfgE3ct6OZU4q3l8QVsQihVtVjzAeCCWYPN0MwR 2yvYitKlg+mzQ65dzKZh+cBCDRUU8MNILbQD2JvitAcpSz31niGkGUZbhdU59M0I ukouR2D8xJ/UBRkSUQztGa6DfLuWb5a+QGmFEz0rpec6bsoaECSA773hYT0G4ju1 QFDjPyAa/29cAjhZhV6mmgBUOW7WLtWFfrE/uPLjNCHrYLPSvW6RpKbLKp0GL6IC 3IRLQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddvhedtheelucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucenucfjughrpefhvf evufffkffojghfggfgsedtkeertdertddtnecuhfhrohhmpefuvghrhhgvihcuofgrkhgr rhhovhcuoehsvghrhhgvihesshgvrhhhvghirdhioheqnecuggftrfgrthhtvghrnheptd euudfftdehgfetheehlefghefffeegieekheefgfdufefgudeifefhvefgudeknecuvehl uhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhepshgvrhhhvghise hsvghrhhgvihdrihhopdhnsggprhgtphhtthhopedvpdhmohguvgepshhmthhpohhuthdp rhgtphhtthhopegvlhhfuhhtihhlshdquggvvhgvlhesshhouhhrtggvfigrrhgvrdhorh hgpdhrtghpthhtohepshgvrhhhvghisehsvghrhhgvihdrihho X-ME-Proxy: Feedback-ID: i572946fc:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 24 Apr 2025 17:48:58 -0400 (EDT) From: Serhei Makarov To: elfutils-devel@sourceware.org Cc: Serhei Makarov Subject: [PATCH v5 11/12] libdwfl_stacktrace [11/12]: add dwflst_perf_sample_getframes Date: Thu, 24 Apr 2025 17:47:14 -0400 Message-ID: <20250424214715.306147-12-serhei@serhei.io> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20250424214715.306147-1-serhei@serhei.io> References: <20250424214715.306147-1-serhei@serhei.io> MIME-Version: 1.0 X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, JMQ_SPF_NEUTRAL, RCVD_IN_DNSWL_LOW, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: elfutils-devel@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Elfutils-devel mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: elfutils-devel-bounces~patchwork=sourceware.org@sourceware.org Changes for v4: - Separate out libdwfl_stacktrace, as requested. Changes for v3: - use const void *stack, not void *, to allow users to pass a const stack sample Changes for v2: - use renamed __libdwfl_set_initial_registers_thread - oops, should use provided sample_arg->perf_regs_mask in sample_set_initial registers * * * This is a new interface for unwinding that doesn't require the Dwfl to be attached to a live process (via ptrace) or via corefile. Instead, data from a perf_events stack sample is provided along with an Elf struct used to identify the architecture. Based on code from eu-stacktrace. * libdwfl_stacktrace/libdwfl_stacktrace.h (dwflst_perf_sample_getframes): New function. * libdwfl_stacktrace/dwflst_perf_frame.c (struct __libdwfl_stacktrace_perf_sample_info): New struct, based on src/stacktrace.c struct sample_arg. (sample_next_thread): New function, based on src/stacktrace.c. (sample_getthread): Ditto. (copy_word_64): New macro, based on src/stacktrace.c. (copy_word_32): Ditto. (copy_word): Ditto. (elf_memory_read): New function, based on src/stacktrace.c. (sample_memory_read): Ditto. (sample_set_initial_registers): Ditto. (sample_detach): Ditto. (sample_thread_callbacks): New struct, set of callbacks based on src/stacktrace.c sample_thread_callbacks. (dwflst_perf_sample_getframes): New function, based on parts of src/stacktrace.c sysprof_find_dwfl. If the Dwfl is not attached, attaches it with sample_thread_callbacks and __libdwfl_stacktrace_perf_sample_info. Populates the __libdwfl_stacktrace_perf_sample_info with data from the stack sample and calls dwfl_getthread_frames to unwind it using the sample_thread_callbacks. * libdw/libdw.map (ELFUTILS_0.193): Add dwflst_perf_sample_getframes. --- libdw/libdw.map | 1 + libdwfl_stacktrace/dwflst_perf_frame.c | 193 +++++++++++++++++++++++- libdwfl_stacktrace/libdwfl_stacktrace.h | 15 +- 3 files changed, 207 insertions(+), 2 deletions(-) diff --git a/libdw/libdw.map b/libdw/libdw.map index 688e415c..137b5738 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -405,4 +405,5 @@ ELFUTILS_0.193_EXPERIMENTAL { dwflst_module_gettracker; dwflst_tracker_linux_proc_find_elf; dwflst_tracker_find_pid; + dwflst_perf_sample_getframes; }; diff --git a/libdwfl_stacktrace/dwflst_perf_frame.c b/libdwfl_stacktrace/dwflst_perf_frame.c index 79e8e482..591097e8 100644 --- a/libdwfl_stacktrace/dwflst_perf_frame.c +++ b/libdwfl_stacktrace/dwflst_perf_frame.c @@ -60,4 +60,195 @@ uint64_t dwflst_perf_sample_preferred_regs_mask (GElf_Half machine) return 0; } -/* XXX dwflst_perf_sample_getframes to be added in subsequent patch */ +struct __libdwfl_stacktrace_perf_sample_info { + pid_t pid; + pid_t tid; + Dwarf_Addr base_addr; + const uint8_t *stack; + size_t stack_size; + const Dwarf_Word *regs; + uint n_regs; + uint64_t perf_regs_mask; + uint abi; + Dwarf_Addr pc; +}; + +/* The next few functions imitate the corefile interface for a single + stack sample, with very restricted access to registers and memory. */ + +/* Just yield the single thread id matching the sample. */ +static pid_t +sample_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, + void **thread_argp) +{ + struct __libdwfl_stacktrace_perf_sample_info *sample_arg = + (struct __libdwfl_stacktrace_perf_sample_info *)dwfl_arg; + if (*thread_argp == NULL) + { + *thread_argp = (void *)0xea7b3375; + return sample_arg->tid; + } + else + return 0; +} + +/* Just check that the thread id matches the sample. */ +static bool +sample_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t tid, + void *dwfl_arg, void **thread_argp) +{ + struct __libdwfl_stacktrace_perf_sample_info *sample_arg = + (struct __libdwfl_stacktrace_perf_sample_info *)dwfl_arg; + *thread_argp = (void *)sample_arg; + if (sample_arg->tid != tid) + { + __libdwfl_seterrno(DWFL_E_INVALID_ARGUMENT); + return false; + } + return true; +} + +#define copy_word_64(result, d) \ + if ((((uintptr_t) (d)) & (sizeof (uint64_t) - 1)) == 0) \ + *(result) = *(uint64_t *)(d); \ + else \ + memcpy ((result), (d), sizeof (uint64_t)); + +#define copy_word_32(result, d) \ + if ((((uintptr_t) (d)) & (sizeof (uint32_t) - 1)) == 0) \ + *(result) = *(uint32_t *)(d); \ + else \ + memcpy ((result), (d), sizeof (uint32_t)); + +#define copy_word(result, d, abi) \ + if ((abi) == PERF_SAMPLE_REGS_ABI_64) \ + { copy_word_64((result), (d)); } \ + else if ((abi) == PERF_SAMPLE_REGS_ABI_32) \ + { copy_word_32((result), (d)); } \ + else \ + *(result) = 0; + +static bool +elf_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) +{ + struct __libdwfl_stacktrace_perf_sample_info *sample_arg = + (struct __libdwfl_stacktrace_perf_sample_info *)arg; + Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (dwfl, addr); + Dwarf_Addr bias; + Elf_Scn *section = INTUSE(dwfl_module_address_section) (mod, &addr, &bias); + + if (!section) + { + __libdwfl_seterrno(DWFL_E_ADDR_OUTOFRANGE); + return false; + } + + Elf_Data *data = elf_getdata(section, NULL); + if (data && data->d_buf && data->d_size > addr) { + uint8_t *d = ((uint8_t *)data->d_buf) + addr; + copy_word(result, d, sample_arg->abi); + return true; + } + __libdwfl_seterrno(DWFL_E_ADDR_OUTOFRANGE); + return false; +} + +static bool +sample_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) +{ + struct __libdwfl_stacktrace_perf_sample_info *sample_arg = + (struct __libdwfl_stacktrace_perf_sample_info *)arg; + /* Imitate read_cached_memory() with the stack sample data as the cache. */ + if (addr < sample_arg->base_addr || + addr - sample_arg->base_addr >= sample_arg->stack_size) + return elf_memory_read(dwfl, addr, result, arg); + const uint8_t *d = &sample_arg->stack[addr - sample_arg->base_addr]; + copy_word(result, d, sample_arg->abi); + return true; +} + +static bool +sample_set_initial_registers (Dwfl_Thread *thread, void *arg) +{ + struct __libdwfl_stacktrace_perf_sample_info *sample_arg = + (struct __libdwfl_stacktrace_perf_sample_info *)arg; + INTUSE(dwfl_thread_state_register_pc) (thread, sample_arg->pc); + Dwfl_Process *process = thread->process; + Ebl *ebl = process->ebl; + return ebl_set_initial_registers_sample + (ebl, sample_arg->regs, sample_arg->n_regs, + sample_arg->perf_regs_mask, sample_arg->abi, + __libdwfl_set_initial_registers_thread, thread); +} + +static void +sample_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg) +{ + struct __libdwfl_stacktrace_perf_sample_info *sample_arg = + (struct __libdwfl_stacktrace_perf_sample_info *)dwfl_arg; + free (sample_arg); +} + +static const Dwfl_Thread_Callbacks sample_thread_callbacks = + { + sample_next_thread, + sample_getthread, + sample_memory_read, + sample_set_initial_registers, + sample_detach, + NULL, /* sample_thread_detach */ + }; + +int +dwflst_perf_sample_getframes (Dwfl *dwfl, Elf *elf, + pid_t pid, pid_t tid, + const void *stack, size_t stack_size, + const Dwarf_Word *regs, uint n_regs, + uint64_t perf_regs_mask, uint abi, + int (*callback) (Dwfl_Frame *state, void *arg), + void *arg) +{ + /* TODO: Lock the dwfl to ensure attach_state does not interfere + with other dwfl_perf_sample_getframes calls. */ + + struct __libdwfl_stacktrace_perf_sample_info *sample_arg; + bool attached = false; + if (dwfl->process != NULL) + { + sample_arg = dwfl->process->callbacks_arg; + attached = true; + } + else + { + sample_arg = malloc (sizeof *sample_arg); + if (sample_arg == NULL) + { + __libdwfl_seterrno(DWFL_E_NOMEM); + return -1; + } + } + + sample_arg->pid = pid; + sample_arg->tid = tid; + sample_arg->stack = (const uint8_t *)stack; + sample_arg->stack_size = stack_size; + sample_arg->regs = regs; + sample_arg->n_regs = n_regs; + sample_arg->perf_regs_mask = perf_regs_mask; + sample_arg->abi = abi; + + if (! attached + && ! INTUSE(dwfl_attach_state) (dwfl, elf, pid, + &sample_thread_callbacks, sample_arg)) + return -1; + + /* Now that Dwfl is attached, we can access its Ebl: */ + Dwfl_Process *process = dwfl->process; + Ebl *ebl = process->ebl; + sample_arg->base_addr = ebl_sample_base_addr(ebl, regs, n_regs, + perf_regs_mask, abi); + sample_arg->pc = ebl_sample_pc(ebl, regs, n_regs, + perf_regs_mask, abi); + + return INTUSE(dwfl_getthread_frames) (dwfl, tid, callback, arg); +} diff --git a/libdwfl_stacktrace/libdwfl_stacktrace.h b/libdwfl_stacktrace/libdwfl_stacktrace.h index ed6a6a5c..ed0b612f 100644 --- a/libdwfl_stacktrace/libdwfl_stacktrace.h +++ b/libdwfl_stacktrace/libdwfl_stacktrace.h @@ -107,7 +107,20 @@ extern int dwflst_tracker_linux_proc_find_elf (Dwfl_Module *mod, void **userdata char **file_name, Elf **); -/* XXX dwflst_perf_sample_getframes to be added in subsequent patch */ +/* Like dwfl_thread_getframes, but iterates through the frames for a + linux perf_events stack sample rather than a live thread. Calls + dwfl_attach_state on DWFL, with architecture specified by ELF, ELF + must remain valid during Dwfl lifetime. Returns zero if all frames + have been processed by the callback, returns -1 on error, or the + value of the callback when not DWARF_CB_OK. -1 returned on error + will set dwfl_errno (). */ +int dwflst_perf_sample_getframes (Dwfl *dwfl, Elf *elf, pid_t pid, pid_t tid, + const void *stack, size_t stack_size, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t perf_regs_mask, uint32_t abi, + int (*callback) (Dwfl_Frame *state, void *arg), + void *arg) + __nonnull_attribute__ (1, 5, 7, 11); /* Returns the linux perf_events register mask describing a set of registers sufficient for unwinding on MACHINE, or 0 if libdwfl does