diff options
62 files changed, 2145 insertions, 339 deletions
diff --git a/bfd/elfxx-aarch64.c b/bfd/elfxx-aarch64.c index 68e004ef..551f74e 100644 --- a/bfd/elfxx-aarch64.c +++ b/bfd/elfxx-aarch64.c @@ -708,7 +708,7 @@ static inline bool bfd_is_non_dynamic_elf_object (bfd *abfd, const struct elf_backend_data *out_be) { const struct elf_backend_data *in_be = get_elf_backend_data (abfd); - + return bfd_get_flavour (abfd) == bfd_target_elf_flavour && bfd_count_sections (abfd) != 0 && (abfd->flags & (DYNAMIC | BFD_PLUGIN | BFD_LINKER_CREATED)) == 0 diff --git a/bfd/version.h b/bfd/version.h index 2fe8d9a..eab1fea 100644 --- a/bfd/version.h +++ b/bfd/version.h @@ -16,7 +16,7 @@ In releases, the date is not included in either version strings or sonames. */ -#define BFD_VERSION_DATE 20250510 +#define BFD_VERSION_DATE 20250514 #define BFD_VERSION @bfd_version@ #define BFD_VERSION_STRING @bfd_version_package@ @bfd_version_string@ #define REPORT_BUGS_TO @report_bugs_to@ diff --git a/binutils/MAINTAINERS b/binutils/MAINTAINERS index 643a604..993a36e 100644 --- a/binutils/MAINTAINERS +++ b/binutils/MAINTAINERS @@ -63,7 +63,6 @@ maintainer. The first maintainer is free to devolve that responsibility among the other maintainers. AARCH64 Richard Earnshaw <rearnsha@arm.com> - AARCH64 Marcus Shawcroft <marcus.shawcroft@arm.com> ARC Claudiu Zissulescu <claziss@gmail.com> ARM Nick Clifton <nickc@redhat.com> ARM Richard Earnshaw <rearnsha@arm.com> diff --git a/binutils/doc/binutils.texi b/binutils/doc/binutils.texi index 7f041d9..d094f7d 100644 --- a/binutils/doc/binutils.texi +++ b/binutils/doc/binutils.texi @@ -3566,6 +3566,7 @@ strip [@option{-F} @var{bfdname} |@option{--target=}@var{bfdname}] [@option{--keep-section-symbols}] [@option{--keep-file-symbols}] [@option{--only-keep-debug}] + [@option{--plugin} @var{name}] [@option{-v} |@option{--verbose}] [@option{-V}|@option{--version}] [@option{--help}] [@option{--info}] @var{objfile}@dots{} @@ -3825,6 +3826,26 @@ currently only supports the presence of one filename containing debugging information, not multiple filenames on a one-per-object-file basis. +@item --plugin @var{name} +@cindex plugins +Load the plugin called @var{name} to add support for extra target +types. This option is only available if the toolchain has been built +with plugin support enabled. + +If @option{--plugin} is not provided, but plugin support has been +enabled then @command{strip} iterates over the files in +@file{$@{libdir@}/bfd-plugins} in alphabetic order and the first +plugin that claims the object in question is used. + +Please note that this plugin search directory is @emph{not} the one +used by @command{ld}'s @option{-plugin} option. In order to make +@command{strip} use the linker plugin it must be copied into the +@file{$@{libdir@}/bfd-plugins} directory. For GCC based compilations +the linker plugin is called @file{liblto_plugin.so.0.0.0}. For Clang +based compilations it is called @file{LLVMgold.so}. The GCC plugin +is always backwards compatible with earlier versions, so it is +sufficient to just copy the newest one. + @item -V @itemx --version Show the version number for @command{strip}. diff --git a/binutils/objcopy.c b/binutils/objcopy.c index 31933e1..a973789 100644 --- a/binutils/objcopy.c +++ b/binutils/objcopy.c @@ -30,6 +30,8 @@ #include "coff/internal.h" #include "libcoff.h" #include "safe-ctype.h" +#include "plugin-api.h" +#include "plugin.h" /* FIXME: See bfd/peXXigen.c for why we include an architecture specific header in generic PE code. */ @@ -165,6 +167,11 @@ static struct section_list *change_sections; /* TRUE if some sections are to be removed. */ static bool sections_removed; +#if BFD_SUPPORTS_PLUGINS +/* TRUE if all GCC LTO sections are to be removed. */ +static bool lto_sections_removed; +#endif + /* TRUE if only some sections are to be copied. */ static bool sections_copied; @@ -359,6 +366,7 @@ enum command_line_switch OPTION_RENAME_SECTION, OPTION_REVERSE_BYTES, OPTION_PE_SECTION_ALIGNMENT, + OPTION_PLUGIN, OPTION_SET_SECTION_FLAGS, OPTION_SET_SECTION_ALIGNMENT, OPTION_SET_START, @@ -402,6 +410,7 @@ static struct option strip_options[] = {"output-file", required_argument, 0, 'o'}, {"output-format", required_argument, 0, 'O'}, /* Obsolete */ {"output-target", required_argument, 0, 'O'}, + {"plugin", required_argument, 0, OPTION_PLUGIN}, {"preserve-dates", no_argument, 0, 'p'}, {"remove-section", required_argument, 0, 'R'}, {"remove-relocations", required_argument, 0, OPTION_REMOVE_RELOCS}, @@ -758,6 +767,10 @@ strip_usage (FILE *stream, int exit_status) --info List object formats & architectures supported\n\ -o <file> Place stripped output into <file>\n\ ")); +#if BFD_SUPPORTS_PLUGINS + fprintf (stream, _("\ + --plugin NAME Load the specified plugin\n")); +#endif list_supported_targets (program_name, stream); if (REPORT_BUGS_TO[0] && exit_status == 0) @@ -1916,20 +1929,11 @@ add_redefine_syms_file (const char *filename) Returns TRUE upon success, FALSE otherwise. */ static bool -copy_unknown_object (bfd *ibfd, bfd *obfd) +copy_unknown_file (bfd *ibfd, bfd *obfd, off_t size, unsigned int mode) { char *cbuf; bfd_size_type tocopy; - off_t size; - struct stat buf; - if (bfd_stat_arch_elt (ibfd, &buf) != 0) - { - bfd_nonfatal_message (NULL, ibfd, NULL, NULL); - return false; - } - - size = buf.st_size; if (size < 0) { non_fatal (_("stat returns negative size for `%s'"), @@ -1974,11 +1978,31 @@ copy_unknown_object (bfd *ibfd, bfd *obfd) /* We should at least to be able to read it back when copying an unknown object in an archive. */ - chmod (bfd_get_filename (obfd), buf.st_mode | S_IRUSR); + chmod (bfd_get_filename (obfd), mode | S_IRUSR); free (cbuf); return true; } +/* Copy unknown object file archive member IBFD onto OBFD. + Returns TRUE upon success, FALSE otherwise. */ + +static bool +copy_unknown_object (bfd *ibfd, bfd *obfd) +{ + struct stat buf; + + if (bfd_stat_arch_elt (ibfd, &buf) != 0) + { + bfd_nonfatal_message (NULL, ibfd, NULL, NULL); + return false; + } + + if (!copy_unknown_file (ibfd, obfd, buf.st_size, buf.st_mode)) + return false; + + return true; +} + typedef struct objcopy_internal_note { Elf_Internal_Note note; @@ -3744,6 +3768,12 @@ copy_archive (bfd *ibfd, bfd *obfd, const char *output_target, goto cleanup_and_exit; } +#if BFD_SUPPORTS_PLUGINS + /* Copy LTO IR file as unknown object. */ + if (bfd_plugin_target_p (ibfd->xvec)) + ok_object = false; + else +#endif if (ok_object) { ok = copy_object (this_element, output_element, input_arch); @@ -3845,6 +3875,7 @@ copy_file (const char *input_filename, const char *output_filename, int ofd, char **obj_matching; char **core_matching; off_t size = get_file_size (input_filename); + const char *target = input_target; if (size < 1) { @@ -3855,9 +3886,16 @@ copy_file (const char *input_filename, const char *output_filename, int ofd, return; } +#if BFD_SUPPORTS_PLUGINS + /* Enable LTO plugin in strip unless all LTO sections should be + removed. */ + if (is_strip && !target && !lto_sections_removed) + target = "plugin"; +#endif + /* To allow us to do "strip *" without dying on the first non-object file, failures are nonfatal. */ - ibfd = bfd_openr (input_filename, input_target); + ibfd = bfd_openr (input_filename, target); if (ibfd == NULL || bfd_stat (ibfd, in_stat) != 0) { bfd_nonfatal_message (input_filename, NULL, NULL, NULL); @@ -3974,17 +4012,31 @@ copy_file (const char *input_filename, const char *output_filename, int ofd, return; } - if (! copy_object (ibfd, obfd, input_arch)) - status = 1; - - /* PR 17512: file: 0f15796a. - If the file could not be copied it may not be in a writeable - state. So use bfd_close_all_done to avoid the possibility of - writing uninitialised data into the file. */ - if (! (status ? bfd_close_all_done (obfd) : bfd_close (obfd))) +#if BFD_SUPPORTS_PLUGINS + if (bfd_plugin_target_p (ibfd->xvec)) { - status = 1; - bfd_nonfatal_message (output_filename, NULL, NULL, NULL); + /* Copy LTO IR file as unknown file. */ + if (!copy_unknown_file (ibfd, obfd, in_stat->st_size, + in_stat->st_mode)) + status = 1; + else if (!bfd_close_all_done (obfd)) + status = 1; + } + else +#endif + { + if (! copy_object (ibfd, obfd, input_arch)) + status = 1; + + /* PR 17512: file: 0f15796a. + If the file could not be copied it may not be in a writeable + state. So use bfd_close_all_done to avoid the possibility of + writing uninitialised data into the file. */ + if (! (status ? bfd_close_all_done (obfd) : bfd_close (obfd))) + { + status = 1; + bfd_nonfatal_message (output_filename, NULL, NULL, NULL); + } } if (!bfd_close (ibfd)) @@ -4837,6 +4889,10 @@ strip_main (int argc, char *argv[]) char *output_file = NULL; bool merge_notes_set = false; +#if BFD_SUPPORTS_PLUGINS + bfd_plugin_set_program_name (argv[0]); +#endif + while ((c = getopt_long (argc, argv, "I:O:F:K:MN:R:o:sSpdgxXHhVvwDU", strip_options, (int *) 0)) != EOF) { @@ -4927,6 +4983,13 @@ strip_main (int argc, char *argv[]) case OPTION_KEEP_SECTION_SYMBOLS: keep_section_symbols = true; break; + case OPTION_PLUGIN: /* --plugin */ +#if BFD_SUPPORTS_PLUGINS + bfd_plugin_set_plugin (optarg); +#else + fatal (_("sorry - this program has been built without plugin support\n")); +#endif + break; case 0: /* We've been given a long option. */ break; @@ -4971,6 +5034,14 @@ strip_main (int argc, char *argv[]) if (output_target == NULL) output_target = input_target; +#if BFD_SUPPORTS_PLUGINS + /* Check if all GCC LTO sections should be removed, assuming all LTO + sections will be removed with -R .gnu.lto_.*. * Remove .gnu.lto_.* + sections will also remove .gnu.debuglto_. sections. */ + lto_sections_removed = !!find_section_list (".gnu.lto_.*", false, + SECTION_CONTEXT_REMOVE); +#endif + i = optind; if (i == argc || (output_file != NULL && (i + 1) < argc)) diff --git a/binutils/resbin.c b/binutils/resbin.c index 3bce84f..889126e 100644 --- a/binutils/resbin.c +++ b/binutils/resbin.c @@ -1250,7 +1250,7 @@ bin_to_res_version (windres_bfd *wrbfd, const bfd_byte *data, vst = res_alloc (sizeof (rc_ver_stringtable)); - if (!get_version_header (wrbfd, data, length, (const char *) NULL, + if (!get_version_header (wrbfd, data, length, "version stringtable", &vst->language, &stverlen, &vallen, &type, &off)) return NULL; @@ -1284,9 +1284,9 @@ bin_to_res_version (windres_bfd *wrbfd, const bfd_byte *data, vs = res_alloc (sizeof (rc_ver_stringinfo)); - if (!get_version_header (wrbfd, data, length, - (const char *) NULL, &vs->key, - &sverlen, &vallen, &type, &off)) + if (!get_version_header (wrbfd, data, length, "version string", + &vs->key, &sverlen, &vallen, + &type, &off)) return NULL; data += off; @@ -1348,7 +1348,7 @@ bin_to_res_version (windres_bfd *wrbfd, const bfd_byte *data, data += off; length -= off; - if (!get_version_header (wrbfd, data, length, (const char *) NULL, + if (!get_version_header (wrbfd, data, length, "version varfileinfo", &vi->u.var.key, &verlen, &vallen, &type, &off)) return NULL; diff --git a/binutils/testsuite/binutils-all/objcopy.exp b/binutils/testsuite/binutils-all/objcopy.exp index ff93fea..cf94570 100644 --- a/binutils/testsuite/binutils-all/objcopy.exp +++ b/binutils/testsuite/binutils-all/objcopy.exp @@ -180,7 +180,7 @@ proc objcopy_test_verilog {testname} { untested "verilog width-4 and width-8 tests" return } - + foreach width {4 8} { set got [binutils_run $OBJCOPY "-O verilog --verilog-data-width $width $binfile $verilog-$width.hex"] if ![string equal "" $got] then { @@ -194,7 +194,7 @@ proc objcopy_test_verilog {testname} { } } - # Test generating endian correct output. + # Test generating endian correct output. set testname "objcopy (verilog output endian-ness == input endian-ness)" set got [binutils_run $OBJCOPY "-O verilog --verilog-data-width 4 $binfile $verilog-I4.hex"] if ![string equal "" $got] then { @@ -202,9 +202,9 @@ proc objcopy_test_verilog {testname} { } send_log "regexp_diff $verilog-I4.hex $srcdir/$subdir/verilog-I4.hex\n" if {! [regexp_diff "$verilog-I4.hex" "$srcdir/$subdir/verilog-I4.hex"]} { - pass $testname + pass $testname } else { - fail $testname + fail $testname } } diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c index d37d0fd..bc981f0 100644 --- a/gas/config/obj-elf.c +++ b/gas/config/obj-elf.c @@ -781,7 +781,7 @@ change_section (const char *name, || startswith (name, ".note.gnu")) flags |= SEC_ELF_OCTETS; } - + if (old_sec == NULL) { symbolS *secsym; diff --git a/gas/dwarf2dbg.c b/gas/dwarf2dbg.c index 2963e52..e4b21bc 100644 --- a/gas/dwarf2dbg.c +++ b/gas/dwarf2dbg.c @@ -678,18 +678,17 @@ get_directory_table_entry (const char *dirname, } static bool -assign_file_to_slot (unsigned int i, const char *file, unsigned int dir) +assign_file_to_slot (valueT i, const char *file, unsigned int dir) { if (i >= files_allocated) { unsigned int want = i + 32; - /* Catch wraparound. */ - if (want < files_allocated - || want < i - || want > UINT_MAX / sizeof (struct file_entry)) + /* If this array is taking 1G or more, someone is using silly + file numbers. */ + if (want < i || want > UINT_MAX / 4 / sizeof (struct file_entry)) { - as_bad (_("file number %u is too big"), i); + as_bad (_("file number %" PRIu64 " is too big"), (uint64_t) i); return false; } @@ -843,7 +842,7 @@ purge_generated_debug (bool thelot) static bool allocate_filename_to_slot (const char *dirname, const char *filename, - unsigned int num, + valueT num, bool with_md5) { const char *file; @@ -921,8 +920,9 @@ allocate_filename_to_slot (const char *dirname, } fail: - as_bad (_("file table slot %u is already occupied by a different file (%s%s%s vs %s%s%s)"), - num, + as_bad (_("file table slot %u is already occupied by a different file" + " (%s%s%s vs %s%s%s)"), + (unsigned int) num, dir == NULL ? "" : dir, dir == NULL ? "" : "/", files[num].filename, @@ -968,7 +968,7 @@ allocate_filename_to_slot (const char *dirname, d = get_directory_table_entry (dirname, file0_dirname, dirlen, num == 0); i = num; - if (! assign_file_to_slot (i, file, d)) + if (!assign_file_to_slot (num, file, d)) return false; if (with_md5) @@ -1228,15 +1228,7 @@ dwarf2_directive_filename (void) purge_generated_debug (false); debug_type = DEBUG_NONE; - if (num != (unsigned int) num - || num >= (size_t) -1 / sizeof (struct file_entry) - 32) - { - as_bad (_("file number %lu is too big"), (unsigned long) num); - return NULL; - } - - if (! allocate_filename_to_slot (dirname, filename, (unsigned int) num, - with_md5)) + if (!allocate_filename_to_slot (dirname, filename, num, with_md5)) return NULL; return filename; @@ -1632,7 +1624,7 @@ size_inc_line_addr (int line_delta, addressT addr_delta) } /* Bias the line delta by the base. */ - tmp = line_delta - DWARF2_LINE_BASE; + tmp = (unsigned) line_delta - DWARF2_LINE_BASE; /* If the line increment is out of range of a special opcode, we must encode it with DW_LNS_advance_line. */ diff --git a/gas/ehopt.c b/gas/ehopt.c index 3e15fc9..ab976ba 100644 --- a/gas/ehopt.c +++ b/gas/ehopt.c @@ -90,17 +90,17 @@ __FRAME_BEGIN__: struct cie_info { + fragS *f; unsigned code_alignment; int z_augmentation; }; /* Extract information from the CIE. */ -static int +static bool get_cie_info (struct cie_info *info) { fragS *f; - fixS *fix; unsigned int offset; char CIE_id; char augmentation[10]; @@ -110,9 +110,10 @@ get_cie_info (struct cie_info *info) /* We should find the CIE at the start of the section. */ f = seg_info (now_seg)->frchainP->frch_root; - fix = seg_info (now_seg)->frchainP->fix_root; - - /* Look through the frags of the section to find the code alignment. */ + while (f != NULL && f->fr_fix == 0) + f = f->fr_next; + if (f != info->f) + return false; /* First make sure that the CIE Identifier Tag is 0/-1. */ @@ -133,7 +134,7 @@ get_cie_info (struct cie_info *info) || f->fr_literal[offset + 1] != CIE_id || f->fr_literal[offset + 2] != CIE_id || f->fr_literal[offset + 3] != CIE_id) - return 0; + return false; /* Next make sure the CIE version number is 1. */ @@ -146,7 +147,7 @@ get_cie_info (struct cie_info *info) if (f == NULL || f->fr_fix - offset < 1 || f->fr_literal[offset] != 1) - return 0; + return false; /* Skip the augmentation (a null terminated string). */ @@ -160,7 +161,7 @@ get_cie_info (struct cie_info *info) f = f->fr_next; } if (f == NULL) - return 0; + return false; while (offset < f->fr_fix && f->fr_literal[offset] != '\0') { @@ -181,7 +182,7 @@ get_cie_info (struct cie_info *info) f = f->fr_next; } if (f == NULL) - return 0; + return false; augmentation[iaug] = '\0'; if (augmentation[0] == '\0') @@ -192,6 +193,7 @@ get_cie_info (struct cie_info *info) { /* We have to skip a pointer. Unfortunately, we don't know how large it is. We find out by looking for a matching fixup. */ + fixS *fix = seg_info (now_seg)->frchainP->fix_root; while (fix != NULL && (fix->fx_frag != f || fix->fx_where != offset)) fix = fix->fx_next; @@ -205,10 +207,10 @@ get_cie_info (struct cie_info *info) f = f->fr_next; } if (f == NULL) - return 0; + return false; } else if (augmentation[0] != 'z') - return 0; + return false; /* We're now at the code alignment factor, which is a ULEB128. If it isn't a single byte, forget it. */ @@ -220,7 +222,7 @@ get_cie_info (struct cie_info *info) info->code_alignment = code_alignment; info->z_augmentation = (augmentation[0] == 'z'); - return 1; + return true; } enum frame_state @@ -240,7 +242,7 @@ struct frame_data { enum frame_state state; - int cie_info_ok; + bool cie_info_ok; struct cie_info cie_info; symbolS *size_end_sym; @@ -320,20 +322,27 @@ check_eh_frame (expressionS *exp, unsigned int *pnbytes) { d->state = state_saw_size; d->size_end_sym = exp->X_add_symbol; + if (!d->cie_info.f) + d->cie_info.f = frag_now; } } break; case state_saw_size: case state_saw_cie_offset: - /* Assume whatever form it appears in, it appears atomically. */ - d->state = (enum frame_state) (d->state + 1); + if (!(*pnbytes == 4 || *pnbytes == 8)) + /* Stop scanning if we don't see the expected FDE fields. */ + d->state = state_error; + else + d->state = (enum frame_state) (d->state + 1); break; case state_saw_pc_begin: /* Decide whether we should see an augmentation. */ - if (! d->cie_info_ok - && ! (d->cie_info_ok = get_cie_info (&d->cie_info))) + if (!(*pnbytes == 4 || *pnbytes == 8)) + d->state = state_error; + else if (!d->cie_info_ok + && !(d->cie_info_ok = get_cie_info (&d->cie_info))) d->state = state_error; else if (d->cie_info.z_augmentation) { @@ -347,7 +356,7 @@ check_eh_frame (expressionS *exp, unsigned int *pnbytes) case state_seeing_aug_size: /* Bytes == -1 means this comes from an leb128 directive. */ - if ((int)*pnbytes == -1 && exp->X_op == O_constant) + if ((int) *pnbytes == -1 && exp->X_op == O_constant) { d->aug_size = exp->X_add_number; d->state = state_skipping_aug; @@ -367,7 +376,7 @@ check_eh_frame (expressionS *exp, unsigned int *pnbytes) break; case state_skipping_aug: - if ((int)*pnbytes < 0) + if ((int) *pnbytes < 0) d->state = state_error; else { diff --git a/gas/testsuite/gas/aarch64/gpc3.d b/gas/testsuite/gas/aarch64/gpc3.d new file mode 100644 index 0000000..2535aef --- /dev/null +++ b/gas/testsuite/gas/aarch64/gpc3.d @@ -0,0 +1,11 @@ +#name: RME_GPC3 System register +#as: -march=armv9.5-a +#objdump: -dr + +.*: file format .* + +Disassembly of section \.text: + +0+ <.*>: +[^:]*: d53e21a0 mrs x0, gpcbw_el3 +[^:]*: d51e21a0 msr gpcbw_el3, x0 diff --git a/gas/testsuite/gas/aarch64/gpc3.s b/gas/testsuite/gas/aarch64/gpc3.s new file mode 100644 index 0000000..349cc08 --- /dev/null +++ b/gas/testsuite/gas/aarch64/gpc3.s @@ -0,0 +1,7 @@ +/* RME Granule Protection Check 3 Extension. */ + + /* Read from system register. */ + mrs x0, gpcbw_el3 + + /* Write to system register. */ + msr gpcbw_el3, x0 diff --git a/gas/testsuite/gas/aarch64/occmo.d b/gas/testsuite/gas/aarch64/occmo.d new file mode 100644 index 0000000..388d8f4 --- /dev/null +++ b/gas/testsuite/gas/aarch64/occmo.d @@ -0,0 +1,18 @@ +#name: FEAT_OCCMO Test +#as: -march=armv9.5-a+memtag +#objdump: -dr + +.*: file format .* + +Disassembly of section .text: + +0+ <.*>: + +[^:]*: d50b7b00 dc cvaoc, x0 +[^:]*: d50b7b1e dc cvaoc, x30 +[^:]*: d50b7be0 dc cgdvaoc, x0 +[^:]*: d50b7bfe dc cgdvaoc, x30 +[^:]*: d50b7f00 dc civaoc, x0 +[^:]*: d50b7f1e dc civaoc, x30 +[^:]*: d50b7fe0 dc cigdvaoc, x0 +[^:]*: d50b7ffe dc cigdvaoc, x30 diff --git a/gas/testsuite/gas/aarch64/occmo.s b/gas/testsuite/gas/aarch64/occmo.s new file mode 100644 index 0000000..92cfaf0 --- /dev/null +++ b/gas/testsuite/gas/aarch64/occmo.s @@ -0,0 +1,8 @@ + dc cvaoc, x0 + dc cvaoc, x30 + dc cgdvaoc, x0 + dc cgdvaoc, x30 + dc civaoc, x0 + dc civaoc, x30 + dc cigdvaoc, x0 + dc cigdvaoc, x30 @@ -90,6 +90,18 @@ info sharedlibrary command are now for the full memory range allocated to the shared library. +info threads [-gid] [-stopped] [-running] [ID]... + If no threads match the given ID(s) or filter options, GDB now prints + + No threads matched. + + without printing the provided arguments. The newly added '-stopped' + option makes GDB list the stopped threads only. Similarly, + '-running' makes GDB list the running threads only. If both options + are given together, both stopped and running threads are listed. + These new flags can be useful to get a reduced list when there is a + large number of threads. + * GDB-internal Thread Local Storage (TLS) support ** Linux targets for the x86_64, aarch64, ppc64, s390x, and riscv @@ -128,6 +140,14 @@ info sharedlibrary when output is going to standard output, and False when output is going to a string. + ** Setting the documentation string (__doc__) of a gdb.Parameter + sub-class to the empty string, means GDB will only display the + set_doc or show_doc strings in the set/show help output. + + ** New gdb.ParameterPrefix class. This can be used to create 'set' + and 'show' gdb.Command prefixes, suitable for use with new + gdb.Parameters. + * Guile API ** New type <gdb:color> for dealing with colors. @@ -135,6 +155,11 @@ info sharedlibrary ** New constant PARAM_COLOR represents color type of a value of a <gdb:parameter> object. Parameter's value is <gdb::color> instance. + ** Eliding the #:doc string from make-parameter now means that GDB + will use a default documentation string. Setting #:doc to the + empty string for make-parameter means GDB will only display the + #:set_doc or #:show_doc strings in the set/show help output. + * New remote packets binary-upload in qSupported reply diff --git a/gdb/break-cond-parse.c b/gdb/break-cond-parse.c index 705f4f6..caf13a7 100644 --- a/gdb/break-cond-parse.c +++ b/gdb/break-cond-parse.c @@ -569,10 +569,10 @@ test (const char *input, const char *condition, int thread = -1, gdb::unique_xmalloc_ptr<char> extracted_rest; int extracted_thread, extracted_inferior, extracted_task; bool extracted_force_condition; - std::string exception_msg, error_str; + std::string exception_msg; - if (error_msg != nullptr) - error_str = std::string (error_msg) + "\n"; + if (error_msg == nullptr) + error_msg = ""; try { @@ -584,10 +584,7 @@ test (const char *input, const char *condition, int thread = -1, } catch (const gdb_exception_error &ex) { - string_file buf; - - exception_print (&buf, ex); - exception_msg = buf.release (); + exception_msg = ex.what (); } if ((condition == nullptr) != (extracted_condition.get () == nullptr) @@ -599,7 +596,7 @@ test (const char *input, const char *condition, int thread = -1, || inferior != extracted_inferior || task != extracted_task || force != extracted_force_condition - || exception_msg != error_str) + || exception_msg != error_msg) { if (run_verbose ()) { diff --git a/gdb/c-exp.y b/gdb/c-exp.y index f53a138..bdf7674 100644 --- a/gdb/c-exp.y +++ b/gdb/c-exp.y @@ -3042,10 +3042,6 @@ classify_name (struct parser_state *par_state, const struct block *block, std::string copy = copy_name (yylval.sval); - /* Initialize this in case we *don't* use it in this call; that way - we can refer to it unconditionally below. */ - memset (&is_a_field_of_this, 0, sizeof (is_a_field_of_this)); - bsym = lookup_symbol (copy.c_str (), block, SEARCH_VFT, par_state->language ()->name_of_this () ? &is_a_field_of_this : NULL); diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 5e5e888..05f5502 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -3807,7 +3807,7 @@ Thread 1 "main" received signal SIGINT, Interrupt. @table @code @anchor{info_threads} @kindex info threads -@item info threads @r{[}-gid@r{]} @r{[}@var{thread-id-list}@r{]} +@item info threads @r{[}-gid@r{]} @r{[}-stopped@r{]} @r{[}-running@r{]} @r{[}@var{thread-id-list}@r{]} Display information about one or more threads. With no arguments displays information about all threads. You can specify the list of @@ -3857,6 +3857,14 @@ If you're debugging multiple inferiors, @value{GDBN} displays thread IDs using the qualified @var{inferior-num}.@var{thread-num} format. Otherwise, only @var{thread-num} is shown. +If you specify the @samp{-stopped} option, @value{GDBN} filters the +output of the command to print the stopped threads only. Similarly, +if you specify the @samp{-running} option, @value{GDBN} filters the +output to print the running threads only. These options can be +helpful to reduce the output list if there is a large number of +threads. If you specify both options, @value{GDBN} prints both +stopped and running threads. + If you specify the @samp{-gid} option, @value{GDBN} displays a column indicating each thread's global thread ID: diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi index c6d889f..7b3f0a9 100644 --- a/gdb/doc/guile.texi +++ b/gdb/doc/guile.texi @@ -2098,8 +2098,10 @@ is the @code{<gdb:parameter>} object representing the parameter, and This function must return a string, and will be displayed to the user. @value{GDBN} will add a trailing newline. -The argument @var{doc} is the help text for the new parameter. -If there is no documentation string, a default value is used. +The argument @var{doc} is the help text for the new parameter. If +there is no documentation string, a default value is used. If the +documentation string is empty, then @value{GDBN} will print just the +@var{set-doc} and @var{show-doc} strings (see below). The argument @var{set-doc} is the help text for this parameter's @code{set} command. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 7bb6503..7f801ab 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -4558,6 +4558,7 @@ documentation string is provided, the default value @samp{This command is not documented.} is used. @end defun +@anchor{Command.dont_repeat} @cindex don't repeat Python command @defun Command.dont_repeat () By default, a @value{GDBN} command is repeated when the user enters a @@ -4568,6 +4569,7 @@ exception). This is similar to the user command @code{dont-repeat}, see @ref{Define, dont-repeat}. @end defun +@anchor{Command.invoke} @defun Command.invoke (argument, from_tty) This method is called by @value{GDBN} when this command is invoked. @@ -5079,7 +5081,9 @@ string from the parameter's class, if there is one. If there is no documentation string, a default value is used. The documentation string is included in the output of the parameters @code{help set} and @code{help show} commands, and should be written taking this into -account. +account. If the documentation string for the parameter's class is the +empty string then @value{GDBN} will only use @code{Parameter.set_doc} +or @code{Parameter.show_doc} (see below) in the @kbd{help} output. @end defun @defvar Parameter.set_doc @@ -5258,6 +5262,99 @@ constants provided when the parameter is created. The value is @code{gdb.Color} instance. @end table +When creating multiple new parameters using @code{gdb.Parameter}, it +is often desirable to create a prefix command that can be used to +group related parameters together, for example, if you wished to add +the parameters @kbd{plugin-name feature-1} and @kbd{plugin-name +feature-2}, then the @kbd{plugin-name} would need to be a prefix +command (@pxref{CLI Commands In Python}). + +However, when creating parameters, you will almost always need to +create two prefix commands, one as a @kbd{set} sub-command, and one as +a @kbd{show} sub-command. @value{GDBN} provides the +@code{gdb.ParameterPrefix} helper class to make creation of these two +prefixes easier. + +@defun ParameterPrefix.__init__ (name, command_class, doc = @code{None}) +The object initializer for @code{ParameterPrefix} registers two new +@code{gdb.Command} prefixes, one as a @kbd{set} sub-command, and the +other as a @kbd{show} sub-command. + +@var{name}, a string, is the name of the new prefix, without either +@kbd{set} or @kbd{show}, similar to the @var{name} passed to +@code{gdb.Parameter} (@pxref{Parameters In Python}). For example, to +create the prefixes @kbd{set plugin-name} and @kbd{show plugin-name}, +you would pass the string @kbd{plugin-name}. + +@var{command_class} should be one of the @samp{COMMAND_} constants +(@pxref{CLI Commands In Python}). This argument tells @value{GDBN} how to +categorize the new parameter prefixes in the help system. + +There are a number of ways in which the help text for the two new +prefix commands can be provided. If the @var{doc} parameter is not +@code{None}, then this will be used as the documentation string for +both prefix commands. + +If @var{doc} is @code{None}, but @code{gdb.ParameterPrefix} has been +sub-classed, then the prefix command documentation will be taken from +sub-classes documentation string (i.e., the @code{__doc__} attribute). + +If @var{doc} is @code{None}, and there is no @code{__doc__} string, +then the default value @samp{This command is not documented.} is used. + +When writing the help text, keep in mind that the same text is used +for both the @kbd{set} and @kbd{show} prefix commands. +@end defun + +@defun ParameterPrefix.invoke_set (argument, from_tty) +If a sub-class defines this method, then @value{GDBN} will call this +when the prefix command is used with an unknown sub-command. The +@var{argument} and @var{from_tty} parameters are the same as for +@code{gdb.Command.invoke} (@pxref{Command.invoke}). + +If this method throws an exception, it is turned into a @value{GDBN} +@code{error} call. Otherwise, the return value is ignored. + +It is not required that a @code{ParameterPrefix} sub-class override +this method. Usually, a parameter prefix only exists as a means to +group related parameters together. @value{GDBN} handles this use case +automatically with no need to implement @code{invoke_set}. +@end defun + +@defun ParameterPrefix.invoke_show (argument, from_tty) +This is like the @code{invoke_set} method, but for the @kbd{show} +prefix command. As with @code{invoke_set}, implementation of this +method is optional, and usually not required. +@end defun + +@cindex don't repeat Python command +@defun ParameterPrefix.dont_repeat () +Like @code{Command.dont_repeat} (@pxref{Command.dont_repeat}), this +can be called from @code{ParameterPrefix.invoke_set} or +@code{ParameterPrefix.invoke_show} to prevent the prefix commands from +being repeated. +@end defun + +Here is a small example that uses @code{gdb.ParameterPrefix} along +with @code{gdb.Parameter} to create two new parameters +@kbd{plugin-name feature-1} and @kbd{plugin-name feature-2}. As +neither @code{invoke_set} or @code{invoke_show} is needed, this +example does not sub-class @code{gdb.ParameterPrefix}: + +@smallexample +class ExampleParam(gdb.Parameter): + def __init__ (self, name): + super ().__init__ (name, gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) + self.value = True + +gdb.ParameterPrefix("plugin-name", gdb.COMMAND_NONE, + """An example parameter prefix. + + This groups together some parameters.""") +ExampleParam("plugin-name feature-1") +ExampleParam("plugin-name feature-2") +@end smallexample + @node Functions In Python @subsubsection Writing new convenience functions diff --git a/gdb/dwarf2/dwz.c b/gdb/dwarf2/dwz.c index 583103b..59fe8e4 100644 --- a/gdb/dwarf2/dwz.c +++ b/gdb/dwarf2/dwz.c @@ -56,35 +56,37 @@ dwz_file::read_string (struct objfile *objfile, LONGEST str_offset) /* A helper function to find the sections for a .dwz file. */ static void -locate_dwz_sections (struct objfile *objfile, bfd *abfd, asection *sectp, - dwz_file *dwz_file) +locate_dwz_sections (objfile *objfile, dwz_file &dwz_file) { - dwarf2_section_info *sect = nullptr; + for (asection *sec : gdb_bfd_sections (dwz_file.dwz_bfd)) + { + dwarf2_section_info *sect = nullptr; - /* Note that we only support the standard ELF names, because .dwz + /* Note that we only support the standard ELF names, because .dwz is ELF-only (at the time of writing). */ - if (dwarf2_elf_names.abbrev.matches (sectp->name)) - sect = &dwz_file->abbrev; - else if (dwarf2_elf_names.info.matches (sectp->name)) - sect = &dwz_file->info; - else if (dwarf2_elf_names.str.matches (sectp->name)) - sect = &dwz_file->str; - else if (dwarf2_elf_names.line.matches (sectp->name)) - sect = &dwz_file->line; - else if (dwarf2_elf_names.macro.matches (sectp->name)) - sect = &dwz_file->macro; - else if (dwarf2_elf_names.gdb_index.matches (sectp->name)) - sect = &dwz_file->gdb_index; - else if (dwarf2_elf_names.debug_names.matches (sectp->name)) - sect = &dwz_file->debug_names; - else if (dwarf2_elf_names.types.matches (sectp->name)) - sect = &dwz_file->types; - - if (sect != nullptr) - { - sect->s.section = sectp; - sect->size = bfd_section_size (sectp); - sect->read (objfile); + if (dwarf2_elf_names.abbrev.matches (sec->name)) + sect = &dwz_file.abbrev; + else if (dwarf2_elf_names.info.matches (sec->name)) + sect = &dwz_file.info; + else if (dwarf2_elf_names.str.matches (sec->name)) + sect = &dwz_file.str; + else if (dwarf2_elf_names.line.matches (sec->name)) + sect = &dwz_file.line; + else if (dwarf2_elf_names.macro.matches (sec->name)) + sect = &dwz_file.macro; + else if (dwarf2_elf_names.gdb_index.matches (sec->name)) + sect = &dwz_file.gdb_index; + else if (dwarf2_elf_names.debug_names.matches (sec->name)) + sect = &dwz_file.debug_names; + else if (dwarf2_elf_names.types.matches (sec->name)) + sect = &dwz_file.types; + + if (sect != nullptr) + { + sect->s.section = sec; + sect->size = bfd_section_size (sec); + sect->read (objfile); + } } } @@ -392,9 +394,7 @@ dwz_file::read_dwz_file (dwarf2_per_objfile *per_objfile) dwz_file_up result (new dwz_file (std::move (dwz_bfd))); - for (asection *sec : gdb_bfd_sections (result->dwz_bfd)) - locate_dwz_sections (per_objfile->objfile, result->dwz_bfd.get (), - sec, result.get ()); + locate_dwz_sections (per_objfile->objfile, *result); gdb_bfd_record_inclusion (per_bfd->obfd, result->dwz_bfd.get ()); bfd_cache_close (result->dwz_bfd.get ()); diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 71fa793..891e10f 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -7512,45 +7512,66 @@ cutu_reader::open_dwo_file (dwarf2_per_bfd *per_bfd, const char *file_name, size of each of the DWO debugging sections we are interested in. */ void -cutu_reader::locate_dwo_sections (struct objfile *objfile, bfd *abfd, - asection *sectp, dwo_sections *dwo_sections) +cutu_reader::locate_dwo_sections (objfile *objfile, dwo_file &dwo_file) { const struct dwop_section_names *names = &dwop_section_names; + dwo_sections &dwo_sections = dwo_file.sections; + bool complained_about_macro_already = false; + + for (asection *sec : gdb_bfd_sections (dwo_file.dbfd)) + { + struct dwarf2_section_info *dw_sect = nullptr; + + if (names->abbrev_dwo.matches (sec->name)) + dw_sect = &dwo_sections.abbrev; + else if (names->info_dwo.matches (sec->name)) + dw_sect = &dwo_sections.infos.emplace_back (dwarf2_section_info {}); + else if (names->line_dwo.matches (sec->name)) + dw_sect = &dwo_sections.line; + else if (names->loc_dwo.matches (sec->name)) + dw_sect = &dwo_sections.loc; + else if (names->loclists_dwo.matches (sec->name)) + dw_sect = &dwo_sections.loclists; + else if (names->macinfo_dwo.matches (sec->name)) + dw_sect = &dwo_sections.macinfo; + else if (names->macro_dwo.matches (sec->name)) + { + /* gcc versions <= 13 generate multiple .debug_macro.dwo sections with + some unresolved links between them. It's not usable, so do as if + there were not there. */ + if (!complained_about_macro_already) + { + if (dwo_sections.macro.s.section == nullptr) + dw_sect = &dwo_sections.macro; + else + { + warning (_("Multiple .debug_macro.dwo sections found in " + "%s, ignoring them."), dwo_file.dbfd->filename); - struct dwarf2_section_info *dw_sect = nullptr; - - if (names->abbrev_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->abbrev; - else if (names->info_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->infos.emplace_back (dwarf2_section_info {}); - else if (names->line_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->line; - else if (names->loc_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->loc; - else if (names->loclists_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->loclists; - else if (names->macinfo_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->macinfo; - else if (names->macro_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->macro; - else if (names->rnglists_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->rnglists; - else if (names->str_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->str; - else if (names->str_offsets_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->str_offsets; - else if (names->types_dwo.matches (sectp->name)) - dw_sect = &dwo_sections->types.emplace_back (dwarf2_section_info {}); - - if (dw_sect != nullptr) - { - /* Make sure we don't overwrite a section info that has been filled in + dwo_sections.macro = dwarf2_section_info {}; + complained_about_macro_already = true; + } + } + } + else if (names->rnglists_dwo.matches (sec->name)) + dw_sect = &dwo_sections.rnglists; + else if (names->str_dwo.matches (sec->name)) + dw_sect = &dwo_sections.str; + else if (names->str_offsets_dwo.matches (sec->name)) + dw_sect = &dwo_sections.str_offsets; + else if (names->types_dwo.matches (sec->name)) + dw_sect = &dwo_sections.types.emplace_back (dwarf2_section_info {}); + + if (dw_sect != nullptr) + { + /* Make sure we don't overwrite a section info that has been filled in already. */ - gdb_assert (!dw_sect->readin); + gdb_assert (!dw_sect->readin); - dw_sect->s.section = sectp; - dw_sect->size = bfd_section_size (sectp); - dw_sect->read (objfile); + dw_sect->s.section = sec; + dw_sect->size = bfd_section_size (sec); + dw_sect->read (objfile); + } } } @@ -7578,9 +7599,7 @@ cutu_reader::open_and_init_dwo_file (dwarf2_cu *cu, const char *dwo_name, dwo_file->comp_dir = comp_dir; dwo_file->dbfd = std::move (dbfd); - for (asection *sec : gdb_bfd_sections (dwo_file->dbfd)) - this->locate_dwo_sections (per_objfile->objfile, dwo_file->dbfd.get (), sec, - &dwo_file->sections); + this->locate_dwo_sections (per_objfile->objfile, *dwo_file); /* There is normally just one .debug_info.dwo section in a DWO file. But when building with -fdebug-types-section, gcc produces multiple .debug_info.dwo diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h index 5b4c8f6..aaac5e7 100644 --- a/gdb/dwarf2/read.h +++ b/gdb/dwarf2/read.h @@ -1029,8 +1029,7 @@ private: dwo_file_up open_and_init_dwo_file (dwarf2_cu *cu, const char *dwo_name, const char *comp_dir); - void locate_dwo_sections (struct objfile *objfile, bfd *abfd, asection *sectp, - struct dwo_sections *dwo_sections); + void locate_dwo_sections (objfile *objfile, dwo_file &dwo_file); void create_dwo_unit_hash_tables (dwo_file &dwo_file, dwarf2_cu &skeleton_cu, dwarf2_section_info §ion, diff --git a/gdb/guile/scm-cmd.c b/gdb/guile/scm-cmd.c index 4757872..453394c 100644 --- a/gdb/guile/scm-cmd.c +++ b/gdb/guile/scm-cmd.c @@ -509,7 +509,7 @@ gdbscm_parse_command_name (const char *name, const char *prefix_text2 = prefix_text.get (); elt = lookup_cmd_1 (&prefix_text2, *start_list, NULL, NULL, 1); - if (elt == NULL || elt == CMD_LIST_AMBIGUOUS) + if (elt == nullptr || elt == CMD_LIST_AMBIGUOUS || *prefix_text2 != '\0') { msg = xstrprintf (_("could not find command prefix '%s'"), prefix_text.get ()).release (); diff --git a/gdb/guile/scm-param.c b/gdb/guile/scm-param.c index 65226ec..5137847 100644 --- a/gdb/guile/scm-param.c +++ b/gdb/guile/scm-param.c @@ -308,13 +308,37 @@ pascm_is_valid (param_smob *p_smob) return p_smob->commands.set != nullptr; } -/* A helper function which return the default documentation string for - a parameter (which is to say that it's undocumented). */ + +/* The different types of documentation string. */ + +enum doc_string_type +{ + doc_string_set, + doc_string_show, + doc_string_description +}; + +/* A helper function which returns the default documentation string for + a parameter CMD_NAME. The DOC_TYPE indicates which type of + documentation string is needed. The returned string is dynamically + allocated. */ static char * -get_doc_string (void) +get_doc_string (doc_string_type doc_type, const char *cmd_name) { - return xstrdup (_("This command is not documented.")); + if (doc_type == doc_string_description) + return xstrdup (_("This command is not documented.")); + else + { + gdb_assert (cmd_name != nullptr); + + if (doc_type == doc_string_show) + return xstrprintf (_("Show the current value of '%s'."), + cmd_name).release (); + else + return xstrprintf (_("Set the current value of '%s'."), + cmd_name).release (); + } } /* Subroutine of pascm_set_func, pascm_show_func to simplify them. @@ -990,11 +1014,14 @@ gdbscm_make_parameter (SCM name_scm, SCM rest) &show_doc_arg_pos, &show_doc, &initial_value_arg_pos, &initial_value_scm); - /* If doc is NULL, leave it NULL. See add_setshow_cmd_full. */ + if (doc == nullptr) + doc = get_doc_string (doc_string_description, nullptr); + else if (*doc == '\0') + doc = nullptr; if (set_doc == NULL) - set_doc = get_doc_string (); + set_doc = get_doc_string (doc_string_set, name); if (show_doc == NULL) - show_doc = get_doc_string (); + show_doc = get_doc_string (doc_string_show, name); s = name; name = gdbscm_canonicalize_command_name (s, 0); diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index bbffb3d..0b08e12 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -630,9 +630,9 @@ mapping_is_anonymous_p (const char *filename) return 0; } -/* Return 0 if the memory mapping (which is related to FILTERFLAGS, V, - MAYBE_PRIVATE_P, MAPPING_ANONYMOUS_P, ADDR and OFFSET) should not - be dumped, or greater than 0 if it should. +/* Return false if the memory mapping represented by MAP should not be + dumped, or true if it should. FILTERFLAGS guides which mappings + should be dumped. In a nutshell, this is the logic that we follow in order to decide if a mapping should be dumped or not. @@ -677,11 +677,14 @@ mapping_is_anonymous_p (const char *filename) header (of a DSO or an executable, for example). If it is, and if the user is interested in dump it, then we should dump it. */ -static int -dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, - int maybe_private_p, int mapping_anon_p, int mapping_file_p, - const char *filename, ULONGEST addr, ULONGEST offset) +static bool +dump_mapping_p (filter_flags filterflags, const smaps_data &map) { + /* Older Linux kernels did not support the "Anonymous:" counter. + If it is missing, we can't be sure what to dump, so dump everything. */ + if (!map.has_anonymous) + return true; + /* Initially, we trust in what we received from our caller. This value may not be very precise (i.e., it was probably gathered from the permission line in the /proc/PID/smaps list, which @@ -689,41 +692,42 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, what we have until we take a look at the "VmFlags:" field (assuming that the version of the Linux kernel being used supports it, of course). */ - int private_p = maybe_private_p; - int dump_p; + int private_p = map.priv; /* We always dump vDSO and vsyscall mappings, because it's likely that there'll be no file to read the contents from at core load time. The kernel does the same. */ - if (strcmp ("[vdso]", filename) == 0 - || strcmp ("[vsyscall]", filename) == 0) - return 1; + if (map.filename == "[vdso]" || map.filename == "[vsyscall]") + return true; - if (v->initialized_p) + if (map.vmflags.initialized_p) { /* We never dump I/O mappings. */ - if (v->io_page) - return 0; + if (map.vmflags.io_page) + return false; /* Check if we should exclude this mapping. */ - if (!dump_excluded_mappings && v->exclude_coredump) - return 0; + if (!dump_excluded_mappings && map.vmflags.exclude_coredump) + return false; /* Update our notion of whether this mapping is shared or private based on a trustworthy value. */ - private_p = !v->shared_mapping; + private_p = !map.vmflags.shared_mapping; /* HugeTLB checking. */ - if (v->uses_huge_tlb) + if (map.vmflags.uses_huge_tlb) { if ((private_p && (filterflags & COREFILTER_HUGETLB_PRIVATE)) || (!private_p && (filterflags & COREFILTER_HUGETLB_SHARED))) - return 1; + return true; - return 0; + return false; } } + int mapping_anon_p = map.mapping_anon_p; + int mapping_file_p = map.mapping_file_p; + bool dump_p; if (private_p) { if (mapping_anon_p && mapping_file_p) @@ -763,7 +767,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, A mapping contains an ELF header if it is a private mapping, its offset is zero, and its first word is ELFMAG. */ - if (!dump_p && private_p && offset == 0 + if (!dump_p && private_p && map.offset == 0 && (filterflags & COREFILTER_ELF_HEADERS) != 0) { /* Useful define specifying the size of the ELF magical @@ -774,7 +778,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, /* Let's check if we have an ELF header. */ gdb_byte h[SELFMAG]; - if (target_read_memory (addr, h, SELFMAG) == 0) + if (target_read_memory (map.start_address, h, SELFMAG) == 0) { /* The EI_MAG* and ELFMAG* constants come from <elf/common.h>. */ @@ -783,7 +787,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, { /* This mapping contains an ELF header, so we should dump it. */ - dump_p = 1; + dump_p = true; } } } @@ -794,20 +798,25 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, /* As above, but return true only when we should dump the NT_FILE entry. */ -static int -dump_note_entry_p (filter_flags filterflags, const struct smaps_vmflags *v, - int maybe_private_p, int mapping_anon_p, int mapping_file_p, - const char *filename, ULONGEST addr, ULONGEST offset) +static bool +dump_note_entry_p (filter_flags filterflags, const smaps_data &map) { + /* No NT_FILE entry for mappings with no filename. */ + if (map.filename.length () == 0) + return false; + + /* Don't add NT_FILE entries for mappings with a zero inode. */ + if (map.inode == 0) + return false; + /* vDSO and vsyscall mappings will end up in the core file. Don't put them in the NT_FILE note. */ - if (strcmp ("[vdso]", filename) == 0 - || strcmp ("[vsyscall]", filename) == 0) - return 0; + if (map.filename == "[vdso]" || map.filename == "[vsyscall]") + return false; /* Otherwise, any other file-based mapping should be placed in the note. */ - return 1; + return true; } /* Implement the "info proc" command. */ @@ -1314,21 +1323,15 @@ linux_core_xfer_siginfo (struct gdbarch *gdbarch, gdb_byte *readbuf, } typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size, - ULONGEST offset, ULONGEST inode, + ULONGEST offset, int read, int write, int exec, int modified, bool memory_tagged, - const char *filename, + const std::string &filename, void *data); -typedef int linux_dump_mapping_p_ftype (filter_flags filterflags, - const struct smaps_vmflags *v, - int maybe_private_p, - int mapping_anon_p, - int mapping_file_p, - const char *filename, - ULONGEST addr, - ULONGEST offset); +typedef bool linux_dump_mapping_p_ftype (filter_flags filterflags, + const smaps_data &map); /* Helper function to parse the contents of /proc/<pid>/smaps into a data structure, for easy access. @@ -1590,35 +1593,15 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, for (const struct smaps_data &map : smaps) { - int should_dump_p = 0; - - if (map.has_anonymous) - { - should_dump_p - = should_dump_mapping_p (filterflags, &map.vmflags, - map.priv, - map.mapping_anon_p, - map.mapping_file_p, - map.filename.c_str (), - map.start_address, - map.offset); - } - else - { - /* Older Linux kernels did not support the "Anonymous:" counter. - If it is missing, we can't be sure - dump all the pages. */ - should_dump_p = 1; - } - /* Invoke the callback function to create the corefile segment. */ - if (should_dump_p) + if (should_dump_mapping_p (filterflags, map)) { func (map.start_address, map.end_address - map.start_address, - map.offset, map.inode, map.read, map.write, map.exec, + map.offset, map.read, map.write, map.exec, 1, /* MODIFIED is true because we want to dump the mapping. */ map.vmflags.memory_tagging != 0, - map.filename.c_str (), obfd); + map.filename, obfd); } } @@ -1644,10 +1627,10 @@ struct linux_find_memory_regions_data static int linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size, - ULONGEST offset, ULONGEST inode, + ULONGEST offset, int read, int write, int exec, int modified, bool memory_tagged, - const char *filename, void *arg) + const std::string &filename, void *arg) { struct linux_find_memory_regions_data *data = (struct linux_find_memory_regions_data *) arg; @@ -1693,8 +1676,6 @@ struct linux_make_mappings_data struct type *long_type; }; -static linux_find_memory_region_ftype linux_make_mappings_callback; - /* A callback for linux_find_memory_regions_full that updates the mappings data for linux_make_mappings_corefile_notes. @@ -1703,17 +1684,16 @@ static linux_find_memory_region_ftype linux_make_mappings_callback; static int linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size, - ULONGEST offset, ULONGEST inode, + ULONGEST offset, int read, int write, int exec, int modified, bool memory_tagged, - const char *filename, void *data) + const std::string &filename, void *data) { struct linux_make_mappings_data *map_data = (struct linux_make_mappings_data *) data; gdb_byte buf[sizeof (ULONGEST)]; - if (*filename == '\0' || inode == 0) - return 0; + gdb_assert (filename.length () > 0); ++map_data->file_count; @@ -1724,7 +1704,7 @@ linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size, pack_long (buf, map_data->long_type, offset); obstack_grow (map_data->data_obstack, buf, map_data->long_type->length ()); - obstack_grow_str0 (map_data->filename_obstack, filename); + obstack_grow_str0 (map_data->filename_obstack, filename.c_str ()); return 0; } diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c index 092127dd..d79ec68 100644 --- a/gdb/loongarch-tdep.c +++ b/gdb/loongarch-tdep.c @@ -74,7 +74,9 @@ loongarch_insn_is_cond_branch (insn_t insn) || (insn & 0xfc000000) == 0x68000000 /* bltu */ || (insn & 0xfc000000) == 0x6c000000 /* bgeu */ || (insn & 0xfc000000) == 0x40000000 /* beqz */ - || (insn & 0xfc000000) == 0x44000000) /* bnez */ + || (insn & 0xfc000000) == 0x44000000 /* bnez */ + || (insn & 0xfc000300) == 0x48000000 /* bceqz */ + || (insn & 0xfc000300) == 0x48000100) /* bcnez */ return true; return false; } @@ -314,6 +316,20 @@ loongarch_next_pc (struct regcache *regcache, CORE_ADDR cur_pc) if (rj != 0) next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1); } + else if ((insn & 0xfc000300) == 0x48000000) /* bceqz cj, offs21 */ + { + LONGEST cj = regcache_raw_get_signed (regcache, + loongarch_decode_imm ("5:3", insn, 0) + LOONGARCH_FIRST_FCC_REGNUM); + if (cj == 0) + next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1); + } + else if ((insn & 0xfc000300) == 0x48000100) /* bcnez cj, offs21 */ + { + LONGEST cj = regcache_raw_get_signed (regcache, + loongarch_decode_imm ("5:3", insn, 0) + LOONGARCH_FIRST_FCC_REGNUM); + if (cj != 0) + next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1); + } else if ((insn & 0xffff8000) == 0x002b0000) /* syscall */ { if (tdep->syscall_next_pc != nullptr) diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 4ea5d06..69c15b1 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -392,3 +392,121 @@ def _handle_missing_objfile(pspace, buildid, filename): return _handle_missing_files( pspace, "objfile", lambda h: h(pspace, buildid, filename) ) + + +class ParameterPrefix: + # A wrapper around gdb.Command for creating set/show prefixes. + # + # When creating a gdb.Parameter sub-classes, it is sometimes necessary + # to first create a gdb.Command object in order to create the needed + # command prefix. However, for parameters, we actually need two + # prefixes, a 'set' prefix, and a 'show' prefix. With this helper + # class, a single instance of this class will create both prefixes at + # once. + # + # It is important that this class-level documentation not be a __doc__ + # string. Users are expected to sub-class this ParameterPrefix class + # and add their own documentation. If they don't, then GDB will + # generate a suitable doc string. But, if this (parent) class has a + # __doc__ string of its own, then sub-classes will inherit that __doc__ + # string, and GDB will not understand that it needs to generate one. + + class _PrefixCommand(_gdb.Command): + """A gdb.Command used to implement both the set and show prefixes. + + This documentation string is not used as the prefix command + documentation as it is overridden in the __init__ method below.""" + + # This private method is connected to the 'invoke' attribute within + # this _PrefixCommand object if the containing ParameterPrefix + # object has an invoke_set or invoke_show method. + # + # This method records within self.__delegate which _PrefixCommand + # object is currently active, and then calls the correct invoke + # method on the delegat object (the ParameterPrefix sub-class + # object). + # + # Recording the currently active _PrefixCommand object is important; + # if from the invoke method the user calls dont_repeat, then this is + # forwarded to the currently active _PrefixCommand object. + def __invoke(self, args, from_tty): + + # A helper class for use as part of a Python 'with' block. + # Records which gdb.Command object is currently running its + # invoke method. + class MarkActiveCallback: + # The CMD is a _PrefixCommand object, and the DELEGATE is + # the ParameterPrefix class, or sub-class object. At this + # point we simple record both of these within the + # MarkActiveCallback object. + def __init__(self, cmd, delegate): + self.__cmd = cmd + self.__delegate = delegate + + # Record the currently active _PrefixCommand object within + # the outer ParameterPrefix sub-class object. + def __enter__(self): + self.__delegate.active_prefix = self.__cmd + + # Once the invoke method has completed, then clear the + # _PrefixCommand object that was stored into the outer + # ParameterPrefix sub-class object. + def __exit__(self, exception_type, exception_value, traceback): + self.__delegate.active_prefix = None + + # The self.__cb attribute is set when the _PrefixCommand object + # is created, and is either invoke_set or invoke_show within the + # ParameterPrefix sub-class object. + assert callable(self.__cb) + + # Record the currently active _PrefixCommand object within the + # ParameterPrefix sub-class object, then call the relevant + # invoke method within the ParameterPrefix sub-class object. + with MarkActiveCallback(self, self.__delegate): + self.__cb(args, from_tty) + + @staticmethod + def __find_callback(delegate, mode): + """The MODE is either 'set' or 'show'. Look for an invoke_MODE method + on DELEGATE, if a suitable method is found, then return it, otherwise, + return None. + """ + cb = getattr(delegate, "invoke_" + mode, None) + if callable(cb): + return cb + return None + + def __init__(self, mode, name, cmd_class, delegate, doc=None): + """Setup this gdb.Command. Mode is a string, either 'set' or 'show'. + NAME is the name for this prefix command, that is, the + words that appear after both 'set' and 'show' in the + command name. CMD_CLASS is the usual enum. And DELEGATE + is the gdb.ParameterPrefix object this prefix is part of. + """ + assert mode == "set" or mode == "show" + if doc is None: + self.__doc__ = delegate.__doc__ + else: + self.__doc__ = doc + self.__cb = self.__find_callback(delegate, mode) + self.__delegate = delegate + if self.__cb is not None: + self.invoke = self.__invoke + super().__init__(mode + " " + name, cmd_class, prefix=True) + + def __init__(self, name, cmd_class, doc=None): + """Create a _PrefixCommand for both the set and show prefix commands. + NAME is the command name without either the leading 'set ' or + 'show ' strings, and CMD_CLASS is the usual enum value. + """ + self.active_prefix = None + self._set_prefix_cmd = self._PrefixCommand("set", name, cmd_class, self, doc) + self._show_prefix_cmd = self._PrefixCommand("show", name, cmd_class, self, doc) + + # When called from within an invoke method the self.active_prefix + # attribute should be set to a gdb.Command sub-class (a _PrefixCommand + # object, see above). Forward the dont_repeat call to this object to + # register the actual command as none repeating. + def dont_repeat(self): + if self.active_prefix is not None: + self.active_prefix.dont_repeat() diff --git a/gdb/python/lib/gdb/dap/sources.py b/gdb/python/lib/gdb/dap/sources.py index 625c01f..efcd799 100644 --- a/gdb/python/lib/gdb/dap/sources.py +++ b/gdb/python/lib/gdb/dap/sources.py @@ -64,9 +64,9 @@ def decode_source(source): """Decode a Source object. Finds and returns the filename of a given Source object.""" - if "path" in source: - return source["path"] - if "sourceReference" not in source: + if "sourceReference" not in source or source["sourceReference"] <= 0: + if "path" in source: + return source["path"] raise DAPException("either 'path' or 'sourceReference' must appear in Source") ref = source["sourceReference"] if ref not in _id_map: diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index 5d98d03..c53138a 100644 --- a/gdb/python/py-cmd.c +++ b/gdb/python/py-cmd.c @@ -385,7 +385,7 @@ gdbpy_parse_command_name (const char *name, prefix_text2 = prefix_text.c_str (); elt = lookup_cmd_1 (&prefix_text2, *start_list, NULL, NULL, 1); - if (elt == NULL || elt == CMD_LIST_AMBIGUOUS) + if (elt == nullptr || elt == CMD_LIST_AMBIGUOUS || *prefix_text2 != '\0') { PyErr_Format (PyExc_RuntimeError, _("Could not find command prefix %s."), prefix_text.c_str ()); diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index 763680e..06237b6 100644 --- a/gdb/python/py-param.c +++ b/gdb/python/py-param.c @@ -495,7 +495,11 @@ get_doc_string (PyObject *object, enum doc_string_type doc_type, } } - if (result == nullptr) + /* For the set/show docs, if these strings are empty then we set then to + a non-empty string. This ensures that the command has some sane + documentation for its 'help' text. */ + if (result == nullptr + || (doc_type != doc_string_description && *result == '\0')) { if (doc_type == doc_string_description) result.reset (xstrdup (_("This command is not documented."))); @@ -904,6 +908,18 @@ parmpy_init (PyObject *self, PyObject *args, PyObject *kwds) show_doc = get_doc_string (self, doc_string_show, name); doc = get_doc_string (self, doc_string_description, cmd_name.get ()); + /* The set/show docs should always be a non-empty string. */ + gdb_assert (set_doc != nullptr && *set_doc != '\0'); + gdb_assert (show_doc != nullptr && *show_doc != '\0'); + + /* For the DOC string only, if it is the empty string, then we convert it + to NULL. This means GDB will not even display a blank line for this + part of the help text, instead the set/show line is all the user will + get. */ + gdb_assert (doc != nullptr); + if (*doc == '\0') + doc = nullptr; + Py_INCREF (self); try diff --git a/gdb/symtab.c b/gdb/symtab.c index 5147aee..313aa5f 100644 --- a/gdb/symtab.c +++ b/gdb/symtab.c @@ -2163,13 +2163,6 @@ lookup_symbol_aux (const char *name, symbol_name_match_type match_type, domain_name (domain).c_str (), language_str (language)); } - /* Make sure we do something sensible with is_a_field_of_this, since - the callers that set this parameter to some non-null value will - certainly use it later. If we don't set it, the contents of - is_a_field_of_this are undefined. */ - if (is_a_field_of_this != NULL) - memset (is_a_field_of_this, 0, sizeof (*is_a_field_of_this)); - langdef = language_def (language); /* Search specified block and its superiors. Don't search diff --git a/gdb/symtab.h b/gdb/symtab.h index e547d10..0a57be5 100644 --- a/gdb/symtab.h +++ b/gdb/symtab.h @@ -2101,17 +2101,17 @@ struct field_of_this_result symbol was not found in 'this'. If non-NULL, then one of the other fields will be non-NULL as well. */ - struct type *type; + struct type *type = nullptr; /* If the symbol was found as an ordinary field of 'this', then this is non-NULL and points to the particular field. */ - struct field *field; + struct field *field = nullptr; /* If the symbol was found as a function field of 'this', then this is non-NULL and points to the particular field. */ - struct fn_fieldlist *fn_field; + struct fn_fieldlist *fn_field = nullptr; }; /* Find the definition for a specified symbol name NAME diff --git a/gdb/testsuite/gdb.base/foll-fork-syscall.c b/gdb/testsuite/gdb.base/foll-fork-syscall.c new file mode 100644 index 0000000..ef695f5 --- /dev/null +++ b/gdb/testsuite/gdb.base/foll-fork-syscall.c @@ -0,0 +1,35 @@ +/* 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 <stdio.h> + +int +main (int argc, char **argv) +{ + int pid, x = 0; + + pid = fork (); + if (pid == 0) /* set breakpoint here */ + printf ("I am the child\n"); + else + printf ("I am the parent\n"); + + chdir ("."); + ++x; /* set exit breakpoint here */ + return 0; +} diff --git a/gdb/testsuite/gdb.base/foll-fork-syscall.exp b/gdb/testsuite/gdb.base/foll-fork-syscall.exp new file mode 100644 index 0000000..4aee683 --- /dev/null +++ b/gdb/testsuite/gdb.base/foll-fork-syscall.exp @@ -0,0 +1,142 @@ +# 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/>. + +# Test catching syscalls with all permutations of follow-fork parent/child +# and detach-on-fork on/off. + +# Test relies on checking follow-fork output. Do not run if gdb debug is +# enabled because it will be redirected to the log. +require !gdb_debug_enabled +require {is_any_target "i?86-*-*" "x86_64-*-*"} + +standard_testfile + +if {[build_executable "failed to prepare" $testfile $srcfile debug]} { + return -1 +} + +proc setup_gdb {} { + global testfile + + clean_restart $testfile + + if {![runto_main]} { + return false + } + + # Set a breakpoint after the fork is "complete." + if {![gdb_breakpoint [gdb_get_line_number "set breakpoint here"]]} { + return false + } + + # Set exit breakpoint (to prevent inferior from exiting). + if {![gdb_breakpoint [gdb_get_line_number "set exit breakpoint here"]]} { + return false + } + return true +} + +# Check that fork catchpoints are supported, as an indicator for whether +# fork-following is supported. Return 1 if they are, else 0. + +proc_with_prefix check_fork_catchpoints {} { + global gdb_prompt + + if { ![setup_gdb] } { + return false + } + + # Verify that the system supports "catch fork". + gdb_test "catch fork" "Catchpoint \[0-9\]* \\(fork\\)" "insert first fork catchpoint" + set has_fork_catchpoints false + gdb_test_multiple "continue" "continue to first fork catchpoint" { + -re ".*Your system does not support this type\r\nof catchpoint.*$gdb_prompt $" { + unsupported "continue to first fork catchpoint" + } + -re ".*Catchpoint.*$gdb_prompt $" { + set has_fork_catchpoints true + pass "continue to first fork catchpoint" + } + } + + return $has_fork_catchpoints +} + +proc_with_prefix test_catch_syscall {follow-fork-mode detach-on-fork} { + # Start with shiny new gdb instance. + if {![setup_gdb]} { + return + } + + # The "Detaching..." and "Attaching..." messages may be hidden by + # default. + gdb_test_no_output "set verbose" + + # Setup modes to test. + gdb_test_no_output "set follow-fork-mode ${follow-fork-mode}" + gdb_test_no_output "set detach-on-fork ${detach-on-fork}" + + gdb_test "catch fork" "Catchpoint . \\(fork\\)" + gdb_test "catch syscall chdir" "Catchpoint . \\(syscall 'chdir'.*\\)" + + # Which inferior we're expecting to follow. Assuming the parent + # will be inferior #1, and the child will be inferior #2. + if {${follow-fork-mode} == "parent"} { + set following_inf 1 + } else { + set followin_inf 2 + } + # Next stop should be the fork catchpoint. + set expected_re "" + append expected_re "Catchpoint . \\(forked process.*" + gdb_test "continue" $expected_re "continue to fork catchpoint" + + # Next stop should be the breakpoint after the fork. + set expected_re ".*" + if {${follow-fork-mode} == "child" || ${detach-on-fork} == "off"} { + append expected_re "\\\[New inferior.*" + } + if {${detach-on-fork} == "on"} { + append expected_re "\\\[Detaching after fork from " + if {${follow-fork-mode} == "parent"} { + append expected_re "child" + } else { + append expected_re "parent" + } + append expected_re " process.*" + } + append expected_re "Breakpoint .*set breakpoint here.*" + gdb_test "continue" $expected_re "continue to breakpoint after fork" + + # Next stop should be the syscall catchpoint. + set expected_re ".*Catchpoint . \\(call to syscall chdir\\).*" + gdb_test continue $expected_re "continue to chdir syscall" +} + +# Check for follow-fork support. +if {![check_fork_catchpoints]} { + untested "follow-fork not supported" + return +} + +# Test all permutations. +foreach_with_prefix follow-fork-mode {"parent" "child"} { + + # Do not run tests when not detaching from the parent. + # See breakpoints/13457 for discussion. + foreach_with_prefix detach-on-fork {"on"} { + test_catch_syscall ${follow-fork-mode} ${detach-on-fork} + } +} diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp index 7822e4a..a0947e2 100644 --- a/gdb/testsuite/gdb.base/options.exp +++ b/gdb/testsuite/gdb.base/options.exp @@ -508,12 +508,26 @@ proc_with_prefix test-thread-apply {} { proc_with_prefix test-info-threads {} { test_gdb_complete_multiple "info threads " "" "" { "-gid" + "-running" + "-stopped" "ID" } + test_gdb_complete_multiple "info threads " "-" "" { + "-gid" + "-running" + "-stopped" + } + test_gdb_complete_unique \ - "info threads -" \ + "info threads -g" \ "info threads -gid" + test_gdb_complete_unique \ + "info threads -r" \ + "info threads -running" + test_gdb_complete_unique \ + "info threads -s" \ + "info threads -stopped" # "ID" isn't really something the user can type. test_gdb_complete_none "info threads I" diff --git a/gdb/testsuite/gdb.guile/scm-cmd.exp b/gdb/testsuite/gdb.guile/scm-cmd.exp index 9caca24..3709cb1 100644 --- a/gdb/testsuite/gdb.guile/scm-cmd.exp +++ b/gdb/testsuite/gdb.guile/scm-cmd.exp @@ -71,6 +71,65 @@ gdb_test_multiline "input subcommand" \ gdb_test "prefix-cmd subcmd ugh" "subcmd output, arg = ugh" "call subcmd" +# Create a sub-command using a partial, but still unique, prefix. + +gdb_test_multiline "sub-command using partial prefix" \ + "guile" "" \ + "(register-command! (make-command \"prefix subcmd2\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:invoke (lambda (self arg from-tty)" "" \ + " (display (format #f \"subcmd2 output, arg = ~a\\n\" arg)))))" "" \ + "end" "" + +gdb_test "prefix-cmd subcmd2 ugh" "subcmd2 output, arg = ugh" "call subcmd2" + +# Now create a second prefix, similar to the first. + +gdb_test_multiline "create prefix-xxx prefix command" \ + "guile" "" \ + "(register-command! (make-command \"prefix-xxx\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:completer-class COMPLETE_NONE" "" \ + " #:prefix? #t))" "" \ + "end" "" + +# Now create a sub-command using an ambiguous prefix. + +gdb_test_multiline "sub-command using ambiguous partial prefix" \ + "guile" "" \ + "(register-command! (make-command \"prefix subcmd3\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:invoke (lambda (self arg from-tty)" "" \ + " (display (format #f \"subcmd3 output, arg = ~a\\n\" arg)))))" "" \ + "end" \ + [multi_line \ + "Out of range: could not find command prefix 'prefix' in position 1: \"prefix subcmd3\"" \ + "Error while executing Scheme code\\."] + +# Check for errors when creating a command with an unknown prefix. + +gdb_test_multiline "try to create 'unknown-prefix subcmd'" \ + "guile" "" \ + "(register-command! (make-command \"unknown-prefix subcmd\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:invoke (lambda (self arg from-tty)" "" \ + " (display \"called unknown-prefix subcmd\"))))" "" \ + "end" \ + [multi_line \ + "Out of range: could not find command prefix 'unknown-prefix' in position 1: \"unknown-prefix subcmd\"" \ + "Error while executing Scheme code\\."] + +gdb_test_multiline "try to create 'prefix-cmd unknown-prefix subcmd'" \ + "guile" "" \ + "(register-command! (make-command \"prefix-cmd unknown-prefix subcmd\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:invoke (lambda (self arg from-tty)" "" \ + " (display \"called prefix-cmd unknown-prefix subcmd\"))))" "" \ + "end" \ + [multi_line \ + "Out of range: could not find command prefix 'prefix-cmd unknown-prefix' in position 1: \"prefix-cmd unknown-prefix subcmd\"" \ + "Error while executing Scheme code\\."] + # Test a subcommand in an existing GDB prefix. gdb_test_multiline "input new subcommand" \ diff --git a/gdb/testsuite/gdb.guile/scm-parameter.exp b/gdb/testsuite/gdb.guile/scm-parameter.exp index 8ab5d93..06eebdd 100644 --- a/gdb/testsuite/gdb.guile/scm-parameter.exp +++ b/gdb/testsuite/gdb.guile/scm-parameter.exp @@ -67,9 +67,19 @@ with_test_prefix "test-param" { gdb_test_no_output "set print test-param off" gdb_test "show print test-param" "The state of the Test Parameter is off." "show parameter off" gdb_test "guile (print (parameter-value test-param))" "= #f" "parameter value, false" - gdb_test "help show print test-param" "Show the state of the boolean test-param.*" "show help" - gdb_test "help set print test-param" "Set the state of the boolean test-param.*" "set help" - gdb_test "help set print" "set print test-param -- Set the state of the boolean test-param.*" "general help" + gdb_test "help show print test-param" \ + [multi_line \ + "^Show the state of the boolean test-param\\." \ + "When enabled, test param does something useful\\. When disabled, does nothing\\."] \ + "show help" + gdb_test "help set print test-param" \ + [multi_line \ + "^Set the state of the boolean test-param\\." \ + "When enabled, test param does something useful\\. When disabled, does nothing\\."] \ + "set help" + gdb_test "help set print" \ + "set print test-param -- Set the state of the boolean test-param.*" \ + "general help" gdb_test "guile (print (parameter? test-param))" "= #t" gdb_test "guile (print (parameter? 42))" "= #f" @@ -314,9 +324,17 @@ with_test_prefix "test-undocumented-param" { gdb_test "show print test-undoc-param" "The state of the Test Parameter is on." "show parameter on" gdb_test_no_output "set print test-undoc-param off" gdb_test "show print test-undoc-param" "The state of the Test Parameter is off." "show parameter off" - gdb_test "help show print test-undoc-param" "This command is not documented." "show help" - gdb_test "help set print test-undoc-param" "This command is not documented." "set help" - gdb_test "help set print" "set print test-undoc-param -- This command is not documented.*" "general help" + gdb_test "help show print test-undoc-param" \ + [multi_line \ + "^Show the current value of 'print test-undoc-param'\\." \ + "This command is not documented\\."] \ + "show help" + gdb_test "help set print test-undoc-param" \ + [multi_line \ + "Set the current value of 'print test-undoc-param'\\." \ + "This command is not documented\\."] \ + "set help" + gdb_test "help set print" "set print test-undoc-param -- Set the current value of 'print test-undoc-param'\\..*" "general help" } # Test a parameter with a restricted range, where we need to notify the user @@ -379,13 +397,85 @@ gdb_test_no_output "guile (register-parameter! prev-ambig)" with_test_prefix "previously-ambiguous" { gdb_test "guile (print (parameter-value prev-ambig))" "= #f" "parameter value, false" - gdb_test "show print s" "Command is not documented is off." "show parameter off" + gdb_test "show print s" \ + "The current value of 'print s' is off\\." "show parameter off" gdb_test_no_output "set print s on" - gdb_test "show print s" "Command is not documented is on." "show parameter on" + gdb_test "show print s" \ + "The current value of 'print s' is on\\." "show parameter on" gdb_test "guile (print (parameter-value prev-ambig))" "= #t" "parameter value, true" - gdb_test "help show print s" "This command is not documented." "show help" - gdb_test "help set print s" "This command is not documented." "set help" - gdb_test "help set print" "set print s -- This command is not documented.*" "general help" + gdb_test "help show print s" \ + [multi_line \ + "^Show the current value of 'print s'\\." \ + "This command is not documented\\."] \ + "show help" + gdb_test "help set print s" \ + [multi_line \ + "Set the current value of 'print s'\\." \ + "This command is not documented\\."] \ + "set help" + gdb_test "help set print" \ + "set print s -- Set the current value of 'print s'\\..*" \ + "general help" +} + +gdb_test_multiline "create set/show foo1 prefix commands" \ + "guile" "" \ + "(register-command! (make-command \"set foo1\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:prefix? #t))" "" \ + "(register-command! (make-command \"show foo1\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:prefix? #t))" "" \ + "end" + +gdb_test_multiline "create 'foo bar' parameter" \ + "guile" "" \ + "(register-parameter! (make-parameter \"foo bar\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:parameter-type PARAM_BOOLEAN" "" \ + " #:show-func (lambda (self value)" "" \ + " (format #f \"The state of 'foo bar' is ~a.\" value))" "" \ + " #:initial-value #t))" "" \ + "end" + +gdb_test "show foo1 bar" "^The state of 'foo bar' is on\\." "show parameter 'foo bar'" + +gdb_test_multiline "create set/show foo2 prefix commands" \ + "guile" "" \ + "(register-command! (make-command \"set foo2\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:prefix? #t))" "" \ + "(register-command! (make-command \"show foo2\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:prefix? #t))" "" \ + "end" "" + +gdb_test_multiline "create ambiguous 'foo baz' parameter" \ + "guile" "" \ + "(register-parameter! (make-parameter \"foo baz\"" "" \ + " #:command-class COMMAND_OBSCURE" "" \ + " #:parameter-type PARAM_BOOLEAN" "" \ + " #:show-func (lambda (self value)" "" \ + " (format #f \"The state of 'foo baz' is ~a.\" value))" "" \ + " #:initial-value #t))" "" \ + "end" \ + [multi_line \ + "Out of range: could not find command prefix 'foo' in position 1: \"foo baz\"" \ + "Error while executing Scheme code."] + +with_test_prefix "empty doc string" { + gdb_test_multiline "empty doc string parameter" \ + "guile" "" \ + "(register-parameter! (make-parameter \"empty-doc-string\"" "" \ + " #:command-class COMMAND_NONE" "" \ + " #:parameter-type PARAM_ZINTEGER" "" \ + " #:doc \"\"" "" \ + " #:set-doc \"Set doc string.\"" "" \ + " #:show-doc \"Show doc string.\"))" "" \ + "end" + + gdb_test "help set empty-doc-string" "^Set doc string\\." + gdb_test "help show empty-doc-string" "^Show doc string\\." } rename scm_param_test_maybe_no_output "" diff --git a/gdb/testsuite/gdb.multi/tids.exp b/gdb/testsuite/gdb.multi/tids.exp index b84f908..dab6275 100644 --- a/gdb/testsuite/gdb.multi/tids.exp +++ b/gdb/testsuite/gdb.multi/tids.exp @@ -290,7 +290,7 @@ with_test_prefix "two inferiors" { # Try both the convenience variable and the literal number. foreach thr {"\$thr" "20" "1.20" "\$inf.1" "30.1" } { set expected [string_to_regexp $thr] - gdb_test "info threads $thr" "No threads match '${expected}'." + gdb_test "info threads $thr" "No threads matched\\." # "info threads" works like a filter. If there's any other # valid thread in the list, there's no error. info_threads "$thr 1.1" "1.1" @@ -412,7 +412,7 @@ with_test_prefix "two inferiors" { # Check that we do parse the inferior number and don't confuse it. gdb_test "info threads 3.1" \ - "No threads match '3.1'\." + "No threads matched\\." } if { [allow_python_tests] } { diff --git a/gdb/testsuite/gdb.python/py-cmd.exp b/gdb/testsuite/gdb.python/py-cmd.exp index f76c176..5ed52a2 100644 --- a/gdb/testsuite/gdb.python/py-cmd.exp +++ b/gdb/testsuite/gdb.python/py-cmd.exp @@ -328,4 +328,31 @@ proc_with_prefix test_command_redefining_itself {} { "call command redefining itself 2" } +# Try to create commands using unknown prefixes and check GDB gives an +# error. There's also a test in here for an ambiguous prefix, which +# gives the same error. +proc_with_prefix test_unknown_prefix {} { + clean_restart + + gdb_test_no_output "python gdb.Command('foo1', gdb.COMMAND_NONE, prefix=True)" + gdb_test_no_output "python gdb.Command('foo cmd', gdb.COMMAND_NONE)" + + foreach prefix { "xxx" "foo xxx" "foo1 xxx" } { + gdb_test "python gdb.Command('$prefix cmd', gdb.COMMAND_NONE)" \ + [multi_line \ + "Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \ + "Error occurred in Python: Could not find command prefix $prefix\\."] + } + + gdb_test_no_output "python gdb.Command('foo2', gdb.COMMAND_NONE, prefix=True)" + + foreach prefix { "foo" "foo xxx" "foo1 xxx" "foo2 xxx" } { + gdb_test "python gdb.Command('$prefix cmd2', gdb.COMMAND_NONE)" \ + [multi_line \ + "Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \ + "Error occurred in Python: Could not find command prefix $prefix\\."] + } +} + test_command_redefining_itself +test_unknown_prefix diff --git a/gdb/testsuite/gdb.python/py-parameter-prefix.exp b/gdb/testsuite/gdb.python/py-parameter-prefix.exp new file mode 100644 index 0000000..aa2f84a --- /dev/null +++ b/gdb/testsuite/gdb.python/py-parameter-prefix.exp @@ -0,0 +1,285 @@ +# Copyright (C) 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/>. + +# This file is part of the GDB testsuite. It tests +# gdb.ParameterPrefix. See each of the test procs for a full +# description of what is being tested. + +load_lib gdb-python.exp + +require allow_python_tests + +clean_restart + +# Helper proc to generate the output of 'help MODE PREFIX', where MODE +# will be either 'set' or 'show'. The HELP_TEXT is the expected help +# text for this prefix command, this should not be a regexp, as this +# proc converts the text to a regexp. +# +# Return a single regexp which should match the output. +proc make_help_re { mode prefix help_text } { + if { $mode == "set" } { + set word "Set" + } else { + set word "Show" + } + + set help_re [string_to_regexp $help_text] + + return \ + [multi_line \ + "$help_re" \ + "" \ + "List of \"$mode $prefix\" subcommands:" \ + "" \ + "$mode $prefix param-1 -- $word the current value of '$prefix param-1'\\." \ + "" \ + "Type \"help $mode $prefix\" followed by subcommand name for full documentation\\." \ + "Type \"apropos word\" to search for commands related to \"word\"\\." \ + "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \ + "Command name abbreviations are allowed if unambiguous\\."] +} + +# Create gdb.ParameterPrefix without using a sub-class, both with, and +# without a doc string. For the doc string case, test single line, +# and multi-line doc strings. +proc_with_prefix test_basic_usage {} { + gdb_test_multiline "some basic ParameterPrefix usage" \ + "python" "" \ + "gdb.ParameterPrefix('prefix-1', gdb.COMMAND_NONE)" "" \ + "gdb.Parameter('prefix-1 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "gdb.Parameter('prefix-1 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "gdb.ParameterPrefix('prefix-2', gdb.COMMAND_NONE," "" \ + " \"\"\"This is prefix-2 help string.\"\"\")" "" \ + "gdb.Parameter('prefix-2 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "gdb.ParameterPrefix('prefix-3', gdb.COMMAND_NONE," "" \ + " \"\"\"This is prefix-3 help string." "" \ + " " "" \ + " This help text spans multiple lines.\"\"\")" "" \ + "gdb.Parameter('prefix-3 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "end" + + foreach mode { "set" "show" } { + gdb_test "help $mode prefix-1" \ + [make_help_re $mode "prefix-1" \ + "This command is not documented."] + + gdb_test "help $mode prefix-2" \ + [make_help_re $mode "prefix-2" \ + "This is prefix-2 help string."] + + gdb_test "help $mode prefix-3" \ + [make_help_re $mode "prefix-3" \ + [multi_line \ + "This is prefix-3 help string." \ + "" \ + "This help text spans multiple lines."]] + + foreach prefix { prefix-1 prefix-2 prefix-3 } { + gdb_test "$mode $prefix xxx" \ + "^Undefined $mode $prefix command: \"xxx\"\\. Try \"help $mode $prefix\"\\." + } + } +} + +# Create a sub-class of gdb.ParameterPrefix, but don't do anything +# particularly interesting. Again test the with and without +# documentation string cases. +proc_with_prefix test_simple_sub_class {} { + gdb_test_multiline "some basic ParameterPrefix usage" \ + "python" "" \ + "class BasicParamPrefix(gdb.ParameterPrefix):" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + "BasicParamPrefix('prefix-4')" "" \ + "gdb.Parameter('prefix-4 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "class BasicParamPrefixWithSingleLineDoc(gdb.ParameterPrefix):" "" \ + " \"\"\"This is a single line doc string.\"\"\"" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + "BasicParamPrefixWithSingleLineDoc('prefix-5')" "" \ + "gdb.Parameter('prefix-5 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "class BasicParamPrefixWithMultiLineDoc(gdb.ParameterPrefix):" "" \ + " \"\"\"This is a multi line doc string." "" \ + " " "" \ + " The rest of the doc string is here.\"\"\"" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + "BasicParamPrefixWithMultiLineDoc('prefix-6')" "" \ + "gdb.Parameter('prefix-6 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "class BasicParamPrefixWithDocParameter(gdb.ParameterPrefix):" "" \ + " \"\"\"This is an unsused doc string.\"\"\"" "" \ + " def __init__(self, name, doc):" "" \ + " super().__init__(name, gdb.COMMAND_NONE, doc)" "" \ + "BasicParamPrefixWithDocParameter('prefix-7'," "" \ + " \"\"\"The doc string text is here.\"\"\")" "" \ + "gdb.Parameter('prefix-7 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "end" + + foreach mode { "set" "show" } { + gdb_test "help $mode prefix-4" \ + [make_help_re $mode "prefix-4" \ + "This command is not documented."] + + gdb_test "help $mode prefix-5" \ + [make_help_re $mode "prefix-5" \ + "This is a single line doc string."] + + gdb_test "help $mode prefix-6" \ + [make_help_re $mode "prefix-6" \ + [multi_line \ + "This is a multi line doc string." \ + "" \ + "The rest of the doc string is here."]] + + gdb_test "help $mode prefix-7" \ + [make_help_re $mode "prefix-7" \ + "The doc string text is here."] + + foreach prefix { prefix-4 prefix-5 prefix-6 prefix-7 } { + gdb_test "$mode $prefix xxx" \ + "^Undefined $mode $prefix command: \"xxx\"\\. Try \"help $mode $prefix\"\\." + } + } +} + +# Create a sub-class of gdb.ParameterPrefix, and make use of +# 'invoke_set' and 'invoke_show'. Test that the invoke method is +# executed when expected, and that, by default, these invoke methods +# repeat when the user issues an empty command. +proc_with_prefix test_prefix_with_invoke {} { + gdb_test_multiline "ParameterPrefix with invoke_set" \ + "python" "" \ + "class PrefixWithInvokeSet(gdb.ParameterPrefix):" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + " def invoke_set(self, args, from_tty):" "" \ + " print(f\"invoke_set (a): \\\"{args}\\\" {from_tty}\")" "" \ + "PrefixWithInvokeSet('prefix-8')" "" \ + "gdb.Parameter('prefix-8 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "class PrefixWithInvokeShow(gdb.ParameterPrefix):" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + " def invoke_show(self, args, from_tty):" "" \ + " print(f\"invoke_show (b): \\\"{args}\\\" {from_tty}\")" "" \ + "PrefixWithInvokeShow('prefix-9')" "" \ + "gdb.Parameter('prefix-9 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "class PrefixWithBothInvoke(gdb.ParameterPrefix):" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + " def invoke_set(self, args, from_tty):" "" \ + " print(f\"invoke_set (c): \\\"{args}\\\" {from_tty}\")" "" \ + " def invoke_show(self, args, from_tty):" "" \ + " print(f\"invoke_show (d): \\\"{args}\\\" {from_tty}\")" "" \ + "PrefixWithBothInvoke('prefix-10')" "" \ + "gdb.Parameter('prefix-10 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "end" + + gdb_test "set prefix-8 xxx yyy" \ + "^invoke_set \\(a\\): \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^\r\ninvoke_set \\(a\\): \"xxx yyy\" True" \ + "repeat set prefix-8 xxx yyy" + + gdb_test "show prefix-8 xxx yyy" \ + "^Undefined show prefix-8 command: \"xxx yyy\"\\. Try \"help show prefix-8\"\\." + + gdb_test "set prefix-9 xxx yyy" \ + "^Undefined set prefix-9 command: \"xxx yyy\"\\. Try \"help set prefix-9\"\\." + + gdb_test "show prefix-9 xxx yyy" \ + "^invoke_show \\(b\\): \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^\r\ninvoke_show \\(b\\): \"xxx yyy\" True" \ + "repeat show prefix-9 xxx yyy" + + gdb_test "set prefix-10 xxx yyy" \ + "^invoke_set \\(c\\): \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^\r\ninvoke_set \\(c\\): \"xxx yyy\" True" \ + "repeat set prefix-10 xxx yyy" + + gdb_test "show prefix-10 xxx yyy" \ + "^invoke_show \\(d\\): \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^\r\ninvoke_show \\(d\\): \"xxx yyy\" True" \ + "repeat show prefix-10 xxx yyy" +} + +# Create ParameterPrefix sub-classes that make use of the +# dont_repeat() method. Check that the relevant set/show invoke +# callback doesn't repeat when an empty command is used. +proc_with_prefix test_dont_repeat {} { + gdb_test_multiline "ParameterPrefix with invoke_set and dont_repeat" \ + "python" "" \ + "class PrefixWithInvokeAndDoNotRepeatSet(gdb.ParameterPrefix):" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + " def invoke_set(self, args, from_tty):" "" \ + " self.dont_repeat()" "" \ + " print(f\"invoke_set: \\\"{args}\\\" {from_tty}\")" "" \ + " def invoke_show(self, args, from_tty):" "" \ + " print(f\"invoke_show: \\\"{args}\\\" {from_tty}\")" "" \ + "PrefixWithInvokeAndDoNotRepeatSet('prefix-11')" "" \ + "gdb.Parameter('prefix-11 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "class PrefixWithInvokeAndDoNotRepeatShow(gdb.ParameterPrefix):" "" \ + " def __init__(self, name):" "" \ + " super().__init__(name, gdb.COMMAND_NONE)" "" \ + " def invoke_set(self, args, from_tty):" "" \ + " print(f\"invoke_set: \\\"{args}\\\" {from_tty}\")" "" \ + " def invoke_show(self, args, from_tty):" "" \ + " self.dont_repeat()" "" \ + " print(f\"invoke_show: \\\"{args}\\\" {from_tty}\")" "" \ + "PrefixWithInvokeAndDoNotRepeatShow('prefix-12')" "" \ + "gdb.Parameter('prefix-12 param-1', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + "end" + + gdb_test "set prefix-11 xxx yyy" \ + "^invoke_set: \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^" \ + "repeat set prefix-11 xxx yyy" + + gdb_test "show prefix-11 xxx yyy" \ + "^invoke_show: \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "invoke_show: \"xxx yyy\" True" \ + "repeat show prefix-11 xxx yyy" + + gdb_test "set prefix-12 xxx yyy" \ + "^invoke_set: \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^\r\ninvoke_set: \"xxx yyy\" True" \ + "repeat set prefix-12 xxx yyy" + + gdb_test "show prefix-12 xxx yyy" \ + "^invoke_show: \"xxx yyy\" True" + + send_gdb "\n" + gdb_test "" "^" \ + "repeat show prefix-12 xxx yyy" +} + +test_basic_usage +test_simple_sub_class +test_prefix_with_invoke +test_dont_repeat diff --git a/gdb/testsuite/gdb.python/py-parameter.exp b/gdb/testsuite/gdb.python/py-parameter.exp index c15bef1..2ca56dc 100644 --- a/gdb/testsuite/gdb.python/py-parameter.exp +++ b/gdb/testsuite/gdb.python/py-parameter.exp @@ -346,6 +346,91 @@ proc_with_prefix test_really_undocumented_parameter { } { "test general help" } +# Test a parameter in which the __doc__ string is empty or None. +proc_with_prefix test_empty_doc_parameter {} { + gdb_test_multiline "empty __doc__ parameter" \ + "python" "" \ + "class EmptyDocParam(gdb.Parameter):" "" \ + " __doc__ = \"\"" "" \ + " def __init__(self, name):" "" \ + " super ().__init__(name, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + " self.value = True" "" \ + "test_empty_doc_param = EmptyDocParam('print test-empty-doc-param')" ""\ + "end" + + # Setting the __doc__ string to empty means GDB will completely + # elide it from the output. + gdb_test "help set print test-empty-doc-param" \ + "^Set the current value of 'print test-empty-doc-param'\\." + + gdb_test_multiline "None __doc__ parameter" \ + "python" "" \ + "class NoneDocParam(gdb.Parameter):" "" \ + " __doc__ = None" "" \ + " def __init__(self, name):" "" \ + " super ().__init__(name, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + " self.value = True" "" \ + "test_none_doc_param = NoneDocParam('print test-none-doc-param')" ""\ + "end" + + # Setting the __doc__ string to None, or anything else that isn't + # a string, causes GDB to use a default string instead. + gdb_test "help set print test-none-doc-param" \ + [multi_line \ + "^Set the current value of 'print test-none-doc-param'\\." \ + "This command is not documented\\."] +} + +# Test a parameter in which the set_doc/show_doc strings are either +# empty, or None. +proc_with_prefix test_empty_set_show_doc_parameter {} { + gdb_test_multiline "empty set/show doc parameter" \ + "python" "" \ + "class EmptySetShowParam(gdb.Parameter):" "" \ + " set_doc = \"\"" "" \ + " show_doc = \"\"" "" \ + " def __init__(self, name):" "" \ + " super ().__init__(name, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + " self.value = True" "" \ + "test_empty_set_show_param = EmptySetShowParam('print test-empty-set-show-param')" ""\ + "end" + + # Setting the set_doc/show_doc string to empty means GDB will use + # a suitable default string. + gdb_test "help set print test-empty-set-show-param" \ + [multi_line \ + "^Set the current value of 'print test-empty-set-show-param'\\." \ + "This command is not documented\\."] + + gdb_test "help show print test-empty-set-show-param" \ + [multi_line \ + "^Show the current value of 'print test-empty-set-show-param'\\." \ + "This command is not documented\\."] + + gdb_test_multiline "None set/show doc parameter" \ + "python" "" \ + "class NoneSetShowParam(gdb.Parameter):" "" \ + " set_doc = None" "" \ + " show_doc = None" "" \ + " def __init__(self, name):" "" \ + " super ().__init__(name, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + " self.value = True" "" \ + "test_none_set_show_param = NoneSetShowParam('print test-none-set-show-param')" ""\ + "end" + + # Setting the set_doc/show_doc string to None (or any non-string + # value) means GDB will use a suitable default string. + gdb_test "help set print test-none-set-show-param" \ + [multi_line \ + "^Set the current value of 'print test-none-set-show-param'\\." \ + "This command is not documented\\."] + + gdb_test "help show print test-none-set-show-param" \ + [multi_line \ + "^Show the current value of 'print test-none-set-show-param'\\." \ + "This command is not documented\\."] +} + # Test deprecated API. Do not use in your own implementations. proc_with_prefix test_deprecated_api_parameter { } { clean_restart @@ -669,6 +754,43 @@ proc_with_prefix test_ambiguous_parameter {} { "Parameter .* is ambiguous.*Error occurred in Python.*" gdb_test "python print(gdb.parameter('test-ambiguous-value-1a'))" \ "Could not find parameter.*Error occurred in Python.*" + + # Create command prefixs 'set foo1' and 'show foo1'. + gdb_test_no_output "python gdb.Command('set foo1', gdb.COMMAND_NONE, prefix=True)" + gdb_test_no_output "python gdb.Command('show foo1', gdb.COMMAND_NONE, prefix=True)" + + # Create a parameter under 'foo1', but use a truncated prefix. At + # this point though, the prefix is not ambiguous. + gdb_test_no_output "python gdb.Parameter('foo bar', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" + gdb_test "python print(gdb.parameter('foo1 bar'))" "False" + + # Create another prefix command, similar in name to the first. + gdb_test_no_output "python gdb.Command('set foo2', gdb.COMMAND_NONE, prefix=True)" + gdb_test_no_output "python gdb.Command('show foo2', gdb.COMMAND_NONE, prefix=True)" + + # An attempt to create a parameter using an ambiguous prefix will give an error. + gdb_test "python gdb.Parameter('foo baz', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" \ + [multi_line \ + "Python Exception <class 'RuntimeError'>: Could not find command prefix foo\\." \ + "Error occurred in Python: Could not find command prefix foo\\."] +} + +# Check that creating a gdb.Parameter with an unknown command prefix results in an error. +proc_with_prefix test_unknown_prefix {} { + gdb_test_multiline "create parameter" \ + "python" "" \ + "class UnknownPrefixParam(gdb.Parameter):" "" \ + " def __init__ (self, name):" "" \ + " super().__init__ (name, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \ + " self.value = True" "" \ + "end" + + foreach prefix { "unknown-prefix" "style unknown-prefix" "style disassembler unknown-prefix"} { + gdb_test "python UnknownPrefixParam('$prefix new-param')" \ + [multi_line \ + "Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \ + "Error occurred in Python: Could not find command prefix $prefix\\."] + } } test_directories @@ -679,11 +801,14 @@ test_color_parameter test_file_parameter test_undocumented_parameter test_really_undocumented_parameter +test_empty_doc_parameter +test_empty_set_show_doc_parameter test_deprecated_api_parameter test_gdb_parameter test_integer_parameter test_throwing_parameter test_language test_ambiguous_parameter +test_unknown_prefix rename py_param_test_maybe_no_output "" diff --git a/gdb/testsuite/gdb.threads/current-lwp-dead.exp b/gdb/testsuite/gdb.threads/current-lwp-dead.exp index 7aa7ab9..c8364df 100644 --- a/gdb/testsuite/gdb.threads/current-lwp-dead.exp +++ b/gdb/testsuite/gdb.threads/current-lwp-dead.exp @@ -47,6 +47,6 @@ gdb_breakpoint $line gdb_continue_to_breakpoint "fn_return" ".*at-fn_return.*" # Confirm thread 2 is really gone. -gdb_test "info threads 2" "No threads match '2'\\." +gdb_test "info threads 2" "No threads matched\\." gdb_continue_to_end "" continue 1 diff --git a/gdb/testsuite/gdb.threads/info-threads-options.c b/gdb/testsuite/gdb.threads/info-threads-options.c new file mode 100644 index 0000000..2c4cd85 --- /dev/null +++ b/gdb/testsuite/gdb.threads/info-threads-options.c @@ -0,0 +1,77 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2022-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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <pthread.h> + +#define NUM 4 + +static pthread_barrier_t threads_started_barrier; + +static void +stop_here () +{ +} + +static void +spin () +{ + while (1) + usleep (1); +} + +static void * +work (void *arg) +{ + int id = *(int *) arg; + + pthread_barrier_wait (&threads_started_barrier); + + if (id % 2 == 0) + stop_here (); + else + spin (); + + pthread_exit (NULL); +} + +int +main () +{ + /* Ensure we stop if GDB crashes and DejaGNU fails to kill us. */ + alarm (10); + + pthread_t threads[NUM]; + int ids[NUM]; + + pthread_barrier_init (&threads_started_barrier, NULL, NUM + 1); + + for (int i = 0; i < NUM; i++) + { + ids[i] = i; + pthread_create (&threads[i], NULL, work, &ids[i]); + } + + /* Wait until all threads are seen running. */ + pthread_barrier_wait (&threads_started_barrier); + + stop_here (); + + return 0; +} diff --git a/gdb/testsuite/gdb.threads/info-threads-options.exp b/gdb/testsuite/gdb.threads/info-threads-options.exp new file mode 100644 index 0000000..38e4e67 --- /dev/null +++ b/gdb/testsuite/gdb.threads/info-threads-options.exp @@ -0,0 +1,131 @@ +# Copyright (C) 2022-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/>. + +# Test the filter flags of the "info threads" command. + +standard_testfile + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ + executable debug] != "" } { + return -1 +} + +save_vars { GDBFLAGS } { + append GDBFLAGS " -ex \"set non-stop on\"" + clean_restart $binfile +} + +if ![runto_main] { + return -1 +} + +gdb_breakpoint "stop_here" +gdb_test_multiple "continue -a&" "" { + -re "Continuing.\r\n$gdb_prompt " { + pass $gdb_test_name + } +} + +set expected_hits 3 +set fill "\[^\r\n\]+" +set num_hits 0 +gdb_test_multiple "" "hit the breakpoint" -lbl { + -re "\r\nThread ${fill} hit Breakpoint ${decimal}," { + incr num_hits + if {$num_hits < $expected_hits} { + exp_continue + } + } +} +gdb_assert {$num_hits == $expected_hits} "expected threads hit the bp" + +# Count the number of running/stopped threads reported +# by the "info threads" command. We also capture thread ids +# for additional tests. +set running_tid "invalid" +set stopped_tid "invalid" + +set eol "(?=\r\n)" + +foreach_with_prefix flag {"" "-running" "-stopped" "-running -stopped"} { + set num_running 0 + set num_stopped 0 + gdb_test_multiple "info threads $flag" "info threads $flag" -lbl { + -re "Id${fill}Target Id${fill}Frame${fill}${eol}" { + exp_continue + } + -re "^\r\n. (${decimal})${fill}Thread ${fill}.running.${eol}" { + incr num_running + set running_tid $expect_out(1,string) + exp_continue + } + -re "^\r\n. (${decimal})${fill}Thread ${fill}stop_here ${fill}${eol}" { + incr num_stopped + set stopped_tid $expect_out(1,string) + exp_continue + } + -re "^\r\n$gdb_prompt $" { + pass $gdb_test_name + } + } + + if {$flag eq "-running"} { + gdb_assert {$num_running == 2} "num running" + gdb_assert {$num_stopped == 0} "num stopped" + } elseif {$flag eq "-stopped"} { + gdb_assert {$num_running == 0} "num running" + gdb_assert {$num_stopped == 3} "num stopped" + } else { + gdb_assert {$num_running == 2} "num running" + gdb_assert {$num_stopped == 3} "num stopped" + } +} + +verbose -log "running_tid=$running_tid, stopped_tid=$stopped_tid" + +# Test specifying thread ids. +gdb_test "info threads -running $stopped_tid" \ + "No threads matched\\." \ + "info thread -running for a stopped thread" +gdb_test "info threads -stopped $running_tid" \ + "No threads matched\\." \ + "info thread -stopped for a running thread" + +set ws "\[ \t\]+" +foreach tid "\"$running_tid\" \"$running_tid $stopped_tid\"" { + gdb_test "info threads -running $tid" \ + [multi_line \ + "${ws}Id${ws}Target Id${ws}Frame${ws}" \ + "${ws}${running_tid}${ws}Thread ${fill}.running."] \ + "info thread -running with [llength $tid] thread ids" +} + +foreach tid "\"$stopped_tid\" \"$stopped_tid $running_tid\"" { + gdb_test "info threads -stopped $tid" \ + [multi_line \ + "${ws}Id${ws}Target Id${ws}Frame${ws}" \ + "${ws}${stopped_tid}${ws}Thread ${fill} stop_here ${fill}"] \ + "info thread -stopped with [llength $tid] thread ids" +} + +gdb_test_multiple "info threads -stopped -running $stopped_tid $running_tid" \ + "filter flags and tids combined" { + -re -wrap ".*stop_here.*running.*" { + pass $gdb_test_name + } + -re -wrap ".*running.*stop_here.*" { + pass $gdb_test_name + } +} diff --git a/gdb/testsuite/gdb.threads/thread-bp-deleted.exp b/gdb/testsuite/gdb.threads/thread-bp-deleted.exp index 2eadd38..8cabb70 100644 --- a/gdb/testsuite/gdb.threads/thread-bp-deleted.exp +++ b/gdb/testsuite/gdb.threads/thread-bp-deleted.exp @@ -147,7 +147,7 @@ if {$is_remote} { exp_continue } - -re "No threads match '99'\\.\r\n$gdb_prompt $" { + -re "No threads matched\\.\r\n$gdb_prompt $" { if {!$saw_thread_exited && !$saw_bp_deleted && $attempt_count > 0} { sleep 1 incr attempt_count -1 diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index 2a5d37c..49ef0d5 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -2301,7 +2301,8 @@ proc default_gdb_exit {} { } } - if { [is_remote host] && [board_info host exists fileid] } { + if { ([is_remote host] && [board_info host exists fileid]) + || [istarget *-*-mingw*] } { send_gdb "quit\n" gdb_expect 10 { -re "y or n" { @@ -6920,7 +6921,7 @@ proc kill_wait_spawned_process { proc_spawn_id } { proc spawn_id_get_pid { spawn_id } { set testpid [exp_pid -i $spawn_id] - if { [istarget "*-*-cygwin*"] } { + if { [istarget "*-*-cygwin*"] || [istarget "*-*-mingw*"] } { # testpid is the Cygwin PID, GDB uses the Windows PID, which # might be different due to the way fork/exec works. set testpid [ exec ps -e | gawk "{ if (\$1 == $testpid) print \$4; }" ] diff --git a/gdb/thread.c b/gdb/thread.c index b659463..0228027 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -1038,6 +1038,37 @@ pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread) && pc < thread->control.step_range_end); } +/* The options for the "info threads" command. */ + +struct info_threads_opts +{ + /* For "-gid". */ + bool show_global_ids = false; + /* For "-running". */ + bool show_running_threads = false; + /* For "-stopped". */ + bool show_stopped_threads = false; +}; + +static const gdb::option::option_def info_threads_option_defs[] = { + + gdb::option::flag_option_def<info_threads_opts> { + "gid", + [] (info_threads_opts *opts) { return &opts->show_global_ids; }, + N_("Show global thread IDs."), + }, + gdb::option::flag_option_def<info_threads_opts> { + "running", + [] (info_threads_opts *opts) { return &opts->show_running_threads; }, + N_("Show running threads only."), + }, + gdb::option::flag_option_def<info_threads_opts> { + "stopped", + [] (info_threads_opts *opts) { return &opts->show_stopped_threads; }, + N_("Show stopped threads only."), + }, +}; + /* Helper for print_thread_info. Returns true if THR should be printed. If REQUESTED_THREADS, a list of GDB ids/ranges, is not NULL, only print THR if its ID is included in the list. GLOBAL_IDS @@ -1046,11 +1077,13 @@ pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread) is a thread from the process PID. Otherwise, threads from all attached PIDs are printed. If both REQUESTED_THREADS is not NULL and PID is not -1, then the thread is printed if it belongs to the - specified process. Otherwise, an error is raised. */ + specified process. Otherwise, an error is raised. OPTS is the + options of the "info threads" command. */ static bool -should_print_thread (const char *requested_threads, int default_inf_num, - int global_ids, int pid, struct thread_info *thr) +should_print_thread (const char *requested_threads, + const info_threads_opts &opts, int default_inf_num, + int global_ids, int pid, thread_info *thr) { if (requested_threads != NULL && *requested_threads != '\0') { @@ -1075,7 +1108,17 @@ should_print_thread (const char *requested_threads, int default_inf_num, if (thr->state == THREAD_EXITED) return false; - return true; + bool is_stopped = (thr->state == THREAD_STOPPED); + if (opts.show_stopped_threads && is_stopped) + return true; + + bool is_running = (thr->state == THREAD_RUNNING); + if (opts.show_running_threads && is_running) + return true; + + /* If the user did not pass a filter flag, show the thread. */ + return (!opts.show_stopped_threads + && !opts.show_running_threads); } /* Return the string to display in "info threads"'s "Target Id" @@ -1104,8 +1147,8 @@ thread_target_id_str (thread_info *tp) static void do_print_thread (ui_out *uiout, const char *requested_threads, - int global_ids, int pid, int show_global_ids, - int default_inf_num, thread_info *tp, + const info_threads_opts &opts, int global_ids, + int pid, int default_inf_num, thread_info *tp, thread_info *current_thread) { int core; @@ -1114,7 +1157,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads, if (current_thread != nullptr) switch_to_thread (current_thread); - if (!should_print_thread (requested_threads, default_inf_num, + if (!should_print_thread (requested_threads, opts, default_inf_num, global_ids, pid, tp)) return; @@ -1130,7 +1173,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads, uiout->field_string ("id-in-tg", print_thread_id (tp)); } - if (show_global_ids || uiout->is_mi_like_p ()) + if (opts.show_global_ids || uiout->is_mi_like_p ()) uiout->field_signed ("id", tp->global_num); /* Switch to the thread (and inferior / target). */ @@ -1191,23 +1234,22 @@ do_print_thread (ui_out *uiout, const char *requested_threads, static void print_thread (ui_out *uiout, const char *requested_threads, - int global_ids, int pid, int show_global_ids, + const info_threads_opts &opts, int global_ids, int pid, int default_inf_num, thread_info *tp, thread_info *current_thread) { do_with_buffered_output (do_print_thread, uiout, requested_threads, - global_ids, pid, show_global_ids, - default_inf_num, tp, current_thread); + opts, global_ids, pid, default_inf_num, tp, + current_thread); } /* Like print_thread_info, but in addition, GLOBAL_IDS indicates whether REQUESTED_THREADS is a list of global or per-inferior - thread ids. */ + thread ids. OPTS is the options of the "info threads" command. */ static void print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, - int global_ids, int pid, - int show_global_ids) + const info_threads_opts &opts, int global_ids, int pid) { int default_inf_num = current_inferior ()->num; @@ -1235,19 +1277,21 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, list_emitter.emplace (uiout, "threads"); else { - int n_threads = 0; + int n_matching_threads = 0; /* The width of the "Target Id" column. Grown below to accommodate the largest entry. */ size_t target_id_col_width = 17; for (thread_info *tp : all_threads ()) { + any_thread = true; + /* In case REQUESTED_THREADS contains $_thread. */ if (current_thread != nullptr) switch_to_thread (current_thread); - if (!should_print_thread (requested_threads, default_inf_num, - global_ids, pid, tp)) + if (!should_print_thread (requested_threads, opts, + default_inf_num, global_ids, pid, tp)) continue; /* Switch inferiors so we're looking at the right @@ -1258,25 +1302,24 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, = std::max (target_id_col_width, thread_target_id_str (tp).size ()); - ++n_threads; + ++n_matching_threads; } - if (n_threads == 0) + if (n_matching_threads == 0) { - if (requested_threads == NULL || *requested_threads == '\0') + if (!any_thread) uiout->message (_("No threads.\n")); else - uiout->message (_("No threads match '%s'.\n"), - requested_threads); + uiout->message (_("No threads matched.\n")); return; } - table_emitter.emplace (uiout, show_global_ids ? 5 : 4, - n_threads, "threads"); + table_emitter.emplace (uiout, opts.show_global_ids ? 5 : 4, + n_matching_threads, "threads"); uiout->table_header (1, ui_left, "current", ""); uiout->table_header (4, ui_left, "id-in-tg", "Id"); - if (show_global_ids) + if (opts.show_global_ids) uiout->table_header (4, ui_left, "id", "GId"); uiout->table_header (target_id_col_width, ui_left, "target-id", "Target Id"); @@ -1287,13 +1330,11 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, for (inferior *inf : all_inferiors ()) for (thread_info *tp : inf->threads ()) { - any_thread = true; - if (tp == current_thread && tp->state == THREAD_EXITED) current_exited = true; - print_thread (uiout, requested_threads, global_ids, pid, - show_global_ids, default_inf_num, tp, current_thread); + print_thread (uiout, requested_threads, opts, global_ids, pid, + default_inf_num, tp, current_thread); } /* This end scope restores the current thread and the frame @@ -1322,27 +1363,10 @@ void print_thread_info (struct ui_out *uiout, const char *requested_threads, int pid) { - print_thread_info_1 (uiout, requested_threads, 1, pid, 0); + info_threads_opts opts; + print_thread_info_1 (uiout, requested_threads, opts, 1, pid); } -/* The options for the "info threads" command. */ - -struct info_threads_opts -{ - /* For "-gid". */ - bool show_global_ids = false; -}; - -static const gdb::option::option_def info_threads_option_defs[] = { - - gdb::option::flag_option_def<info_threads_opts> { - "gid", - [] (info_threads_opts *opts) { return &opts->show_global_ids; }, - N_("Show global thread IDs."), - }, - -}; - /* Create an option_def_group for the "info threads" options, with IT_OPTS as context. */ @@ -1367,7 +1391,7 @@ info_threads_command (const char *arg, int from_tty) gdb::option::process_options (&arg, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, grp); - print_thread_info_1 (current_uiout, arg, 0, -1, it_opts.show_global_ids); + print_thread_info_1 (current_uiout, arg, it_opts, 0, -1); } /* Completer for the "info threads" command. */ diff --git a/gdbsupport/event-loop.cc b/gdbsupport/event-loop.cc index c080490..e7b21e7 100644 --- a/gdbsupport/event-loop.cc +++ b/gdbsupport/event-loop.cc @@ -827,7 +827,8 @@ update_wait_timeout (void) /* Update the timeout for select/ poll. */ #ifdef HAVE_POLL if (use_poll) - gdb_notifier.poll_timeout = timeout.tv_sec * 1000; + gdb_notifier.poll_timeout = (timeout.tv_sec * 1000 + + (timeout.tv_usec + 1000 - 1) / 1000); else #endif /* HAVE_POLL */ { diff --git a/ld/testsuite/ld-elfvers/vers7.c b/ld/testsuite/ld-elfvers/vers7.c index 54316c9..a4fb254 100644 --- a/ld/testsuite/ld-elfvers/vers7.c +++ b/ld/testsuite/ld-elfvers/vers7.c @@ -2,8 +2,8 @@ * Test program that goes with test7.so */ -extern int hide_a(); -extern int show_b(); +extern int hide_a(int e); +extern int show_b(int e); int main() diff --git a/ld/testsuite/ld-plugin/lto-binutils.exp b/ld/testsuite/ld-plugin/lto-binutils.exp new file mode 100644 index 0000000..36c7380 --- /dev/null +++ b/ld/testsuite/ld-plugin/lto-binutils.exp @@ -0,0 +1,339 @@ +# Expect script for binutils tests with LTO +# Copyright (C) 2025 Free Software Foundation, Inc. +# +# This file is part of the GNU Binutils. +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, +# MA 02110-1301, USA. +# + +# Make sure that binutils can correctly handle LTO IR in ELF. + +if { !([istarget *-*-linux*] + || [istarget arm*-*-uclinuxfdpiceabi] + || [istarget *-*-nacl*] + || [istarget *-*-gnu*]) || [istarget *ecoff] } then { + return +} + +# Check to see if the C and C++ compilers work +if { ![check_compiler_available] || [which $CXX_FOR_TARGET] == 0 } { + return +} + +# These tests require plugin and LTO. +if { ![check_plugin_api_available] + || ![check_lto_available] } { + return +} + +set lto_fat "" +set lto_no_fat "" +if { [check_lto_fat_available] } { + set lto_fat "-ffat-lto-objects" + set lto_no_fat "-fno-fat-lto-objects" + set no_lto "-fno-lto" +} + +# List contains test-items: +# 0:program name +# 1:program options +# 2:input file +# 3:output file +# 4:action list (optional) +# +proc run_lto_binutils_test { lto_tests } { + global srcdir + global subdir + global nm + global objcopy + global objdump + global READELF + global strip + global plug_opt + + foreach testitem $lto_tests { + set prog_name [lindex $testitem 0] + set prog_options [lindex $testitem 1] + set input tmpdir/[lindex $testitem 2] + set output tmpdir/[lindex $testitem 3] + set actions [lindex $testitem 4] + set objfiles {} + set is_unresolved 0 + set failed 0 + +# eval set prog \$$prog_name + switch -- $prog_name { + objcopy + { + set prog $objcopy + set prog_output "$output" + } + strip + { + set prog $strip + set prog_output "-o $output" + } + default + { + perror "Unrecognized action $action" + set is_unresolved 1 + break + } + } + + # Don't leave previous output around + if { $output ne "tmpdir/" } { + remote_file host delete $output + } + + append prog_options " $plug_opt" + + set cmd_options "$prog_options $prog_output $input" + set test_name "$prog_name $cmd_options" + + set cmd "$prog $cmd_options" + send_log "$cmd\n" + set got [remote_exec host "$cmd"] + if { [lindex $got 0] != 0 || ![string match "" [lindex $got 1]] } then { + send_log "$got\n" + fail "$test_name" + continue + } + + if { $failed == 0 } { + foreach actionlist $actions { + set action [lindex $actionlist 0] + set progopts [lindex $actionlist 1] + + # There are actions where we run regexp_diff on the + # output, and there are other actions (presumably). + # Handling of the former look the same. + set dump_prog "" + switch -- $action { + objdump + { set dump_prog $objdump } + nm + { set dump_prog $nm } + readelf + { set dump_prog $READELF } + default + { + perror "Unrecognized action $action" + set is_unresolved 1 + break + } + } + + if { $dump_prog != "" } { + set dumpfile [lindex $actionlist 2] + set binary $dump_prog + + # Ensure consistent sorting of symbols + if {[info exists env(LC_ALL)]} { + set old_lc_all $env(LC_ALL) + } + set env(LC_ALL) "C" + set cmd "$binary $progopts $output > tmpdir/dump.out" + send_log "$cmd\n" + catch "exec $cmd" comp_output + if {[info exists old_lc_all]} { + set env(LC_ALL) $old_lc_all + } else { + unset env(LC_ALL) + } + set comp_output [prune_warnings $comp_output] + + if ![string match "" $comp_output] then { + send_log "$comp_output\n" + set failed 1 + break + } + + if { [regexp_diff "tmpdir/dump.out" "$srcdir/$subdir/$dumpfile"] } then { + verbose -log "output is [file_contents "tmpdir/dump.out"]" 2 + set failed 1 + break + } + } + } + } + + if { $failed } { + fail $test_name + } elseif { $is_unresolved } { + unresolved $test_name + } else { + pass $test_name + } + } +} + +run_cc_link_tests [list \ + [list \ + "Build strip-1a.o" \ + "" \ + "-O2 -flto $lto_no_fat" \ + { strip-1a.c } \ + ] \ + [list \ + "Build libstrip-1a.a" \ + "$plug_opt" \ + "-O2 -flto $lto_no_fat" \ + { strip-1a.c } \ + {} \ + "libstrip-1a.a" \ + ] \ + [list \ + "Build strip-1a-fat.o" \ + "" \ + "-O2 -flto $lto_fat" \ + { strip-1a-fat.c } \ + ] \ + [list \ + "Build libstrip-1a-fat.a" \ + "$plug_opt" \ + "-O2 -flto $lto_fat" \ + { strip-1a-fat.c } \ + {} \ + "libstrip-1a-fat.a" \ + ] \ +] + +run_lto_binutils_test [list \ + [list \ + "strip" \ + "--strip-unneeded" \ + "libstrip-1a.a" \ + "libstrip-1a-s.a" \ + ] \ + [list \ + "strip" \ + "--strip-unneeded" \ + "strip-1a.o" \ + "strip-1a-s.o" \ + ] \ + [list \ + "strip" \ + "--strip-unneeded -R .gnu.*lto_* -N __gnu_lto_v1" \ + "libstrip-1a-fat.a" \ + "libstrip-1a-fat-s.a" \ + {{readelf -SW strip-1a-fat.rd}} \ + ] \ + [list \ + "strip" \ + "--strip-unneeded -R .gnu.*lto_* -N __gnu_lto_v1" \ + "strip-1a-fat.o" \ + "strip-1a-fat-s.o" \ + {{readelf -SW strip-1a-fat.rd}} \ + ] \ + [list \ + "strip" \ + "--strip-unneeded -R .gnu.debuglto_*" \ + "libstrip-1a-fat.a" \ + "libstrip-1b-fat-s.a" \ + {{readelf -SW strip-1b-fat.rd}} \ + ] \ + [list \ + "strip" \ + "--strip-unneeded -R .gnu.debuglto_*" \ + "strip-1a-fat.o" \ + "strip-1b-fat-s.o" \ + {{readelf -SW strip-1b-fat.rd}} \ + ] \ +] + +run_cc_link_tests [list \ + [list \ + "Build strip-1a (strip-1a.o)" \ + "" \ + "-O2 -flto $lto_no_fat" \ + { strip-1b.c } \ + {} \ + "libstrip-1a" \ + "C" \ + "tmpdir/strip-1a.o" \ + ] \ + [list \ + "Build strip-1b (strip-1a-s.o)" \ + "" \ + "-O2 -flto $lto_no_fat" \ + { strip-1b.c } \ + {} \ + "libstrip-1b" \ + "C" \ + "tmpdir/strip-1a-s.o" \ + ] \ + [list \ + "Build strip-1c (libstrip-1a.a)" \ + "" \ + "-O2 -flto $lto_no_fat" \ + { strip-1b.c } \ + {} \ + "libstrip-1c" \ + "C" \ + "tmpdir/libstrip-1a.a" \ + ] \ + [list \ + "Build strip-1d (libstrip-1a-s.a)" \ + "" \ + "-O2 -flto $lto_no_fat" \ + { strip-1b.c } \ + {} \ + "libstrip-1d" \ + "C" \ + "tmpdir/libstrip-1a-s.a" \ + ] \ + [list \ + "Build strip-1e (strip-1a-fat-s.o)" \ + "" \ + "-O2 -flto $lto_fat" \ + { strip-1b-fat.c } \ + {} \ + "libstrip-1e" \ + "C" \ + "tmpdir/strip-1a-fat-s.o" \ + ] \ + [list \ + "Build strip-1f (libstrip-1a-fat-s.a)" \ + "" \ + "-O2 -flto $lto_fat" \ + { strip-1b-fat.c } \ + {} \ + "libstrip-1f" \ + "C" \ + "tmpdir/libstrip-1a-fat-s.a" \ + ] \ + [list \ + "Build strip-1g (strip-1b-fat-s.o)" \ + "" \ + "-O2 -flto $lto_fat" \ + { strip-1b-fat.c } \ + {} \ + "libstrip-1g" \ + "C" \ + "tmpdir/strip-1b-fat-s.o" \ + ] \ + [list \ + "Build strip-1h (libstrip-1b-fat-s.a)" \ + "" \ + "-O2 -flto $lto_fat" \ + { strip-1b-fat.c } \ + {} \ + "libstrip-1h" \ + "C" \ + "tmpdir/libstrip-1b-fat-s.a" \ + ] \ +] diff --git a/ld/testsuite/ld-plugin/lto.exp b/ld/testsuite/ld-plugin/lto.exp index 3a56fb5..f0d0954 100644 --- a/ld/testsuite/ld-plugin/lto.exp +++ b/ld/testsuite/ld-plugin/lto.exp @@ -1158,9 +1158,8 @@ remote_exec host "mv" "tmpdir/dump tmpdir/lto-5.o" run_dump_test "lto-10r" remote_exec host "mv" "tmpdir/dump tmpdir/lto-10.o" set testname "nm mixed object" -set lto_plugin [string trim [run_host_cmd "$CC_FOR_TARGET" "-print-prog-name=liblto_plugin.so"]] -if { [ regexp "liblto_plugin.so" $lto_plugin ] } { - set exec_output [run_host_cmd "$NM" "--plugin $lto_plugin tmpdir/lto-10.o"] +if { $plug_opt != "" } { + set exec_output [run_host_cmd "$NM" "$plug_opt tmpdir/lto-10.o"] if { [ regexp "(D|T) main" $exec_output ] } { pass $testname } else { diff --git a/ld/testsuite/ld-plugin/strip-1a-fat.c b/ld/testsuite/ld-plugin/strip-1a-fat.c new file mode 100644 index 0000000..03b2a5c --- /dev/null +++ b/ld/testsuite/ld-plugin/strip-1a-fat.c @@ -0,0 +1 @@ +#include "strip-1a.c" diff --git a/ld/testsuite/ld-plugin/strip-1a-fat.rd b/ld/testsuite/ld-plugin/strip-1a-fat.rd new file mode 100644 index 0000000..aefe1c5 --- /dev/null +++ b/ld/testsuite/ld-plugin/strip-1a-fat.rd @@ -0,0 +1,6 @@ +#failif +#... +Section Headers: +#... + \[[ 0-9]+\] \.gnu.lto_.* +#... diff --git a/ld/testsuite/ld-plugin/strip-1a.c b/ld/testsuite/ld-plugin/strip-1a.c new file mode 100644 index 0000000..d84af20 --- /dev/null +++ b/ld/testsuite/ld-plugin/strip-1a.c @@ -0,0 +1,4 @@ +extern void foo2(void); +extern void foo3(void); +void foo1(void) { foo3(); } +int main(void) { foo2(); } diff --git a/ld/testsuite/ld-plugin/strip-1b-fat.c b/ld/testsuite/ld-plugin/strip-1b-fat.c new file mode 100644 index 0000000..1a2e4d2 --- /dev/null +++ b/ld/testsuite/ld-plugin/strip-1b-fat.c @@ -0,0 +1 @@ +#include "strip-1b.c" diff --git a/ld/testsuite/ld-plugin/strip-1b-fat.rd b/ld/testsuite/ld-plugin/strip-1b-fat.rd new file mode 100644 index 0000000..e3a266f --- /dev/null +++ b/ld/testsuite/ld-plugin/strip-1b-fat.rd @@ -0,0 +1,5 @@ +#... +Section Headers: +#... + \[[ 0-9]+\] \.gnu.lto_.* +#pass diff --git a/ld/testsuite/ld-plugin/strip-1b.c b/ld/testsuite/ld-plugin/strip-1b.c new file mode 100644 index 0000000..967872a --- /dev/null +++ b/ld/testsuite/ld-plugin/strip-1b.c @@ -0,0 +1,3 @@ +extern void foo1(void); +void foo2(void) { foo1(); } +void foo3(void) {} diff --git a/ld/testsuite/lib/ld-lib.exp b/ld/testsuite/lib/ld-lib.exp index 9615271..119410b 100644 --- a/ld/testsuite/lib/ld-lib.exp +++ b/ld/testsuite/lib/ld-lib.exp @@ -860,14 +860,15 @@ proc run_ld_link_exec_tests { ldtests args } { } # List contains test-items with 3 items followed by 2 lists, one item and -# one optional item: +# 2 optional items: # 0:name -# 1:ld or ar options +# 1:leading ld or ar options # 2:compile options # 3:filenames of source files # 4:action and options. # 5:name of output file # 6:language (optional) +# 7:trailing ld options (optional), placed after object files # # Actions: # objdump: Apply objdump options on result. Compare with regex (last arg). @@ -899,6 +900,7 @@ proc run_cc_link_tests { ldtests } { set actions [lindex $testitem 4] set binfile tmpdir/[lindex $testitem 5] set lang [lindex $testitem 6] + set trailing_ldflags [lindex $testitem 7] set objfiles {} set is_unresolved 0 set failed 0 @@ -927,6 +929,7 @@ proc run_cc_link_tests { ldtests } { #verbose -log "actions is $actions" #verbose -log "binfile is $binfile" #verbose -log "lang is $lang" + #verbose -log "trailing_ldflags is $trailing_ldflags" foreach actionlist $actions { set action [lindex $actionlist 0] @@ -1006,7 +1009,7 @@ proc run_cc_link_tests { ldtests } { untested $testname continue } - ld_link $cc_cmd $binfile "-L$srcdir/$subdir $ldflags $objfiles" + ld_link $cc_cmd $binfile "-L$srcdir/$subdir $ldflags $objfiles $trailing_ldflags" set ld_output "$exec_output" if { $check_ld(source) == "regexp" } then { diff --git a/opcodes/aarch64-opc.c b/opcodes/aarch64-opc.c index 3a52251..3bb93a9 100644 --- a/opcodes/aarch64-opc.c +++ b/opcodes/aarch64-opc.c @@ -5214,6 +5214,8 @@ const aarch64_sys_ins_reg aarch64_sys_regs_dc[] = { "cvac", CPENS (3, C7, C10, 1), F_HASXT, AARCH64_NO_FEATURES }, { "cgvac", CPENS (3, C7, C10, 3), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (MEMTAG) }, { "cgdvac", CPENS (3, C7, C10, 5), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (MEMTAG) }, + { "cvaoc", CPENS (3, C7, C11, 0), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (V9_5A) }, + { "cgdvaoc", CPENS (3, C7, C11, 7), F_HASXT | F_ARCHEXT, AARCH64_FEATURES (2, V9_5A, MEMTAG) }, { "csw", CPENS (0, C7, C10, 2), F_HASXT, AARCH64_NO_FEATURES }, { "cgsw", CPENS (0, C7, C10, 4), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (MEMTAG) }, { "cgdsw", CPENS (0, C7, C10, 6), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (MEMTAG) }, @@ -5230,6 +5232,8 @@ const aarch64_sys_ins_reg aarch64_sys_regs_dc[] = { "cisw", CPENS (0, C7, C14, 2), F_HASXT, AARCH64_NO_FEATURES }, { "cigsw", CPENS (0, C7, C14, 4), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (MEMTAG) }, { "cigdsw", CPENS (0, C7, C14, 6), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (MEMTAG) }, + { "civaoc", CPENS (3, C7, C15, 0), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (V9_5A) }, + { "cigdvaoc", CPENS (3, C7, C15, 7), F_HASXT | F_ARCHEXT, AARCH64_FEATURES (2, V9_5A, MEMTAG) }, { "cipae", CPENS (4, C7, C14, 0), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (V8_7A) }, { "cigdpae", CPENS (4, C7, C14, 7), F_HASXT | F_ARCHEXT, AARCH64_FEATURE (V8_7A) }, { "cipapa", CPENS (6, C7, C14, 1), F_HASXT, AARCH64_NO_FEATURES }, diff --git a/opcodes/aarch64-sys-regs.def b/opcodes/aarch64-sys-regs.def index c1b07c7..9713ff0 100644 --- a/opcodes/aarch64-sys-regs.def +++ b/opcodes/aarch64-sys-regs.def @@ -437,6 +437,7 @@ SYSREG ("gcscr_el3", CPENC (3,6,2,5,0), F_ARCHEXT, AARCH64_FEATURE (GCS)) SYSREG ("gcr_el1", CPENC (3,0,1,0,6), F_ARCHEXT, AARCH64_FEATURE (MEMTAG)) SYSREG ("gmid_el1", CPENC (3,1,0,0,4), F_REG_READ|F_ARCHEXT, AARCH64_FEATURE (MEMTAG)) + SYSREG ("gpcbw_el3", CPENC (3,6,2,1,5), F_ARCHEXT, AARCH64_FEATURE (V9_5A)) SYSREG ("gpccr_el3", CPENC (3,6,2,1,6), 0, AARCH64_NO_FEATURES) SYSREG ("gptbr_el3", CPENC (3,6,2,1,4), 0, AARCH64_NO_FEATURES) SYSREG ("hacr_el2", CPENC (3,4,1,1,7), 0, AARCH64_NO_FEATURES) diff --git a/opcodes/aarch64-tbl.h b/opcodes/aarch64-tbl.h index 77c3dc8..f43e1e3 100644 --- a/opcodes/aarch64-tbl.h +++ b/opcodes/aarch64-tbl.h @@ -4642,14 +4642,14 @@ const struct aarch64_opcode aarch64_opcode_table[] = PREDRES_INSN ("cpp", 0xd50b73e0, 0xffffffe0, ic_system, OP2 (SYSREG_SR, Rt), QL_SRC_X, F_ALIAS), PREDRES2_INSN ("cosp", 0xd50b73c0, 0xffffffe0, ic_system, OP2 (SYSREG_SR, Rt), QL_SRC_X, F_ALIAS), BRBE_INSN ("brb", 0xd5097280, 0xffffffc0, OP2 (BRBOP, Rt_IN_SYS_ALIASES), QL_IMM_NIL_NIL, F_ALIAS | F_OPD1_OPT | F_DEFAULT (0x1F)), - /* Armv8.4-a flag setting instruction, However this encoding has an encoding clash with the msr - below it. Usually we can resolve this by setting an alias condition on the flags, however that - depends on the disassembly masks to be able to quickly find the alias. The problem is the - cfinv instruction has no arguments, so all bits are set in the mask. Which means it will - potentially alias with too many instructions and so the tree can't be constructed. As a work - around we just place cfinv before msr. This means the order between these two shouldn't be - changed. */ FLAGM_INSN ("cfinv", 0xd500401f, 0xffffffff, ic_system, OP0 (), {}, 0), + /* This msr entry has a lot of aliases, and some of these (such as "hint") + have their own (recursive) aliases. We currently use a flat alias + structure, so to avoid creating an excessively long list of aliases for + the entire msr space we instead handle the top level of disambiguation + outside the alias infrastructure. This requires that all of the top-level + aliases of msr must appear earlier in the opcode table, since normal + (non-alias) disassembly is done on a "first match" basis. */ CORE_INSN ("msr", 0xd5000000, 0xffe00000, ic_system, 0, OP2 (SYSREG, Rt), QL_SRC_X, F_SYS_WRITE), CORE_INSN ("sysl",0xd5280000, 0xfff80000, ic_system, 0, OP5 (Rt, UIMM3_OP1, CRn, CRm, UIMM3_OP2), QL_SYSL, 0), CORE_INSN ("mrs", 0xd5200000, 0xffe00000, ic_system, 0, OP2 (Rt, SYSREG), QL_DST_X, F_SYS_READ), |