diff options
Diffstat (limited to 'gdb')
77 files changed, 2112 insertions, 556 deletions
diff --git a/gdb/MAINTAINERS b/gdb/MAINTAINERS index 927be43..187a1f6 100644 --- a/gdb/MAINTAINERS +++ b/gdb/MAINTAINERS @@ -366,7 +366,6 @@ the native maintainer when resolving ABI issues. mcore Deleted mep --target=mep-elf - Kevin Buettner kevinb@redhat.com microblaze --target=microblaze-xilinx-elf --target=microblaze-linux-gnu @@ -514,13 +513,9 @@ ARM Richard Earnshaw rearnsha@arm.com Blackfin Mike Frysinger vapier@gentoo.org CRIS Hans-Peter Nilsson hp@axis.com IA64 Jeff Johnston jjohnstn@redhat.com -PowerPC Kevin Buettner kevinb@redhat.com S390 Ulrich Weigand uweigand@de.ibm.com djgpp DJ Delorie dj@delorie.com [Please use this address to contact DJ about DJGPP] -ia64 Kevin Buettner kevinb@redhat.com -AIX Kevin Buettner kevinb@redhat.com -GNU/Linux PPC native Kevin Buettner kevinb@redhat.com Pascal support Pierre Muller muller@sourceware.org diff --git a/gdb/Makefile.in b/gdb/Makefile.in index b726b1d..1619c82 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -741,6 +741,7 @@ ALL_64_TARGET_OBS = \ amd64-darwin-tdep.o \ amd64-dicos-tdep.o \ amd64-fbsd-tdep.o \ + amd64-gnu-tdep.o \ amd64-linux-tdep.o \ amd64-netbsd-tdep.o \ amd64-obsd-tdep.o \ @@ -1700,6 +1701,7 @@ ALLDEPFILES = \ amd64-dicos-tdep.c \ amd64-fbsd-nat.c \ amd64-fbsd-tdep.c \ + amd64-gnu-tdep.c \ amd64-linux-nat.c \ amd64-linux-tdep.c \ amd64-nat.c \ @@ -1755,7 +1757,6 @@ ALLDEPFILES = \ i386-dicos-tdep.c \ i386-fbsd-nat.c \ i386-fbsd-tdep.c \ - i386-gnu-nat.c \ i386-gnu-tdep.c \ i386-linux-nat.c \ i386-linux-tdep.c \ @@ -1882,6 +1883,7 @@ ALLDEPFILES = \ vax-tdep.c \ windows-nat.c \ windows-tdep.c \ + x86-gnu-nat.c \ x86-nat.c \ x86-tdep.c \ xcoffread.c \ @@ -62,6 +62,21 @@ binary-upload in qSupported reply stub doesn't report this feature supported, then GDB will not use the 'x' packet. +* MI changes + +** The =library-unloaded event now includes the 'ranges' field, which + has the same meaning as for the =library-loaded event. + +** The =library-unloaded event now includes the 'still-in-use' field. + This field is 'true' when a library is unloaded (removed from the + inferior's list of loaded libraries), but the mapping within the + inferior's address space is retained, as the library was mapped + multiple times, and the same mapping was being reused. In all + other cases, this field will have the value 'false'. + +* Support for stabs debugging format and the a.out/dbx object format is + deprecated, and will be removed in GDB 18. + *** Changes in GDB 16 * Support for Nios II targets has been removed as this architecture diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index 840f987..6e712b1 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -3290,6 +3290,9 @@ aarch64_pseudo_read_value (gdbarch *gdbarch, const frame_info_ptr &next_frame, return aarch64_pseudo_read_value_1 (next_frame, pseudo_reg_num, pseudo_offset - AARCH64_SVE_V0_REGNUM); + if (tdep->has_pauth () && pseudo_reg_num == tdep->ra_sign_state_regnum) + return value::zero (builtin_type (gdbarch)->builtin_uint64, lval_register); + gdb_assert_not_reached ("regnum out of bound"); } diff --git a/gdb/amd64-gnu-tdep.c b/gdb/amd64-gnu-tdep.c new file mode 100644 index 0000000..435095e --- /dev/null +++ b/gdb/amd64-gnu-tdep.c @@ -0,0 +1,231 @@ +/* Target-dependent code for the GNU Hurd. + Copyright (C) 2024 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "extract-store-integer.h" +#include "gdbcore.h" +#include "osabi.h" +#include "solib-svr4.h" + +#include "amd64-tdep.h" +#include "glibc-tdep.h" + +/* Recognizing signal handler frames. */ + +/* When the GNU/Hurd libc calls a signal handler, the return address points + inside the trampoline assembly snippet. + + If the trampoline function name can not be identified, we resort to reading + memory from the process in order to identify it. */ + +static const gdb_byte gnu_sigtramp_code[] = +{ +/* rpc_wait_trampoline: */ + 0x48, 0xc7, 0xc0, 0xe7, 0xff, 0xff, 0xff, /* mov $-25,%rax */ + 0x0f, 0x05, /* syscall */ + 0x49, 0x89, 0x04, 0x24, /* mov %rax,(%r12) */ + 0x48, 0x89, 0xdc, /* mov %rbx,%rsp */ + +/* trampoline: */ + 0x5f, /* pop %rdi */ + 0x5e, /* pop %rsi */ + 0x5a, /* pop %rdx */ + 0x48, 0x83, 0xc4, 0x08, /* add $0x8,%rsp */ + 0x41, 0xff, 0xd5, /* call *%r13 */ + +/* RA HERE */ + 0x48, 0x8b, 0x7c, 0x24, 0x10, /* mov 0x10(%rsp),%rdi */ + 0xc3, /* ret */ + +/* firewall: */ + 0xf4, /* hlt */ +}; + +#define GNU_SIGTRAMP_LEN (sizeof gnu_sigtramp_code) +#define GNU_SIGTRAMP_TAIL 7 /* length of tail after RA */ + +/* If THIS_FRAME is a sigtramp routine, return the address of the + start of the routine. Otherwise, return 0. */ + +static CORE_ADDR +amd64_gnu_sigtramp_start (frame_info_ptr this_frame) +{ + CORE_ADDR pc = get_frame_pc (this_frame); + gdb_byte buf[GNU_SIGTRAMP_LEN]; + + if (!safe_frame_unwind_memory (this_frame, + pc + GNU_SIGTRAMP_TAIL - GNU_SIGTRAMP_LEN, + buf)) + return 0; + + if (memcmp (buf, gnu_sigtramp_code, GNU_SIGTRAMP_LEN) != 0) + return 0; + + return pc; +} + +/* Return whether THIS_FRAME corresponds to a Hurd sigtramp routine. */ + +static int +amd64_gnu_sigtramp_p (const frame_info_ptr &this_frame) +{ + CORE_ADDR pc = get_frame_pc (this_frame); + const char *name; + + find_pc_partial_function (pc, &name, NULL, NULL); + + /* If we have a NAME, we can check for the trampoline function */ + if (name != NULL && strcmp (name, "trampoline") == 0) + return 1; + + return amd64_gnu_sigtramp_start (this_frame) != 0; +} + +/* Offset to sc_i386_thread_state in sigcontext, from <bits/sigcontext.h>. */ +#define AMD64_GNU_SIGCONTEXT_THREAD_STATE_OFFSET 32 + +/* Assuming THIS_FRAME is a Hurd sigtramp routine, return the + address of the associated sigcontext structure. */ + +static CORE_ADDR +amd64_gnu_sigcontext_addr (const frame_info_ptr &this_frame) +{ + struct gdbarch *gdbarch = get_frame_arch (this_frame); + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + CORE_ADDR pc; + CORE_ADDR sp; + gdb_byte buf[8]; + + get_frame_register (this_frame, AMD64_RSP_REGNUM, buf); + sp = extract_unsigned_integer (buf, 8, byte_order); + + pc = amd64_gnu_sigtramp_start (this_frame); + if (pc) + { + CORE_ADDR sigcontext_addr; + + /* The sigcontext structure address is passed as the third argument + * of the signal handler but %RDX is not saved across calls. Luckily, + * the structured is saved underneath the &__sigreturn and a dummy word + * to fill the slot for the address for __sigreturn to return to. + */ + read_memory (sp + 16, buf, 8); + sigcontext_addr = extract_unsigned_integer (buf, 8, byte_order); + return sigcontext_addr + AMD64_GNU_SIGCONTEXT_THREAD_STATE_OFFSET; + } + + error (_("Couldn't recognize signal trampoline.")); + return 0; +} + +/* Mapping between the general-purpose registers in `struct + sigcontext' format (starting at sc_i386_thread_state) + and GDB's register cache layout. */ + +/* From <bits/sigcontext.h>. */ +static int amd64_gnu_sc_reg_offset[] = +{ + 15 * 8, /* %rax */ + 12 * 8, /* %rbx */ + 14 * 8, /* %rcx */ + 13 * 8, /* %rdx */ + 10 * 8, /* %rsi */ + 9 * 8, /* %rdi */ + 10 * 8, /* %rbp */ + 11 * 8, /* %rsp */ + 0 * 8, /* %r8 ... */ + 8 * 8, + 7 * 8, + 6 * 8, + 3 * 8, + 2 * 8, + 1 * 8, + 0 * 8, /* ... %r15 */ + 16 * 8, /* %rip */ + 18 * 8, /* %eflags */ + 17 * 8, /* %cs */ +}; + +/* From <sys/ucontext.h>. */ +static int amd64_gnu_gregset_reg_offset[] = +{ + 10 * 8, /* %rax */ + 5 * 8, /* %rbx */ + 11 * 8, /* %rcx */ + 12 * 8, /* %rdx */ + 13 * 8, /* %rsi */ + 14 * 8, /* %rdi */ + 4 * 8, /* %rbp */ + 19 * 8, /* %rsp */ + 9 * 8, /* %r8 ... */ + 8 * 8, + 7 * 8, + 6 * 8, + 3 * 8, + 2 * 8, + 1 * 8, + 0 * 8, /* ... %r15 */ + 16 * 8, /* %rip */ + 18 * 8, /* %eflags */ + 17 * 8, /* %cs */ + -1, /* %ss */ + -1, /* %ds */ + -1, /* %es */ + -1, /* %fs */ + -1, /* %gs */ +}; + +static void +amd64_gnu_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) +{ + i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch); + + amd64_init_abi (info, gdbarch, + amd64_target_description (X86_XSTATE_SSE_MASK, true)); + + /* Enable TLS support. */ + set_gdbarch_fetch_tls_load_module_address (gdbarch, + svr4_fetch_objfile_link_map); + + /* Hurd uses SVR4-style shared libraries. */ + set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target); + + /* Hurd uses the dynamic linker included in the GNU C Library. */ + set_gdbarch_skip_solib_resolver (gdbarch, glibc_skip_solib_resolver); + + tdep->gregset_reg_offset = amd64_gnu_gregset_reg_offset; + tdep->gregset_num_regs = ARRAY_SIZE (amd64_gnu_gregset_reg_offset); + tdep->sizeof_gregset = 21 * 8; /* sizeof (struct i386_thread_state); */ + + tdep->sigtramp_p = amd64_gnu_sigtramp_p; + tdep->sigcontext_addr = amd64_gnu_sigcontext_addr; + tdep->sc_reg_offset = amd64_gnu_sc_reg_offset; + tdep->sc_num_regs = ARRAY_SIZE (amd64_gnu_sc_reg_offset); + + /* Hurd uses SVR4-style shared libraries. */ + set_solib_svr4_fetch_link_map_offsets + (gdbarch, svr4_lp64_fetch_link_map_offsets); +} + +void _initialize_amd64_gnu_tdep (); +void +_initialize_amd64_gnu_tdep () +{ + gdbarch_register_osabi (bfd_arch_i386, bfd_mach_x86_64, + GDB_OSABI_HURD, amd64_gnu_init_abi); +} diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index e7fdeca..6457285 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -8090,11 +8090,18 @@ disable_breakpoints_in_shlibs (program_space *pspace) /* Disable any breakpoints and tracepoints that are in SOLIB upon notification of unloaded_shlib. Only apply to enabled breakpoints, - disabled ones can just stay disabled. */ + disabled ones can just stay disabled. + + When STILL_IN_USE is true, SOLIB hasn't really been unmapped from + the inferior. In this case, don't disable anything. */ static void -disable_breakpoints_in_unloaded_shlib (program_space *pspace, const solib &solib) +disable_breakpoints_in_unloaded_shlib (program_space *pspace, const solib &solib, + bool still_in_use) { + if (still_in_use) + return; + bool disabled_shlib_breaks = false; for (bp_location *loc : all_bp_locations ()) diff --git a/gdb/bsd-uthread.c b/gdb/bsd-uthread.c index eb1ed42..67db0ca 100644 --- a/gdb/bsd-uthread.c +++ b/gdb/bsd-uthread.c @@ -294,9 +294,10 @@ bsd_uthread_solib_loaded (solib &so) } static void -bsd_uthread_solib_unloaded (program_space *pspace, const solib &so) +bsd_uthread_solib_unloaded (program_space *pspace, const solib &so, + bool still_in_use) { - if (bsd_uthread_solib_name.empty ()) + if (bsd_uthread_solib_name.empty () || still_in_use) return; if (so.so_original_name == bsd_uthread_solib_name) diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c index c5eab2f..e5de702 100644 --- a/gdb/cli/cli-decode.c +++ b/gdb/cli/cli-decode.c @@ -2912,14 +2912,20 @@ parse_cli_var_color (const char **args) if (len != 7) error_no_arg (_("invalid RGB hex triplet format")); + uint32_t rgb; uint8_t r, g, b; int scanned_chars = 0; - int parsed_args = sscanf (*args, "#%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%n", - &r, &g, &b, &scanned_chars); + int parsed_args = sscanf (*args, "#%6" SCNx32 "%n", + &rgb, &scanned_chars); - if (parsed_args != 3 || scanned_chars != 7) + if (parsed_args != 1 || scanned_chars != 7) error_no_arg (_("invalid RGB hex triplet format")); + gdb_assert ((rgb >> 24) == 0); + r = (rgb >> 16) & 0xff; + g = (rgb >> 8) & 0xff; + b = rgb & 0xff; + *args += len; return ui_file_style::color (r, g, b); } diff --git a/gdb/config/i386/nm-i386gnu.h b/gdb/config/i386/nm-x86-gnu.h index d2d5de8..ed4d172 100644 --- a/gdb/config/i386/nm-i386gnu.h +++ b/gdb/config/i386/nm-x86-gnu.h @@ -22,9 +22,16 @@ /* Thread flavors used in re-setting the T bit. */ #define THREAD_STATE_FLAVOR i386_REGS_SEGS_STATE #define THREAD_STATE_SIZE i386_THREAD_STATE_COUNT +#ifdef __x86_64__ +#define THREAD_STATE_SET_TRACED(state) \ + ((struct i386_thread_state *) (state))->rfl |= 0x100 +#define THREAD_STATE_CLEAR_TRACED(state) \ + ((((struct i386_thread_state *) (state))->rfl &= ~0x100), 1) +#else #define THREAD_STATE_SET_TRACED(state) \ ((struct i386_thread_state *) (state))->efl |= 0x100 #define THREAD_STATE_CLEAR_TRACED(state) \ ((((struct i386_thread_state *) (state))->efl &= ~0x100), 1) +#endif /* __x86_64__ */ #endif /* CONFIG_I386_NM_I386GNU_H */ diff --git a/gdb/config/i386/i386gnu.mn b/gdb/config/i386/x86-gnu.mn index 24c58b0..24c58b0 100644 --- a/gdb/config/i386/i386gnu.mn +++ b/gdb/config/i386/x86-gnu.mn diff --git a/gdb/configure.host b/gdb/configure.host index 22855cd..fdd6519 100644 --- a/gdb/configure.host +++ b/gdb/configure.host @@ -183,6 +183,7 @@ x86_64-*-mingw*) gdb_host=mingw64 gdb_host_obs=mingw-hdep.o ;; x86_64-*-cygwin*) gdb_host=cygwin64 ;; +x86_64-*-gnu*) gdb_host=gnu64 ;; m32r*-*-linux*) gdb_host=linux ;; xtensa*-*-linux*) gdb_host=linux ;; diff --git a/gdb/configure.nat b/gdb/configure.nat index 9e78091..7b38962 100644 --- a/gdb/configure.nat +++ b/gdb/configure.nat @@ -211,23 +211,44 @@ case ${gdb_host} in ;; esac ;; + gnu64) + case ${gdb_host_cpu} in + i386) + # Host: x86_64 running the GNU Hurd + NATDEPFILES='x86-gnu-nat.o gnu-nat.o \ + x86-nat.o nat/x86-dregs.o \ + amd64-nat.o fork-child.o \ + nat/fork-inferior.o \ + notify_S.o process_reply_S.o msg_reply_S.o \ + msg_U.o exc_request_U.o exc_request_S.o' + HAVE_NATIVE_GCORE_HOST=1 + + NAT_FILE='nm-x86-gnu.h' + MH_CFLAGS='-D_GNU_SOURCE' + + XM_CLIBS='-lshouldbeinlibc' + + nat_makefile_frag="${srcdir}/config/${gdb_host_cpu}/x86-gnu.mn" + ;; + esac + ;; i386gnu) case ${gdb_host_cpu} in i386) # Host: Intel 386 running the GNU Hurd - NATDEPFILES='i386-gnu-nat.o gnu-nat.o \ + NATDEPFILES='x86-gnu-nat.o gnu-nat.o \ x86-nat.o nat/x86-dregs.o fork-child.o \ nat/fork-inferior.o \ notify_S.o process_reply_S.o msg_reply_S.o \ msg_U.o exc_request_U.o exc_request_S.o' HAVE_NATIVE_GCORE_HOST=1 - NAT_FILE='nm-i386gnu.h' + NAT_FILE='nm-x86-gnu.h' MH_CFLAGS='-D_GNU_SOURCE' XM_CLIBS='-lshouldbeinlibc' - nat_makefile_frag="${srcdir}/config/${gdb_host_cpu}/i386gnu.mn" + nat_makefile_frag="${srcdir}/config/${gdb_host_cpu}/x86-gnu.mn" ;; esac ;; diff --git a/gdb/configure.tgt b/gdb/configure.tgt index f7b9e32..18a15c0 100644 --- a/gdb/configure.tgt +++ b/gdb/configure.tgt @@ -327,7 +327,7 @@ i[34567]86-*-linux*) ;; i[34567]86-*-gnu*) # Target: Intel 386 running the GNU Hurd - gdb_target_obs="i386-gnu-tdep.o solib-svr4.o" + gdb_target_obs="i386-gnu-tdep.o glibc-tdep.o solib-svr4.o" ;; i[34567]86-*-cygwin*) # Target: Intel 386 running win32 @@ -734,6 +734,10 @@ x86_64-*-openbsd*) x86_64-*-rtems*) gdb_target_obs="${amd64_tobjs} ${i386_tobjs} i386-bsd-tdep.o" ;; +x86_64-*-gnu*) + # Target: x86_64 running the GNU Hurd + gdb_target_obs="amd64-gnu-tdep.o glibc-tdep.o solib-svr4.o" + ;; xtensa*-*-*linux*) # Target: GNU/Linux Xtensa gdb_target_obs="xtensa-linux-tdep.o symfile-mem.o linux-tdep.o" diff --git a/gdb/corelow.c b/gdb/corelow.c index ee57a9c..59c1667 100644 --- a/gdb/corelow.c +++ b/gdb/corelow.c @@ -1188,11 +1188,11 @@ core_target_open (const char *arg, int from_tty) } else { - gdb::unique_xmalloc_ptr<char> failing_command = make_unique_xstrdup - (bfd_core_file_failing_command (current_program_space->core_bfd ())); + const char *failing_command + = bfd_core_file_failing_command (current_program_space->core_bfd ()); if (failing_command != nullptr) gdb_printf (_("Core was generated by `%s'.\n"), - failing_command.get ()); + failing_command); } /* Clearing any previous state of convenience variables. */ diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index b37266b..88b6c68 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -22976,16 +22976,48 @@ The DWARF specification documents an optional index section called section. However, in order to work with @value{GDBN}, some extensions were necessary. -@value{GDBN} uses the augmentation string @samp{GDB2}. Earlier -versions used the string @samp{GDB}, but these versions of the index -are no longer supported. +@value{GDBN} uses an augmentation string to specify which extensions +are in use and to allow support of backwards-incompatible changes in +this functionality. The augmentation string has the form +@samp{GDB@var{n}}, where @var{n} is an integral version number of the +extensions, which is incremented when the extensions are added or +modified. The smallest @var{n} is 2; earlier versions of +@value{GDBN} used just @samp{GDB} with no version number, but these +versions of the index are no longer supported. + +Here is a list of augmentation string versions along with the changes +introduced with each version, compared to the previous version. + +@table @samp + +@item GDB2 +Specifies the use of attributes @code{DW_IDX_GNU_internal}, +@code{DW_IDX_GNU_main}, @code{DW_IDX_GNU_language} and +@code{DW_IDX_GNU_linkage_name}, described below. + +@item GDB3 +Changes the semantic of the @code{DW_IDX_parent} attribute. +With @samp{GDB2}, @code{DW_IDX_parent} provided an offset into the name +table. With @samp{GDB3}, it now provides an offset to the index entry +of the parent, relative to the start of the entry pool region. + +@end table + +@value{GDBN} produces indexes with the augmentation string @samp{GDB3}. + +@value{GDBN} can read indexes with augmentation strings @samp{GDB2} or +@samp{GDB3}. @value{GDBN} does not support reading indexes with any +other augmentation strings. @value{GDBN} does not use the specified hash table. Therefore, because this hash table is optional, @value{GDBN} also does not write it. -@value{GDBN} also generates and uses some extra index attributes: +@value{GDBN} generates and uses the following non-standard index +attributes: + @table @code + @item DW_IDX_GNU_internal This has the value @samp{0x2000}. It is a flag that, when set, indicates that the associated entry has @code{static} linkage. @@ -23002,6 +23034,7 @@ indicating the language of the associated entry. This has the value @samp{0x2004}. It is a flag that, when set, indicates that the associated entry is a linkage name, and not a source name. + @end table @node Symbol Errors @@ -32253,12 +32286,22 @@ to this library. @item =library-unloaded,... Reports that a library was unloaded by the program. This notification -has 3 fields---@var{id}, @var{target-name} and @var{host-name} with -the same meaning as for the @code{=library-loaded} notification. +has the following fields---@var{id}, @var{target-name}, +@var{host-name} and @var{ranges} with the same meaning as for the +@code{=library-loaded} notification. + +It is possible that a library can appear multiple times in an +inferior's library list, but the library is only mapped once into the +inferior's address space. When this happens, and one copy of the +library is unloaded, but there are remaining copies, the +@var{still-in-use} field will be @samp{true}. In all other +situations, the @var{still-in-use} field will have the value +@samp{false}. + The @var{thread-group} field, if present, specifies the id of the -thread group in whose context the library was unloaded. If the field is -absent, it means the library was unloaded in the context of all present -thread groups. +thread group in whose context the library was unloaded. If the field +is absent, it means the library was unloaded in the context of all +present thread groups. @item =traceframe-changed,num=@var{tfnum},tracepoint=@var{tpnum} @itemx =traceframe-changed,end diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 2d9f6e9..896f587 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -647,11 +647,12 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit) } void -cooked_index::set_contents (vec_type &&vec, deferred_warnings *warn, +cooked_index::set_contents (std::vector<cooked_index_shard_up> &&shards, + deferred_warnings *warn, const parent_map_map *parent_maps) { - gdb_assert (m_vector.empty ()); - m_vector = std::move (vec); + gdb_assert (m_shards.empty ()); + m_shards = std::move (shards); m_state->set (cooked_state::MAIN_AVAILABLE); @@ -667,10 +668,10 @@ cooked_index::set_contents (vec_type &&vec, deferred_warnings *warn, m_state->set (cooked_state::CACHE_DONE); }); - for (auto &idx : m_vector) + for (auto &shard : m_shards) { - auto this_index = idx.get (); - finalizers.add_task ([=] () { this_index->finalize (parent_maps); }); + auto this_shard = shard.get (); + finalizers.add_task ([=] () { this_shard->finalize (parent_maps); }); } finalizers.start (); @@ -696,9 +697,9 @@ cooked_index::lookup (unrelocated_addr addr) { /* Ensure that the address maps are ready. */ wait (cooked_state::MAIN_AVAILABLE, true); - for (const auto &index : m_vector) + for (const auto &shard : m_shards) { - dwarf2_per_cu_data *result = index->lookup (addr); + dwarf2_per_cu_data *result = shard->lookup (addr); if (result != nullptr) return result; } @@ -713,8 +714,8 @@ cooked_index::get_addrmaps () /* Ensure that the address maps are ready. */ wait (cooked_state::MAIN_AVAILABLE, true); std::vector<const addrmap *> result; - for (const auto &index : m_vector) - result.push_back (index->m_addrmap); + for (const auto &shard : m_shards) + result.push_back (shard->m_addrmap); return result; } @@ -725,9 +726,9 @@ cooked_index::find (const std::string &name, bool completing) { wait (cooked_state::FINALIZED, true); std::vector<cooked_index_shard::range> result_range; - result_range.reserve (m_vector.size ()); - for (auto &entry : m_vector) - result_range.push_back (entry->find (name, completing)); + result_range.reserve (m_shards.size ()); + for (auto &shard : m_shards) + result_range.push_back (shard->find (name, completing)); return range (std::move (result_range)); } @@ -751,9 +752,9 @@ const cooked_index_entry * cooked_index::get_main () const { const cooked_index_entry *best_entry = nullptr; - for (const auto &index : m_vector) + for (const auto &shard : m_shards) { - const cooked_index_entry *entry = index->get_main (); + const cooked_index_entry *entry = shard->get_main (); /* Choose the first "main" we see. We only do this for names not requiring canonicalization. At this point in the process names might not have been canonicalized. However, currently, @@ -841,12 +842,15 @@ cooked_index::dump (gdbarch *arch) std::vector<const addrmap *> addrmaps = this->get_addrmaps (); for (i = 0; i < addrmaps.size (); ++i) { - const addrmap &addrmap = *addrmaps[i]; + const addrmap *addrmap = addrmaps[i]; - gdb_printf (" [%zu] ((addrmap *) %p)\n", i, &addrmap); + gdb_printf (" [%zu] ((addrmap *) %p)\n", i, addrmap); gdb_printf ("\n"); - addrmap.foreach ([arch] (CORE_ADDR start_addr, const void *obj) + if (addrmap == nullptr) + continue; + + addrmap->foreach ([arch] (CORE_ADDR start_addr, const void *obj) { QUIT; diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index d1d81f8..b7a38b8 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -319,6 +319,9 @@ private: found. */ dwarf2_per_cu_data *lookup (unrelocated_addr addr) { + if (m_addrmap == nullptr) + return nullptr; + return (static_cast<dwarf2_per_cu_data *> (m_addrmap->find ((CORE_ADDR) addr))); } @@ -364,6 +367,8 @@ private: std::vector<gdb::unique_xmalloc_ptr<char>> m_names; }; +using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>; + class cutu_reader; /* An instance of this is created when scanning DWARF to create a @@ -621,11 +626,6 @@ protected: class cooked_index : public dwarf_scanner_base { public: - - /* A convenience typedef for the vector that is contained in this - object. */ - using vec_type = std::vector<std::unique_ptr<cooked_index_shard>>; - cooked_index (dwarf2_per_objfile *per_objfile, std::unique_ptr<cooked_index_worker> &&worker); ~cooked_index () override; @@ -633,7 +633,7 @@ public: DISABLE_COPY_AND_ASSIGN (cooked_index); /* Start reading the DWARF. */ - void start_reading (); + void start_reading () override; /* Called by cooked_index_worker to set the contents of this index and transition to the MAIN_AVAILABLE state. WARN is used to @@ -641,7 +641,8 @@ public: PARENT_MAPS is used when resolving pending parent links. PARENT_MAPS may be NULL if there are no IS_PARENT_DEFERRED entries in VEC. */ - void set_contents (vec_type &&vec, deferred_warnings *warn, + void set_contents (std::vector<cooked_index_shard_up> &&vec, + deferred_warnings *warn, const parent_map_map *parent_maps); /* A range over a vector of subranges. */ @@ -657,9 +658,9 @@ public: { wait (cooked_state::FINALIZED, true); std::vector<cooked_index_shard::range> result_range; - result_range.reserve (m_vector.size ()); - for (auto &entry : m_vector) - result_range.push_back (entry->all_entries ()); + result_range.reserve (m_shards.size ()); + for (auto &shard : m_shards) + result_range.push_back (shard->all_entries ()); return range (std::move (result_range)); } @@ -669,7 +670,9 @@ public: dwarf2_per_cu_data *lookup (unrelocated_addr addr) override; /* Return a new vector of all the addrmaps used by all the indexes - held by this object. */ + held by this object. + + Elements of the vector may be nullptr. */ std::vector<const addrmap *> get_addrmaps (); /* Return the entry that is believed to represent the program's @@ -707,7 +710,7 @@ private: /* The vector of cooked_index objects. This is stored because the entries are stored on the obstacks in those objects. */ - vec_type m_vector; + std::vector<cooked_index_shard_up> m_shards; /* This tracks the current state. When this is nullptr, it means that the state is CACHE_DONE -- it's important to note that only diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c index e82d38a..f36f388 100644 --- a/gdb/dwarf2/index-write.c +++ b/gdb/dwarf2/index-write.c @@ -130,6 +130,15 @@ public: ::store_unsigned_integer (grow (len), len, byte_order, val); } + /* Accept a host-format integer in VAL and write it in the buffer at offset + OFFSET as a target-format integer which is LEN bytes long. */ + void write_uint (size_t offset, size_t len, bfd_endian byte_order, + ULONGEST val) + { + gdb_assert (offset + len <= m_vec.size ()); + ::store_unsigned_integer (&m_vec[offset], len, byte_order, val); + } + /* Copy VALUE to the end of the buffer, little-endian. */ void append_offset (offset_type value) { @@ -675,10 +684,7 @@ public: if (entry->lang == language_ada && entry->tag == DW_TAG_namespace) return; - const auto insertpair - = m_name_to_value_set.try_emplace (c_str_view (entry->name)); - entry_list &elist = insertpair.first->second; - elist.entries.push_back (entry); + m_name_to_value_set[entry->name].emplace_back (entry); } /* Build all the tables. All symbols must be already inserted. @@ -692,25 +698,16 @@ public: m_name_table_string_offs.reserve (name_count); m_name_table_entry_offs.reserve (name_count); - /* The name table is indexed from 1. The numbers are needed here - so that parent entries can be handled correctly. */ - int next_name = 1; - for (auto &item : m_name_to_value_set) - item.second.index = next_name++; - /* The next available abbrev number. */ int next_abbrev = 1; - for (auto &item : m_name_to_value_set) + for (auto &[name, these_entries] : m_name_to_value_set) { - const c_str_view &name = item.first; - entry_list &these_entries = item.second; - /* Sort the items within each bucket. This ensures that the generated index files will be the same no matter the order in which symbols were added into the index. */ - std::sort (these_entries.entries.begin (), - these_entries.entries.end (), + std::sort (these_entries.begin (), + these_entries.end (), [] (const cooked_index_entry *a, const cooked_index_entry *b) { @@ -730,7 +727,7 @@ public: (m_debugstrlookup.lookup (name.c_str ())); /* ??? */ m_name_table_entry_offs.push_back_reorder (m_entry_pool.size ()); - for (const cooked_index_entry *entry : these_entries.entries) + for (const cooked_index_entry *entry : these_entries) { unit_kind kind = (entry->per_cu->is_debug_types ? unit_kind::tu @@ -778,7 +775,7 @@ public: if (parent != nullptr) { m_abbrev_table.append_unsigned_leb128 (DW_IDX_parent); - m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata); + m_abbrev_table.append_unsigned_leb128 (DW_FORM_data4); } /* Terminate attributes list. */ @@ -786,6 +783,14 @@ public: m_abbrev_table.append_unsigned_leb128 (0); } + /* Record the offset in the pool at which this entry will + reside. */ + const auto offset_inserted + = (m_entry_pool_offsets.emplace (entry, m_entry_pool.size ()) + .second); + gdb_assert (offset_inserted); + + /* Write the entry to the pool. */ m_entry_pool.append_unsigned_leb128 (idx); const auto it = m_cu_index_htab.find (entry->per_cu); @@ -800,11 +805,11 @@ public: if (parent != nullptr) { - c_str_view par_name (parent->name); - auto name_iter = m_name_to_value_set.find (par_name); - gdb_assert (name_iter != m_name_to_value_set.end ()); - gdb_assert (name_iter->second.index != 0); - m_entry_pool.append_unsigned_leb128 (name_iter->second.index); + m_offsets_to_patch.emplace_back (m_entry_pool.size (), parent); + + /* Write a dummy number, this gets patched later. */ + m_entry_pool.append_uint (4, m_dwarf5_byte_order, + 0xfafafafa); } } @@ -814,6 +819,15 @@ public: /* Terminate tags list. */ m_abbrev_table.append_unsigned_leb128 (0); + + /* Write the parent offset values. */ + for (const auto &[reloc_offset, parent] : m_offsets_to_patch) + { + const auto parent_offset_it = m_entry_pool_offsets.find (parent); + gdb_assert (parent_offset_it != m_entry_pool_offsets.cend ()); + m_entry_pool.write_uint (reloc_offset, 4, m_dwarf5_byte_order, + parent_offset_it->second); + } } /* Return .debug_names names count. This must be called only after @@ -1059,15 +1073,26 @@ private: offset_vec_tmpl<OffsetSize> m_name_table_entry_offs; }; - struct entry_list - { - unsigned index = 0; - std::vector<const cooked_index_entry *> entries; - }; + /* Store the index entries for each name. + + Note that we rely on the sorting behavior of map to make the output + stable. */ + std::map<c_str_view, std::vector<const cooked_index_entry *>> + m_name_to_value_set; + + /* Offset at which each entry is written in the entry pool. */ + gdb::unordered_map<const cooked_index_entry *, offset_type> + m_entry_pool_offsets; + + /* The locations where we need to patch offset to entries. + + The first element of the pair is the offset into the pool that needs to + be patched. - /* Store value of each symbol. Note that we rely on the sorting - behavior of map to make the output stable. */ - std::map<c_str_view, entry_list> m_name_to_value_set; + The second element is the entry the offset to which needs to be + patched in. */ + std::vector<std::pair<offset_type, const cooked_index_entry *>> + m_offsets_to_patch; const bfd_endian m_dwarf5_byte_order; dwarf_tmpl<uint32_t> m_dwarf32; @@ -1333,7 +1358,8 @@ write_gdbindex (dwarf2_per_bfd *per_bfd, cooked_index *table, /* Dump the address map. */ data_buf addr_vec; for (auto map : table->get_addrmaps ()) - write_address_map (map, addr_vec, cu_index_htab); + if (map != nullptr) + write_address_map (map, addr_vec, cu_index_htab); /* Ensure symbol hash is built domestically. */ symtab.sort (); @@ -1406,7 +1432,7 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table, const offset_type bytes_of_header = ((dwarf5_is_dwarf64 ? 12 : 4) + 2 + 2 + 7 * 4 - + sizeof (dwarf5_augmentation)); + + sizeof (dwarf5_augmentation_3)); size_t expected_bytes = 0; expected_bytes += bytes_of_header; expected_bytes += cu_list.size (); @@ -1458,9 +1484,9 @@ write_debug_names (dwarf2_per_bfd *per_bfd, cooked_index *table, /* augmentation_string_size - The size in bytes of the augmentation string. This value is rounded up to a multiple of 4. */ - static_assert (sizeof (dwarf5_augmentation) % 4 == 0); - header.append_uint (4, dwarf5_byte_order, sizeof (dwarf5_augmentation)); - header.append_array (dwarf5_augmentation); + static_assert (sizeof (dwarf5_augmentation_3) % 4 == 0); + header.append_uint (4, dwarf5_byte_order, sizeof (dwarf5_augmentation_3)); + header.append_array (dwarf5_augmentation_3); gdb_assert (header.size () == bytes_of_header); diff --git a/gdb/dwarf2/mapped-index.h b/gdb/dwarf2/mapped-index.h index 8bc81b4..b32fe6a 100644 --- a/gdb/dwarf2/mapped-index.h +++ b/gdb/dwarf2/mapped-index.h @@ -32,6 +32,11 @@ struct dwarf_scanner_base virtual ~dwarf_scanner_base () = default; DISABLE_COPY_AND_ASSIGN (dwarf_scanner_base); + /* Start the reading. This is only really relevant to the cooked + index; see cooked-index.h. */ + virtual void start_reading () + { } + /* Return a quick_symbol_functions instance that refers back to this dwarf_scanner_base. */ virtual quick_symbol_functions_up make_quick_functions () const = 0; diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c index ffc4f3a..fe31a58 100644 --- a/gdb/dwarf2/read-debug-names.c +++ b/gdb/dwarf2/read-debug-names.c @@ -28,6 +28,7 @@ #include "read.h" #include "stringify.h" #include "extract-store-integer.h" +#include "gdbsupport/thread-pool.h" /* This is just like cooked_index_functions, but overrides a single method so the test suite can distinguish the .debug_names case from @@ -74,7 +75,15 @@ struct mapped_debug_names_reader bfd *abfd = nullptr; bfd_endian dwarf5_byte_order {}; bool dwarf5_is_dwarf64 = false; + + /* True if the augmentation string indicates the index was produced by + GDB. */ bool augmentation_is_gdb = false; + + /* If AUGMENTATION_IS_GDB is true, this indicates the version. Otherwise, + this value is meaningless. */ + unsigned int gdb_augmentation_version = 0; + uint8_t offset_size = 0; uint32_t cu_count = 0; uint32_t tu_count = 0, bucket_count = 0, name_count = 0; @@ -105,8 +114,33 @@ struct mapped_debug_names_reader std::unordered_map<ULONGEST, index_val> abbrev_map; - std::unique_ptr<cooked_index_shard> shard; + /* Even though the scanning of .debug_names and creation of the cooked index + entries is done serially, we create multiple shards so that the + finalization step can be parallelized. The shards are filled in a round + robin fashion. */ + std::vector<cooked_index_shard_up> shards; + + /* Next shard to insert an entry in. */ + int next_shard = 0; + + /* Maps entry pool offsets to cooked index entries. */ + gdb::unordered_map<ULONGEST, cooked_index_entry *> + entry_pool_offsets_to_entries; + + /* Cooked index entries for which the parent needs to be resolved. + + The second value of the pair is the DW_IDX_parent value. Its meaning + depends on the augmentation string: + + - GDB2: an index in the name table + - GDB3: an offset offset into the entry pool */ std::vector<std::pair<cooked_index_entry *, ULONGEST>> needs_parent; + + /* All the cooked index entries created, in the same order and groups as + listed in the name table. + + The size of the outer vector is equal to the number of entries in the name + table (NAME_COUNT). */ std::vector<std::vector<cooked_index_entry *>> all_entries; }; @@ -121,6 +155,7 @@ mapped_debug_names_reader::scan_one_entry (const char *name, std::optional<ULONGEST> &parent) { unsigned int bytes_read; + const auto offset_in_entry_pool = entry - entry_pool; const ULONGEST abbrev = read_unsigned_leb128 (abfd, entry, &bytes_read); entry += bytes_read; if (abbrev == 0) @@ -239,8 +274,18 @@ mapped_debug_names_reader::scan_one_entry (const char *name, /* Skip if we couldn't find a valid CU/TU index. */ if (per_cu != nullptr) - *result = shard->add (die_offset, (dwarf_tag) indexval.dwarf_tag, flags, - lang, name, nullptr, per_cu); + { + *result + = shards[next_shard]->add (die_offset, (dwarf_tag) indexval.dwarf_tag, + flags, lang, name, nullptr, per_cu); + + ++next_shard; + if (next_shard == shards.size ()) + next_shard = 0; + + entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result); + } + return entry; } @@ -296,19 +341,43 @@ mapped_debug_names_reader::scan_all_names () scan_entries (i, name, entry); } - /* Now update the parent pointers for all entries. This has to be - done in a funny way because DWARF specifies the parent entry to - point to a name -- but we don't know which specific one. */ - for (auto [entry, parent_idx] : needs_parent) + /* Resolve the parent pointers for all entries that have a parent. + + If the augmentation string is "GDB2", the DW_IDX_parent value is an index + into the name table. Since there may be multiple index entries associated + to that name, we have a little heuristic to figure out which is the right + one. + + Otherwise, the DW_IDX_parent value is an offset into the entry pool, which + is not ambiguous. */ + for (auto &[entry, parent_val] : needs_parent) { - /* Name entries are indexed from 1 in DWARF. */ - std::vector<cooked_index_entry *> &entries = all_entries[parent_idx - 1]; - for (const auto &parent : entries) - if (parent->lang == entry->lang) - { - entry->set_parent (parent); - break; - } + if (augmentation_is_gdb && gdb_augmentation_version == 2) + { + /* Name entries are indexed from 1 in DWARF. */ + std::vector<cooked_index_entry *> &entries + = all_entries[parent_val - 1]; + + for (const auto &parent : entries) + if (parent->lang == entry->lang) + { + entry->set_parent (parent); + break; + } + } + else + { + const auto parent_it + = entry_pool_offsets_to_entries.find (parent_val); + + if (parent_it == entry_pool_offsets_to_entries.cend ()) + { + complaint (_ ("Parent entry not found for .debug_names entry")); + continue; + } + + entry->set_parent (parent_it->second); + } } } @@ -348,14 +417,13 @@ cooked_index_debug_names::do_reading () complaint_handler.release (), std::move (exceptions), parent_map ()); - std::vector<std::unique_ptr<cooked_index_shard>> indexes; - indexes.push_back (std::move (m_map.shard)); cooked_index *table = (gdb::checked_static_cast<cooked_index *> (per_bfd->index_table.get ())); + /* Note that this code never uses IS_PARENT_DEFERRED, so it is safe to pass nullptr here. */ - table->set_contents (std::move (indexes), &m_warnings, nullptr); + table->set_contents (std::move (m_map.shards), &m_warnings, nullptr); bfd_thread_cleanup (); } @@ -404,16 +472,6 @@ check_signatured_type_table_from_debug_names /* DWARF-5 debug_names reader. */ -/* The old, no-longer-supported GDB augmentation. */ -static const gdb_byte old_gdb_augmentation[] - = { 'G', 'D', 'B', 0 }; -static_assert (sizeof (old_gdb_augmentation) % 4 == 0); - -/* DWARF-5 augmentation string for GDB's DW_IDX_GNU_* extension. This - must have a size that is a multiple of 4. */ -const gdb_byte dwarf5_augmentation[8] = { 'G', 'D', 'B', '2', 0, 0, 0, 0 }; -static_assert (sizeof (dwarf5_augmentation) % 4 == 0); - /* A helper function that reads the .debug_names section in SECTION and fills in MAP. FILENAME is the name of the file containing the section; it is used for error reporting. @@ -525,18 +583,24 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile, addr += 4; augmentation_string_size += (-augmentation_string_size) & 3; - if (augmentation_string_size == sizeof (old_gdb_augmentation) - && memcmp (addr, old_gdb_augmentation, - sizeof (old_gdb_augmentation)) == 0) + const auto augmentation_string + = gdb::make_array_view (addr, augmentation_string_size); + + if (augmentation_string == gdb::make_array_view (dwarf5_augmentation_1)) { warning (_(".debug_names created by an old version of gdb; ignoring")); return false; } - - map.augmentation_is_gdb = ((augmentation_string_size - == sizeof (dwarf5_augmentation)) - && memcmp (addr, dwarf5_augmentation, - sizeof (dwarf5_augmentation)) == 0); + else if (augmentation_string == gdb::make_array_view (dwarf5_augmentation_2)) + { + map.augmentation_is_gdb = true; + map.gdb_augmentation_version = 2; + } + else if (augmentation_string == gdb::make_array_view (dwarf5_augmentation_3)) + { + map.augmentation_is_gdb = true; + map.gdb_augmentation_version = 3; + } if (!map.augmentation_is_gdb) { @@ -544,6 +608,7 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile, return false; } + /* Skip past augmentation string. */ addr += augmentation_string_size; /* List of CUs */ @@ -766,16 +831,24 @@ do_dwarf2_read_debug_names (dwarf2_per_objfile *per_objfile) &addrmap, &warnings); warnings.emit (); - map.shard = std::make_unique<cooked_index_shard> (); - map.shard->install_addrmap (&addrmap); + const auto n_workers + = std::max<std::size_t> (gdb::thread_pool::g_thread_pool->thread_count (), + 1); + + /* Create as many index shard as there are worker threads. */ + for (int i = 0; i < n_workers; ++i) + map.shards.emplace_back (std::make_unique<cooked_index_shard> ()); - cooked_index *idx - = new debug_names_index (per_objfile, - (std::make_unique<cooked_index_debug_names> - (per_objfile, std::move (map)))); - per_bfd->index_table.reset (idx); + /* There is a single address map for the whole index (coming from + .debug_aranges). We only need to install it into a single shard for it to + get searched by cooked_index. */ + map.shards[0]->install_addrmap (&addrmap); - idx->start_reading (); + auto cidn = (std::make_unique<cooked_index_debug_names> + (per_objfile, std::move (map))); + auto idx = std::make_unique<debug_names_index> (per_objfile, + std::move (cidn)); + per_bfd->start_reading (std::move (idx)); return true; } diff --git a/gdb/dwarf2/read-debug-names.h b/gdb/dwarf2/read-debug-names.h index 729fe89..ef91a70 100644 --- a/gdb/dwarf2/read-debug-names.h +++ b/gdb/dwarf2/read-debug-names.h @@ -22,7 +22,30 @@ struct dwarf2_per_objfile; -extern const gdb_byte dwarf5_augmentation[8]; +/* DWARF-5 augmentation strings. + + They must have a size that is a multiple of 4. + + "GDB" is the old, no-longer-supported GDB augmentation. + + The "GDB2" augmentation string specifies the use of the DW_IDX_GNU_* + attributes. + + The meaning of the "GDB3" augmentation string is identical to "GDB2", except + for the meaning of DW_IDX_parent. With "GDB2", DW_IDX_parent represented an + index in the name table. With "GDB3", it represents an offset into the entry + pool. */ + +constexpr gdb_byte dwarf5_augmentation_1[4] = { 'G', 'D', 'B', 0 }; +static_assert (sizeof (dwarf5_augmentation_1) % 4 == 0); + +constexpr gdb_byte dwarf5_augmentation_2[8] + = { 'G', 'D', 'B', '2', 0, 0, 0, 0 }; +static_assert (sizeof (dwarf5_augmentation_2) % 4 == 0); + +constexpr gdb_byte dwarf5_augmentation_3[8] + = { 'G', 'D', 'B', '3', 0, 0, 0, 0 }; +static_assert (sizeof (dwarf5_augmentation_3) % 4 == 0); /* Read .debug_names. If everything went ok, initialize the "quick" elements of all the CUs and return true. Otherwise, return false. */ diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 70e9022..be33bea 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -1608,6 +1608,16 @@ dwarf2_per_bfd::map_info_sections (struct objfile *objfile) section.read (objfile); } +/* See dwarf2/read.h. */ + +void +dwarf2_per_bfd::start_reading (std::unique_ptr<dwarf_scanner_base> new_table) +{ + gdb_assert (index_table == nullptr); + index_table = std::move (new_table); + index_table->start_reading (); +} + /* DWARF quick_symbol_functions support. */ @@ -16340,12 +16350,9 @@ start_debug_info_reader (dwarf2_per_objfile *per_objfile) scanning; and then start the scanning. */ dwarf2_per_bfd *per_bfd = per_objfile->per_bfd; std::unique_ptr<cooked_index_worker> worker - (new cooked_index_debug_info (per_objfile)); - cooked_index *idx = new cooked_index (per_objfile, std::move (worker)); - per_bfd->index_table.reset (idx); - /* Don't start reading until after 'index_table' is set. This - avoids races. */ - idx->start_reading (); + = std::make_unique<cooked_index_debug_info> (per_objfile); + per_bfd->start_reading (std::make_unique<cooked_index> (per_objfile, + std::move (worker))); } diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h index 12d5f06..ebea8b7 100644 --- a/gdb/dwarf2/read.h +++ b/gdb/dwarf2/read.h @@ -451,6 +451,10 @@ struct dwarf2_per_bfd .debug_info. */ void map_info_sections (struct objfile *objfile); + /* Set the 'index_table' member and then call start_reading on + it. */ + void start_reading (std::unique_ptr<dwarf_scanner_base> new_table); + private: /* This function is mapped across the sections and remembers the offset and size of each of the debugging sections we are diff --git a/gdb/i386-gnu-tdep.c b/gdb/i386-gnu-tdep.c index 98e1151..5ffc138 100644 --- a/gdb/i386-gnu-tdep.c +++ b/gdb/i386-gnu-tdep.c @@ -21,6 +21,7 @@ #include "osabi.h" #include "solib-svr4.h" +#include "glibc-tdep.h" #include "i386-tdep.h" /* Recognizing signal handler frames. */ @@ -72,8 +73,7 @@ i386_gnu_sigtramp_start (const frame_info_ptr &this_frame) return pc; } -/* Return whether THIS_FRAME corresponds to a GNU/Linux sigtramp - routine. */ +/* Return whether THIS_FRAME corresponds to a Hurd sigtramp routine. */ static int i386_gnu_sigtramp_p (const frame_info_ptr &this_frame) @@ -178,9 +178,18 @@ i386gnu_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) /* GNU uses ELF. */ i386_elf_init_abi (info, gdbarch); + /* Hurd uses SVR4-style shared libraries. */ + set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target); set_solib_svr4_fetch_link_map_offsets (gdbarch, svr4_ilp32_fetch_link_map_offsets); + /* Hurd uses the dynamic linker included in the GNU C Library. */ + set_gdbarch_skip_solib_resolver (gdbarch, glibc_skip_solib_resolver); + + /* Enable TLS support. */ + set_gdbarch_fetch_tls_load_module_address (gdbarch, + svr4_fetch_objfile_link_map); + tdep->gregset_reg_offset = i386gnu_gregset_reg_offset; tdep->gregset_num_regs = ARRAY_SIZE (i386gnu_gregset_reg_offset); tdep->sizeof_gregset = 19 * 4; diff --git a/gdb/infcmd.c b/gdb/infcmd.c index b6b21a4..00703e4 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -517,12 +517,6 @@ run_command (const char *args, int from_tty) static void start_command (const char *args, int from_tty) { - /* Some languages such as Ada need to search inside the program - minimal symbols for the location where to put the temporary - breakpoint before starting. */ - if (!have_minimal_symbols (current_program_space)) - error (_("No symbol table loaded. Use the \"file\" command.")); - /* Run the program until reaching the main procedure... */ run_command_1 (args, from_tty, RUN_STOP_AT_MAIN); } diff --git a/gdb/interps.c b/gdb/interps.c index bd65d1a..8b8b578 100644 --- a/gdb/interps.c +++ b/gdb/interps.c @@ -496,9 +496,9 @@ interps_notify_solib_loaded (const solib &so) /* See interps.h. */ void -interps_notify_solib_unloaded (const solib &so) +interps_notify_solib_unloaded (const solib &so, bool still_in_use) { - interps_notify (&interp::on_solib_unloaded, so); + interps_notify (&interp::on_solib_unloaded, so, still_in_use); } /* See interps.h. */ diff --git a/gdb/interps.h b/gdb/interps.h index 987465c..2dcf244 100644 --- a/gdb/interps.h +++ b/gdb/interps.h @@ -153,8 +153,11 @@ public: /* Notify the interpreter that solib SO has been loaded. */ virtual void on_solib_loaded (const solib &so) {} - /* Notify the interpreter that solib SO has been unloaded. */ - virtual void on_solib_unloaded (const solib &so) {} + /* Notify the interpreter that solib SO has been unloaded. When + STILL_IN_USE is true, the objfile backing SO is still in use, + this indicates that SO was loaded multiple times, but only mapped + in once (the mapping was reused). */ + virtual void on_solib_unloaded (const solib &so, bool still_in_use) {} /* Notify the interpreter that a command it is executing is about to cause the inferior to proceed. */ @@ -332,8 +335,11 @@ extern void interps_notify_target_resumed (ptid_t ptid); /* Notify all interpreters that solib SO has been loaded. */ extern void interps_notify_solib_loaded (const solib &so); -/* Notify all interpreters that solib SO has been unloaded. */ -extern void interps_notify_solib_unloaded (const solib &so); +/* Notify all interpreters that solib SO has been unloaded. When + STILL_IN_USE is true, the objfile backing SO is still in use, this + indicates that SO was loaded multiple times, but only mapped in + once (the mapping was reused). */ +extern void interps_notify_solib_unloaded (const solib &so, bool still_in_use); /* Notify all interpreters that the selected traceframe changed. diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c index 1cab29b..00c72d1 100644 --- a/gdb/loongarch-tdep.c +++ b/gdb/loongarch-tdep.c @@ -351,11 +351,13 @@ loongarch_deal_with_atomic_sequence (struct regcache *regcache, CORE_ADDR cur_pc { return {}; } - /* Look for a conditional branch instruction, put a breakpoint in its destination address. */ + /* Look for a conditional branch instruction, put a breakpoint in its destination address + which is outside of the ll/sc atomic instruction sequence. */ else if (loongarch_insn_is_cond_branch (insn)) { next_pc = loongarch_next_pc (regcache, cur_pc); - next_pcs.push_back (next_pc); + if (next_pc != cur_pc + insn_len) + next_pcs.push_back (next_pc); } /* Look for a Store Conditional instruction which closes the atomic sequence. */ else if (loongarch_insn_is_sc (insn)) diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c index 5c78acd..b44d676 100644 --- a/gdb/mi/mi-cmd-break.c +++ b/gdb/mi/mi-cmd-break.c @@ -20,8 +20,6 @@ #include "arch-utils.h" #include "exceptions.h" #include "mi-cmds.h" -#include "ui-out.h" -#include "mi-out.h" #include "breakpoint.h" #include "mi-getopt.h" #include "observable.h" @@ -30,7 +28,6 @@ #include "language.h" #include "location.h" #include "linespec.h" -#include "gdbsupport/gdb_obstack.h" #include <ctype.h> #include "tracepoint.h" diff --git a/gdb/mi/mi-cmd-disas.c b/gdb/mi/mi-cmd-disas.c index a311e25..cbae0e2 100644 --- a/gdb/mi/mi-cmd-disas.c +++ b/gdb/mi/mi-cmd-disas.c @@ -19,7 +19,6 @@ #include "arch-utils.h" #include "progspace.h" -#include "target.h" #include "value.h" #include "mi-cmds.h" #include "mi-getopt.h" diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c index ae8c5e8..53077e9 100644 --- a/gdb/mi/mi-cmd-env.c +++ b/gdb/mi/mi-cmd-env.c @@ -19,11 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "inferior.h" -#include "value.h" #include "mi-out.h" #include "mi-cmds.h" #include "mi-getopt.h" -#include "symtab.h" #include "target.h" #include "gdbsupport/environ.h" #include "command.h" diff --git a/gdb/mi/mi-cmd-file.c b/gdb/mi/mi-cmd-file.c index f2fe435..5119e21 100644 --- a/gdb/mi/mi-cmd-file.c +++ b/gdb/mi/mi-cmd-file.c @@ -20,13 +20,12 @@ #include "mi-cmds.h" #include "mi-getopt.h" #include "mi-interp.h" +#include "progspace.h" #include "ui-out.h" #include "symtab.h" #include "source.h" -#include "objfiles.h" #include "solib.h" #include "solist.h" -#include "gdbsupport/gdb_regex.h" /* Return to the client the absolute path and line number of the current file being executed. */ diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c index 5e50428..05bc0ff 100644 --- a/gdb/mi/mi-cmd-stack.c +++ b/gdb/mi/mi-cmd-stack.c @@ -18,18 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "event-top.h" -#include "target.h" #include "frame.h" #include "value.h" #include "mi-cmds.h" #include "ui-out.h" #include "symtab.h" #include "block.h" -#include "stack.h" #include "dictionary.h" #include "language.h" #include "valprint.h" -#include "utils.h" #include "mi-getopt.h" #include "extension.h" #include <ctype.h> @@ -37,7 +34,6 @@ #include <optional> #include "gdbsupport/gdb-safe-ctype.h" #include "inferior.h" -#include "observable.h" enum what_to_list { locals, arguments, all }; diff --git a/gdb/mi/mi-cmd-var.c b/gdb/mi/mi-cmd-var.c index f311204..d889329 100644 --- a/gdb/mi/mi-cmd-var.c +++ b/gdb/mi/mi-cmd-var.c @@ -29,7 +29,6 @@ #include "mi-getopt.h" #include "gdbthread.h" #include "mi-parse.h" -#include <optional> #include "inferior.h" static void varobj_update_one (struct varobj *var, diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index c7afb10..02d4dc3 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -18,7 +18,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "top.h" #include "mi-cmds.h" #include "mi-main.h" #include "mi-parse.h" diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c index ff4a0ff..8de3e47 100644 --- a/gdb/mi/mi-interp.c +++ b/gdb/mi/mi-interp.c @@ -23,7 +23,6 @@ #include "exceptions.h" #include "interps.h" #include "event-top.h" -#include "gdbsupport/event-loop.h" #include "inferior.h" #include "infrun.h" #include "ui-out.h" @@ -721,15 +720,17 @@ mi_interp::on_target_resumed (ptid_t ptid) /* See mi-interp.h. */ -void -mi_output_solib_attribs (ui_out *uiout, const solib &solib) +static void +mi_output_solib_attribs_1 (ui_out *uiout, const solib &solib, + bool include_symbols_loaded_p) { gdbarch *gdbarch = current_inferior ()->arch (); uiout->field_string ("id", solib.so_original_name); uiout->field_string ("target-name", solib.so_original_name); uiout->field_string ("host-name", solib.so_name); - uiout->field_signed ("symbols-loaded", solib.symbols_loaded); + if (include_symbols_loaded_p) + uiout->field_signed ("symbols-loaded", solib.symbols_loaded); if (!gdbarch_has_global_solist (current_inferior ()->arch ())) uiout->field_fmt ("thread-group", "i%d", current_inferior ()->num); @@ -742,6 +743,14 @@ mi_output_solib_attribs (ui_out *uiout, const solib &solib) } } +/* See mi-interp.h. */ + +void +mi_output_solib_attribs (ui_out *uiout, const solib &solib) +{ + mi_output_solib_attribs_1 (uiout, solib, true); +} + void mi_interp::on_solib_loaded (const solib &solib) { @@ -760,7 +769,7 @@ mi_interp::on_solib_loaded (const solib &solib) } void -mi_interp::on_solib_unloaded (const solib &solib) +mi_interp::on_solib_unloaded (const solib &solib, bool still_in_use) { ui_out *uiout = this->interp_ui_out (); @@ -771,11 +780,9 @@ mi_interp::on_solib_unloaded (const solib &solib) ui_out_redirect_pop redir (uiout, this->event_channel); - uiout->field_string ("id", solib.so_original_name); - uiout->field_string ("target-name", solib.so_original_name); - uiout->field_string ("host-name", solib.so_name); - if (!gdbarch_has_global_solist (current_inferior ()->arch ())) - uiout->field_fmt ("thread-group", "i%d", current_inferior ()->num); + /* Pass false here so that 'symbols-loaded' is not included. */ + mi_output_solib_attribs_1 (uiout, solib, false); + uiout->field_string ("still-in-use", still_in_use ? "true" : "false"); gdb_flush (this->event_channel); } diff --git a/gdb/mi/mi-interp.h b/gdb/mi/mi-interp.h index 8f5eee6..beff1c1 100644 --- a/gdb/mi/mi-interp.h +++ b/gdb/mi/mi-interp.h @@ -61,7 +61,7 @@ public: const char *format) override; void on_target_resumed (ptid_t ptid) override; void on_solib_loaded (const solib &so) override; - void on_solib_unloaded (const solib &so) override; + void on_solib_unloaded (const solib &so, bool still_in_use) override; void on_about_to_proceed () override; void on_traceframe_changed (int tfnum, int tpnum) override; void on_tsv_created (const trace_state_variable *tsv) override; diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 1d0fae9..9d084c3 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -30,11 +30,9 @@ #include "mi-cmds.h" #include "mi-parse.h" #include "mi-getopt.h" -#include "mi-console.h" #include "ui-out.h" #include "mi-out.h" #include "interps.h" -#include "gdbsupport/event-loop.h" #include "event-top.h" #include "gdbcore.h" #include "value.h" @@ -45,7 +43,6 @@ #include "language.h" #include "valprint.h" #include "osdata.h" -#include "gdbsupport/gdb_splay_tree.h" #include "tracepoint.h" #include "ada-lang.h" #include "linespec.h" @@ -60,7 +57,6 @@ #include <chrono> #include "progspace-and-thread.h" #include "gdbsupport/rsp-low.h" -#include <algorithm> #include <set> #include <map> diff --git a/gdb/mi/mi-parse.c b/gdb/mi/mi-parse.c index 8804c98..3ef9da5 100644 --- a/gdb/mi/mi-parse.c +++ b/gdb/mi/mi-parse.c @@ -21,7 +21,6 @@ #include "mi-cmds.h" #include "mi-parse.h" -#include "charset.h" #include <ctype.h> #include "cli/cli-utils.h" diff --git a/gdb/nat/fork-inferior.c b/gdb/nat/fork-inferior.c index 41765b1..46fe897 100644 --- a/gdb/nat/fork-inferior.c +++ b/gdb/nat/fork-inferior.c @@ -27,6 +27,8 @@ #include "gdbsupport/signals-state-save-restore.h" #include "gdbsupport/gdb_tilde_expand.h" #include "gdbsupport/gdb_signals.h" +#include "gdbsupport/buildargv.h" +#include "gdbsupport/gdb_argv_vec.h" #include <vector> extern char **environ; @@ -50,7 +52,7 @@ public: strings to which the array point. */ char **argv () { - return const_cast<char **> (&m_argv[0]); + return m_argv.argv (); } private: @@ -69,66 +71,28 @@ private: const std::string &allargs, const char *shell_file); - /* The argument vector built. Holds non-owning pointers. Elements - either point to the strings passed to the execv_argv ctor, or - inside M_STORAGE. */ - std::vector<const char *> m_argv; - - /* Storage. In the no-shell case, this contains a copy of the - arguments passed to the ctor, split by '\0'. In the shell case, - this contains the quoted shell command. I.e., SHELL_COMMAND in - {"$SHELL" "-c", SHELL_COMMAND, NULL}. */ - std::string m_storage; + /* The argument vector. This owns the strings within it. */ + gdb::argv_vec m_argv; }; -/* Create argument vector for straight call to execvp. Breaks up - ALLARGS into an argument vector suitable for passing to execvp and - stores it in M_ARGV. E.g., on "run a b c d" this routine would get - as input the string "a b c d", and as output it would fill in - M_ARGV with the four arguments "a", "b", "c", "d". Each argument - in M_ARGV points to a substring of a copy of ALLARGS stored in - M_STORAGE. */ +/* Create argument vector for straight call to execvp. Breaks up ALLARGS + into an argument vector suitable for passing to execvp and stores it in + M_ARGV. EXEC_FILE is the executable to be run. + + E.g., if EXEC_FILE is "foo", and the user does "run a b c d" then + ALLARGS would be "a b c d", and this function would fill M_ARGV with + give arguments "foo", "a", "b", "c", and "d". */ void execv_argv::init_for_no_shell (const char *exec_file, const std::string &allargs) { + m_argv.push_back (xstrdup (exec_file)); - /* Save/work with a copy stored in our storage. The pointers pushed - to M_ARGV point directly into M_STORAGE, which is modified in - place with the necessary NULL terminators. This avoids N heap - allocations and string dups when 1 is sufficient. */ - std::string &args_copy = m_storage = allargs; - - m_argv.push_back (exec_file); - - for (size_t cur_pos = 0; cur_pos < args_copy.size ();) - { - /* Skip whitespace-like chars. */ - std::size_t pos = args_copy.find_first_not_of (" \t\n", cur_pos); - - if (pos != std::string::npos) - cur_pos = pos; - - /* Find the position of the next separator. */ - std::size_t next_sep = args_copy.find_first_of (" \t\n", cur_pos); - - if (next_sep == std::string::npos) - { - /* No separator found, which means this is the last - argument. */ - next_sep = args_copy.size (); - } - else - { - /* Replace the separator with a terminator. */ - args_copy[next_sep++] = '\0'; - } - - m_argv.push_back (&args_copy[cur_pos]); + gdb_argv argv (allargs.c_str ()); - cur_pos = next_sep; - } + for (const auto &a : argv) + m_argv.push_back (xstrdup (a)); /* NULL-terminate the vector. */ m_argv.push_back (NULL); @@ -182,11 +146,7 @@ execv_argv::init_for_shell (const char *exec_file, /* We're going to call a shell. */ bool escape_bang = escape_bang_in_quoted_argument (shell_file); - /* We need to build a new shell command string, and make argv point - to it. So build it in the storage. */ - std::string &shell_command = m_storage; - - shell_command = "exec "; + std::string shell_command = "exec "; /* Add any exec wrapper. That may be a program name with arguments, so the user must handle quoting. */ @@ -255,10 +215,9 @@ execv_argv::init_for_shell (const char *exec_file, /* If we decided above to start up with a shell, we exec the shell. "-c" says to interpret the next arg as a shell command to execute, and this command is "exec <target-program> <args>". */ - m_argv.reserve (4); - m_argv.push_back (shell_file); - m_argv.push_back ("-c"); - m_argv.push_back (shell_command.c_str ()); + m_argv.push_back (xstrdup (shell_file)); + m_argv.push_back (xstrdup ("-c")); + m_argv.push_back (xstrdup (shell_command.c_str ())); m_argv.push_back (NULL); } diff --git a/gdb/observable.h b/gdb/observable.h index 077014c..deea1ff 100644 --- a/gdb/observable.h +++ b/gdb/observable.h @@ -104,7 +104,8 @@ extern observable<solib &/* solib */> solib_loaded; /* The shared library SOLIB has been unloaded from program space PSPACE. Note when gdb calls this observer, the library's symbols have not been unloaded yet, and thus are still available. */ -extern observable<program_space *, const solib &/* solib */> solib_unloaded; +extern observable<program_space *, const solib &/* solib */, + bool /* still_in_use */> solib_unloaded; /* The symbol file specified by OBJFILE has been loaded. */ extern observable<struct objfile */* objfile */> new_objfile; diff --git a/gdb/solib.c b/gdb/solib.c index 4a04f1d..eee02ca 100644 --- a/gdb/solib.c +++ b/gdb/solib.c @@ -691,13 +691,17 @@ notify_solib_loaded (solib &so) gdb::observers::solib_loaded.notify (so); } -/* Notify interpreters and observers that solib SO has been unloaded. */ +/* Notify interpreters and observers that solib SO has been unloaded. + When STILL_IN_USE is true, the objfile backing SO is still in use, + this indicates that SO was loaded multiple times, but only mapped + in once (the mapping was reused). */ static void -notify_solib_unloaded (program_space *pspace, const solib &so) +notify_solib_unloaded (program_space *pspace, const solib &so, + bool still_in_use) { - interps_notify_solib_unloaded (so); - gdb::observers::solib_unloaded.notify (pspace, so); + interps_notify_solib_unloaded (so, still_in_use); + gdb::observers::solib_unloaded.notify (pspace, so, still_in_use); } /* See solib.h. */ @@ -792,18 +796,23 @@ update_solib_list (int from_tty) /* If it's not on the inferior's list, remove it from GDB's tables. */ else { + bool still_in_use + = (gdb_iter->objfile != nullptr + && solib_used (current_program_space, *gdb_iter)); + /* Notify any observer that the shared object has been unloaded before we remove it from GDB's tables. */ - notify_solib_unloaded (current_program_space, *gdb_iter); - - current_program_space->deleted_solibs.push_back (gdb_iter->so_name); + notify_solib_unloaded (current_program_space, *gdb_iter, + still_in_use); /* Unless the user loaded it explicitly, free SO's objfile. */ if (gdb_iter->objfile != nullptr && !(gdb_iter->objfile->flags & OBJF_USERLOADED) - && !solib_used (current_program_space, *gdb_iter)) + && !still_in_use) gdb_iter->objfile->unlink (); + current_program_space->deleted_solibs.push_back (gdb_iter->so_name); + /* Some targets' section tables might be referring to sections from so.abfd; remove them. */ current_program_space->remove_target_sections (&*gdb_iter); @@ -1158,7 +1167,10 @@ clear_solib (program_space *pspace) for (solib &so : pspace->so_list) { - notify_solib_unloaded (pspace, so); + bool still_in_use + = (so.objfile != nullptr && solib_used (pspace, so)); + + notify_solib_unloaded (pspace, so, still_in_use); pspace->remove_target_sections (&so); }; diff --git a/gdb/stack.c b/gdb/stack.c index 4a92449..6f986aa 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -2863,9 +2863,11 @@ find_frame_for_function (const char *function_name) do { + CORE_ADDR frame_pc = get_frame_address_in_block (frame); + for (size_t i = 0; (i < sals.size () && !found); i++) - found = (get_frame_pc (frame) >= func_bounds[i].low - && get_frame_pc (frame) < func_bounds[i].high); + found = (frame_pc >= func_bounds[i].low + && frame_pc < func_bounds[i].high); if (!found) { level = 1; diff --git a/gdb/testsuite/gdb.ada/inline-section-gc.exp b/gdb/testsuite/gdb.ada/inline-section-gc.exp index 55cd156..78539aa 100644 --- a/gdb/testsuite/gdb.ada/inline-section-gc.exp +++ b/gdb/testsuite/gdb.ada/inline-section-gc.exp @@ -19,14 +19,20 @@ require allow_ada_tests standard_ada_testfile caller +# The ordering here works around a bug in older versions of dejagnu. +# In particular we use "additional_flags" and not "ldflags" to ensure +# the ordering (this is ok because the distinction doesn't really +# matter for gnatmake anyway) and furthermore we take care to end with +# "-margs" so that any flags appended by dejagnu are applied in the +# correct mode. set options { debug optimize=-O2 additional_flags=-ffunction-sections - ldflags=-margs additional_flags=-gnatn - ldflags=-largs - ldflags=-Wl,--gc-sections + additional_flags=-largs + additional_flags=-Wl,--gc-sections + additional_flags=-margs } if {[gdb_compile_ada "${srcfile}" "${binfile}" executable $options] != ""} { return -1 diff --git a/gdb/testsuite/gdb.base/args.exp b/gdb/testsuite/gdb.base/args.exp index f9235f2..34d722a 100644 --- a/gdb/testsuite/gdb.base/args.exp +++ b/gdb/testsuite/gdb.base/args.exp @@ -29,30 +29,54 @@ if {[build_executable $testfile.exp $testfile $srcfile] == -1} { return -1 } +set startup_with_shell_modes { "on" } +if {![gdb_protocol_is_remote]} { + lappend startup_with_shell_modes "off" +} else { + # Some of these tests will not work when using the remote protocol + # due to bug PR gdb/28392. + unsupported "gdbserver 'startup-with-shell off' broken PR gdb/28392" +} + # NAME is the name to use for the tests and ARGLIST is the list of # arguments that are passed to GDB when it is started. +# +# The optional RE_LIST is the list of patterns to check the arguments +# against, these patterns should match ARGLIST. If the arguments are +# expected to show up unmodified in the test output then RE_LIST can +# be dropped, and this proc will reuse ARGLIST. + +proc args_test { name arglist {re_list {}} } { + + # If RE_LIST is not supplied then we can reuse ARGLIST, this + # implies that the arguments will appear unmodified in the test + # output. + if {[llength $re_list] == 0} { + set re_list $arglist + } -proc args_test { name arglist } { - save_vars { ::GDBFLAGS } { - set ::GDBFLAGS "$::GDBFLAGS --args $::binfile $arglist" + foreach_with_prefix startup_with_shell $::startup_with_shell_modes { + save_vars { ::GDBFLAGS } { + set ::GDBFLAGS "$::GDBFLAGS --args $::binfile $arglist" - clean_restart $::binfile + clean_restart $::binfile - runto_main - gdb_breakpoint [gdb_get_line_number "set breakpoint here"] - gdb_continue_to_breakpoint "breakpoint for $name" + gdb_test_no_output "set startup-with-shell ${startup_with_shell}" \ + "set startup-with-shell for $name" - set expected_len [expr 1 + [llength $arglist]] - gdb_test "print argc" "\\\$$::decimal = $expected_len" "argc for $name" + runto_main + gdb_breakpoint [gdb_get_line_number "set breakpoint here"] + gdb_continue_to_breakpoint "breakpoint for $name" - set i 1 - foreach arg $arglist { - if { $arg eq "\n" } { - set arg {\\n} + set expected_len [expr 1 + [llength $re_list]] + gdb_test "print argc" "\\\$$::decimal = $expected_len" "argc for $name" + + set i 1 + foreach arg $re_list { + gdb_test "print argv\[$i\]" "\\\$$::decimal = $::hex \"$arg\"" \ + "argv\[$i\] for $name" + set i [expr $i + 1] } - gdb_test "print argv\[$i\]" "\\\$$::decimal = $::hex \"$arg\"" \ - "argv\[$i\] for $name" - set i [expr $i + 1] } } } @@ -78,6 +102,10 @@ args_test "two empty with single quotes" {{1} {''} {''} {3}} # Try with arguments containing literal newlines. -args_test "one newline" {{1} "\n" {3}} +args_test "one newline" {{1} "\n" {3}} {1 \\\\n 3} + +args_test "two newlines" {{1} "\n" "\n" {3}} {1 \\\\n \\\\n 3} + +args_test "lone single quote" {{1} \' {3}} -args_test "two newlines" {{1} "\n" "\n" {3}} +args_test "lone double quote" {{1} \" {3}} {1 \\\\\" 3} diff --git a/gdb/testsuite/gdb.base/condbreak-multi-context.cc b/gdb/testsuite/gdb.base/condbreak-multi-context.cc index b5628f0..a674fd2 100644 --- a/gdb/testsuite/gdb.base/condbreak-multi-context.cc +++ b/gdb/testsuite/gdb.base/condbreak-multi-context.cc @@ -18,7 +18,7 @@ class Base { public: - static const int b = 20; + static const int bbb = 20; void func () {} }; @@ -26,7 +26,7 @@ public: class A : public Base { public: - static const int a = 10; + static const int aaa = 10; void func () {} }; @@ -34,7 +34,7 @@ public: class C : public Base { public: - static const int c = 30; + static const int ccc = 30; void func () {} }; diff --git a/gdb/testsuite/gdb.base/condbreak-multi-context.exp b/gdb/testsuite/gdb.base/condbreak-multi-context.exp index 3a4fe37..024f768 100644 --- a/gdb/testsuite/gdb.base/condbreak-multi-context.exp +++ b/gdb/testsuite/gdb.base/condbreak-multi-context.exp @@ -22,7 +22,7 @@ set flags {} lappend flags debug lappend flags c++ -if {[prepare_for_testing "failed to prepare" ${binfile} ${srcfile} $flags]} { +if {[build_executable "failed to prepare" ${binfile} ${srcfile} $flags]} { return } @@ -41,17 +41,32 @@ set loc_index(A) 0 set loc_index(Base) 0 set loc_index(C) 0 -# Find breakpoint location contexts. +# Find breakpoint location contexts. This is called before any of the +# tests are run and captures the order in which GDB will create the +# breakpoint locations. +# +# This whole test script does rely on GDB always creating the b/p +# locations in the same order, but that's true right now, and doesn't +# seem likely to change in the near future. + +proc find_location_contexts { } { + global loc_name loc_index fill + global decimal hex gdb_prompt binfile + + clean_restart ${binfile} + + if {![runto_main]} { + return + } -proc find_location_contexts {} { - global loc_name loc_index bpnum1 fill - global decimal hex gdb_prompt + gdb_breakpoint func + set bpnum [get_integer_valueof "\$bpnum" "*UNKNOWN*"] - gdb_test_multiple "info breakpoint $bpnum1" "find_location_indices" { - -re "stop only if ${fill}\r\n" { + gdb_test_multiple "info breakpoint $bpnum" "find_location_indices" { + -re "\r\n$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" { exp_continue } - -re "^${bpnum1}\.($decimal) ${fill} ${hex} in (A|Base|C)::func${fill}\r\n" { + -re "^${bpnum}\.($decimal) ${fill} ${hex} in (A|Base|C)::func${fill}\r\n" { set index $expect_out(1,string) set name $expect_out(2,string) set loc_name($index) $name @@ -76,7 +91,7 @@ proc find_location_contexts {} { # B::func, and 'n' at C::func, respectively. proc check_bp_locations {bpnum states cond {msg ""}} { - global loc_name fill + global loc_name fill loc_states # Map location names to location states. set loc_states(A) [lindex $states 0] @@ -107,67 +122,87 @@ proc check_bp_locations {bpnum states cond {msg ""}} { # Scenario 1: Define breakpoints conditionally, using the "break N if # cond" syntax. Run the program, check that we hit those locations # only. +# +# When START_BEFORE is true we create the breakpoints after running to +# main. When START_BEFORE is false we create the breakpoints after +# starting GDB, but before running to main. + +proc_with_prefix scenario_1 { start_before } { + global warning decimal fill bkptno_num_re binfile + + clean_restart ${binfile} + + if { $start_before } { + if {![runto_main temporary]} { + return + } + } -with_test_prefix "scenario 1" { # Define the conditional breakpoints. Two locations (Base::func # and C::func) should be disabled. We do not test location # indices strictly at this moment, because we don't know them, # yet. We have strict location index tests below. - gdb_test "break func if a == 10" \ + gdb_test "break func if aaa == 10" \ [multi_line \ "${warning} at location $decimal, disabling:" \ - " No symbol \"a\" in current context." \ + " No symbol \"aaa\" in current context." \ "${warning} at location $decimal, disabling:" \ - " No symbol \"a\" in current context." \ + " No symbol \"aaa\" in current context." \ "Breakpoint $decimal at $fill .3 locations."] \ - "define bp with condition a == 10" + "define bp with condition aaa == 10" set bpnum1 [get_integer_valueof "\$bpnum" 0 "get bpnum1"] - gdb_test "break func if c == 30" \ + gdb_test "break func if ccc == 30" \ [multi_line \ ".*${warning} at location $decimal, disabling:" \ - " No symbol \"c\" in current context." \ + " No symbol \"ccc\" in current context." \ ".*${warning} at location $decimal, disabling:" \ - " No symbol \"c\" in current context." \ + " No symbol \"ccc\" in current context." \ ".*Breakpoint $decimal at $fill .3 locations."] \ - "define bp with condition c == 30" + "define bp with condition ccc == 30" set bpnum2 [get_integer_valueof "\$bpnum" 0 "get bpnum2"] - find_location_contexts - - with_test_prefix "before run" { - check_bp_locations $bpnum1 {y N* N*} "a == 10" - check_bp_locations $bpnum2 {N* N* y} "c == 30" + with_test_prefix "after creating b/p" { + check_bp_locations $bpnum1 {y N* N*} "aaa == 10" + check_bp_locations $bpnum2 {N* N* y} "ccc == 30" } - # Do not use runto_main, it deletes all breakpoints. - gdb_run_cmd + if { !$start_before } { + if {![runto_main temporary no-delete-breakpoints]} { + return + } + } # Check our conditional breakpoints. - gdb_test "" ".*Breakpoint $bkptno_num_re, A::func .*" \ + gdb_test "continue" ".*Breakpoint $bkptno_num_re, A::func .*" \ "run until A::func" - gdb_test "print a" " = 10" + gdb_test "print aaa" " = 10" gdb_test "continue" "Continuing.*Breakpoint $bkptno_num_re, C::func .*" \ "run until C::func" - gdb_test "print c" " = 30" + gdb_test "print ccc" " = 30" # No more hits! gdb_continue_to_end with_test_prefix "after run" { - check_bp_locations $bpnum1 {y N* N*} "a == 10" - check_bp_locations $bpnum2 {N* N* y} "c == 30" + check_bp_locations $bpnum1 {y N* N*} "aaa == 10" + check_bp_locations $bpnum2 {N* N* y} "ccc == 30" } } -# Start GDB with two breakpoints and define the conditions separately. +# Assuming GDB is already started, create two breakpoints and define +# the conditions separately. +# +# BPNUM1_NAME and BPNUM2_NAME contain the name of two variables in the +# parent contents. The breakpoint numbers of the two created +# breakpoints are placed into these variables in the parent content. -proc setup_bps {} { - global srcfile binfile srcfile2 decimal - global bpnum1 bpnum2 bp_location warning loc_index +proc setup_bps { bpnum1_name bpnum2_name } { + global warning loc_index - clean_restart ${binfile} + upvar $bpnum1_name bpnum1 + upvar $bpnum2_name bpnum2 # Define the breakpoints. gdb_breakpoint "func" @@ -176,74 +211,105 @@ proc setup_bps {} { gdb_breakpoint "func" set bpnum2 [get_integer_valueof "\$bpnum" 0 "get bpnum2"] - # Defining a condition on 'a' disables 2 locations. + # Defining a condition on 'aaa' disables 2 locations. set locs [lsort -integer "$loc_index(Base) $loc_index(C)"] - gdb_test "cond $bpnum1 a == 10" \ + gdb_test "cond $bpnum1 aaa == 10" \ [multi_line \ "$warning at location ${bpnum1}.[lindex $locs 0], disabling:" \ - " No symbol \"a\" in current context." \ + " No symbol \"aaa\" in current context." \ "$warning at location ${bpnum1}.[lindex $locs 1], disabling:" \ - " No symbol \"a\" in current context."] + " No symbol \"aaa\" in current context."] # Defining a condition on 'c' disables 2 locations. set locs [lsort -integer "$loc_index(Base) $loc_index(A)"] - gdb_test "cond $bpnum2 c == 30" \ + gdb_test "cond $bpnum2 ccc == 30" \ [multi_line \ "$warning at location ${bpnum2}.[lindex $locs 0], disabling:" \ - " No symbol \"c\" in current context." \ + " No symbol \"ccc\" in current context." \ "$warning at location ${bpnum2}.[lindex $locs 1], disabling:" \ - " No symbol \"c\" in current context."] + " No symbol \"ccc\" in current context."] } # Scenario 2: Define breakpoints unconditionally, and then define # conditions using the "cond N <cond>" syntax. Expect that the # locations where <cond> is not evaluatable are disabled. Run the # program, check that we hit the enabled locations only. +# +# When START_BEFORE is true we create the breakpoints after running to +# main. When START_BEFORE is false we create the breakpoints after +# starting GDB, but before running to main. + +proc_with_prefix scenario_2 { start_before } { + global binfile bkptno_num_re + + clean_restart ${binfile} -with_test_prefix "scenario 2" { - setup_bps + if { $start_before } { + if {![runto_main temporary]} { + return + } + } + + setup_bps bpnum1 bpnum2 - with_test_prefix "before run" { - check_bp_locations $bpnum1 {y N* N*} "a == 10" - check_bp_locations $bpnum2 {N* N* y} "c == 30" + with_test_prefix "after creating b/p" { + check_bp_locations $bpnum1 {y N* N*} "aaa == 10" + check_bp_locations $bpnum2 {N* N* y} "ccc == 30" } - # Do not use runto_main, it deletes all breakpoints. - gdb_run_cmd + if { !$start_before } { + if {![runto_main temporary no-delete-breakpoints]} { + return + } + } # Check that we hit enabled locations only. - gdb_test "" ".*Breakpoint $bkptno_num_re, A::func .*" \ + gdb_test "continue" ".*Breakpoint $bkptno_num_re, A::func .*" \ "run until A::func" - gdb_test "print a" " = 10" + gdb_test "print aaa" " = 10" gdb_test "continue" "Continuing.*Breakpoint $bkptno_num_re, C::func .*" \ "run until C::func" - gdb_test "print c" " = 30" + gdb_test "print ccc" " = 30" # No more hits! gdb_continue_to_end with_test_prefix "after run" { - check_bp_locations $bpnum1 {y N* N*} "a == 10" - check_bp_locations $bpnum2 {N* N* y} "c == 30" + check_bp_locations $bpnum1 {y N* N*} "aaa == 10" + check_bp_locations $bpnum2 {N* N* y} "ccc == 30" } } # Scenario 3: Apply misc. checks on the already-defined breakpoints. +# +# When START_BEFORE is true we create the breakpoints after running to +# main. When START_BEFORE is false we create the breakpoints after +# starting GDB, but before running to main. + +proc_with_prefix scenario_3 { start_before } { + global binfile bkptno_num_re loc_index warning + + clean_restart ${binfile} -with_test_prefix "scenario 3" { - setup_bps + if { $start_before } { + if {![runto_main temporary]} { + return + } + } + + setup_bps bpnum1 bpnum2 set locs [lsort -integer "$loc_index(Base) $loc_index(A)"] - gdb_test "cond $bpnum1 c == 30" \ + gdb_test "cond $bpnum1 ccc == 30" \ [multi_line \ "${warning} at location ${bpnum1}.[lindex $locs 0], disabling:" \ - " No symbol \"c\" in current context." \ + " No symbol \"ccc\" in current context." \ "${warning} at location ${bpnum1}.[lindex $locs 1], disabling:" \ - " No symbol \"c\" in current context." \ + " No symbol \"ccc\" in current context." \ "Breakpoint ${bpnum1}'s condition is now valid at location $loc_index(C), enabling."] \ "change the condition of bp 1" - check_bp_locations $bpnum1 {N* N* y} "c == 30" "after changing the condition" + check_bp_locations $bpnum1 {N* N* y} "ccc == 30" "after changing the condition" gdb_test "cond $bpnum1" \ [multi_line \ @@ -254,7 +320,7 @@ with_test_prefix "scenario 3" { check_bp_locations $bpnum1 {y y y} "" "after resetting the condition" gdb_test_no_output "disable ${bpnum2}.$loc_index(A)" - check_bp_locations $bpnum2 {N* N* y} "c == 30" "after disabling loc for A" + check_bp_locations $bpnum2 {N* N* y} "ccc == 30" "after disabling loc for A" gdb_test "cond $bpnum2" ".*" "reset the condition of bp 2" check_bp_locations $bpnum2 {n y y} "" "loc for A should remain disabled" @@ -262,17 +328,17 @@ with_test_prefix "scenario 3" { gdb_test_no_output "disable ${bpnum2}.$loc_index(C)" check_bp_locations $bpnum2 {n y n} "" "after disabling loc for C" - gdb_test "cond $bpnum2 c == 30" \ + gdb_test "cond $bpnum2 ccc == 30" \ [multi_line \ "${warning} at location ${bpnum2}.$loc_index(Base), disabling:" \ - " No symbol \"c\" in current context."] \ + " No symbol \"ccc\" in current context."] \ "re-define a condition" - check_bp_locations $bpnum2 {N* N* n} "c == 30" "loc for C should remain disabled" + check_bp_locations $bpnum2 {N* N* n} "ccc == 30" "loc for C should remain disabled" gdb_test "enable ${bpnum2}.$loc_index(Base)" \ "Breakpoint ${bpnum2}'s condition is invalid at location $loc_index(Base), cannot enable." \ "reject enabling a location that is disabled-by-cond" - check_bp_locations $bpnum2 {N* N* n} "c == 30" "after enable attempt" + check_bp_locations $bpnum2 {N* N* n} "ccc == 30" "after enable attempt" gdb_test "cond $bpnum2 garbage" \ "No symbol \"garbage\" in current context." \ @@ -281,19 +347,34 @@ with_test_prefix "scenario 3" { gdb_test_no_output "delete $bpnum1" # Do not use runto_main, it deletes all breakpoints. - gdb_breakpoint "main" - gdb_run_cmd - gdb_test "" ".*reakpoint .*, main .*${srcfile}.*" "start" + if { !$start_before } { + if {![runto_main temporary no-delete-breakpoints]} { + return + } + } # The second BP's locations are all disabled. No more hits! gdb_continue_to_end } # Scenario 4: Test the '-force'/'-force-condition' flag. +# +# When START_BEFORE is true we create the breakpoints after running to +# main. When START_BEFORE is false we create the breakpoints after +# starting GDB, but before running to main, in fact we don't even +# bother with a run to main in this case. + +proc_with_prefix scenario_4 { start_before } { + global binfile bkptno_num_re loc_index warning -with_test_prefix "force" { clean_restart ${binfile} + if { $start_before } { + if {![runto_main temporary]} { + return + } + } + gdb_breakpoint "func" # Pick a condition that is invalid at every location. set bpnum1 [get_integer_valueof "\$bpnum" 0 "get bpnum1"] @@ -313,3 +394,15 @@ with_test_prefix "force" { set bpnum2 [get_integer_valueof "\$bpnum" 0 "get bpnum2"] check_bp_locations $bpnum2 {N* N* N*} "baz" "set using the break command" } + +# Some initial setup. Needed for most of the different scenarios +# below. +find_location_contexts + +# Now run all of the separate scenarios. +foreach_with_prefix start_before { true false } { + scenario_1 $start_before + scenario_2 $start_before + scenario_3 $start_before + scenario_4 $start_before +} diff --git a/gdb/testsuite/gdb.base/corefile-exec-context.exp b/gdb/testsuite/gdb.base/corefile-exec-context.exp index d6d4c0d..6bd2b04 100644 --- a/gdb/testsuite/gdb.base/corefile-exec-context.exp +++ b/gdb/testsuite/gdb.base/corefile-exec-context.exp @@ -108,7 +108,7 @@ gdb_test_multiple "up" "move up to main" { pass $gdb_test_name } - -re -wrap "#$decimal\\s+\[^\r\n\]+ in .*" { + -re -wrap "#$decimal\\s+\[^\r\n\]+ (in|at) .*" { send_gdb "up\n" exp_continue } diff --git a/gdb/testsuite/gdb.base/dlmopen.exp b/gdb/testsuite/gdb.base/dlmopen.exp index dee046a..14f9084 100644 --- a/gdb/testsuite/gdb.base/dlmopen.exp +++ b/gdb/testsuite/gdb.base/dlmopen.exp @@ -77,15 +77,27 @@ if { [build_executable "build shlib" $binfile_lib2 $srcfile_lib \ return } -if { [prepare_for_testing "failed to prepare" $testfile $srcfile \ +if { [build_executable "failed to build" $testfile $srcfile \ [list additional_flags=-DDSO1_NAME=\"$binfile_lib1\" \ additional_flags=-DDSO2_NAME=\"$binfile_lib2\" \ shlib_load debug]] } { - return -1 + return +} + +# Some locations needed by the tests. +set bp_inc [gdb_get_line_number "bp.inc" $srcfile_lib] +set bp_main [gdb_get_line_number "bp.main" $srcfile] + +# Figure out the file name for the dynamic linker. +set dyln_name [section_get $binfile .interp] +if { $dyln_name eq "" } { + unsupported "couldn't find dynamic linker name" + return } -if { ![runto_main] } { - return -1 +# Return true if FILENAME is the dynamic linker. Otherwise return false. +proc is_dyln { filename } { + return [expr {$filename eq $::dyln_name}] } # Check that 'info shared' show NUM occurrences of DSO. @@ -158,41 +170,213 @@ proc test_dlmopen {} { } } -# Remove the pause. We only need it for the attach test. -gdb_test "print wait_for_gdb = 0" "\\\$1 = 0" +# Setup for calling 'test_dlmopen', this is the version of the test +# that doesn't use 'attach'. +proc test_dlmopen_no_attach {} { + clean_restart $::binfile -# Break in the to-be-loaded library and at the end of main. -set bp_inc [gdb_get_line_number "bp.inc" $srcfile_lib] -set bp_main [gdb_get_line_number "bp.main" $srcfile] + if { ![runto_main] } { + return + } -delete_breakpoints -gdb_breakpoint $srcfile_lib:$bp_inc allow-pending -gdb_breakpoint $srcfile:$bp_main + # Remove the pause. We only need it for the attach test. + gdb_test "print wait_for_gdb = 0" "\\\$1 = 0" -test_dlmopen + # Break in the to-be-loaded library and at the end of main. + delete_breakpoints + gdb_breakpoint $::srcfile_lib:$::bp_inc allow-pending + gdb_breakpoint $::srcfile:$::bp_main -# Try the same again when attaching after dlmopen(). -require can_spawn_for_attach + test_dlmopen +} -clean_restart $binfile +# Setup for calling 'test_dlmopen', this is the version of the test +# that does use 'attach'. +proc test_dlmopen_with_attach {} { + if { ![can_spawn_for_attach] } { + unsupported "cannot run attach tests" + return + } -# Start the test program. -set test_spawn_id [spawn_wait_for_attach $binfile] -set testpid [spawn_id_get_pid $test_spawn_id] + clean_restart $::binfile -# Attach. -if { ![gdb_attach $testpid] } { - return + # Start the test program. + set test_spawn_id [spawn_wait_for_attach $::binfile] + set testpid [spawn_id_get_pid $test_spawn_id] + + # Attach. + if { ![gdb_attach $testpid] } { + return + } + + with_test_prefix "attach" { + # Remove the pause. We no longer need it. + gdb_test "print wait_for_gdb = 0" "\\\$1 = 0" + + # Set the same breakpoints again. This time, however, we do not allow the + # breakpoint to be pending since the library has already been loaded. + gdb_breakpoint $::srcfile_lib:$::bp_inc + gdb_breakpoint $::srcfile:$::bp_main + + test_dlmopen + } } -with_test_prefix "attach" { - # Remove the pause. We no longer need it. - gdb_test "print wait_for_gdb = 0" "\\\$1 = 0" +# Run 'info sharedlibrary' and count the number of mappings that look +# like they might be the dynamic linker. This will only work for +# Linux right now. +proc get_dyld_info {} { + if { ![istarget *-linux*] } { + return [list 0 ""] + } - # Set the same breakpoints again. This time, however, we do not allow the - # breakpoint to be pending since the library has already been loaded. - gdb_breakpoint $srcfile_lib:$bp_inc - gdb_breakpoint $srcfile:$bp_main + set dyld_count 0 + set dyld_start_addr "" + gdb_test_multiple "info sharedlibrary" "" { + -re "From\\s+To\\s+Syms\\s+Read\\s+Shared Object Library\r\n" { + exp_continue + } + -re "^($::hex)\\s+$::hex\\s+\[^/\]+(/\[^\r\n\]+)\r\n" { + set addr $expect_out(1,string) + set lib $expect_out(2,string) + + if { [is_dyln $lib] } { + # This looks like it might be the dynamic linker. + incr dyld_count + if { $dyld_start_addr eq "" } { + set dyld_start_addr $addr + } elseif { $dyld_start_addr ne $addr } { + set dyld_start_addr "MULTIPLE" + } + } - test_dlmopen + exp_continue + } + -re "\\(\\*\\): Shared library is missing debugging information\\.\r\n" { + exp_continue + } + -re "^$::gdb_prompt $" { + } + } + + if { $dyld_start_addr eq "MULTIPLE" } { + set dyld_start_addr "" + } + + return [list $dyld_count $dyld_start_addr] } + +# The inferior for this test causes the dynamic linker to be appear +# multiple times in the inferior's shared library list, but (at least +# with glibc), the dynamic linker is really only mapped in once. That +# is, each of the dynamic linker instances that appear in the 'info +# sharedlibrary' output, will have the same address range. +# +# This test creates a user breakpoint in the dynamic linker, and then +# runs over the dlcose calls, which unmap all but one of the dynamic +# linker instances. +# +# The expectation is that the user breakpoint in the dynamic linker +# should still be active. Older versions of GDB had a bug where the +# breakpoint would become pending. +proc_with_prefix test_solib_unmap_events { } { + + # This test relies on finding the dynamic linker library, and is + # currently written assuming Linux. + if { ![istarget *-linux*] } { + unsupport "cannot find dynamic linker library on this target" + return + } + + clean_restart $::binfile + + if { ![runto_main] } { + return + } + + # Check that before any of our dlopen/dlmopen calls, we can find a + # single copy of the dynamic linker in the shared library list. + set dyld_info [get_dyld_info] + set dyld_count [lindex $dyld_info 0] + if { $dyld_count != 1 } { + unsupported "initial dyld state appears strange" + return + } + + # Continue the inferior until all solib are loaded. + set alarm_lineno [gdb_get_line_number "alarm" $::srcfile] + gdb_breakpoint ${::srcfile}:${alarm_lineno} + gdb_continue_to_breakpoint "all solib are now loaded" + + # Check that we have multiple copies of dynamic linker loaded, and + # that the dynamic linker is only loaded at a single address. + set dyld_info [get_dyld_info] + set dyld_count [lindex $dyld_info 0] + set dyld_start_addr [lindex $dyld_info 1] + + # If we didn't find a suitable dynamic linker address, or we + # didn't find multiple copies of the dynamic linker, then + # something has gone wrong with the test setup. + if { $dyld_count < 2 } { + unsupported "multiple copies of the dynamic linker not found" + return + } + if { $dyld_start_addr eq "" } { + unsupported "unable to find suitable dynamic linker start address" + return + } + + # Check the address we found is (likely) writable. + gdb_test_multiple "x/1i $dyld_start_addr" "check b/p address" { + -re -wrap "Cannot access memory at address \[^\r\n\]+" { + unsupported "dynamic linker address is not accessible" + return + } + -re -wrap "" { + } + } + + # Create a breakpoint within the dynamic linker. It is pretty unlikely + # that this breakpoint will ever be hit, but just in case it is, make it + # conditional, with a condition that will never be true. All we really + # care about for this test is whether the breakpoint will be made + # pending or not (it should not). + gdb_test "break *$dyld_start_addr if (0)" \ + "Breakpoint $::decimal at $::hex\[^\r\n\]+" \ + "create breakpoint within dynamic linker" + set bpnum [get_integer_valueof "\$bpnum" INVALID "get bpnum"] + + # Now continue until the 'bp.main' location, this will unload some + # copies, but not all copies, of the dynamic linker. + gdb_test "print wait_for_gdb = 0" " = 0" + set bp_main [gdb_get_line_number "bp.main" $::srcfile] + + gdb_breakpoint $::srcfile:$bp_main + gdb_continue_to_breakpoint "stop at bp.main" + + # At one point, GDB would incorrectly mark the breakpoints in the + # dynamic linker as pending when some instances of the library were + # unloaded, despite there really only being one copy of the dynamic + # linker actually loaded into the inferior's address space. + gdb_test_multiple "info breakpoints $bpnum" "check b/p status" { + -re -wrap "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+\\*$::hex\\s*\r\n\\s+stop only if \\(0\\)" { + fail $gdb_test_name + } + + -re -wrap "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+$::hex\\s*\[^\r\n\]+\r\n\\s+stop only if \\(0\\)" { + pass $gdb_test_name + } + } + + # With all the dlclose calls now complete, we should be back to a + # single copy of the dynamic linker. + set dyld_info [get_dyld_info] + set dyld_count [lindex $dyld_info 0] + gdb_assert { $dyld_count == 1 } \ + "one dynamic linker found after dlclose calls" +} + +# Run the actual tests. +test_dlmopen_no_attach +test_dlmopen_with_attach +test_solib_unmap_events diff --git a/gdb/testsuite/gdb.base/frame-selection.c b/gdb/testsuite/gdb.base/frame-selection.c index 237e155..18a58e4 100644 --- a/gdb/testsuite/gdb.base/frame-selection.c +++ b/gdb/testsuite/gdb.base/frame-selection.c @@ -15,6 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <stdlib.h> + int frame_2 (void) { @@ -40,6 +42,23 @@ recursive (int arg) return v; } +/* A function that never returns. */ +void __attribute__((noreturn)) +func_that_never_returns (void) +{ + exit (0); +} + +/* A function that tail calls. Calling a 'noreturn' function isn't + required for a tail call, but at low optimisation levels, gcc will apply + the tail call optimisation only for 'noreturn' calls. */ + +void +func_that_tail_calls (void) +{ + func_that_never_returns (); +} + int main (void) { @@ -48,5 +67,7 @@ main (void) i = frame_1 (); j = recursive (0); + func_that_tail_calls (); + return i + j; } diff --git a/gdb/testsuite/gdb.base/frame-selection.exp b/gdb/testsuite/gdb.base/frame-selection.exp index e8d9c87..32ed92d 100644 --- a/gdb/testsuite/gdb.base/frame-selection.exp +++ b/gdb/testsuite/gdb.base/frame-selection.exp @@ -186,3 +186,40 @@ with_test_prefix "second frame_2 breakpoint" { gdb_test "frame function recursive" "#1 $hex in recursive.*" \ "select frame for function recursive, third attempt" } + +# At one point using the 'function' sub-command (e.g. 'frame function +# ...') would fail to select a frame if the frame ended with a +# tail-call, and the address within the frame was outside the bounds +# of the function. +with_test_prefix "stack with tail call" { + gdb_breakpoint func_that_never_returns + gdb_continue_to_breakpoint func_that_never_returns + + gdb_test "bt" \ + [multi_line \ + "#0 func_that_never_returns \\(\\) at \[^\r\n\]+" \ + "#1 $hex in func_that_tail_calls \\(\\) at \[^\r\n\]+" \ + "#2 $hex in main \\(\\) at \[^\r\n\]+"] \ + "bt from func_that_never_returns" + + # Reset the frame addresses based on the new stack. + gdb_test "frame 0" "#0 func_that_never_returns.*" + set frame_0_address [ get_frame_address "frame 0" ] + gdb_test "frame 1" "#1 $hex in func_that_tail_calls.*" + set frame_1_address [ get_frame_address "frame 1" ] + gdb_test "frame 2" "#2 $hex in main.*" + set frame_2_address [ get_frame_address "frame 2" ] + + # Test 'select-frame function ...' command. + gdb_test_no_output "select-frame function func_that_never_returns" + check_frame "0" "${frame_0_address}" "func_that_never_returns" + gdb_test_no_output "select-frame function func_that_tail_calls" + check_frame "1" "${frame_1_address}" "func_that_tail_calls" + gdb_test_no_output "select-frame function main" + check_frame "2" "${frame_2_address}" "main" + + # Test 'frame function ...' command. + gdb_test "frame function func_that_never_returns" "#0 func_that_never_returns.*" + gdb_test "frame function func_that_tail_calls" "#1 $hex in func_that_tail_calls.*" + gdb_test "frame function main" "#2 $hex in main.*" +} diff --git a/gdb/testsuite/gdb.base/inferior-args.exp b/gdb/testsuite/gdb.base/inferior-args.exp index b165fb8..79b73e6 100644 --- a/gdb/testsuite/gdb.base/inferior-args.exp +++ b/gdb/testsuite/gdb.base/inferior-args.exp @@ -25,16 +25,27 @@ if {[build_executable "failed to prepare" $testfile $srcfile \ return } -proc do_test { method } { +# STARTUP_WITH_SHELL is either 'on' or 'off' and determines if the +# inferior is started under a shell or not. INFERIOR_ARGS is the list +# of inferior arguments. EXPECTED_RESULTS is the list of expected +# results, one for each argument. +# +# When STUB_SUITABLE is true this test is suitable for use with +# gdbserver, i.e. INFERIOR_ARGS can be passed through to +# gdbserver_start via gdb_run_cmd. Some of the weird quoting used in +# some of the tests doesn't seem to play well with gdbserver_start. +# This is a TCL issue, not a gdbserver issue. Manually testing with +# gdbserver shows no problems. It's just that when we try to invoke +# gdbserver from TCL the argument quoting gets messed up. For tests +# that are problematic, STUB_SUITABLE is false. +proc do_test { method startup_with_shell inferior_args expected_results \ + stub_suitable } { global binfile hex - # The second arg is an empty string on purpose. The last argument - # must be the empty argument -- we once had a bug where that - # wouldn't work! - set inferior_args { "first arg" "" "third-arg" "'" "\"" " " "" } - clean_restart $binfile + gdb_test_no_output "set startup-with-shell $startup_with_shell" + if { $method == "start" } { # The start command does not make sense for a stub. if { [use_gdb_stub] } { @@ -80,6 +91,10 @@ proc do_test { method } { return -1 } + if { [use_gdb_stub] && !$stub_suitable } { + return + } + # The run command does not make sense for a stub, but GDB_RUN_CMD # does the right thing when the target is a stub (start the stub, # connect to it, and "continue"). @@ -110,18 +125,74 @@ proc do_test { method } { error "invalid method $method" } + set argc [expr [llength $expected_results] + 1] + # Now that we are stopped at main, inspect argc/argv. - gdb_test "print argc" " = 8" - gdb_test "print argv\[0\]" " = $hex \".*\"" - gdb_test "print argv\[1\]" " = $hex \"first arg\"" - gdb_test "print argv\[2\]" " = $hex \"\"" - gdb_test "print argv\[3\]" " = $hex \"third-arg\"" - gdb_test "print argv\[4\]" " = $hex \"'\"" - gdb_test "print argv\[5\]" " = $hex \"\\\\\"\"" - gdb_test "print argv\[6\]" " = $hex \" \"" - gdb_test "print argv\[7\]" " = $hex \"\"" + gdb_test "print argc" " = $argc" + gdb_test "print argv\[0\]" " = $hex \"\[^\r\n\]+\"" + for { set i 1 } { $i < $argc } { incr i } { + set idx [expr $i - 1] + gdb_test "print argv\[$i\]" " = [lindex $expected_results $idx]" + } +} + +set test_desc_list [] + +# test one +# -------- +# +# The second arg is an empty string on purpose. The last argument +# must be the empty argument -- we once had a bug where that wouldn't +# work! +lappend test_desc_list [list "test one" \ + true \ + { "first arg" "" "third-arg" "'" "\"" " " "" } \ + [list "$hex \"first arg\"" \ + "$hex \"\"" \ + "$hex \"third-arg\"" \ + "$hex \"'\"" \ + "$hex \"\\\\\"\"" \ + "$hex \" \"" \ + "$hex \"\"" ]] + +# test two +# -------- +# +# The argument being passed here is '"', that is a single double quote +# contained within single quotes. +# +# I build the test descriptor using this mess of code to avoid having +# unbalanced quotes, which messes up indentation and syntax +# highlighting within (at least) emacs. The 'format' of ascii code 34 +# gives us the double quote character. Then I have to jump through +# the rest of this mess in order to avoid TCL escaping the quote for +# me. It's super important that what we send to GDB is '"' not '\"'. +set item [list "test two" false] +set cmd [format "lappend item \{ '%c' '\\%c' \}" 34 34] +eval $cmd +set bs "\\\\" +lappend item [list "$hex \"$bs\"\"" "$hex \"$bs$bs$bs\"\""] +lappend test_desc_list $item + +set startup_with_shell_modes { "on" } +if {![gdb_protocol_is_remote]} { + lappend startup_with_shell_modes "off" +} else { + # Due to PR gdb/28392 gdbserver doesn't currently support having + # startup-with-shell off, and then attempting to pass arguments + # containing whitespace. + unsupported "bug gdb/28392: gdbserver doesn't support this" } -foreach_with_prefix method { "start" "starti" "run" "set args" } { - do_test $method + +foreach desc $test_desc_list { + lassign $desc name stub_suitable args re_list + with_test_prefix $name { + foreach_with_prefix set_method { "start" "starti" "run" "set args" } { + foreach_with_prefix startup_with_shell $startup_with_shell_modes { + do_test $set_method $startup_with_shell $args $re_list \ + $stub_suitable + } + } + } } diff --git a/gdb/testsuite/gdb.base/libsegfault.exp b/gdb/testsuite/gdb.base/libsegfault.exp index 2c16fe8..61a54ff 100644 --- a/gdb/testsuite/gdb.base/libsegfault.exp +++ b/gdb/testsuite/gdb.base/libsegfault.exp @@ -42,7 +42,7 @@ proc gdb_spawn_with_ld_preload {lib cmdline_opts} { # ASan runtime does not come first in initial library list; you should # either link runtime to your application or manually preload it with # LD_PRELOAD. - set_sanitizer_default ASAN_OPTIONS verify_asan_link_order 0 + append_environment_default ASAN_OPTIONS verify_asan_link_order 0 gdb_spawn_with_cmdline_opts $cmdline_opts } diff --git a/gdb/testsuite/gdb.base/startup-with-shell.exp b/gdb/testsuite/gdb.base/startup-with-shell.exp index 87a7559..495c43e 100644 --- a/gdb/testsuite/gdb.base/startup-with-shell.exp +++ b/gdb/testsuite/gdb.base/startup-with-shell.exp @@ -43,55 +43,134 @@ proc initial_setup_simple { startup_with_shell run_args } { clean_restart $binfile gdb_test_no_output "set startup-with-shell $startup_with_shell" + gdb_test_no_output "set print characters unlimited" gdb_test_no_output "set args $run_args" \ "set args \$run_args" - set test "inferior started" - if { [runto_main] } { - pass $test - } else { - fail $test + return [runto_main] +} + +# Start GDB, set the inferior arguments to ARGS, and then run to main. +# Once at main, read the first argument from the inferior and compare +# it to ON_RE if startup-with-shell is on, otherwise compare to +# OFF_RE. +# +# If PROBLEMATIC_ON is true then when startup-with-shell is on we +# expect the comparison to fail, so setup an xfail. +# +# If PROBLEMATIC_OFF is true then when startup-with-shell is off we +# expect the comparison to fail, so setup an xfail. +# +# TESTNAME is a string used in the test names. +proc run_test { args on_re off_re testname { problematic_on false } \ + { problematic_off false } } { + foreach startup_with_shell { "on" "off" } { + with_test_prefix "$testname, startup_with_shell: ${startup_with_shell}" { + if {![initial_setup_simple $startup_with_shell $args]} { + return -1 + } + + if { $startup_with_shell } { + set re $on_re + set problematic $problematic_on + } else { + set re $off_re + set problematic $problematic_off + } + + if { $problematic } { + setup_xfail "*-*-*" gdb/28392 + } + + gdb_test "print argv\[1\]" "\\\$$::decimal = $::hex $re" $testname + } } } +# This is like the run_test proc except that RE is used as the +# expected argument regexp when startup-with-shell is both on and off. +# For the other arguments, see run_test. +proc run_test_same { args re testname { problematic_on false } \ + { problematic_off false } } { + run_test $args $re $re $testname $problematic_on $problematic_off +} + +# The regexp to match a single '\' character. +set bs "\\\\" + # Are we using 'remote' or 'extended-remote' protocol? set is_remote_p [gdb_protocol_is_remote] ## Run the actual tests -with_test_prefix "startup_with_shell = on; run_args = *.unique-extension" { - initial_setup_simple "on" "$unique_file_dir/*.unique-extension" - gdb_test_no_output "set print characters unlimited" - if { $is_remote_p } { - setup_xfail "*-*-*" gdb/28392 - } - gdb_test "print argv\[1\]" "\\\$$decimal = $hex \"$unique_file\"" \ - "first argument expanded" -} +run_test "$unique_file_dir/*.unique-extension" \ + "\"$unique_file\"" \ + "\"$unique_file_dir/\\\*\.unique-extension\"" \ + "arg is glob" \ + $is_remote_p -with_test_prefix "startup_with_shell = off; run_args = *.unique-extension" { - initial_setup_simple "off" "$unique_file_dir/*.unique-extension" - gdb_test_no_output "set print characters unlimited" - gdb_test "print argv\[1\]" "\\\$$decimal = $hex \"$unique_file_dir/\\\*\.unique-extension\"" \ - "first argument not expanded" -} +run_test_same "$unique_file_dir/\\*.unique-extension" \ + "\"$unique_file_dir/\\\*\.unique-extension\"" \ + "arg is escaped glob" -with_test_prefix "startup_with_shell = on; run_args = \$TEST" { +save_vars { env(TEST) } { set env(TEST) "1234" - initial_setup_simple "on" "\$TEST" - if { $is_remote_p } { - setup_xfail "*-*-*" gdb/28392 - } - gdb_test "print argv\[1\]" "\\\$$decimal = $hex \"1234\"" \ - "testing first argument" - unset env(TEST) + run_test "\$TEST" \ + "\"1234\"" \ + "\"\\\$TEST\"" \ + "arg is shell variable" \ + $is_remote_p + + run_test_same "\\\$TEST" \ + "\"\\\$TEST\"" \ + "arg is escaped shell variable" } -with_test_prefix "startup_with_shell = off; run_args = \$TEST" { - set env(TEST) "1234" - initial_setup_simple "off" "\$TEST" - gdb_test "print argv\[1\]" "\\\$$decimal = $hex \"\\\$TEST\"" \ - "testing first argument" - unset env(TEST) -} +run_test_same "\"\\a\"" \ + "\"${bs}${bs}a\"" \ + "retain backslash in double quote arg" \ + false $is_remote_p + +run_test_same "'\\a'" \ + "\"${bs}${bs}a\"" \ + "retain backslash in single quote arg" \ + false $is_remote_p + +run_test_same "\"\\\$\"" \ + "\"\\\$\"" \ + "'\$' can be escaped in double quote arg" + +run_test_same "'\\\$'" \ + "\"${bs}${bs}\\\$\"" \ + "'\$' is not escaped in single quote arg" \ + false $is_remote_p + +run_test_same "\"\\`\"" \ + "\"\\`\"" \ + "'`' can be escaped in double quote arg" + +run_test_same "'\\`'" \ + "\"${bs}${bs}`\"" \ + "'`' is not escaped in single quote arg" \ + false $is_remote_p + +run_test_same "\"\\\"\"" \ + "\"${bs}\"\"" \ + "'\"' can be escaped in double quote arg" \ + false $is_remote_p + +run_test_same "'\\\"'" \ + "\"${bs}${bs}${bs}\"\"" \ + "'\"' is not escaped in single quote arg" \ + false $is_remote_p + +run_test_same "\"\\\\\"" \ + "\"${bs}${bs}\"" \ + "'\\' can be escaped in double quote arg" \ + false $is_remote_p + +run_test_same "'\\\\'" \ + "\"${bs}${bs}${bs}${bs}\"" \ + "'\\' is not escaped in single quote arg" \ + false $is_remote_p diff --git a/gdb/testsuite/gdb.dap/memory.exp b/gdb/testsuite/gdb.dap/memory.exp index 89ae8bb..41f4f8d 100644 --- a/gdb/testsuite/gdb.dap/memory.exp +++ b/gdb/testsuite/gdb.dap/memory.exp @@ -29,8 +29,8 @@ save_vars { env(ASAN_OPTIONS) env(TSAN_OPTIONS) } { # The request readMemory with count 18446744073709551615 triggers address # sanitizer. Suppress the error, leaving us with just this warning: # WARNING: AddressSanitizer failed to allocate 0xffffffffffffffff bytes - set_sanitizer ASAN_OPTIONS allocator_may_return_null 1 - set_sanitizer TSAN_OPTIONS allocator_may_return_null 1 + append_environment ASAN_OPTIONS allocator_may_return_null 1 + append_environment TSAN_OPTIONS allocator_may_return_null 1 if {[dap_initialize] == ""} { return diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen-lib-dep.c b/gdb/testsuite/gdb.mi/mi-dlmopen-lib-dep.c new file mode 100644 index 0000000..f981bea --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-dlmopen-lib-dep.c @@ -0,0 +1,21 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2021-2025 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 <http://www.gnu.org/licenses/>. + +*/ + +__attribute__((visibility ("default"))) +int gdb_dlmopen_glob = 1; diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen-lib.c b/gdb/testsuite/gdb.mi/mi-dlmopen-lib.c new file mode 100644 index 0000000..36ccbf02 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-dlmopen-lib.c @@ -0,0 +1,28 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2021-2025 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 <http://www.gnu.org/licenses/>. + +*/ + +extern int gdb_dlmopen_glob; + +__attribute__((visibility ("default"))) +int +inc (int n) +{ + int amount = gdb_dlmopen_glob; + return n + amount; /* bp.inc. */ +} diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen.c b/gdb/testsuite/gdb.mi/mi-dlmopen.c new file mode 100644 index 0000000..3cdee68 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-dlmopen.c @@ -0,0 +1,59 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2021-2025 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 <http://www.gnu.org/licenses/>. + +*/ + +#define _GNU_SOURCE +#include <dlfcn.h> +#include <stddef.h> +#include <assert.h> +#include <unistd.h> + +int +main (void) +{ + void *handle[4]; + int (*fun) (int); + Lmid_t lmid; + int dl; + + handle[0] = dlmopen (LM_ID_NEWLM, DSO1_NAME, RTLD_LAZY | RTLD_LOCAL); + assert (handle[0] != NULL); + + dlinfo (handle[0], RTLD_DI_LMID, &lmid); + + handle[1] = dlopen (DSO1_NAME, RTLD_LAZY | RTLD_LOCAL); + assert (handle[1] != NULL); + + handle[2] = dlmopen (LM_ID_NEWLM, DSO1_NAME, RTLD_LAZY | RTLD_LOCAL); + assert (handle[2] != NULL); + + handle[3] = dlmopen (lmid, DSO2_NAME, RTLD_LAZY | RTLD_LOCAL); + assert (handle[3] != NULL); /* bp.loaded */ + + for (dl = 0; dl < 4; ++dl) + { + fun = dlsym (handle[dl], "inc"); + assert (fun != NULL); + + fun (42); + + dlclose (handle[dl]); + } + + return 0; /* bp.main */ +} diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen.exp b/gdb/testsuite/gdb.mi/mi-dlmopen.exp new file mode 100644 index 0000000..a5743f8 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-dlmopen.exp @@ -0,0 +1,222 @@ +# This testcase is part of GDB, the GNU debugger. +# +# Copyright 2024-2025 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 <http://www.gnu.org/licenses/>. + +# MI tests related to loading shared libraries into different namespaces +# with dlmopen(). The source files for this test are copied (almost) +# verbatim from the gdb.base/dlmopen.exp test. + +load_lib "mi-support.exp" + +require allow_dlmopen_tests + +standard_testfile .c -lib.c -lib-dep.c + +set basename_lib dlmopen-lib +set srcfile_lib $srcfile2 +set binfile_lib1 [standard_output_file $basename_lib.1.so] +set binfile_lib2 [standard_output_file $basename_lib.2.so] +set srcfile_lib_dep $srcfile3 +set binfile_lib_dep [standard_output_file $basename_lib-dep.so] + +if { [build_executable "build shlib dep" $binfile_lib_dep $srcfile_lib_dep \ + {debug shlib}] == -1 } { + return +} + +if { [build_executable "build shlib" $binfile_lib1 $srcfile_lib \ + [list debug shlib_load shlib libs=$binfile_lib_dep]] == -1 } { + return +} + +if { [build_executable "build shlib" $binfile_lib2 $srcfile_lib \ + [list debug shlib_load shlib libs=$binfile_lib_dep]] == -1 } { + return +} + +if { [build_executable "failed to build" $testfile $srcfile \ + [list additional_flags=-DDSO1_NAME=\"$binfile_lib1\" \ + additional_flags=-DDSO2_NAME=\"$binfile_lib2\" \ + shlib_load debug]] } { + return +} + +# Figure out the file name for the dynamic linker. +set dyln_name [section_get $binfile .interp] +if { $dyln_name eq "" } { + unsupported "couldn't find dynamic linker name" + return +} + +# Some source locations needed by the tests. +set bp_main [gdb_get_line_number "bp.main" $srcfile] +set bp_loaded [gdb_get_line_number "bp.loaded" $srcfile] + +# Return true if FILENAME is the dynamic linker. Otherwise return false. +proc is_dyln { filename } { + return [expr {$filename eq $::dyln_name}] +} + +# Run 'info sharedlibrary' and count the number of mappings that look +# like they might be the dynamic linker. This will only work for +# Linux right now. +proc get_dyld_info {} { + if { ![istarget *-linux*] } { + return [list 0 ""] + } + + set dyld_count 0 + set dyld_start_addr "" + gdb_test_multiple "info sharedlibrary" "" { + -re "~\"From\\s+To\\s+Syms\\s+Read\\s+Shared Object Library\\\\n\"\r\n" { + exp_continue + } + -re "^~\"($::hex)\\s+$::hex\\s+\[^/\]+(/\[^\r\n\]+)\\\\n\"\r\n" { + set addr $expect_out(1,string) + set lib $expect_out(2,string) + + if { [is_dyln $lib] } { + # This looks like it might be the dynamic linker. + incr dyld_count + if { $dyld_start_addr eq "" } { + set dyld_start_addr $addr + } elseif { $dyld_start_addr ne $addr } { + set dyld_start_addr "MULTIPLE" + } + } + + exp_continue + } + -re "~\"\\(\\*\\): Shared library is missing debugging information\\.\\\\n\"\r\n" { + exp_continue + } + -re "^\\^done\r\n" { + exp_continue + } + -re "^$::mi_gdb_prompt$" { + } + } + + if { $dyld_start_addr eq "MULTIPLE" } { + set dyld_start_addr "" + } + + return [list $dyld_count $dyld_start_addr] +} + +# Run the inferior over all the 'dlclose' calls and capture the +# resulting library-unloaded events. Check that we see the expected +# number of unload events for the libraries created for this test, and +# additionally, check for dynamic linker unload events. +proc check_solib_unload_events {} { + mi_clean_restart $::binfile + + if {[mi_runto_main] == -1} { + return + } + + # After starting we expect the dynamic linker to be loaded exactly + # once. If it is not then we'll not be able to check the dynamic + # linker unloaded events later in this script. + set dyld_info [get_dyld_info] + set dyld_count [lindex $dyld_info 0] + if { $dyld_count != 1 } { + unsupported "dynamic linker doesn't appear to be loaded" + return + } + + # Create breakpoints. + mi_create_breakpoint "$::srcfile:$::bp_loaded" \ + "create b/p once libraries are loaded" \ + -disp keep -func main -file ".*$::srcfile" -line $::bp_loaded + mi_create_breakpoint "$::srcfile:$::bp_main" "create b/p at dlclose" \ + -disp keep -func main -file ".*$::srcfile" -line $::bp_main + + # Run past all the dlopen and dlmopen calls. + mi_execute_to "exec-continue" "breakpoint-hit" main "" ".*" $::bp_loaded \ + {"" "disp=\"keep\""} "continue until all libraries are loaded" + + # Check that the dynamic linker has now been loaded multiple times. + set dyld_info [get_dyld_info] + set dyld_count [lindex $dyld_info 0] + if { $dyld_count < 2 } { + unsupported "not enough instances of the dynamic linker are mapped in" + return + } + + # Continue. This will run until the end of 'main', and will pass + # over all the dlclose calls. + if {[mi_send_resuming_command "exec-continue" "exec-next"] == -1} { + return + } + + # As a result of all the dlclose calls we should see some library + # unload events. Process them now. + set dyld_unload_count 0 + array set unload_counts {} + set still_in_use_fields_correct true + gdb_test_multiple "" "" -prompt $::mi_gdb_prompt { + -re "=library-unloaded,id=\"(\[^\"\]+)\",\[^\r\n\]+,ranges=\\\[\\{from=\"$::hex\",to=\"$::hex\"\\}\\\],still-in-use=\"(true|false)\"\r\n" { + set lib $expect_out(1,string) + set in_use $expect_out(2,string) + if {[is_dyln $lib]} { + # This is the dynamic linker being unloaded. + incr dyld_unload_count + set expected_in_use "true" + } else { + set expected_in_use "false" + } + + if { $in_use ne $expected_in_use } { + set still_in_use_fields_correct false + } + + set filename [file tail $lib] + incr unload_counts($filename) + exp_continue + } + -re "\\*stopped,reason=\"breakpoint-hit\",\[^\r\n\]+\r\n$::mi_gdb_prompt" { + } + } + + # Check we saw the dynamic linker being unloaded the expected number of + # times. + gdb_assert { $dyld_unload_count == $dyld_count - 1 } \ + "expected number of dynamic linker unloads" + + gdb_assert { $still_in_use_fields_correct } \ + "still-in-use fields were all correct" + + # Check that we saw the expected number of library-unloaded events for + # each library. Each DESC is a list of two elements, a filename for a + # library, and the number of times it should have been unloaded. + foreach desc [list [list $::binfile_lib1 3] \ + [list $::binfile_lib_dep 3] \ + [list $::binfile_lib2 1]] { + set filename [file tail [lindex $desc 0]] + set count [lindex $desc 1] + gdb_assert { $unload_counts($filename) == $count } \ + "check unload count for $filename" + } + + # Check that the dynamic linker still shows as loaded exactly once. + set dyld_info [get_dyld_info] + set dyld_count [lindex $dyld_info 0] + gdb_assert { $dyld_count == 1 } \ + "dynamic linker is mapped once at final b/p" +} + +check_solib_unload_events diff --git a/gdb/testsuite/gdb.mi/mi-sym-info.exp b/gdb/testsuite/gdb.mi/mi-sym-info.exp index b8db2af..47ca515 100644 --- a/gdb/testsuite/gdb.mi/mi-sym-info.exp +++ b/gdb/testsuite/gdb.mi/mi-sym-info.exp @@ -245,22 +245,34 @@ mi_gdb_test "120-symbol-info-types --name _int_" \ "120\\^done,symbols=\{debug=\\\[\{filename=\"\[^\"\]*$srcfile\",fullname=\"\[^\"\]+$srcfile\",symbols=\\\[$my_int_re\\\]\},\{filename=\"\[^\"\]*$srcfile2\",fullname=\"\[^\"\]+$srcfile2\",symbols=\\\[$another_int_re\\\]\}\\\]\}" \ "List all types matching _int_" +# Return the number of matched symbols in the last match. + +proc count_symbol_matches { } { + # `0,string`, `1,string` and `2,string` respectively contain the + # command + result, command and result. The symbols match is at + # `3,string`. + return [regexp -all $::fun_re $::expect_out(3,string)] +} + # Test the --max-results parameter. mi_gdb_test "121-symbol-info-functions --max-results 0" \ "121\\^done,symbols=\{\}" \ "-symbol-info-functions --max-results 0" mi_gdb_test "122-symbol-info-functions --max-results 1 --name ^\[^_\]" \ - "122\\^done,symbols=\{debug=\\\[\{filename=\"\[^\"\]*$srcfile2\",fullname=\"\[^\"\]+$srcfile2\",symbols=\\\[(?:$f2_re|$f3_re)\\\]\}\\\]\}" \ + "122\\^done,($debug_only_syms)" \ "-symbol-info-functions --max-results 1" +gdb_assert {[count_symbol_matches] == 1} "-symbol-info-functions --max-results 1, result count" mi_gdb_test "123-symbol-info-functions --max-results 2 --name ^\[^_\]" \ - "123\\^done,symbols=\{debug=\\\[\{filename=\"\[^\"\]*$srcfile2\",fullname=\"\[^\"\]+$srcfile2\",symbols=\\\[$f2_re,$f3_re\\\]\}\\\]\}" \ + "123\\^done,($debug_only_syms)" \ "-symbol-info-functions --max-results 2" +gdb_assert {[count_symbol_matches] == 2} "-symbol-info-functions --max-results 2, result count" mi_gdb_test "124-symbol-info-variables --max-results 3 --name ^\[^_\]" \ - "124\\^done,symbols=\{debug=\\\[\{filename=\"\[^\"\]*$srcfile2\",fullname=\"\[^\"\]+$srcfile2\",symbols=\\\[$global_f2_re,$global_i2_re,(?:$global_i1_re|$global_f1_s2_re)\\\]\}\\\]\}" \ - "-symbol-info-types --max-results 3" + "124\\^done,($debug_only_syms)" \ + "-symbol-info-variables --max-results 3" +gdb_assert {[count_symbol_matches] == 3} "-symbol-info-variables --max-results 3, result count" mi_gdb_test "125-symbol-info-types --max-results 4 --name another_" \ "125\\^done,symbols=\{debug=\\\[\{filename=\"\[^\"\]*$srcfile2\",fullname=\"\[^\"\]+$srcfile2\",symbols=\\\[$another_char_re,$another_float_re,$another_int_re,$another_short_re\\\]\}\\\]\}" \ diff --git a/gdb/testsuite/gdb.python/py-inferior.exp b/gdb/testsuite/gdb.python/py-inferior.exp index 58475bd..dbde7fe 100644 --- a/gdb/testsuite/gdb.python/py-inferior.exp +++ b/gdb/testsuite/gdb.python/py-inferior.exp @@ -32,8 +32,8 @@ save_vars { env(ASAN_OPTIONS) env(TSAN_OPTIONS) } { # triggers address sanitizer. Suppress the error, leaving us with just # this warning: # WARNING: AddressSanitizer failed to allocate 0xffffffffffffffff bytes - set_sanitizer ASAN_OPTIONS allocator_may_return_null 1 - set_sanitizer TSAN_OPTIONS allocator_may_return_null 1 + append_environment ASAN_OPTIONS allocator_may_return_null 1 + append_environment TSAN_OPTIONS allocator_may_return_null 1 clean_restart ${testfile} } diff --git a/gdb/testsuite/gdb.python/py-symbol.exp b/gdb/testsuite/gdb.python/py-symbol.exp index 40f9038..55cdebe 100644 --- a/gdb/testsuite/gdb.python/py-symbol.exp +++ b/gdb/testsuite/gdb.python/py-symbol.exp @@ -149,10 +149,12 @@ gdb_breakpoint "function_in_other_file" gdb_continue_to_breakpoint "function_in_other_file" gdb_test "python print (gdb.lookup_static_symbol ('rr').value ())" "99" \ "print value of rr from other file" -gdb_test "python print (gdb.lookup_static_symbols ('rr')\[0\].value ())" "99" \ - "print value of gdb.lookup_static_symbols ('rr')\[0\], from the other file" -gdb_test "python print (gdb.lookup_static_symbols ('rr')\[1\].value ())" "42" \ - "print value of gdb.lookup_static_symbols ('rr')\[1\], from the other file" +# GDB doesn't really guarantee the order of these, so sort the values. +gdb_test_no_output "python rrs = gdb.lookup_static_symbols ('rr')" \ + "fetch all rr symbols, from the other file" +gdb_test "python print (sorted(\[int(x.value()) for x in rrs\]))" \ + "\\\[42, 99\\\]" \ + "print values of all 'rr' symbols, from the other file" # Now continue back to the first source file. set linenum [gdb_get_line_number "Break at end."] @@ -164,10 +166,12 @@ gdb_py_test_silent_cmd "python frame = gdb.selected_frame()" "Get Frame" 0 # static symbol from the second source file. gdb_test "python print (gdb.lookup_static_symbol ('rr').value ())" "42" \ "print value of rr from main file" -gdb_test "python print (gdb.lookup_static_symbols ('rr')\[0\].value ())" "99" \ - "print value of gdb.lookup_static_symbols ('rr')\[0\], from the main file" -gdb_test "python print (gdb.lookup_static_symbols ('rr')\[1\].value ())" "42" \ - "print value of gdb.lookup_static_symbols ('rr')\[1\], from the main file" +# This should be consistent with the first file. +gdb_test_no_output "python rrs = gdb.lookup_static_symbols ('rr')" \ + "fetch all rr symbols, from the main file" +gdb_test "python print (sorted(\[int(x.value()) for x in rrs\]))" \ + "\\\[42, 99\\\]" \ + "print values of all 'rr' symbols, from the main file" # Test is_variable attribute. gdb_py_test_silent_cmd "python a = gdb.lookup_symbol(\'a\')" "Get variable a" 0 diff --git a/gdb/testsuite/gdb.threads/attach-slow-waitpid.exp b/gdb/testsuite/gdb.threads/attach-slow-waitpid.exp index 28d70da..942088f 100644 --- a/gdb/testsuite/gdb.threads/attach-slow-waitpid.exp +++ b/gdb/testsuite/gdb.threads/attach-slow-waitpid.exp @@ -83,7 +83,7 @@ proc gdb_spawn_with_ld_preload {lib} { # ASan runtime does not come first in initial library list; you should # either link runtime to your application or manually preload it with # LD_PRELOAD. - set_sanitizer_default ASAN_OPTIONS verify_asan_link_order 0 + append_environment_default ASAN_OPTIONS verify_asan_link_order 0 gdb_start } diff --git a/gdb/testsuite/gdb.trace/basic-libipa.exp b/gdb/testsuite/gdb.trace/basic-libipa.exp index dcde297..0da94af 100644 --- a/gdb/testsuite/gdb.trace/basic-libipa.exp +++ b/gdb/testsuite/gdb.trace/basic-libipa.exp @@ -40,7 +40,7 @@ save_vars { env(ASAN_OPTIONS) } { # ASan runtime does not come first in initial library list; you should # either link runtime to your application or manually preload it with # LD_PRELOAD. - set_sanitizer_default ASAN_OPTIONS verify_asan_link_order 0 + append_environment_default ASAN_OPTIONS verify_asan_link_order 0 clean_restart $binfile } diff --git a/gdb/testsuite/gdb.tui/flush-after-run.c b/gdb/testsuite/gdb.tui/flush-after-run.c new file mode 100644 index 0000000..f2dfded --- /dev/null +++ b/gdb/testsuite/gdb.tui/flush-after-run.c @@ -0,0 +1,54 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2025 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 <http://www.gnu.org/licenses/>. */ + +#include <unistd.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <errno.h> +#include <stdio.h> + +int +main (int argc, char *argv[]) +{ + /* Don't let this test stay alive forever. */ + alarm (300); + + if (argc != 2) + abort (); + + /* Check the file doesn't already exist. */ + const char *filename = argv[1]; + struct stat buf; + if (stat (filename, &buf) == 0 || errno != ENOENT) + abort (); + + /* Create the file, and write something into it. */ + FILE *out = fopen (filename, "w"); + if (out == NULL) + abort (); + + fprintf (out, "Hello World\n"); + + if (fclose (out) != 0) + abort (); + + /* Spin until the marker file is deleted. */ + while (stat (filename, &buf) == 0) + sleep (1); + + return 0; +} diff --git a/gdb/testsuite/gdb.tui/flush-after-run.exp b/gdb/testsuite/gdb.tui/flush-after-run.exp new file mode 100644 index 0000000..c222b19 --- /dev/null +++ b/gdb/testsuite/gdb.tui/flush-after-run.exp @@ -0,0 +1,66 @@ +# Copyright 2025 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 <http://www.gnu.org/licenses/>. + +# Check that the 'Starting program' message is correctly flushed to +# the TUI terminal as soon as it is available. + +require allow_tui_tests +require target_can_use_run_cmd + +tuiterm_env + +standard_testfile + +if {[build_executable "failed to prepare" ${testfile} ${srcfile}] == -1} { + return -1 +} + +Term::clean_restart 24 80 $testfile + +if {![Term::enter_tui]} { + unsupported "TUI not supported" + return +} + +# Pick a name for a marker file, and ensure it doesn't exist. +set marker_file [standard_output_file "marker"] +file delete $marker_file + +# Run the inferior, which will create MARKER_FILE. +send_gdb "run \"$marker_file\"\n" + +# Spin until MARKER_FILE appears. +while { ! [file exists $marker_file] } { + sleep 1 +} + +# We now know that the inferior has started, and that the 'Starting +# program: ' string should have been printed to the terminal. Don't +# use Term::wait_for here as there will be no prompt after the +# 'Starting program' message. +gdb_assert {[Term::wait_for_region_contents 0 16 80 7 "Starting program: "]} \ + "starting program message has appeared" + +# Delete MARKER_FILE. This will cause the inferior to exit. +file delete $marker_file + +# Now wait for the prompt, and check that the inferior exited message +# appeared. +gdb_assert {[Term::wait_for ""]} \ + "wait for prompt after inferior exits" +Term::check_region_contents \ + "check for inferior exited message" \ + 0 16 80 8 \ + "\\\[Inferior $decimal \[^\r\n\]+ exited normally\\\]" diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index 56dc835..d4634c8 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -45,9 +45,9 @@ proc cond_wrap { cond wrap body } { } } -# Helper function for set_sanitizer/set_sanitizer_default. +# Helper function for append_environment/append_environment_default. -proc set_sanitizer_1 { env_var var_id val default} { +proc append_environment_1 { env_var var_id val default} { global env if { ![info exists env($env_var) ] @@ -70,17 +70,17 @@ proc set_sanitizer_1 { env_var var_id val default} { # Add VAR_ID=VAL to ENV_VAR. -proc set_sanitizer { env_var var_id val } { - set_sanitizer_1 $env_var $var_id $val 0 +proc append_environment { env_var var_id val } { + append_environment_1 $env_var $var_id $val 0 } # Add VAR_ID=VAL to ENV_VAR, unless ENV_VAR already contains a VAR_ID setting. -proc set_sanitizer_default { env_var var_id val } { - set_sanitizer_1 $env_var $var_id $val 1 +proc append_environment_default { env_var var_id val } { + append_environment_1 $env_var $var_id $val 1 } -set_sanitizer_default TSAN_OPTIONS suppressions \ +append_environment_default TSAN_OPTIONS suppressions \ $srcdir/../tsan-suppressions.txt # When using ThreadSanitizer we may run into the case that a race is detected, @@ -89,14 +89,14 @@ set_sanitizer_default TSAN_OPTIONS suppressions \ # Try to prevent this by setting history_size to the maximum (7) by default. # See also the ThreadSanitizer docs ( # https://github.com/google/sanitizers/wiki/ThreadSanitizerFlags ). -set_sanitizer_default TSAN_OPTIONS history_size 7 +append_environment_default TSAN_OPTIONS history_size 7 # If GDB is built with ASAN (and because there are leaks), it will output a # leak report when exiting as well as exit with a non-zero (failure) status. # This can affect tests that are sensitive to what GDB prints on stderr or its # exit status. Add `detect_leaks=0` to the ASAN_OPTIONS environment variable # (which will affect any spawned sub-process) to avoid this. -set_sanitizer_default ASAN_OPTIONS detect_leaks 0 +append_environment_default ASAN_OPTIONS detect_leaks 0 # List of procs to run in gdb_finish. set gdb_finish_hooks [list] @@ -11016,5 +11016,38 @@ gdb_caching_proc root_user {} { return [expr $uid == 0] } +# Return nul-terminated string read from section SECTION of EXEC. Return "" +# if no such section or nul-terminated string was found. Function is useful +# for sections ".interp" or ".gnu_debuglink". + +proc section_get {exec section} { + global subdir + set tmp [standard_output_file section_get.tmp] + set objcopy_program [gdb_find_objcopy] + + set command "exec $objcopy_program -O binary --set-section-flags $section=A --change-section-address $section=0 -j $section $exec $tmp" + verbose -log "command is $command" + set result [catch $command output] + verbose -log "result is $result" + verbose -log "output is $output" + if {$result == 1} { + return "" + } + set fi [open $tmp] + fconfigure $fi -translation binary + set data [read $fi] + close $fi + file delete $tmp + # .interp has size $len + 1 but .gnu_debuglink contains garbage after \000. + set len [string first \000 $data] + if {$len < 0} { + verbose -log "section $section not found" + return "" + } + set retval [string range $data 0 [expr $len - 1]] + verbose -log "section $section is <$retval>" + return $retval +} + # Always load compatibility stuff. load_lib future.exp diff --git a/gdb/testsuite/lib/gnat_debug_info_test.adb b/gdb/testsuite/lib/gnat_debug_info_test.adb index b8f0b03..b195cb8 100644 --- a/gdb/testsuite/lib/gnat_debug_info_test.adb +++ b/gdb/testsuite/lib/gnat_debug_info_test.adb @@ -1,3 +1,18 @@ +-- Copyright 2019-2025 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 <http://www.gnu.org/licenses/>. + with Ada.Text_IO; procedure GNAT_Debug_Info_Test is diff --git a/gdb/testsuite/lib/prelink-support.exp b/gdb/testsuite/lib/prelink-support.exp index 894af39..3aaea0a 100644 --- a/gdb/testsuite/lib/prelink-support.exp +++ b/gdb/testsuite/lib/prelink-support.exp @@ -13,39 +13,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# Return nul-terminated string read from section SECTION of EXEC. Return "" -# if no such section or nul-terminated string was found. Function is useful -# for sections ".interp" or ".gnu_debuglink". - -proc section_get {exec section} { - global subdir - set tmp [standard_output_file section_get.tmp] - set objcopy_program [gdb_find_objcopy] - - set command "exec $objcopy_program -O binary --set-section-flags $section=A --change-section-address $section=0 -j $section $exec $tmp" - verbose -log "command is $command" - set result [catch $command output] - verbose -log "result is $result" - verbose -log "output is $output" - if {$result == 1} { - return "" - } - set fi [open $tmp] - fconfigure $fi -translation binary - set data [read $fi] - close $fi - file delete $tmp - # .interp has size $len + 1 but .gnu_debuglink contains garbage after \000. - set len [string first \000 $data] - if {$len < 0} { - verbose -log "section $section not found" - return "" - } - set retval [string range $data 0 [expr $len - 1]] - verbose -log "section $section is <$retval>" - return $retval -} - # Resolve symlinks. proc symlink_resolve {file} { diff --git a/gdb/tui/tui-hooks.c b/gdb/tui/tui-hooks.c index 25358d0..69294cc 100644 --- a/gdb/tui/tui-hooks.c +++ b/gdb/tui/tui-hooks.c @@ -35,6 +35,7 @@ #include "tui/tui-regs.h" #include "tui/tui-status.h" #include "tui/tui-winsource.h" +#include "tui/tui-wingeneral.h" static void tui_new_objfile_hook (struct objfile* objfile) @@ -106,6 +107,8 @@ tui_refresh_frame_and_register_information () target_terminal::scoped_restore_terminal_state term_state; target_terminal::ours_for_output (); + tui_batch_rendering defer; + if (from_stack) { frame_info_ptr fi; @@ -150,6 +153,8 @@ tui_dummy_print_frame_info_listing_hook (struct symtab *s, static void tui_inferior_exit (struct inferior *inf) { + tui_batch_rendering defer; + /* Leave the SingleKey mode to make sure the gdb prompt is visible. */ tui_set_key_mode (TUI_COMMAND_MODE); tui_show_frame_info (0); diff --git a/gdb/tui/tui-status.c b/gdb/tui/tui-status.c index 5239415..d76c2db 100644 --- a/gdb/tui/tui-status.c +++ b/gdb/tui/tui-status.c @@ -253,8 +253,8 @@ tui_status_window::rerender () waddstr (handle.get (), string.c_str ()); wclrtoeol (handle.get ()); (void) wstandend (handle.get ()); + refresh_window (); - wmove (handle.get (), 0, 0); } /* Function to print the frame information for the TUI. The windows diff --git a/gdb/tui/tui-win.c b/gdb/tui/tui-win.c index 33b24d8..95639f5 100644 --- a/gdb/tui/tui-win.c +++ b/gdb/tui/tui-win.c @@ -969,6 +969,8 @@ tui_set_win_size (const char *arg, bool set_width_p) new_size = curr_size + input_no; } + tui_batch_rendering defer; + /* Now change the window's height, and adjust all other windows around it. */ if (set_width_p) diff --git a/gdb/tui/tui-wingeneral.c b/gdb/tui/tui-wingeneral.c index 369d152..56e7abd 100644 --- a/gdb/tui/tui-wingeneral.c +++ b/gdb/tui/tui-wingeneral.c @@ -56,7 +56,12 @@ void tui_win_info::refresh_window () { if (handle != NULL) - wnoutrefresh (handle.get ()); + { + if (suppress_output) + wnoutrefresh (handle.get ()); + else + wrefresh (handle.get ()); + } } /* Draw a border around the window. */ diff --git a/gdb/tui/tui-winsource.c b/gdb/tui/tui-winsource.c index 83d9fc0..587162b 100644 --- a/gdb/tui/tui-winsource.c +++ b/gdb/tui/tui-winsource.c @@ -37,6 +37,7 @@ #include "tui/tui-source.h" #include "tui/tui-disasm.h" #include "tui/tui-location.h" +#include "tui/tui-wingeneral.h" #include "gdb_curses.h" /* Function to display the "main" routine. */ @@ -52,10 +53,10 @@ tui_display_main () tui_get_begin_asm_address (&gdbarch, &addr); if (addr != (CORE_ADDR) 0) { - struct symtab *s; + tui_batch_rendering defer; tui_update_source_windows_with_addr (gdbarch, addr); - s = find_pc_line_symtab (addr); + struct symtab *s = find_pc_line_symtab (addr); tui_location.set_location (s); } } @@ -607,6 +608,8 @@ tui_source_window_base::set_is_exec_point_at (struct tui_line_or_address l) void tui_update_all_breakpoint_info (struct breakpoint *being_deleted) { + tui_batch_rendering defer; + for (tui_source_window_base *win : tui_source_windows ()) { if (win->update_breakpoint_info (being_deleted, false)) diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c index 59aa1bc..8b79d8b 100644 --- a/gdb/tui/tui.c +++ b/gdb/tui/tui.c @@ -387,6 +387,8 @@ tui_enable (void) if (tui_active) return; + tui_batch_rendering defer; + /* To avoid to initialize curses when gdb starts, there is a deferred curses initialization. This initialization is made only once and the first time the curses mode is entered. */ diff --git a/gdb/unittests/array-view-selftests.c b/gdb/unittests/array-view-selftests.c index c07b572..cc3fcfd 100644 --- a/gdb/unittests/array-view-selftests.c +++ b/gdb/unittests/array-view-selftests.c @@ -554,6 +554,19 @@ run_tests () SELF_CHECK (view[i] == data[i]); } + /* gdb::make_array_view with an array. */ + { + const gdb_byte data[] = {0x55, 0x66, 0x77, 0x88}; + const auto len = sizeof (data) / sizeof (data[0]); + const auto view = gdb::make_array_view (data); + + SELF_CHECK (view.data () == data); + SELF_CHECK (view.size () == len); + + for (std::size_t i = 0; i < len; ++i) + SELF_CHECK (view[i] == data[i]); + } + /* Test slicing. */ { gdb_byte data[] = {0x55, 0x66, 0x77, 0x88, 0x99}; diff --git a/gdb/i386-gnu-nat.c b/gdb/x86-gnu-nat.c index 52d4a4b..da3b7e9 100644 --- a/gdb/i386-gnu-nat.c +++ b/gdb/x86-gnu-nat.c @@ -34,7 +34,13 @@ extern "C" #include "floatformat.h" #include "regcache.h" + +#ifdef __x86_64__ +#include "amd64-tdep.h" +#include "amd64-nat.h" +#else #include "i386-tdep.h" +#endif #include "inf-child.h" #include "i387-tdep.h" @@ -42,21 +48,82 @@ extern "C" /* Offset to the thread_state_t location where REG is stored. */ #define REG_OFFSET(reg) offsetof (struct i386_thread_state, reg) +#ifdef __x86_64__ + /* At REG_OFFSET[N] is the offset to the thread_state_t location where the GDB register N is stored. */ -static int reg_offset[] = +static int amd64_gnu_thread_state_reg_offset[] = { - REG_OFFSET (eax), REG_OFFSET (ecx), REG_OFFSET (edx), REG_OFFSET (ebx), - REG_OFFSET (uesp), REG_OFFSET (ebp), REG_OFFSET (esi), REG_OFFSET (edi), - REG_OFFSET (eip), REG_OFFSET (efl), REG_OFFSET (cs), REG_OFFSET (ss), - REG_OFFSET (ds), REG_OFFSET (es), REG_OFFSET (fs), REG_OFFSET (gs) + REG_OFFSET (rax), /* %rax */ + REG_OFFSET (rbx), /* %rbx */ + REG_OFFSET (rcx), /* %rcx */ + REG_OFFSET (rdx), /* %rdx */ + REG_OFFSET (rsi), /* %rsi */ + REG_OFFSET (rdi), /* %rdi */ + REG_OFFSET (rbp), /* %rbp */ + REG_OFFSET (ursp), /* %rsp */ + REG_OFFSET (r8), /* %r8 ... */ + REG_OFFSET (r9), + REG_OFFSET (r10), + REG_OFFSET (r11), + REG_OFFSET (r12), + REG_OFFSET (r13), + REG_OFFSET (r14), + REG_OFFSET (r15), /* ... %r15 */ + REG_OFFSET (rip), /* %rip */ + REG_OFFSET (rfl), /* %rflags */ + REG_OFFSET (cs) /* %cs */ }; -#define REG_ADDR(state, regnum) ((char *)(state) + reg_offset[regnum]) +static const int amd64_gnu_thread_state_num_regs = + ARRAY_SIZE (amd64_gnu_thread_state_reg_offset); + +#define REG_ADDR(state, regnum) \ + ((char *)(state) + amd64_gnu_thread_state_reg_offset[regnum]) +#define VALID_REGISTER(regnum) \ + ((regnum) >= 0 && (regnum) < amd64_gnu_thread_state_num_regs) +#define NUM_GREGS amd64_gnu_thread_state_num_regs +#define FLAGS_REGISTER rfl + +#else + +/* At REG_OFFSET[N] is the offset to the thread_state_t location where + the GDB register N is stored. */ +static int i386_gnu_thread_state_reg_offset[] = +{ + REG_OFFSET (eax), /* %eax */ + REG_OFFSET (ecx), /* %ecx */ + REG_OFFSET (edx), /* %edx */ + REG_OFFSET (ebx), /* %ebx */ + REG_OFFSET (uesp), /* %esp */ + REG_OFFSET (ebp), /* %ebp */ + REG_OFFSET (esi), /* %esi */ + REG_OFFSET (edi), /* %edi */ + REG_OFFSET (eip), /* %eip */ + REG_OFFSET (efl), /* %efl */ + REG_OFFSET (cs), /* %cs */ + REG_OFFSET (ss), /* %ss */ + REG_OFFSET (ds), /* %ds */ + REG_OFFSET (es), /* %es */ + REG_OFFSET (fs), /* %fs */ + REG_OFFSET (gs) /* gs */ +}; + +static const int i386_gnu_thread_state_num_regs = + ARRAY_SIZE (i386_gnu_thread_state_reg_offset); + +#define REG_ADDR(state, regnum) \ + ((char *)(state) + i386_gnu_thread_state_reg_offset[regnum]) +#define VALID_REGISTER(regnum) \ + ((regnum) >= 0 && (regnum) < i386_gnu_thread_state_num_regs) +#define NUM_GREGS i386_gnu_thread_state_num_regs +#define FLAGS_REGISTER efl + +#endif /* __x86_64__ */ -/* The i386 GNU Hurd target. */ +/* The x86 GNU Hurd target. */ #ifdef i386_DEBUG_STATE using gnu_base_target = x86_nat_target<gnu_nat_target>; @@ -64,13 +131,13 @@ using gnu_base_target = x86_nat_target<gnu_nat_target>; using gnu_base_target = gnu_nat_target; #endif -struct i386_gnu_nat_target final : public gnu_base_target +struct x86_gnu_nat_target final : public gnu_base_target { void fetch_registers (struct regcache *, int) override; void store_registers (struct regcache *, int) override; }; -static i386_gnu_nat_target the_i386_gnu_nat_target; +static x86_gnu_nat_target the_x86_gnu_nat_target; /* Get the whole floating-point state of THREAD and record the values of the corresponding (pseudo) registers. */ @@ -105,7 +172,7 @@ fetch_fpregs (struct regcache *regcache, struct proc *thread) /* Fetch register REGNO, or all regs if REGNO is -1. */ void -i386_gnu_nat_target::fetch_registers (struct regcache *regcache, int regno) +x86_gnu_nat_target::fetch_registers (struct regcache *regcache, int regno) { struct proc *thread; ptid_t ptid = regcache->ptid (); @@ -118,7 +185,7 @@ i386_gnu_nat_target::fetch_registers (struct regcache *regcache, int regno) error (_("Can't fetch registers from thread %s: No such thread"), target_pid_to_str (ptid).c_str ()); - if (regno < I386_NUM_GREGS || regno == -1) + if (VALID_REGISTER (regno) || regno == -1) { thread_state_t state; @@ -137,7 +204,7 @@ i386_gnu_nat_target::fetch_registers (struct regcache *regcache, int regno) proc_debug (thread, "fetching all register"); - for (i = 0; i < I386_NUM_GREGS; i++) + for (i = 0; i < NUM_GREGS; i++) regcache->raw_supply (i, REG_ADDR (state, i)); thread->fetched_regs = ~0; } @@ -152,7 +219,7 @@ i386_gnu_nat_target::fetch_registers (struct regcache *regcache, int regno) } } - if (regno >= I386_NUM_GREGS || regno == -1) + if (!VALID_REGISTER(regno) || regno == -1) { proc_debug (thread, "fetching floating-point registers"); @@ -195,7 +262,7 @@ store_fpregs (const struct regcache *regcache, struct proc *thread, int regno) /* Store at least register REGNO, or all regs if REGNO == -1. */ void -i386_gnu_nat_target::store_registers (struct regcache *regcache, int regno) +x86_gnu_nat_target::store_registers (struct regcache *regcache, int regno) { struct proc *thread; struct gdbarch *gdbarch = regcache->arch (); @@ -209,7 +276,7 @@ i386_gnu_nat_target::store_registers (struct regcache *regcache, int regno) error (_("Couldn't store registers into thread %s: No such thread"), target_pid_to_str (ptid).c_str ()); - if (regno < I386_NUM_GREGS || regno == -1) + if (VALID_REGISTER (regno) || regno == -1) { thread_state_t state; thread_state_data_t old_state; @@ -230,14 +297,14 @@ i386_gnu_nat_target::store_registers (struct regcache *regcache, int regno) /* Save the T bit. We might try to restore the %eflags register below, but changing the T bit would seriously confuse GDB. */ - trace = ((struct i386_thread_state *)state)->efl & 0x100; + trace = ((struct i386_thread_state *)state)->FLAGS_REGISTER & 0x100; if (!was_aborted && was_valid) /* See which registers have changed after aborting the thread. */ { int check_regno; - for (check_regno = 0; check_regno < I386_NUM_GREGS; check_regno++) + for (check_regno = 0; check_regno < NUM_GREGS; check_regno++) if ((thread->fetched_regs & (1 << check_regno)) && memcpy (REG_ADDR (&old_state, check_regno), REG_ADDR (state, check_regno), @@ -262,7 +329,7 @@ i386_gnu_nat_target::store_registers (struct regcache *regcache, int regno) proc_debug (thread, "storing all registers"); - for (i = 0; i < I386_NUM_GREGS; i++) + for (i = 0; i < NUM_GREGS; i++) if (REG_VALID == regcache->get_register_status (i)) regcache->raw_collect (i, REG_ADDR (state, i)); } @@ -276,11 +343,11 @@ i386_gnu_nat_target::store_registers (struct regcache *regcache, int regno) } /* Restore the T bit. */ - ((struct i386_thread_state *)state)->efl &= ~0x100; - ((struct i386_thread_state *)state)->efl |= trace; + ((struct i386_thread_state *)state)->FLAGS_REGISTER &= ~0x100; + ((struct i386_thread_state *)state)->FLAGS_REGISTER |= trace; } - if (regno >= I386_NUM_GREGS || regno == -1) + if (!VALID_REGISTER (regno) || regno == -1) { proc_debug (thread, "storing floating-point registers"); @@ -295,7 +362,7 @@ i386_gnu_nat_target::store_registers (struct regcache *regcache, int regno) /* Get debug registers for thread THREAD. */ static void -i386_gnu_dr_get (struct i386_debug_state *regs, struct proc *thread) +x86_gnu_dr_get (struct i386_debug_state *regs, struct proc *thread) { mach_msg_type_number_t count = i386_DEBUG_STATE_COUNT; kern_return_t err; @@ -310,7 +377,7 @@ i386_gnu_dr_get (struct i386_debug_state *regs, struct proc *thread) /* Set debug registers for thread THREAD. */ static void -i386_gnu_dr_set (const struct i386_debug_state *regs, struct proc *thread) +x86_gnu_dr_set (const struct i386_debug_state *regs, struct proc *thread) { kern_return_t err; @@ -324,23 +391,23 @@ i386_gnu_dr_set (const struct i386_debug_state *regs, struct proc *thread) /* Set DR_CONTROL in THREAD. */ static void -i386_gnu_dr_set_control_one (struct proc *thread, void *arg) +x86_gnu_dr_set_control_one (struct proc *thread, void *arg) { unsigned long *control = (unsigned long *) arg; struct i386_debug_state regs; - i386_gnu_dr_get (®s, thread); + x86_gnu_dr_get (®s, thread); regs.dr[DR_CONTROL] = *control; - i386_gnu_dr_set (®s, thread); + x86_gnu_dr_set (®s, thread); } /* Set DR_CONTROL to CONTROL in all threads. */ static void -i386_gnu_dr_set_control (unsigned long control) +x86_gnu_dr_set_control (unsigned long control) { inf_update_procs (gnu_current_inf); - inf_threads (gnu_current_inf, i386_gnu_dr_set_control_one, &control); + inf_threads (gnu_current_inf, x86_gnu_dr_set_control_one, &control); } /* Parameters to set a debugging address. */ @@ -354,20 +421,20 @@ struct reg_addr /* Set address REGNUM (zero based) to ADDR in THREAD. */ static void -i386_gnu_dr_set_addr_one (struct proc *thread, void *arg) +x86_gnu_dr_set_addr_one (struct proc *thread, void *arg) { struct reg_addr *reg_addr = (struct reg_addr *) arg; struct i386_debug_state regs; - i386_gnu_dr_get (®s, thread); + x86_gnu_dr_get (®s, thread); regs.dr[reg_addr->regnum] = reg_addr->addr; - i386_gnu_dr_set (®s, thread); + x86_gnu_dr_set (®s, thread); } /* Set address REGNUM (zero based) to ADDR in all threads. */ static void -i386_gnu_dr_set_addr (int regnum, CORE_ADDR addr) +x86_gnu_dr_set_addr (int regnum, CORE_ADDR addr) { struct reg_addr reg_addr; @@ -377,13 +444,13 @@ i386_gnu_dr_set_addr (int regnum, CORE_ADDR addr) reg_addr.addr = addr; inf_update_procs (gnu_current_inf); - inf_threads (gnu_current_inf, i386_gnu_dr_set_addr_one, ®_addr); + inf_threads (gnu_current_inf, x86_gnu_dr_set_addr_one, ®_addr); } /* Get debug register REGNUM value from only the one LWP of PTID. */ static unsigned long -i386_gnu_dr_get_reg (ptid_t ptid, int regnum) +x86_gnu_dr_get_reg (ptid_t ptid, int regnum) { struct i386_debug_state regs; struct proc *thread; @@ -392,7 +459,7 @@ i386_gnu_dr_get_reg (ptid_t ptid, int regnum) inf_update_procs (gnu_current_inf); thread = inf_tid_to_thread (gnu_current_inf, ptid.lwp ()); - i386_gnu_dr_get (®s, thread); + x86_gnu_dr_get (®s, thread); return regs.dr[regnum]; } @@ -400,46 +467,50 @@ i386_gnu_dr_get_reg (ptid_t ptid, int regnum) /* Return the inferior's debug register REGNUM. */ static CORE_ADDR -i386_gnu_dr_get_addr (int regnum) +x86_gnu_dr_get_addr (int regnum) { gdb_assert (DR_FIRSTADDR <= regnum && regnum <= DR_LASTADDR); - return i386_gnu_dr_get_reg (inferior_ptid, regnum); + return x86_gnu_dr_get_reg (inferior_ptid, regnum); } /* Get DR_STATUS from only the one thread of INFERIOR_PTID. */ static unsigned long -i386_gnu_dr_get_status (void) +x86_gnu_dr_get_status (void) { - return i386_gnu_dr_get_reg (inferior_ptid, DR_STATUS); + return x86_gnu_dr_get_reg (inferior_ptid, DR_STATUS); } /* Return the inferior's DR7 debug control register. */ static unsigned long -i386_gnu_dr_get_control (void) +x86_gnu_dr_get_control (void) { - return i386_gnu_dr_get_reg (inferior_ptid, DR_CONTROL); + return x86_gnu_dr_get_reg (inferior_ptid, DR_CONTROL); } #endif /* i386_DEBUG_STATE */ -void _initialize_i386gnu_nat (); +void _initialize_x86_gnu_nat (); void -_initialize_i386gnu_nat () +_initialize_x86_gnu_nat () { #ifdef i386_DEBUG_STATE - x86_dr_low.set_control = i386_gnu_dr_set_control; + x86_dr_low.set_control = x86_gnu_dr_set_control; gdb_assert (DR_FIRSTADDR == 0 && DR_LASTADDR < i386_DEBUG_STATE_COUNT); - x86_dr_low.set_addr = i386_gnu_dr_set_addr; - x86_dr_low.get_addr = i386_gnu_dr_get_addr; - x86_dr_low.get_status = i386_gnu_dr_get_status; - x86_dr_low.get_control = i386_gnu_dr_get_control; + x86_dr_low.set_addr = x86_gnu_dr_set_addr; + x86_dr_low.get_addr = x86_gnu_dr_get_addr; + x86_dr_low.get_status = x86_gnu_dr_get_status; + x86_dr_low.get_control = x86_gnu_dr_get_control; +#ifdef __x86_64__ + x86_set_debug_register_length (8); +#else x86_set_debug_register_length (4); +#endif #endif /* i386_DEBUG_STATE */ - gnu_target = &the_i386_gnu_nat_target; + gnu_target = &the_x86_gnu_nat_target; /* Register the target. */ - add_inf_child_target (&the_i386_gnu_nat_target); + add_inf_child_target (&the_x86_gnu_nat_target); } |