From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id +czAGgbwXWqP6RkAWB0awg (envelope-from ) for ; Mon, 20 Jul 2026 05:53:10 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=KU1Ujsnk; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 687C51E099; Mon, 20 Jul 2026 05:53:10 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 69E251E099 for ; Mon, 20 Jul 2026 05:53:06 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 9C3F44BA79BE for ; Mon, 20 Jul 2026 09:53:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9C3F44BA79BE Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=KU1Ujsnk Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 839C04BA79A8 for ; Mon, 20 Jul 2026 09:52:31 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 839C04BA79A8 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 839C04BA79A8 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1784541151; cv=none; b=S5UOo4XvLAWKwapn/1i9y8QIUXfLCeGnFdycW3pN3nIbeyeFUZZk4KhWsoKIqsFhVe1HVkA1uaiU7hlataiZoky5gQD3MDdrba8OCtJzJBApZz5If2axycWvZYlAgohRYVWV3/JG0D69LUo1zU0/sDkcWvzYCaijvSeaO3geUK8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1784541151; c=relaxed/simple; bh=KJRKyNcKRiugWaupMmnPGdmpmv80bDkf+Yi118kcWCg=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=n08jC/QeyMkcYrJk92uAbNhRzlN7K5CSlYC8eDJiBz7gpv2bNeIl+8xVLyYOkw1e+SDxXha39rY2BQlOzHkTmQN6h3oEOgIM/5bLDYvlnOPoqr04YBpJ7LD1KQjPrkDRivQgXhDXjV2jNQ41o2mArUuHgmMc/jZw2EruR0vgN1o= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=KU1Ujsnk DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 839C04BA79A8 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1784541151; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=zgzk3CZDgeT6PPzoY/JDFmR/Dj+qd+QrW94akCwjVRc=; b=KU1Ujsnk8QJIjxF22pahxE6Dl+wsz/FIt2kKyk4RgUW0lQfaXz+wvRT1weiEqs92nqrLzC 0xUnk40a6kuMriqOLu6qDEt5jtrEigInQ95PTJ+/ASJ+tSRbeOjCWNIBIuat4KkTSZ56ap ctYzPJT52YpNN4w2Nwzz9Gxf7EWXxSE= Received: from mail-wr1-f71.google.com (mail-wr1-f71.google.com [209.85.221.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-664--o3iuuzvMKm3hmVyRY0gCQ-1; Mon, 20 Jul 2026 05:52:29 -0400 X-MC-Unique: -o3iuuzvMKm3hmVyRY0gCQ-1 X-Mimecast-MFC-AGG-ID: -o3iuuzvMKm3hmVyRY0gCQ_1784541149 Received: by mail-wr1-f71.google.com with SMTP id ffacd0b85a97d-47f726186c4so764932f8f.2 for ; Mon, 20 Jul 2026 02:52:29 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784541148; x=1785145948; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=zgzk3CZDgeT6PPzoY/JDFmR/Dj+qd+QrW94akCwjVRc=; b=VPJe6qpmn4zTuUuaiPAufBOr8A37vyxBjongQ+ohvyN7qS2FbpI10olRX5iIrC03Wo PjUFwFeSBVTBXDTokFobtK6vnbhZZL8oMeK7Ckf4TeVxrDiotWcUuXTs+dHy4oDNkLoV RlLATx72Echt957UpoV/YfcT7dDCi64XI5pAB++zMwEHT4B5YNEE4gutAaX4z6IWvEOi nGAkKH2n1BBFAptUDcKGhARM78SYjfonVN28wKkQ9Y5brquKn1ZBNI9lTUlhArwNUWgt 61OXnva609E/FiSNnypU8X6SHQ+7eqcRZd/XKa18tYlXiGTpc4AoFDnOmPVCCO6l8DFx 1SBg== X-Gm-Message-State: AOJu0Yxp6+O0KK+dJxTg9K/Iqo68hXour4bioqP9VuxuJAN/SV4Lcr+j 8W/EoRX1qAxhH/jgYsL8mhT15GTU14s+nNcIUQWERh6/iWaskDfdEJYHV4r2YfIgQGa0srH51uS VnodngEA/HiRg29iI1YOAYm5Jn0WYDyXYvqzxu3cys8BN9sC6LI6i1FRzkCaHGZ7ocAGXGy+aPn X7/v3gU2EQRSuDbg03NUDBXk6YjXLGSieEGNpLGElfhC+wAl8= X-Gm-Gg: AfdE7cnVEDtXnr4QCOi2PhYbVuF+OzS0cjC6ZLi90lCWdpaejf1sufqLbjjhi8JTQ0Y kTQ0fnLBOycYxm5kp/3LSs7z47UgIZgpZfNp0S5YpE9Uv36MncrAl2HxfurK+F7p0hba/UkA+in isMdD91Lt+RzBY9H5lPgRNPTc529+UovzdAah1S8ykVPtPW6mPbxCXNTuQMuluYXqUsOraKQoHi DpNT13T6OabWzjFXG+QMuhHwWosMKejo7S6sqxzZXSEIKxzV5xleY9IAdQZt5FhWlhb6xstV19Y SwZ+WetMLdw6db6GssqtXHXfF3UccSV/NKrmCTgs4tjq98ENylr5cN/eZwdFFdD+8UXFRTXs X-Received: by 2002:a05:600c:1989:b0:495:699:84ca with SMTP id 5b1f17b1804b1-4954a798f17mr138298005e9.28.1784541147777; Mon, 20 Jul 2026 02:52:27 -0700 (PDT) X-Received: by 2002:a05:600c:1989:b0:495:699:84ca with SMTP id 5b1f17b1804b1-4954a798f17mr138297075e9.28.1784541146325; Mon, 20 Jul 2026 02:52:26 -0700 (PDT) Received: from localhost ([31.111.209.233]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49549c96fecsm315834615e9.11.2026.07.20.02.52.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 20 Jul 2026 02:52:25 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Eli Zaretskii , Simon Marchi Subject: [PATCHv8 2/4] gdb: introduce program_space::get_entry_point_info function Date: Mon, 20 Jul 2026 10:52:18 +0100 Message-Id: <84c61e3074647dcc35f9ba7f20f8b429642a07b5.1784541057.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: cc1nTT4hCS7WbB9wesvfmJBlcxsBeUAF6bd9bvHKsaE_1784541149 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org I noticed that when debugging a dynamically linked executable, if I started the inferior with 'starti' then used 'bt' I would see some bogus frames: (gdb) starti Starting program: /tmp/hello Program stopped. 0x00007ffff7fd3110 in _start () from /lib64/ld-linux-x86-64.so.2 (gdb) bt #0 0x00007ffff7fd3110 in _start () from /lib64/ld-linux-x86-64.so.2 #1 0x0000000000000001 in ?? () #2 0x00007fffffffac13 in ?? () #3 0x0000000000000000 in ?? () (gdb) This surprised me as 'backtrace past-entry' was off: (gdb) show backtrace past-entry Whether backtraces should continue past the entry point of a program is off. I was expecting GDB to stop the backtrace at the inferior's entry address. Frame unwinding starts in get_prev_frame, and in here we find this block: if (this_frame->level >= 0 && get_frame_type (this_frame) == NORMAL_FRAME && !user_set_backtrace_options.backtrace_past_entry && frame_pc.has_value () && inside_entry_func (this_frame)) { frame_debug_got_null_frame (this_frame, "inside entry func"); return NULL; } Which uses inside_entry_func to terminate the backtrace when we reach the entry frame. The inside_entry_func function calls current_program_space->exec_entry_point_address_if_available and uses the result to figure out if we are in the entry frame. And here the problem becomes obvious, we are only checking if we are in the "entry frame" for the main executable, not for the inferior as a whole. And indeed, if I compile the same test program as a static binary, where there will be no run-time linker, and the entry point of the executable is the entry point for the inferior, then the 'bt' problem I saw above goes away. This suggests, I think, that we need to track two different entry addresses, the entry address for the executable file, and the entry address for the entire inferior. Then, for dynamically linked executables, these two addresses can be different, the former will still be the same address within the main executable file, while the latter will be the address of the entry point within the run-time linker. If we had this information then we could extend inside_entry_func to check both addresses, and the 'bt' problem seen above will be resolved. To make this information available I added a new solib_ops method, solib_ops::inferior_entry_point_address, for svr4 targets this figures out if the main executable is dynamically linked, and if it is, uses the AT_BASE auxv entry and the entry address pulled from the ELF header to compute the inferior entry address. I then added program_space::get_entry_point_info, which returns a struct containing the two entry point addresses, one comes from the new solib_ops method, and one comes from the existing method program_space::exec_entry_point_address_if_available. With the infrastructure in place I can then update inside_entry_func to check against both entry addresses. To aid in debugging GDB, I added a new maintenance command: maintenance info entry-address which just calls program_space::get_entry_point_info and then prints the two addresses. I left this as a maintenance command as I don't see much user utility in this right now, but it made it easier for me to see what GDB was doing, so I left the command in this commit. Reviewed-By: Eli Zaretskii Approved-By: Simon Marchi --- gdb/NEWS | 7 + gdb/doc/gdb.texinfo | 22 ++ gdb/frame.c | 10 +- gdb/progspace.c | 60 +++++ gdb/progspace.h | 49 ++++ gdb/solib-svr4.c | 85 +++++++ gdb/solib-svr4.h | 1 + gdb/solib.h | 14 ++ gdb/testsuite/gdb.base/bt-after-starti.exp | 259 +++++++++++++++++++++ gdb/testsuite/lib/gdb.exp | 39 ++++ 10 files changed, 541 insertions(+), 5 deletions(-) create mode 100644 gdb/testsuite/gdb.base/bt-after-starti.exp diff --git a/gdb/NEWS b/gdb/NEWS index 8f40ca5cb11..9c51f476c84 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -179,6 +179,13 @@ disable skip These are new aliases for 'skip delete', 'skip enable', and 'skip disable' respectively. +maint info entry-address + Display the inferior and main executable entry addresses for the + current inferior. The inferior entry address is the address of the + first instruction in the inferior that was executed. The main + executable entry address is the address of the first instruction in + the main executable that will be executed. + * MI changes ** The "-trace-save" command no longer supports the "-ctf" flag. diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 0030698dcee..35fb9f78b08 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -43260,6 +43260,28 @@ Maintenance Commands Ignoring SystemTap probe libc longjmp in /lib64/libc.so.6.^M Ignoring SystemTap probe libc longjmp in /lib64/libc.so.6.^M @end smallexample + +@kindex maint info entry-address +@item maint info entry-address +Display the entry addresses for the currently selected inferior. Two +addresses are displayed, the inferior entry address and the executable +entry address. + +Neither address is the address of @code{main}. When a program is +started, low-level startup code (typically a function called +@code{_start}) runs before @code{main} is called. + +The executable entry address is the address of this startup code +within the main executable (@pxref{Files, ,Commands to Specify +Files}). + +The inferior entry address is the address of the very first +instruction executed when the inferior is started. For statically +linked programs this is the same as the executable entry address. For +dynamically linked programs the run-time linker must execute first in +order to load shared libraries, so the inferior entry address will be +an address within the run-time linker rather than within the main +executable. @end table The following command is useful for non-interactive invocations of diff --git a/gdb/frame.c b/gdb/frame.c index a83f0b20686..e61bfd8d1f7 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -2703,12 +2703,12 @@ inside_main_func (const frame_info_ptr &this_frame) static bool inside_entry_func (const frame_info_ptr &this_frame) { - std::optional entry_point - = current_program_space->exec_entry_point_address_if_available (); - if (!entry_point.has_value ()) - return false; + const entry_point_info &ep_info + = current_program_space->get_entry_point_info (); - return get_frame_func (this_frame) == *entry_point; + CORE_ADDR frame_func_addr = get_frame_func (this_frame); + return (ep_info.exec_entry_address () == frame_func_addr + || ep_info.inferior_entry_address () == frame_func_addr); } /* Return a structure containing various interesting information about diff --git a/gdb/progspace.c b/gdb/progspace.c index 0e8516f4640..4de32bc522f 100644 --- a/gdb/progspace.c +++ b/gdb/progspace.c @@ -26,6 +26,7 @@ #include #include "cli/cli-style.h" #include "observable.h" +#include "arch-utils.h" /* The last program space number assigned. */ static int last_program_space_num = 0; @@ -288,6 +289,55 @@ program_space::exec_entry_point_address () const /* See progspace.h. */ +entry_point_info +program_space::get_entry_point_info () const +{ + std::optional exec_entry_address + = this->exec_entry_point_address_if_available (); + + std::optional inferior_entry_address; + if (m_solib_ops != nullptr) + inferior_entry_address = m_solib_ops->inferior_entry_point_address (); + + return entry_point_info (std::move (inferior_entry_address), + std::move (exec_entry_address)); +} + +/* Implement the 'maint info entry-address' command. */ + +static void +maintenance_info_entry_address (const char *args, int from_tty) +{ + if (args != nullptr && *args != '\0') + error (_("unknown argument: %s"), args); + + struct gdbarch *gdbarch = get_current_arch (); + + const entry_point_info &ep_info + = current_program_space->get_entry_point_info (); + + /* Display a single entry address ADDR with TITLE as a description. */ + auto display_entry_address + = [&gdbarch] (const char *title, + const std::optional &addr) -> void + { + gdb_puts (title); + if (addr.has_value ()) + fputs_styled (paddress (gdbarch, addr.value ()), + address_style.style (), gdb_stdout); + else + fputs_styled ("", metadata_style.style (), gdb_stdout); + gdb_puts ("\n"); + }; + + display_entry_address (_("Inferior entry address: "), + ep_info.inferior_entry_address ()); + display_entry_address (_("Executable entry address: "), + ep_info.exec_entry_address ()); +} + +/* See progspace.h. */ + bool program_space::has_partial_symbols () { @@ -498,6 +548,16 @@ initialize_progspace () _("Info about currently known program spaces."), &maintenanceinfolist); + add_cmd ("entry-address", class_maintenance, + maintenance_info_entry_address, + _("Information about the current inferior's entry addresses.\n\ +Display the address of the first instruction executed within the\n\ +inferior, and the first instruction executed within the main executable.\n\ +The entry address of the whole inferior will be prior to the\n\ +inferior starting, and either or both addresses can be if\n\ +GDB is unable to find the required information."), + &maintenanceinfolist); + /* There's always one program space. Note that this function isn't an automatic _initialize_foo function, since other _initialize_foo routines may need to install their per-pspace diff --git a/gdb/progspace.h b/gdb/progspace.h index 1ae1e42f3bb..be2d3f7e88c 100644 --- a/gdb/progspace.h +++ b/gdb/progspace.h @@ -67,6 +67,50 @@ new_address_space () return address_space_ref_ptr::new_reference (new address_space); } +/* Class for tracking two possible entry points that an inferior might + have, the entry point for the entire inferior, and the entry point + within the main executable. */ + +struct entry_point_info +{ + entry_point_info (std::optional inferior_entry_address, + std::optional exec_entry_address) + : m_inferior_entry_address (inferior_entry_address), + m_exec_entry_address (exec_entry_address) + { /* Nothing. */ } + + DISABLE_COPY_AND_ASSIGN (entry_point_info); + + /* The entry address within the inferior as a whole. See the member + variable definition below for more details. */ + std::optional inferior_entry_address () const + { + return m_inferior_entry_address; + } + + /* The entry address within the main executable. See the member + variable definition below for more details. */ + std::optional exec_entry_address () const + { + return m_exec_entry_address; + } + +private: + /* The entry address within the inferior. This can be outside of the + main executable, e.g. for dynamically linked executables this could + be the entry address for the run-time linker. For statically linked + executables this will be the entry address of the main executable. + This can be empty if GDB doesn't know how to figure out the correct + entry address for any reason. */ + std::optional m_inferior_entry_address; + + /* The entry address within the main executable. This can be empty if + the main executable is not set yet, or GDB doesn't have an objfile + associated with the main executable, e.g. in some attach, or remote + debug cases. */ + std::optional m_exec_entry_address; +}; + /* A program space represents a symbolic view of an address space. Roughly speaking, it holds all the data associated with a non-running-yet program (main executable, main symbols), and when @@ -323,6 +367,11 @@ struct program_space return m_target_sections; } + /* Return information about the entry point in the main executable, and + the entry point for the inferior, which might be different from the + main executable. */ + entry_point_info get_entry_point_info () const; + /* If there is a valid and known entry point in the main executable of this program space, return it. Otherwise return an empty optional. */ std::optional exec_entry_point_address_if_available () const; diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c index 8e3de4d3ea1..12f3e783744 100644 --- a/gdb/solib-svr4.c +++ b/gdb/solib-svr4.c @@ -3777,6 +3777,91 @@ svr4_solib_ops::get_solibs_in_ns (int nsid) const return ns_solibs; } +/* See solib.h. */ + +std::optional +svr4_solib_ops::inferior_entry_point_address () const +{ + std::optional interp_name_holder + = svr4_find_program_interpreter (); + + /* No interpreter means this is a static executable. Ask the + program_space for the entry address within the main executable. */ + if (!interp_name_holder.has_value ()) + return m_pspace->exec_entry_point_address_if_available (); + + /* For a dynamically linked executable the inferior's true entry point + is the entry point of the dynamic linker. We find this using the + AT_BASE auxiliary vector entry, which gives the dynamic linker's + load address, combined with e_entry address pulled from the + inferior. We assume that the ELF header can be read from AT_BASE. */ + CORE_ADDR at_base_addr; + if (target_auxv_search (AT_BASE, &at_base_addr) <= 0) + return {}; + + /* Determine ELF architecture type. Use the size of a program header + entry to determine which ELF header we can expect to find. */ + size_t e_entry_offset = 0; + size_t e_entry_size = 0; + CORE_ADDR at_phent; + if (target_auxv_search (AT_PHENT, &at_phent) <= 0) + return {}; + if (at_phent == sizeof (Elf32_External_Phdr)) + { + e_entry_offset = offsetof (Elf32_External_Ehdr, e_entry); + e_entry_size = 4; + } + else if (at_phent == sizeof (Elf64_External_Phdr)) + { + e_entry_offset = offsetof (Elf64_External_Ehdr, e_entry); + e_entry_size = 8; + } + else + return {}; + + /* Architecture of the current inferior. */ + gdbarch *gdbarch = current_inferior ()->arch (); + + /* Read the entry address from the ELF header at AT_BASE_ADDR. */ + CORE_ADDR e_entry; + gdb_byte buffer[sizeof (CORE_ADDR)]; + if (target_read_memory (at_base_addr + e_entry_offset, buffer, e_entry_size)) + return {}; + bfd_endian byte_order = gdbarch_byte_order (gdbarch); + e_entry = extract_unsigned_integer (buffer, e_entry_size, byte_order); + + /* Ensure AT_BASE_ADDR has proper sign in its possible upper bits so + that `+ at_base_addr' will overflow CORE_ADDR width not creating + invalid addresses like 0x101234567 for 32bit inferiors on 64bit + GDB. */ + int addr_bit = gdbarch_addr_bit (gdbarch); + if (addr_bit < (sizeof (CORE_ADDR) * HOST_CHAR_BIT)) + { + CORE_ADDR space_size = (CORE_ADDR) 1 << addr_bit; + + gdb_assert (at_base_addr < space_size); + + /* E_ENTRY exceeding SPACE_SIZE would be for prelinked + 64bit ld.so with 32bit executable, it should not happen. */ + if (e_entry < space_size + && e_entry + at_base_addr >= space_size) + at_base_addr -= space_size; + } + + /* Compute the entry address. */ + e_entry = at_base_addr + e_entry; + + /* Handle the case where E_ENTRY is a function descriptor. Also remove + any non-address (e.g. tag) bits from E_ENTRY. */ + e_entry + = gdbarch_convert_from_func_ptr_addr (gdbarch, e_entry, + current_inferior ()->top_target ()); + e_entry + = gdbarch_addr_bits_remove (gdbarch, e_entry); + + return e_entry; +} + INIT_GDB_FILE (svr4_solib) { gdb::observers::free_objfile.attach (svr4_free_objfile_observer, diff --git a/gdb/solib-svr4.h b/gdb/solib-svr4.h index 3078a092778..f76f350d951 100644 --- a/gdb/solib-svr4.h +++ b/gdb/solib-svr4.h @@ -115,6 +115,7 @@ struct svr4_solib_ops : public solib_ops void iterate_over_objfiles_in_search_order (iterate_over_objfiles_in_search_order_cb_ftype cb, objfile *current_objfile) const override; + std::optional inferior_entry_point_address () const override; /* Return the appropriate link map offsets table for the architecture. */ virtual link_map_offsets *fetch_link_map_offsets () const = 0; diff --git a/gdb/solib.h b/gdb/solib.h index 662d467b0cc..2db836dc005 100644 --- a/gdb/solib.h +++ b/gdb/solib.h @@ -283,6 +283,20 @@ struct solib_ops (iterate_over_objfiles_in_search_order_cb_ftype cb, objfile *current_objfile) const; + /* Return the inferior entry point address. This isn't always the entry + point of the main executable, but will be the actual entry point where + the inferior starts (or started) executing. + + For example on SVR4 targets, for dynamically linked executables, this + will be the entry address of the dynamic linker. But for statically + linked executables, this will be the entry point of the main + executable. + + If the entry address cannot be found then an empty optional is + returned. */ + virtual std::optional inferior_entry_point_address () const + { return {}; } + protected: /* The program space for which this solib_ops was created. */ program_space *m_pspace; diff --git a/gdb/testsuite/gdb.base/bt-after-starti.exp b/gdb/testsuite/gdb.base/bt-after-starti.exp new file mode 100644 index 00000000000..2b452365f89 --- /dev/null +++ b/gdb/testsuite/gdb.base/bt-after-starti.exp @@ -0,0 +1,259 @@ +# Copyright 2026 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Check that 'bt' from the very first instruction (where 'starti' +# stops GDB) doesn't display any unexpected frames. + +standard_testfile main.c + +# Build default executable, hopefully dynamically linked, but we check +# this below. +foreach pie_mode { pie nopie } { + if { [build_executable "failed to build" $testfile-$pie_mode $srcfile \ + [list debug $pie_mode]] } { + return + } +} + +# Build a statically linked executable. +set testfile_static ${testfile}-static +if { [build_executable "failed to build" $testfile_static $srcfile \ + {debug additional_flags=-static}] } { + return +} + +# Run the 'maint info entry-address' command. Expect INF_RE to match +# the inferior entry address and EXE_RE to match the executable entry +# address. TESTNAME is used as the test name. +proc check_maint_info_entry_addr { inf_re exe_re testname } { + gdb_test "maint info entry-address" \ + [multi_line \ + "Inferior entry address: ${inf_re}" \ + "Executable entry address: ${exe_re}"] \ + $testname +} + +# Use 'info proc mappings' to find the address of the first mapping of +# TESTFILE, return this address. If the address cannot be found then +# the empty string is returned. +# +# This emits a PASS or FAIL depending on whether the address is found +# or not. +proc get_mapped_file_base_addr { testfile } { + set base_addr "" + gdb_test_multiple "info proc mappings" "" -lbl { + -re "\r\n($::hex) \[^\r\n\]+/${testfile}\\s*(?=\r\n)" { + if { $base_addr eq "" } { + set base_addr $expect_out(1,string) + } + exp_continue + } + -re "\r\n$::gdb_prompt $" { + gdb_assert { $base_addr ne "" } \ + "find base address of $testfile" + } + } + return $base_addr +} + +# Use 'objdump' to extract the entry point address from TESTFILE. +proc get_exec_entry_address { testfile } { + set objdump_program [gdb_find_objdump] + set command "exec $objdump_program -f [standard_output_file $testfile]" + verbose -log "command is $command" + set result [catch {{*}$command} output] + verbose -log "result is $result" + verbose -log "output is $output" + + set testname "get_exec_entry_address" + + if {$result != 0} { + fail $testname + return "UNKNOWN" + } + + if {![regexp "\nstart address ($::hex)\n" $output trash addr]} { + fail "$testname (no entry point address header)" + return "UNKNOWN" + } + + # For ARM the least significant bit will be set to 1 for thumb + # code, clear that now. + if { [istarget "arm*-*-*"] } { + set addr [expr {$addr & ~0x1}] + } + + return 0x[format %lx $addr] +} + +# Start TESTFILE using 'starti'. Check if the 'backtrace' command +# prints any additional frames; we don't expect any. Create a core +# file from the running inferior. Restart GDB and load the core file, +# check that GDB can still figure out the entry addresses. +# +# When IS_DYNAMIC is true then TESTFILE is a dynamically linked +# executable, otherwise TESTFILE is statically linked. +proc run_test { testfile is_dynamic } { + clean_restart $testfile + + set exe_entry_addr [get_exec_entry_address $testfile] + + # The inferior entry address is always unknown before starting the + # inferior because GDB has no solib_ops yet, and it is only + # through that that we figure out the inferior entry address. + check_maint_info_entry_addr "" $exe_entry_addr \ + "check 'maint info entry-address' before inferior starts" + + if {$::use_gdb_stub && [target_info exists gdb,do_reload_on_run]} { + # This is the path taken by the 'native-gdbserver' board. + # Start gdbserver and connect, this should leave the inferior + # at the first instruction. + if { [gdb_reload] != 0 } { + return -1 + } + pass "connected to gdbserver" + } else { + # Start inferior with 'starti' and then wait for a prompt. + if { [gdb_starti_cmd] < 0 } { + fail "could not issue starti command" + return -1 + } + gdb_test "" "Program stopped.*" "prompt after starti" + } + + # Not every target supports finding the process entry point. + # Targets that don't support this report '', we check for + # this too on those targets. + if {[supports_process_entry_point]} { + set pc [get_hexadecimal_valueof "\$pc" "UNKNOWN" \ + "get current program counter"] + + if { !$is_dynamic } { + # In static binaries, the first address should be the first + # address in the executable. + gdb_assert { $pc == $exe_entry_addr } \ + "stopped at executable entry address" + } + + set inf_entry_addr $pc + } else { + set inf_entry_addr "" + } + + # If using PIE then the executable entry address we looked up from + # the objfile will have been relocated. Attempt to compute the + # relocated address and updated EXE_ENTRY_ADDR, otherwise set + # EXE_ENTRY_ADDR to a generic pattern that matches any address. + if {[exec_is_pie [standard_output_file $testfile]]} { + set base_addr [get_mapped_file_base_addr $testfile] + if { $base_addr ne "" } { + set exe_entry_addr \ + 0x[format %lx [expr {$base_addr + $exe_entry_addr}]] + } else { + # We failed to find the base address. The + # get_mapped_file_base_addr will have already emitted a + # FAIL. Set the expected pattern to something generic and + # push on, we can still test a bunch of things, we just + # don't know the address is correct. + set exe_entry_addr $::hex + } + } + + check_maint_info_entry_addr $inf_entry_addr $exe_entry_addr \ + "check 'maint info entry-address' after starti" + + # Allow backtrace past the entry frame, count how many frames GDB + # finds. If on this target there are no additional frames then + # the following test isn't going to tell us much, so skip it. + gdb_test_no_output "set backtrace past-entry on" + set frame_count 0 + set first_frame_is_unnamed false + gdb_test_multiple "bt" "count possible frames" -lbl { + -re "\r\n#0\\s+$::hex in \\?\\? \\(\\) \[^\r\n\]+(?=\r\n)" { + incr frame_count + set first_frame_is_unnamed true + exp_continue + } + -re "\r\n#($::decimal)\\s+\[^\r\n\]+(?=\r\n)" { + incr frame_count + exp_continue + } + -re "\r\n$::gdb_prompt $" { + gdb_assert { $frame_count > 0 } $gdb_test_name + } + } + + if { $frame_count <= 1 } { + unsupported "no additional frames that GDB can hide" + } elseif { $first_frame_is_unnamed } { + # GDB's entry frame detection relies on figuring out the $pc + # for the start of the frame. We know that frame #0 should be + # the entry frame, but if it is nameless then GDB will have + # failed to find the start of the frame, and so will not be + # able to perform entry frame detection. + unsupported "first frame is unnamed" + } else { + # Turn off backtrace past the entry frame. Use the 'bt' command, + # and check we see only a single frame. + gdb_test_no_output "set backtrace past-entry off" + gdb_test "bt" "^#0 \[^\r\n\]+" \ + "single frame when backtrace past-entry is off" + + # Check 'bt -past-entry'. + set frame_count_with_past_entry_flag 0 + gdb_test_multiple "bt -past-entry" "count frames again" -lbl { + -re "\r\n#($::decimal)\\s+\[^\r\n\]+(?=\r\n)" { + incr frame_count_with_past_entry_flag + exp_continue + } + -re "\r\n$::gdb_prompt $" { + gdb_assert \ + { $frame_count == $frame_count_with_past_entry_flag } \ + $gdb_test_name + } + } + } + + # Create a core file. Restart GDB. Load the core file. Check + # that 'maint info entry-address' gives the correct output. + set corefile [host_standard_output_file $testfile].core + if {![gdb_gcore_cmd $corefile "generate corefile"]} { + return + } + + clean_restart $testfile + + if {[gdb_core_cmd $corefile "load corefile"] != 1} { + return + } + + check_maint_info_entry_addr $inf_entry_addr $exe_entry_addr \ + "check 'maint info entry-address' after loading core file" +} + +with_test_prefix "dynamic" { + foreach_with_prefix pie_mode { pie nopie } { + set dyln_name [section_get $binfile-$pie_mode .interp] + if { $dyln_name eq "" } { + unsupported "couldn't find dynamic linker name" + } else { + run_test $testfile-$pie_mode true + } + } +} + +with_test_prefix "static" { + run_test $testfile_static false +} diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index a40c87c6727..11aa6bfbf35 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -4217,6 +4217,45 @@ proc is_aarch64_target {} { return [expr {![is_aarch32_target]}] } +# Return true for svr4 targets, otherwise, return false. + +proc is_svr4_target {} { + return [expr {[istarget *-linux*] || [istarget *-freebsd*] + || [istarget *-netbsd*] || [istarget *-openbsd*] + || [istarget *-solaris*] || [istarget *-gnu]}] +} + +# Return true for targets that support finding the whole process entry +# address, otherwise, return false. + +proc supports_process_entry_point {} { + # SVR4 targets support finding the entry point. This is done + # within GDB so will work even for remote targets. + if {[is_svr4_target]} { + return true + } + + # Windows and Darwin don't currently support this. + if {[istarget *-*-mingw*] + || [istarget *-*-cygwin*] + || [istarget *-*-pe*] + || [istarget *-*-darwin*]} { + return false + } + + # For remote targets there is no RSP packet to retrieve the entry + # point, so this won't work unless the solib code can handle this + # within GDB, see the svr4 check above. + if {[gdb_protocol_is_remote]} { + return false + } + + # Assume everything else supports this by default. If a test + # fails because we get here then either fix GDB to support this + # feature, or add a new deny list entry above. + return true +} + # Return 1 if displaced stepping is supported on target, otherwise, return 0. proc support_displaced_stepping {} { -- 2.25.4