diff options
Diffstat (limited to 'gdb/testsuite/gdb.base')
50 files changed, 2036 insertions, 162 deletions
diff --git a/gdb/testsuite/gdb.base/attach-deleted-exec.exp b/gdb/testsuite/gdb.base/attach-deleted-exec.exp index 82a7bb4..45fac0d 100644 --- a/gdb/testsuite/gdb.base/attach-deleted-exec.exp +++ b/gdb/testsuite/gdb.base/attach-deleted-exec.exp @@ -67,5 +67,49 @@ if { [regexp $re_nfs $filename] } { gdb_assert { [string equal $filename /proc/${testpid}/exe] } $test } +# Restart GDB. +clean_restart + +# Setup an empty sysroot. GDB will fail to find the executable within +# the sysroot. Additionally, the presence of a sysroot should prevent +# GDB from trying to load the executable from /proc/PID/exe. +set sysroot [standard_output_file "sysroot"] +gdb_test_no_output "set sysroot $sysroot" \ + "setup sysroot" + +# Attach to the inferior. GDB should complain about failing to find +# the executable. It is the name of the executable that GDB doesn't +# find that we're interesting in here. For native targets GDB should +# be looking for BINFILE, not /proc/PID/exe. +# +# For extended-remote targets things are unfortunately harder. Native +# GDB looks for BINFILE because it understands that GDB will be +# looking in the sysroot. But remote GDB doesn't know if GDB is using +# a sysroot or not. As such, gdbserver will return /proc/PID/exe if +# it knows that the file has been deleted locally. This isn't great +# if GDB then plans to look in a sysroot, but equally, if the remote +# file has been deleted, then the name GDB will return, will have had +# " (deleted" appended, so we're unlikely to get a hit in the sysroot +# either way. +if { [target_info gdb_protocol] == "extended-remote" } { + set filename_re "/proc/$testpid/exe" +} else { + set filename_re "\[^\r\n\]+/${testfile} \\(deleted\\)" +} + +verbose -log "APB: warning: No executable has been specified, and target executable $filename_re could not be found\\. Try using the \"file\" command\\." + +gdb_test "attach $testpid" \ + [multi_line \ + "Attaching to process $decimal" \ + "warning: No executable has been specified, and target executable $filename_re could not be found\\. Try using the \"file\" command\\." \ + ".*"] \ + "attach to inferior" + +# Check GDB hasn't managed to load an executable. +gdb_test "info inferior" \ + "\\*\[^)\]+\\)\\s*" \ + "confirm no executable is loaded." + # Cleanup. kill_wait_spawned_process $test_spawn_id diff --git a/gdb/testsuite/gdb.base/bp-cond-failure.exp b/gdb/testsuite/gdb.base/bp-cond-failure.exp index d645454..4d03e7b 100644 --- a/gdb/testsuite/gdb.base/bp-cond-failure.exp +++ b/gdb/testsuite/gdb.base/bp-cond-failure.exp @@ -75,7 +75,7 @@ proc run_test { cond_eval access_type bpexpr nloc } { "Error in testing condition for breakpoint ${bp_num}.2:" \ "Cannot access memory at address 0x0" \ "" \ - "Breakpoint ${bp_num}.2, foo \\(c=49 ...\\) at \[^\r\n\]+:\[0-9\]+" \ + "(Thread \[^\r\n\]+ hit )?Breakpoint ${bp_num}.2, foo \\(c=49 ...\\) at \[^\r\n\]+:\[0-9\]+" \ "${::decimal}\\s+\[^\r\n\]+ breakpoint here\\. \[^\r\n\]+"] } else { gdb_test "continue" \ @@ -84,7 +84,7 @@ proc run_test { cond_eval access_type bpexpr nloc } { "Error in testing condition for breakpoint ${bp_num}:" \ "Cannot access memory at address 0x0" \ "" \ - "Breakpoint ${bp_num}, bar \\(\\) at \[^\r\n\]+:\[0-9\]+" \ + "(Thread \[^\r\n\]+ hit )?Breakpoint ${bp_num}, bar \\(\\) at \[^\r\n\]+:\[0-9\]+" \ "${::decimal}\\s+\[^\r\n\]+ breakpoint here\\. \[^\r\n\]+"] } } diff --git a/gdb/testsuite/gdb.base/bp-permanent.c b/gdb/testsuite/gdb.base/bp-permanent.c index d586acc..72e5e8a 100644 --- a/gdb/testsuite/gdb.base/bp-permanent.c +++ b/gdb/testsuite/gdb.base/bp-permanent.c @@ -101,7 +101,7 @@ test_signal_no_handler (void) } static void -test_signal_nested_handler () +test_signal_nested_handler (int sig) { test (); } diff --git a/gdb/testsuite/gdb.base/bp-permanent.exp b/gdb/testsuite/gdb.base/bp-permanent.exp index 62ce3f6..c6c6269 100644 --- a/gdb/testsuite/gdb.base/bp-permanent.exp +++ b/gdb/testsuite/gdb.base/bp-permanent.exp @@ -134,7 +134,7 @@ proc test {always_inserted sw_watchpoint} { unsupported "failed to stop at permanent breakpoint" return } - -re "Program received signal SIGTRAP.*$gdb_prompt $" { + -re "received signal SIGTRAP.*$gdb_prompt $" { pass $test } } @@ -174,7 +174,7 @@ proc test {always_inserted sw_watchpoint} { # disabled, it should act as if we hadn't created it in the first # place. IOW, we should get a random signal, and, the breakpoint's # command should not run. - gdb_test "continue" "Program received signal SIGTRAP.*" \ + gdb_test "continue" "received signal SIGTRAP.*" \ "disabled permanent breakpoint doesn't explain stop" gdb_test "info breakpoints" \ diff --git a/gdb/testsuite/gdb.base/break-dbg.cc b/gdb/testsuite/gdb.base/break-dbg.cc new file mode 100644 index 0000000..642ded0 --- /dev/null +++ b/gdb/testsuite/gdb.base/break-dbg.cc @@ -0,0 +1,31 @@ +/* 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/>. */ + +volatile int global_var = 0; + +int +foo () +{ + return global_var; +} + +int +main () +{ + int res = foo (); + return res; +} diff --git a/gdb/testsuite/gdb.base/break-dbg.exp b/gdb/testsuite/gdb.base/break-dbg.exp new file mode 100644 index 0000000..3652b8e --- /dev/null +++ b/gdb/testsuite/gdb.base/break-dbg.exp @@ -0,0 +1,70 @@ +# 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/>. */ + +# Some basic testing of 'set debug breakpoint on'. At one point a bug +# meant that some breakpoints would immediately trigger a segfault if +# GDB tried to run with breakpoint debugging turned on. +# +# Test is compiled as C++ only so 'catch catch/throw/rethrow' have a +# something to do. The original bug would trigger for any 'catch' +# style breakpoint, so C++ isn't really a hard requirement. + +standard_testfile .cc + +require allow_cplus_tests + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug c++}] } { + return +} + +if {![runto_main]} { + return +} + +gdb_test "catch catch" "^Catchpoint $decimal \\(catch\\)" +gdb_test "catch throw" "^Catchpoint $decimal \\(throw\\)" +gdb_test "catch rethrow" "^Catchpoint $decimal \\(rethrow\\)" + +gdb_test "catch exec" "^Catchpoint $decimal \\(exec\\)" +gdb_test "catch fork" "^Catchpoint $decimal \\(fork\\)" +gdb_test "catch vfork" "^Catchpoint $decimal \\(vfork\\)" + +gdb_test "catch load" "^Catchpoint $decimal \\(load\\)" +gdb_test "catch unload" "^Catchpoint $decimal \\(unload\\)" + +gdb_test "catch signal" "^Catchpoint $decimal \\(standard signals\\)" +gdb_test "catch syscall" "^Catchpoint $decimal \\(any syscall\\)" + +gdb_test "watch -l global_var" "\[Ww]atchpoint $decimal: -location global_var" + +gdb_test_no_output "set debug breakpoint on" + +set saw_bp_debug_line false +gdb_test_multiple "step" "" { + -re "^step\r\n" { + exp_continue + } + -re "^\\\[breakpoint\\\] \[^\r\n\]+\r\n" { + set saw_bp_debug_line true + exp_continue + } + -re "^$gdb_prompt $" { + gdb_assert { $saw_bp_debug_line } $gdb_test_name + } + -re "^\[^\r\n\]*\r\n" { + exp_continue + } +} diff --git a/gdb/testsuite/gdb.base/catch-fork-kill.exp b/gdb/testsuite/gdb.base/catch-fork-kill.exp index 0fd853b..224a8df 100644 --- a/gdb/testsuite/gdb.base/catch-fork-kill.exp +++ b/gdb/testsuite/gdb.base/catch-fork-kill.exp @@ -32,6 +32,8 @@ standard_testfile +require allow_fork_tests + # Build two programs -- one for fork, and another for vfork. set testfile_fork "${testfile}-fork" set testfile_vfork "${testfile}-vfork" diff --git a/gdb/testsuite/gdb.base/catch-fork-static.exp b/gdb/testsuite/gdb.base/catch-fork-static.exp index b171a6d..9d50d5d 100644 --- a/gdb/testsuite/gdb.base/catch-fork-static.exp +++ b/gdb/testsuite/gdb.base/catch-fork-static.exp @@ -21,9 +21,7 @@ # ld.so probes before reaching main, and ptrace flags were set then. But a # static executable would just keep running and never catch the fork. -# Until "catch fork" is implemented on other targets... -# -require {is_any_target "*-*-linux*" "*-*-openbsd*"} +require allow_fork_tests # Reusing foll-fork.c since it's a simple forking program. standard_testfile foll-fork.c diff --git a/gdb/testsuite/gdb.base/catch-signal-fork.exp b/gdb/testsuite/gdb.base/catch-signal-fork.exp index dea0cc4..2a33ee1 100644 --- a/gdb/testsuite/gdb.base/catch-signal-fork.exp +++ b/gdb/testsuite/gdb.base/catch-signal-fork.exp @@ -14,6 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. require {!target_info exists gdb,nosignals} +require allow_fork_tests standard_testfile diff --git a/gdb/testsuite/gdb.base/coredump-filter-build-id.exp b/gdb/testsuite/gdb.base/coredump-filter-build-id.exp index 7594cc2..eb5b489 100644 --- a/gdb/testsuite/gdb.base/coredump-filter-build-id.exp +++ b/gdb/testsuite/gdb.base/coredump-filter-build-id.exp @@ -28,7 +28,7 @@ if { ![istarget *-*-linux*] } { untested "$testfile.exp" return -1 } -require is_x86_64_m64_target +require is_x86_64_m64_target gcore_cmd_available if { [prepare_for_testing "failed to prepare" $testfile $srcfile {debug build-id}] } { return -1 diff --git a/gdb/testsuite/gdb.base/corefile-shmem-zero-id-lib.c b/gdb/testsuite/gdb.base/corefile-shmem-zero-id-lib.c new file mode 100644 index 0000000..58fdec6 --- /dev/null +++ b/gdb/testsuite/gdb.base/corefile-shmem-zero-id-lib.c @@ -0,0 +1,522 @@ +/* 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/>. */ + +/* This file contains a library that can be preloaded into GDB on Linux + using the LD_PRELOAD technique. + + The library intercepts calls to OPEN, CLOSE, READ, and PREAD in order to + fake the inode number of a shared memory mapping. + + When GDB creates a core file (e.g. with the 'gcore' command), then + shared memory mappings should be included in the generated core file. + + The 'id' for the shared memory mapping shares the inode slot in the + /proc/PID/smaps file, which is what GDB consults to decide which + mappings should be included in the core file. + + It is possible for a shared memory mapping to have an 'id' of zero. + + At one point there was a bug in GDB where mappings with an inode of zero + would not be included in the generated core file. This meant that most + shared memory mappings would be included in the generated core file, + but, if a shared memory mapping happened to get an 'id' of zero, then, + because this would appear as a zero inode in the smaps file, this shared + memory mapping would be excluded from the generated core file. + + This preload library spots when GDB opens a /proc/PID/smaps file and + immediately copies the contents of this file into an internal buffer. + The buffer is then scanned looking for a shared memory mapping, and, if + a shared memory mapping is found, its 'id' (in the inode position) is + changed to zero. + + Calls to read/pread are intercepted, and attempts to read from the smaps + file are then served from the modified buffer contents. + + The close calls are monitored and, when the smaps file is closed, the + internal buffer is released. + + This works with GDB (currently) because the requirements for access to + the smaps file are pretty simple. GDB opens the file and grabs the + entire contents with a single pread call and a large buffer. There's no + seeking within the file or anything like that. + + The intention is that this library is preloaded into a GDB session which + is then used to start an inferior and generate a core file. GDB will + then see the zero inode for the shared memory mapping and should, if the + bug is correctly fixed, still add the shared memory mapping to the + generated core file. */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <dlfcn.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdarg.h> +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <stdbool.h> +#include <assert.h> + +/* Logging. */ + +static void +log_msg (const char *fmt, ...) +{ +#ifdef LOGGING + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +#endif /* LOGGING */ +} + +/* Error handling, message and exit. */ + +static void +error (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + + exit (EXIT_FAILURE); +} + +/* The type of the open() function. */ +typedef int (*open_func_type)(const char *pathname, int flags, ...); + +/* The type of the close() function. */ +typedef int (*close_func_type)(int fd); + +/* The type of the read() function. */ +typedef ssize_t (*read_func_type)(int fd, void *buf, size_t count); + +/* The type of the pread() function. */ +typedef ssize_t (*pread_func_type) (int fd, void *buf, size_t count, off_t offset); + +/* Structure that holds information about a /proc/PID/smaps file that has + been opened. */ +struct interesting_file +{ + /* The file descriptor for the opened file. */ + int fd; + + /* The read offset within the file. Set to zero when the file is + opened. Any 'read' calls will update this offset. */ + size_t offset; + + /* The size of the contents within the buffer. This is not the total + buffer size (which might be larger). Attempts to read beyond SIZE + indicate an attempt to read beyond the end of the file. */ + size_t size; + + /* The (possibly modified) contents of the file. */ + char *content; +}; + +/* We only track a single interesting file. Currently, for the use case + we imagine, GDB will only ever open one /proc/PID/smaps file at once. */ +struct interesting_file the_file = { -1, 0, 0, NULL }; + +/* Update the contents of the global THE_FILE buffer. It is assumed that + the file contents have already been loaded into THE_FILE's content + buffer. + + Look for any lines that represent a shared memory mapping and modify + the inode field (which holds the shared memory id) to be zero. */ +static void +update_file_content_buffer (void) +{ + assert (the_file.content != NULL); + + char *start = the_file.content; + do + { + /* Every line, even the last one, ends with a newline. */ + char *end = strchrnul (start, '\n'); + assert (end != NULL); + assert (*end != '\0'); + + /* Attribute lines start with an uppercase letter. The lines we want + to modify should start with a lower case hex character, + i.e. [0-9a-f]. Also, every line that we want to consider should + be long enough, but just in case, check the longest possible + filename that we care about. */ + if (isxdigit (*start) && (isdigit (*start) || islower (*start)) + && (end - start) > 23) + { + /* There are two possible filenames that we look for: + /SYSV%08x + /SYSV%08x (deleted) + The END pointer is pointing to the first character after the + filename. + + Setup OFFSET to be the offset from END to the start of the + filename. As we check the filename we set OFFSET to 0 if the + filename doesn't match one of the expected patterns. */ + size_t offset; + if (strncmp ((end - 13), "/SYSV", 5) == 0) + offset = 13; + else if (strncmp ((end - 23), "/SYSV", 5) == 0) + { + if (strncmp ((end - 10), " (deleted)", 10) == 0) + offset = 23; + else + offset = 0; + } + else + offset = 0; + + for (int i = 0; i < 8 && offset != 0; ++i) + { + if (!isdigit (*(end - offset + 5 + i))) + offset = 0; + } + + /* If OFFSET is non-zero then the filename on this line looks + like a shared memory mapping, and OFFSET is the offset from + END to the first character of the filename. */ + if (offset != 0) + { + log_msg ("[LD_PRELOAD] shared memory entry: %.*s\n", + offset, (end - offset)); + + /* Set PTR to the first character before the filename. This + should be a white space character. */ + char *ptr = end - offset - 1; + assert (isspace (*ptr)); + + /* Walk backwards until we find the inode field. */ + while (isspace (*ptr)) + --ptr; + + /* Now replace every character in the inode field, except the + first one, with a space character. */ + while (!isspace (*(ptr - 1))) + { + assert (isdigit (*ptr)); + *ptr = ' '; + --ptr; + } + + /* Replace the first character with '0'. */ + assert (isdigit (*ptr)); + *ptr = '0'; + + /* This print is checked for from GDB. */ + printf ("[LD_PRELOAD] updated a shared memory mapping\n"); + } + } + + /* Update START to point to the next line. The last line of the + file will be empty. */ + assert (*end == '\n'); + start = end; + while (*start == '\n') + ++start; + } + while (*start != '\0'); +} + +/* Return true if PATHNAME has for form "/proc/PID/smaps" (without the + quotes). Otherwise, return false. */ + +static bool +is_smaps_file (const char *pathname) +{ + if (strncmp (pathname, "/proc/", 6) == 0) + { + int idx = 6; + while (isdigit (pathname[idx])) + idx++; + if (idx > 6 && strcmp (&pathname[idx], "/smaps") == 0) + return true; + } + + return false; +} + +/* Return true if PATHNAME should be considered interesting. PATHNAME is + interesting if it has the form /proc/PID/smaps, and there is no + interesting file already opened. */ + +static bool +is_interesting_pathname (const char *pathname) +{ + return the_file.fd == -1 && is_smaps_file (pathname); +} + +/* Read the contents of an interesting file from FD (and open file + descriptor) into the global THE_FILE variable, making the file FD the + current interesting file. There should be no already open interesting + file when this function is called. + + The contents of the file FD are read into a memory buffer and updated so + that any shared memory mappings listed within FD (which will be an smaps + file) will have the id zero. */ + +static void +read_interesting_file_contents (int fd) +{ +#define BLOCK_SIZE 1024 + /* Slurp contents into a local buffer. */ + size_t buffer_size = 1024; + size_t offset = 0; + + assert (the_file.size == 0); + assert (the_file.content == NULL); + assert (the_file.fd == -1); + assert (the_file.offset == 0); + + do + { + the_file.content = (char *) realloc (the_file.content, buffer_size); + if (the_file.content == NULL) + error ("[LD_PRELOAD] Failed allocating memory: %s\n", strerror (errno)); + + ssize_t bytes_read = read (fd, the_file.content + offset, BLOCK_SIZE); + if (bytes_read == -1) + error ("[LD_PRELOAD] Failed reading file: %s\n", strerror (errno)); + + the_file.size += bytes_read; + + if (bytes_read < BLOCK_SIZE) + break; + + offset += BLOCK_SIZE; + buffer_size += BLOCK_SIZE; + } + while (true); + + /* Add a null terminator. This makes the update easier. We know + there will be space because we only break out of the loop above + when the last read returns less than BLOCK_SIZE bytes. This means + we allocated an extra BLOCK_SIZE bytes, but didn't fill them all. + This means there must be at least 1 byte available for the null. */ + the_file.content[the_file.size] = '\0'; + + /* Reset the seek pointer. */ + if (lseek (fd, 0, SEEK_SET) == (off_t) -1) + error ("[LD_PRELOAD] Failed to lseek in file: %s\n", strerror (errno)); + + /* Record the file descriptor, this is used in read, pread, and close + in order to spot when we need to intercept the call. */ + the_file.fd = fd; + + update_file_content_buffer (); +#undef BLOCK_SIZE +} + +/* Intercept calls to 'open'. If this is an attempt to open a + /proc/PID/smaps file then intercept it, load the file contents into a + buffer and update the file contents. For all other open requests, just + forward to the real open function. */ +int +open (const char *pathname, int flags, ...) +{ + /* Pointer to the real open function. */ + static open_func_type real_open = NULL; + + /* Mode is only used if the O_CREAT flag is set in FLAGS. */ + mode_t mode = 0; + + /* Set true if this is a /proc/PID/smaps file. */ + bool is_interesting = is_interesting_pathname (pathname); + + /* Check if O_CREAT is in flags. If it is, get the mode. */ + if (flags & O_CREAT) + { + va_list args; + va_start (args, flags); + mode = va_arg (args, mode_t); + va_end (args); + } + + /* Debug. */ + if (is_interesting) + log_msg ("[LD_PRELOAD] Opening file: %s\n", pathname); + + /* Make sure we have a pointer to the real open() function. */ + if (real_open == NULL) + { + /* Get the address of the real open() function. */ + real_open = (open_func_type) dlsym (RTLD_NEXT, "open"); + if (real_open == NULL) + error ("[LD_PRELOAD] dlsym() error for 'open': %s\n", dlerror ()); + } + + /* Call the original open() function with the provided arguments. */ + int res = -1; + if (flags & O_CREAT) + res = real_open (pathname, flags, mode); + else + res = real_open (pathname, flags); + + if (res != -1 && is_interesting) + read_interesting_file_contents (res); + + return res; +} + +/* Like above, but for open64. */ + +int +open64 (const char *pathname, int flags, ...) +{ + /* Pointer to the real open64 function. */ + static open_func_type real_open64 = NULL; + + /* Mode is only used if the O_CREAT flag is set in FLAGS. */ + mode_t mode = 0; + + /* Set true if this is a /proc/PID/smaps file. */ + bool is_interesting = is_interesting_pathname (pathname); + + /* Check if O_CREAT is in flags. If it is, get the mode. */ + if (flags & O_CREAT) + { + va_list args; + va_start (args, flags); + mode = va_arg (args, mode_t); + va_end (args); + } + + /* Debug. */ + if (is_interesting) + log_msg ("[LD_PRELOAD] Opening file: %s\n", pathname); + + /* Make sure we have a pointer to the real open64() function. */ + if (real_open64 == NULL) + { + /* Get the address of the real open64() function. */ + real_open64 = (open_func_type) dlsym (RTLD_NEXT, "open64"); + if (real_open64 == NULL) + error ("[LD_PRELOAD] dlsym() error for 'open64': %s\n", dlerror ()); + } + + /* Call the original open64() function with the provided arguments. */ + int res = -1; + if (flags & O_CREAT) + res = real_open64 (pathname, flags, mode); + else + res = real_open64 (pathname, flags); + + if (res != -1 && is_interesting) + read_interesting_file_contents (res); + + return res; +} + +/* Intercept the 'close' function. If this is a previously opened + interesting file then clean up. Otherwise, forward to the normal close + function. */ +int +close (int fd) +{ + static close_func_type real_close = NULL; + + if (fd == the_file.fd) + { + the_file.fd = -1; + free (the_file.content); + the_file.content = NULL; + the_file.offset = 0; + the_file.size = 0; + log_msg ("[LD_PRELOAD] Closing file.\n"); + } + + /* Make sure we have a pointer to the real open() function. */ + if (real_close == NULL) + { + /* Get the address of the real open() function. */ + real_close = (close_func_type) dlsym (RTLD_NEXT, "close"); + if (real_close == NULL) + error ("[LD_PRELOAD] dlsym() error for 'close': %s\n", dlerror ()); + } + + return real_close (fd); +} + +/* Intercept 'pread' calls. If this is a pread from a previously opened + interesting file, then read from the in memory buffer. Otherwise, + forward to the real pread function. */ +ssize_t +pread (int fd, void *buf, size_t count, off_t offset) +{ + static pread_func_type real_pread = NULL; + + if (fd == the_file.fd) + { + size_t max; + + if (offset > the_file.size) + max = 0; + else + max = the_file.size - offset; + if (count > max) + count = max; + + memcpy (buf, the_file.content + offset, count); + log_msg ("[LD_PRELOAD] Read from file.\n"); + return count; + } + + if (real_pread == NULL) + { + /* Get the address of the real read() function. */ + real_pread = (pread_func_type) dlsym (RTLD_NEXT, "pread"); + if (real_pread == NULL) + error ("[LD_PRELOAD] dlsym() error for 'pread': %s\n", dlerror ()); + } + + return real_pread (fd, buf, count, offset); +} + +/* Intercept 'read' calls. If this is a read from a previously opened + interesting file, then read from the in memory buffer. Otherwise, + forward to the real read function. */ +ssize_t +read (int fd, void *buf, size_t count) +{ + static read_func_type real_read = NULL; + + if (fd == the_file.fd) + { + ssize_t bytes_read = pread (fd, buf, count, the_file.offset); + if (bytes_read > 0) + the_file.offset += bytes_read; + return bytes_read; + } + + if (real_read == NULL) + { + /* Get the address of the real read() function. */ + real_read = (read_func_type) dlsym (RTLD_NEXT, "read"); + if (real_read == NULL) + error ("[LD_PRELOAD] dlsym() error for 'read': %s\n", dlerror ()); + } + + return real_read (fd, buf, count); +} diff --git a/gdb/testsuite/gdb.base/corefile-shmem-zero-id.c b/gdb/testsuite/gdb.base/corefile-shmem-zero-id.c new file mode 100644 index 0000000..92d2edf --- /dev/null +++ b/gdb/testsuite/gdb.base/corefile-shmem-zero-id.c @@ -0,0 +1,63 @@ +/* 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 <sys/ipc.h> +#include <sys/shm.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <time.h> + +void +breakpt (void) +{ + /* Nothing. */ +} + +int +main (void) +{ + /* Create a shared memory mapping. */ + int sid = shmget (IPC_PRIVATE, 0x1000, IPC_CREAT | IPC_EXCL | 0777); + if (sid == -1) + { + perror ("shmget"); + exit (1); + } + + /* Attach the shared memory mapping. */ + void *addr = shmat (sid, NULL, SHM_RND); + if (addr == (void *) -1L) + { + perror ("shmat"); + exit (1); + } + + breakpt (); + + /* Mark the shared memory mapping as deleted -- once the last user + has finished with it. */ + if (shmctl (sid, IPC_RMID, NULL) != 0) + { + perror ("shmctl"); + exit (1); + } + + return 0; +} diff --git a/gdb/testsuite/gdb.base/corefile-shmem-zero-id.exp b/gdb/testsuite/gdb.base/corefile-shmem-zero-id.exp new file mode 100644 index 0000000..57c665e --- /dev/null +++ b/gdb/testsuite/gdb.base/corefile-shmem-zero-id.exp @@ -0,0 +1,228 @@ +# 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/>. + +# This test script tries to check GDB's ability to create a core file +# (e.g. with 'gcore' command) when there's a shared memory mapping +# with the id zero. +# +# Testing this case is hard. Older kernels don't even seem to give +# out the shared memory id zero. And on new kernels you still cannot +# guarantee to grab the zero id for testing; the id might be in use by +# some other process, or the kernel might just not give out that id +# for some other reason. +# +# To figure out which mappings to include in the core file, GDB reads +# the /proc/PID/smaps file. There is a field in this file which for +# file backed mappings, holds the inode of the file. But for shared +# memory mappings this field holds the shared memory id. The problem +# was that GDB would ignore any entry in /proc/PID/smaps with an inode +# entry of zero, which would catch the shared memory mapping with a +# zero id. +# +# There was an attempt to write a test which spammed out requests for +# shared memory mappings and tried to find the one with id zero, but +# this was still really unreliable. +# +# This test takes a different approach. We compile a library which we +# preload into the GDB process. This library intercepts calls to +# open, close, read, and pread, and watches for an attempt to open the +# /proc/PID/smaps file. +# +# When we see that file being opened, we copy the file contents into a +# memory buffer and modify the buffer so that the inode field for any +# shared memory mappings is set to zero. We then intercept calls to +# read and pread and return results from that in memory buffer. +# +# The test executable itself create a shared memory mapping (which +# might have any id). +# +# GDB, with the pre-load library in place, start the inferior and then +# uses the 'gcore' command to dump a core file. When GDB opens the +# smaps file and reads from it, the preload library ensures that GDB +# sees an inode of zero. +# + +# This test only works on Linux +require isnative +require {!is_remote host} +require {!is_remote target} +require {istarget *-linux*} +require gcore_cmd_available + +standard_testfile .c -lib.c + +set libfile ${testfile}-lib +set libobj [standard_output_file ${libfile}.so] + +# Compile the preload library. We only get away with this as we +# limit this test to running when ISNATIVE is true. +if { [build_executable "build preload lib" $libobj $srcfile2 \ + {debug shlib libs=-ldl}] == -1 } { + return +} + +# Now compile the inferior executable. +if {[build_executable "build executable" $testfile $srcfile] == -1} { + return +} + +# Spawn GDB with LIBOBJ preloaded using LD_PRELOAD. +save_vars { env(LD_PRELOAD) env(ASAN_OPTIONS) } { + if { ![info exists env(LD_PRELOAD) ] + || $env(LD_PRELOAD) == "" } { + set env(LD_PRELOAD) "$libobj" + } else { + append env(LD_PRELOAD) ":$libobj" + } + + # Prevent address sanitizer error: + # ASan runtime does not come first in initial library list; you should + # either link runtime to your application or manually preload it with + # LD_PRELOAD. + append_environment_default ASAN_OPTIONS verify_asan_link_order 0 + + clean_restart $binfile + + # Start GDB with the modified environment, this means that, when + # using remote targets, gdbserver will also use the preload + # library. + if {![runto_main]} { + return + } +} + +gdb_breakpoint breakpt +gdb_continue_to_breakpoint "run to breakpt" + +# Check the /proc/PID/smaps file itself. The call to 'cat' should +# inherit the preload library, so should see the modified file +# contents. Check that the shared memory mapping line has an id of +# zero. This confirms that the preload library is working. If the +# preload library breaks then we'll start seeing non-zero shared +# memory ids, which always worked, so we'd never know that this test +# is broken! +# +# This check ensures the test is working as expected. +set shmem_line_count 0 +set fixup_line_count 0 +set inf_pid [get_inferior_pid] +gdb_test_multiple "shell cat /proc/${inf_pid}/smaps" "check smaps" { + -re "^\\\[LD_PRELOAD\\\] updated a shared memory mapping\r\n" { + incr fixup_line_count + exp_continue + } + -re "^\[^\r\n\]+($decimal)\\s+/SYSV\[0-9\]{8}(?: \\(deleted\\))?\r\n" { + set id $expect_out(1,string) + if { $id == 0 } { + incr shmem_line_count + } + exp_continue + } + -re "^$gdb_prompt $" { + with_test_prefix $gdb_test_name { + gdb_assert { $shmem_line_count == 1 } \ + "single shared memory mapping found" + gdb_assert { $fixup_line_count == 1 } \ + "single fixup line found" + } + } + -re "^\[^\r\n\]+\r\n" { + exp_continue + } +} + +# Now generate a core file. This will use the preload library to read +# the smaps file. The code below is copied from 'proc gdb_gcore_cmd', +# but we don't use that as we also look for a message that is printed +# by the LD_PRELOAD library. This is an extra level of check that the +# preload library is triggering when needed. +set corefile [standard_output_file ${testfile}.core] +set saw_ld_preload_msg false +set saw_saved_msg false +with_timeout_factor 3 { + gdb_test_multiple "gcore $corefile" "save core file" { + -re "^\\\[LD_PRELOAD\\\] updated a shared memory mapping\r\n" { + # GDB actually reads the smaps file multiple times when + # creating a core file, so we'll see multiple of these + # fixup lines. + set saw_ld_preload_msg true + exp_continue + } + -re "^Saved corefile \[^\r\n\]+\r\n" { + set saw_saved_msg true + exp_continue + } + -re "^$gdb_prompt $" { + with_test_prefix $gdb_test_name { + gdb_assert { $saw_saved_msg } \ + "saw 'Saved corefile' message" + + # If we're using a remote target then the message from + # the preload library will go to gdbservers stdout, + # not GDB's, so don't check for it. + if { [gdb_protocol_is_native] } { + gdb_assert { $saw_ld_preload_msg } \ + "saw LD_PRELOAD message from library" + } + } + } + -re "^\[^\r\n\]*\r\n" { + exp_continue + } + } +} + +# Restart GDB. This time we are _not_ using the preload library. We +# no longer need it as we are only analysing the core file now. +clean_restart $binfile + +# Load the core file. +gdb_test "core-file $corefile" \ + "Program terminated with signal SIGTRAP, Trace/breakpoint trap\\..*" \ + "load core file" + +# Look through the mappings. We _should_ see the shared memory +# mapping. We _should_not_ see any of the special '[blah]' style +# mappings, e.g. [vdso], [vstack], [vsyscalls], etc. +set saw_special_mapping false +set saw_shmem_mapping false +gdb_test_multiple "info proc mappings" "" { + -re "\r\nStart Addr\[^\r\n\]+File\\s*\r\n" { + exp_continue + } + + -re "^$hex\\s+$hex\\s+$hex\\s+$hex\\s+\\\[\\S+\\\]\\s*\r\n" { + set saw_special_mapping true + exp_continue + } + + -re "^$hex\\s+$hex\\s+$hex\\s+$hex\\s+/SYSV\[0-9\]+ \\(deleted\\)\\s*\r\n" { + set saw_shmem_mapping true + exp_continue + } + + -re "^$hex\\s+$hex\\s+$hex\\s+$hex\[^\r\n\]*\r\n" { + exp_continue + } + + -re "^$gdb_prompt $" { + with_test_prefix $gdb_test_name { + gdb_assert { $saw_shmem_mapping } \ + "check shared memory mapping exists" + gdb_assert { !$saw_special_mapping } \ + "check no special mappings added" + } + } +} diff --git a/gdb/testsuite/gdb.base/default.exp b/gdb/testsuite/gdb.base/default.exp index 3abd049..01e3cc1 100644 --- a/gdb/testsuite/gdb.base/default.exp +++ b/gdb/testsuite/gdb.base/default.exp @@ -253,10 +253,39 @@ gdb_test "h" "List of classes of commands:(\[^\r\n\]*\[\r\n\])+aliases -- User-d gdb_test "help" "List of classes of commands:(\[^\r\n\]*\[\r\n\])+aliases -- User-defined aliases of other commands(\[^\r\n\]*\[\r\n\])+breakpoints -- Making program stop at certain points(\[^\r\n\]*\[\r\n\])+data -- Examining data(\[^\r\n\]*\[\r\n\])+files -- Specifying and examining files(\[^\r\n\]*\[\r\n\])+obscure -- Obscure features(\[^\r\n\]*\[\r\n\])+running -- Running the program(\[^\r\n\]*\[\r\n\])+stack -- Examining the stack(\[^\r\n\]*\[\r\n\])+status -- Status inquiries(\[^\r\n\]*\[\r\n\])+support -- Support facilities(\[^\r\n\]*\[\r\n\])+user-defined -- User-defined commands(\[^\r\n\]*\[\r\n\])+Type \"help\" followed by a class name for a list of commands in that class.(\[^\r\n\]*\[\r\n\])+Type \"help\" followed by command name for full documentation.(\[^\r\n\]*\[\r\n\])+Command name abbreviations are allowed if unambiguous." #test handle gdb_test "handle" "Argument required .signal to handle.*" -#test info "i" abbreviation -gdb_test "i" "List of \"info\" subcommands:(\[^\r\n\]*\[\r\n\])+Type \"help info\" followed by subcommand name for full documentation.(\[^\r\n\]*\[\r\n\])+Command name abbreviations are allowed if unambiguous." "info \"i\" abbreviation" + +proc test_info_command { command message } { + set saw_info_header 0 + set saw_help_info 0 + set saw_command_abbrev 0 + gdb_test_multiple $command $message -lbl { + -re "\r\nList of \"info\" subcommands:" { + verbose "Info header displayed" + set saw_info_header 1 + exp_continue + } + -re "Type \"help info\" followed by subcommand name for full documentation\\." { + verbose "Help info displayed" + set saw_help_info 1 + exp_continue + } + -re "\r\nCommand name abbreviations are allowed if unambiguous\\." { + verbose "Command name abbreviations displayed" + set saw_command_abbrev 1 + exp_continue + } + -re -wrap "" { + gdb_assert { $saw_info_header && $saw_help_info + && $saw_command_abbrev } $gdb_test_name + } + } +} + +#test info "i" abbreviation +test_info_command "i" "info \"i\" abbreviation" #test info -gdb_test "info" "List of \"info\" subcommands:(\[^\r\n\]*\[\r\n\])+Type \"help info\" followed by subcommand name for full documentation.(\[^\r\n\]*\[\r\n\])+Command name abbreviations are allowed if unambiguous." +test_info_command "info" "info" + #test ignore gdb_test "ignore" "Argument required .a breakpoint number.*" #test info address @@ -379,38 +408,52 @@ gdb_test "info registers" "The program has no registers now." gdb_test "info s" "No stack." "info stack \"s\" abbreviation" #test info stack gdb_test "info stack" "No stack." -#test info set -# Test improved to check three parts: -# 1) confirm -# 2) prompt -# 3) write -# And only succeed if all three are matched. -# This should fix an old problem on native solaris 2.8, -# where this test fails due to this line: + +#test "info set" and "show" commands +# The test needs to match the "prompt: ..." part to fix an old problem on native +# Solaris 2.8, where this test fails due to this line: # prompt: Gdb's prompt is "(gdb) ".^M -set set_confirm_seen 0 -set set_prompt_seen 0 -gdb_test_multiple "info set" "info set" { - -re "confirm: Whether to confirm potentially dangerous operations is o\[a-z\]*.(\[^\r\n\]*\[\r\n\])+history filename: The filename in which to record the command history is (\[^\r\n\]*\[\r\n\])+listsize: Number of source lines gdb will list by default is 10" { - verbose "Confirm dislayed" - set set_confirm_seen 1 - exp_continue - } - -re "Gdb's prompt is \"$gdb_prompt \"" { - verbose "GDB prompt displayed" - set set_prompt_seen 1 - exp_continue - } - -re "Writing into executable.*$gdb_prompt $" { - verbose "write displayed" - if { $set_prompt_seen && $set_confirm_seen } { - pass "info set" - } else { - verbose "prompt $set_prompt_seen confirm $set_confirm_seen" - fail "info set (incomplete output)" +proc test_info_set_show { command } { + set set_confirm_seen 0 + set set_history_filename_seen 0 + set set_listsize_seen 0 + set set_prompt_seen 0 + set set_write_seen 0 + gdb_test_multiple $command $command -lbl { + -re "\r\nconfirm: Whether to confirm potentially dangerous operations is o\[a-z\]+\\." { + verbose "Confirm displayed" + set set_confirm_seen 1 + exp_continue + } + -re "\r\nhistory filename: The filename in which to record the command history is \[^\r\n\]+\\." { + verbose "History filename displayed" + set set_history_filename_seen 1 + exp_continue + } + -re "\r\nlistsize: Number of source lines gdb will list by default is 10\\." { + verbose "Listsize displayed" + set set_listsize_seen 1 + exp_continue + } + -re "\r\nprompt: Gdb's prompt is \"$::gdb_prompt \"" { + verbose "GDB prompt displayed" + set set_prompt_seen 1 + exp_continue + } + -re "write: Writing into executable and core files is o\[a-z\]+\\." { + verbose "Write displayed" + set set_write_seen 1 + exp_continue + } + -re -wrap "" { + gdb_assert { $set_confirm_seen && $set_history_filename_seen + && $set_listsize_seen && $set_prompt_seen + && $set_write_seen } $gdb_test_name } } } +test_info_set_show "info set" + gdb_test "info symbol" "Argument required .address.." #test info source gdb_test "info source" "No current source file..*" @@ -591,12 +634,41 @@ gdb_test "set history" "List of \"set history\" subcommands:(\[^\r\n\]*\[\r\n\]) gdb_test "set language" "Requires an argument. Valid arguments are auto, local, unknown, ada, asm, c, c.., d, fortran, go, minimal, modula-2, objective-c, opencl, pascal, rust." #test set listsize gdb_test "set listsize" "Argument required .integer to set it to.*" + +proc test_set_print { command message } { + set saw_info_header 0 + set saw_help_info 0 + set saw_command_abbrev 0 + gdb_test_multiple $command $message -lbl { + -re "\r\nList of \"set print\" subcommands:" { + verbose "Info header displayed" + set saw_info_header 1 + exp_continue + } + -re "Type \"help set print\" followed by subcommand name for full documentation\\." { + verbose "Help info displayed" + set saw_help_info 1 + exp_continue + } + -re "\r\nCommand name abbreviations are allowed if unambiguous\\." { + verbose "Command name abbreviations displayed" + set saw_command_abbrev 1 + exp_continue + } + -re -wrap "" { + gdb_assert { $saw_info_header && $saw_help_info + && $saw_command_abbrev } $gdb_test_name + } + } +} + #test set print "p" abbreviation -gdb_test "set p" "List of \"set print\" subcommands:(\[^\r\n\]*\[\r\n\])+Type \"help set print\" followed by subcommand name for full documentation.(\[^\r\n\]*\[\r\n\])+Command name abbreviations are allowed if unambiguous." "set print \"p\" abbreviation" +test_set_print "set p" "set print \"p\" abbreviation" #test set print "pr" abbreviation -gdb_test "set pr" "List of \"set print\" subcommands:(\[^\r\n\]*\[\r\n\])+Type \"help set print\" followed by subcommand name for full documentation.(\[^\r\n\]*\[\r\n\])+Command name abbreviations are allowed if unambiguous." "set print \"pr\" abbreviation" +test_set_print "set pr" "set print \"pr\" abbreviation" #test set print -gdb_test "set print" "List of \"set print\" subcommands:(\[^\r\n\]*\[\r\n\])+Type \"help set print\" followed by subcommand name for full documentation.(\[^\r\n\]*\[\r\n\])+Command name abbreviations are allowed if unambiguous." +test_set_print "set print" "set print" + #test set print address gdb_test_no_output "set print address" "set print address" #test set print array @@ -700,7 +772,7 @@ set show_conv_list \ {$_shell_exitsignal = void} \ {$_shell_exitcode = 0} \ {$_active_linker_namespaces = 1} \ - {$_current_linker_namespace = <error: No registers.>}\ + {$_linker_namespace = <error: No registers.>}\ } if [allow_python_tests] { append show_conv_list \ @@ -741,12 +813,47 @@ gdb_test "show history" "history expansion: *History expansion on command input gdb_test "show language" "The current source language is \"auto; currently c\"." #test show listsize gdb_test "show listsize" "Number of source lines gdb will list by default is 10." + +proc test_show_print { command } { + set saw_print_address 0 + set saw_print_frame_args 0 + set saw_print_symbol 0 + set saw_print_vtbl 0 + gdb_test_multiple $command $command -lbl { + -re "\r\nprint address: Printing of addresses is o\[a-z\]+\\." { + verbose "Print address displayed" + set saw_print_address 1 + exp_continue + } + -re "\r\nprint frame-arguments: Printing of non-scalar frame arguments is \[^\r\n\]+\\." { + verbose "Print frame-arguments displayed" + set saw_print_frame_args 1 + exp_continue + } + -re "\r\nprint symbol: Printing of symbols when printing pointers is o\[a-z\]+\\." { + verbose "Print symbol displayed" + set saw_print_symbol 1 + exp_continue + } + -re "\r\nprint vtbl: Printing of C\\+\\+ virtual function tables is o\[a-z\]+\\." { + verbose "Print vtbl displayed" + set saw_print_vtbl 1 + exp_continue + } + -re -wrap "" { + gdb_assert { $saw_print_address && $saw_print_frame_args + && $saw_print_symbol && $saw_print_vtbl } $gdb_test_name + } + } +} + #test show print "p" abbreviation -gdb_test "show p" ".*" +test_show_print "show p" #test show print "pr" abbreviation -gdb_test "show pr" ".*" +test_show_print "show pr" #test show print -gdb_test "show print" ".*" +test_show_print "show print" + #test show paths gdb_test "show paths" "Executable and object file path:.*" #test show print address @@ -792,30 +899,10 @@ gdb_test "show width" "Number of characters gdb thinks are in a line is.*" #test show write # This is only supported on targets which use exec.o. gdb_test "show write" "Writing into executable and core files is o.*" + #test show -set show_confirm_seen 0 -set show_prompt_seen 0 -gdb_test_multiple "show" "show" { - -re "confirm: *Whether to confirm potentially dangerous operations is on.(\[^\r\n\]*\[\r\n\])+history filename: *The filename in which to record the command history is (\[^\r\n\]*\[\r\n\])+history save: *Saving of the history record on exit is on.(\[^\r\n\]*\[\r\n\])+history size: *The size of the command history is(\[^\r\n\]*\[\r\n\])+listsize: *Number of source lines gdb will list by default is 10(\[^\r\n]*\[\r\n\])+print elements: *Limit on string chars or array elements to print is 200." { - verbose "Confirm displayed" - set show_confirm_seen 1 - exp_continue - } - -re "Gdb's prompt is \"$gdb_prompt \"" { - verbose "GDB prompt displayed" - set show_prompt_seen 1 - exp_continue - } - -re "Writing into executable.*$gdb_prompt $" { - verbose "write displayed" - if { $show_prompt_seen && $show_confirm_seen } { - pass "show" - } else { - verbose "prompt $show_prompt_seen confirm $show_confirm_seen" - fail "show (incomplete output)" - } - } -} +test_info_set_show "show" + #history saving should stay disabled gdb_test_no_output "set history save off" "set history save off" #test stepi "si" abbreviation diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp index 8f52199..4d3e8eb 100644 --- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp @@ -112,7 +112,7 @@ proc_with_prefix test_conv_vars {} { gdb_test "print \$_active_linker_namespaces" "1" \ "1 namespace before starting inferior" - gdb_test "print \$_current_linker_namespace" "No registers." \ + gdb_test "print \$_linker_namespace" "No registers." \ "No current namespace before starting inferior" if { ![runto_main] } { @@ -121,7 +121,7 @@ proc_with_prefix test_conv_vars {} { gdb_test "print \$_active_linker_namespaces" "1" \ "Before activating namespaces" - gdb_test "print \$_current_linker_namespace" ".*\"\\\[\\\[0\\\]\\\]\"" \ + gdb_test "print \$_linker_namespace" ".* = 0" \ "Still in the default namespace" gdb_breakpoint "inc" allow-pending @@ -130,10 +130,16 @@ proc_with_prefix test_conv_vars {} { foreach_with_prefix dl {3 2 1} { gdb_continue_to_breakpoint "inc" - gdb_test "print \$_current_linker_namespace" ".*\"\\\[\\\[$dl\\\]\\\]\"" \ + gdb_test "print \$_linker_namespace" ".* = $dl" \ "Verify we're in namespace $dl" } + # Check that we display the namespace of the selected + # frame, not the lowermost one. + gdb_test "up" "\#1.*in main.*" + gdb_test "print \$_linker_namespace" ".* = 0" \ + "print namespace of selected frame" + gdb_continue_to_breakpoint "first dlclose" gdb_test "print \$_active_linker_namespaces" "4" "all SOs loaded" @@ -154,7 +160,7 @@ proc_with_prefix test_conv_vars {} { # breakpoints and pending breakpoints at the same time with # gdb_breakpoint. gdb_test "next" ".*assert.*" "load the first SO" - gdb_breakpoint "inc if \$_streq(\$_current_linker_namespace, \"\[\[2\]\]\")" + gdb_breakpoint "inc if \$_linker_namespace == 2" gdb_continue_to_breakpoint "inc" gdb_continue_to_end "" continue 1 } @@ -163,6 +169,12 @@ proc_with_prefix test_conv_vars {} { proc test_info_linker_namespaces {} { clean_restart $::binfile + # Check that "info linker-namespaces" while the inferior is not running + # doesn't crash. + gdb_test "info linker-namespaces" \ + "Current inferior does not support linker namespaces\\. Use \"info sharedlibrary\" instead\\." \ + "info linker-namespaces before running" + if { ![runto_main] } { return } diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp index cb3fc90..c10941c 100644 --- a/gdb/testsuite/gdb.base/filename-completion.exp +++ b/gdb/testsuite/gdb.base/filename-completion.exp @@ -385,7 +385,7 @@ proc run_quoting_and_escaping_tests { root } { remove-symbol-file \ "target core" "target exec" "target tfile" \ "maint print c-tdesc" "save gdb-index" - "save gdb-index -dwarf-5" } + "save gdb-index -dwarf-5" "shell ls"} if { [allow_compile_tests] } { lappend all_cmds "compile file" } @@ -408,6 +408,31 @@ proc run_quoting_and_escaping_tests { root } { run_quoting_and_escaping_tests_1 $root $cmd } } + + # Some additional testing of shell command. Test 'shell' and '!' + # when there are multiple filenames on the command line. This + # focuses on completion of the final filename. There is also some + # testing of the shell command above, this tests completion within + # the line. + foreach_with_prefix shell_cmd { "shell " "!" "pipe print 1 | " } { + foreach suffix { "aaa/aa bb" "bb2/dir 1/unique file" } { + set dir $root/$suffix + + regsub -all " " "$dir" "\\ " dir_with_backslash + + with_test_prefix "suffix='$suffix'" { + with_test_prefix "with_backslash" { + run_quoting_and_escaping_tests_1 $root "${shell_cmd}ls $dir_with_backslash" + } + with_test_prefix "with double quotes" { + run_quoting_and_escaping_tests_1 $root "${shell_cmd}ls \"$dir\"" + } + with_test_prefix "with single quotes" { + run_quoting_and_escaping_tests_1 $root "${shell_cmd}ls '$dir'" + } + } + } + } } # Helper for run_unquoted_tests. ROOT is the root directory as setup diff --git a/gdb/testsuite/gdb.base/foll-exec-c++.exp b/gdb/testsuite/gdb.base/foll-exec-c++.exp new file mode 100644 index 0000000..d96310b --- /dev/null +++ b/gdb/testsuite/gdb.base/foll-exec-c++.exp @@ -0,0 +1,24 @@ +# 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/>. + +# This file is part of the gdb testsuite + +# See foll-exec.exp.tcl for test details. This file runs the test +# using the C++ compiler. + +require allow_cplus_tests +set lang c++ + +source $srcdir/$subdir/foll-exec.exp.tcl diff --git a/gdb/testsuite/gdb.base/foll-exec-c.exp b/gdb/testsuite/gdb.base/foll-exec-c.exp new file mode 100644 index 0000000..67f62cc --- /dev/null +++ b/gdb/testsuite/gdb.base/foll-exec-c.exp @@ -0,0 +1,23 @@ +# 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/>. + +# This file is part of the gdb testsuite + +# See foll-exec.exp.tcl for test details. This file runs the test +# using the C compiler. + +set lang c + +source $srcdir/$subdir/foll-exec.exp.tcl diff --git a/gdb/testsuite/gdb.base/foll-exec.c b/gdb/testsuite/gdb.base/foll-exec.c index a1c9b70..291f803 100644 --- a/gdb/testsuite/gdb.base/foll-exec.c +++ b/gdb/testsuite/gdb.base/foll-exec.c @@ -19,25 +19,38 @@ #include <stdlib.h> #include <unistd.h> #include <string.h> - +#include <libgen.h> +#include <assert.h> #include <limits.h> int global_i = 100; +#ifndef EXECD_PROG +#define EXECD_PROG "execd-prog" +#endif + int main (int argc, char ** argv) { int local_j = global_i + 1; int local_k = local_j + 1; char prog[PATH_MAX]; - int len; + size_t len = PATH_MAX - 1; + + printf ("foll-exec is about to execlp(%s)...\n", EXECD_PROG); + + prog [len] = '\0'; + + strncpy (prog, dirname (argv[0]), len); + len -= strlen (prog); + assert (len > 0); - printf ("foll-exec is about to execlp(execd-prog)...\n"); + strncat (prog, "/", len); + len -= 1; + assert (len > 0); - strcpy (prog, argv[0]); - len = strlen (prog); - /* Replace "foll-exec" with "execd-prog". */ - memcpy (prog + len - 9, "execd-prog", 10); - prog[len + 1] = 0; + strncat (prog, EXECD_PROG, len); + len -= strlen (EXECD_PROG); + assert (len > 0); /* In the following function call, maximum line length exceed the limit 80. This is intentional and required for clang compiler such that complete @@ -45,7 +58,7 @@ int main (int argc, char ** argv) multi-line. */ execlp (prog, /* tbreak-execlp */ prog, "execlp arg1 from foll-exec", (char *) 0); - printf ("foll-exec is about to execl(execd-prog)...\n"); + printf ("foll-exec is about to execl(%s)...\n", EXECD_PROG); /* In the following function call, maximum line length exceed the limit 80. This is intentional and required for clang compiler such that complete @@ -61,7 +74,7 @@ int main (int argc, char ** argv) argv[0] = prog; - printf ("foll-exec is about to execv(execd-prog)...\n"); + printf ("foll-exec is about to execv(%s)...\n", EXECD_PROG); execv (prog, argv); /* tbreak-execv */ } diff --git a/gdb/testsuite/gdb.base/foll-exec.exp b/gdb/testsuite/gdb.base/foll-exec.exp.tcl index ad4c3516..8f96a55 100644 --- a/gdb/testsuite/gdb.base/foll-exec.exp +++ b/gdb/testsuite/gdb.base/foll-exec.exp.tcl @@ -22,33 +22,55 @@ require {istarget "*-linux*"} standard_testfile foll-exec.c -set testfile2 "execd-prog" -set srcfile2 ${testfile2}.c -set binfile2 [standard_output_file ${testfile2}] +# Compile a program that performs an exec as EXECER_LANG, and a +# program that will be exec'd as EXECEE_LANG. Either language can be +# 'c' or 'c++'. Then run various test associated with 'catch exec' +# using the compiled programs. +proc do_exec_tests { execer_lang execee_lang } { + global srcfile testfile + global gdb_prompt -set compile_options debug + # First compile the program to be exec'd, the execee. + set execee_base_filename "execd-prog" + set srcfile2 ${execee_base_filename}.c + set execee_testfile "execd-prog-${execee_lang}" + set execee_testfile_re [string_to_regexp $execee_testfile] + set execee_binfile [standard_output_file $execee_testfile] -# build the first test case -if { [gdb_compile "${srcdir}/${subdir}/${srcfile2}" "${binfile2}" executable $compile_options] != "" } { - untested "failed to compile" - return -1 -} + set execee_flags debug + if { $execee_lang == "c++" } { + lappend execee_flags "c++" + } -if { [is_remote target] } { - gdb_remote_download target $binfile2 -} + if { [build_executable "failed to build $execee_testfile" $execee_testfile \ + $srcfile2 $execee_flags] == -1 } { + return + } -if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable $compile_options] != "" } { - untested "failed to compile" - return -1 -} + if { [is_remote target] } { + gdb_remote_download target $execee_binfile + } -proc do_exec_tests {} { - global binfile srcfile srcfile2 testfile testfile2 - global gdb_prompt + + # Now compile the program to do the exec, the execer. + set execer_testfile "$testfile-${execee_lang}" + set execer_binfile [standard_output_file $execer_testfile] + + set execer_flags debug + if { $execer_lang == "c++" } { + lappend execer_flags "c++" + } + lappend execer_flags "additional_flags=-DEXECD_PROG=\"${execee_testfile}\"" + + if { [build_executable "failed to build $execer_testfile" $execer_testfile \ + $srcfile $execer_flags] == -1 } { + return + } + + # Now we can start running the tests. + clean_restart $execer_binfile # Start the program running, and stop at main. - # if {![runto_main]} { return } @@ -71,7 +93,7 @@ proc do_exec_tests {} { return } - clean_restart $binfile + clean_restart $execer_binfile # Start the program running, and stop at main. # @@ -120,7 +142,7 @@ proc do_exec_tests {} { set execd_line [gdb_get_line_number "after-exec" $srcfile2] send_gdb "next\n" gdb_expect { - -re ".*xecuting new program: .*${testfile2}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ + -re ".*xecuting new program: .*${execee_testfile_re}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ {pass "step through execlp call"} -re "$gdb_prompt $" {fail "step through execlp call"} timeout {fail "(timeout) step through execlp call"} @@ -160,7 +182,7 @@ proc do_exec_tests {} { # Explicitly kill this program, or a subsequent rerun actually runs # the exec'd program, not the original program... - clean_restart $binfile + clean_restart $execer_binfile # Start the program running, and stop at main. # @@ -193,7 +215,7 @@ proc do_exec_tests {} { send_gdb "continue\n" gdb_expect { - -re ".*xecuting new program:.*${testfile2}.*Catchpoint .*(exec\'d .*${testfile2}).*$gdb_prompt $"\ + -re ".*xecuting new program:.*${execee_testfile_re}.*Catchpoint .*(exec\'d .*${execee_testfile_re}).*$gdb_prompt $"\ {pass "hit catch exec"} -re "$gdb_prompt $" {fail "hit catch exec"} timeout {fail "(timeout) hit catch exec"} @@ -210,7 +232,7 @@ proc do_exec_tests {} { # set msg "info shows catchpoint exec pathname" gdb_test_multiple "info breakpoints" $msg { - -re ".*catchpoint.*keep y.*exec, program \".*${testfile2}\".*$gdb_prompt $" { + -re ".*catchpoint.*keep y.*exec, program \".*${execee_testfile_re}\".*$gdb_prompt $" { pass $msg } } @@ -228,7 +250,7 @@ proc do_exec_tests {} { # Explicitly kill this program, or a subsequent rerun actually runs # the exec'd program, not the original program... - clean_restart $binfile + clean_restart $execer_binfile # Start the program running, and stop at main. # @@ -269,7 +291,7 @@ proc do_exec_tests {} { # send_gdb "next 2\n" gdb_expect { - -re ".*xecuting new program: .*${testfile2}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ + -re ".*xecuting new program: .*${execee_testfile_re}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ {pass "step through execl call"} -re "$gdb_prompt $" {fail "step through execl call"} timeout {fail "(timeout) step through execl call"} @@ -295,7 +317,7 @@ proc do_exec_tests {} { # Explicitly kill this program, or a subsequent rerun actually runs # the exec'd program, not the original program... - clean_restart $binfile + clean_restart $execer_binfile # Start the program running, and stop at main. # @@ -330,7 +352,7 @@ proc do_exec_tests {} { } send_gdb "next\n" gdb_expect { - -re ".*xecuting new program: .*${testfile2}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ + -re ".*xecuting new program: .*${execee_testfile_re}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ {pass "step through execv call"} -re "$gdb_prompt $" {fail "step through execv call"} timeout {fail "(timeout) step through execv call"} @@ -356,7 +378,7 @@ proc do_exec_tests {} { # Explicitly kill this program, or a subsequent rerun actually runs # the exec'd program, not the original program... - clean_restart $binfile + clean_restart $execer_binfile # Start the program running, and stop at main. # @@ -370,13 +392,13 @@ proc do_exec_tests {} { # send_gdb "continue\n" gdb_expect { - -re ".*xecuting new program: .*${testfile2}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ + -re ".*xecuting new program: .*${execee_testfile_re}.*${srcfile2}:${execd_line}.*int local_j = argc;.*$gdb_prompt $"\ {pass "continue through exec"} -re "$gdb_prompt $" {fail "continue through exec"} timeout {fail "(timeout) continue through exec"} } } -clean_restart $binfile - -do_exec_tests +foreach_with_prefix execee_lang { c c++ } { + do_exec_tests $lang $execee_lang +} 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..21ef334 --- /dev/null +++ b/gdb/testsuite/gdb.base/foll-fork-syscall.exp @@ -0,0 +1,143 @@ +# 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-*-*"} +require allow_fork_tests + +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/foll-fork.exp b/gdb/testsuite/gdb.base/foll-fork.exp index 94755c6..12db516 100644 --- a/gdb/testsuite/gdb.base/foll-fork.exp +++ b/gdb/testsuite/gdb.base/foll-fork.exp @@ -17,6 +17,8 @@ # enabled as it will be redirected to the log. require !gdb_debug_enabled +require allow_fork_tests + standard_testfile if {[build_executable "failed to prepare" $testfile $srcfile debug]} { diff --git a/gdb/testsuite/gdb.base/foll-vfork.exp b/gdb/testsuite/gdb.base/foll-vfork.exp index 266df46..6ca7711 100644 --- a/gdb/testsuite/gdb.base/foll-vfork.exp +++ b/gdb/testsuite/gdb.base/foll-vfork.exp @@ -18,12 +18,7 @@ # either execs or exits --- since those events take somewhat different # code paths in GDB, both variants are exercised. -# Until "set follow-fork-mode" and "catch vfork" are implemented on -# other targets... -# -if {![istarget "*-linux*"]} { - continue -} +require allow_fork_tests standard_testfile .c -exit.c vforked-prog.c diff --git a/gdb/testsuite/gdb.base/fork-no-detach-follow-child-dlopen.exp b/gdb/testsuite/gdb.base/fork-no-detach-follow-child-dlopen.exp index 311d7ba..2d47d5d 100644 --- a/gdb/testsuite/gdb.base/fork-no-detach-follow-child-dlopen.exp +++ b/gdb/testsuite/gdb.base/fork-no-detach-follow-child-dlopen.exp @@ -23,6 +23,7 @@ # in the source of the shlib, and "list" should display the source where # the program stopped. +require allow_fork_tests require allow_shlib_tests standard_testfile .c -shlib.c diff --git a/gdb/testsuite/gdb.base/fork-print-inferior-events.exp b/gdb/testsuite/gdb.base/fork-print-inferior-events.exp index 26ed2f9..19ace00 100644 --- a/gdb/testsuite/gdb.base/fork-print-inferior-events.exp +++ b/gdb/testsuite/gdb.base/fork-print-inferior-events.exp @@ -19,6 +19,8 @@ # inferior-events [on,off]', 'set follow-fork-mode [child,parent]' and # 'set detach-on-fork [on,off]' are the correct ones. +require allow_fork_tests + # This test relies on "run", so it cannot run on target remote stubs. require !use_gdb_stub diff --git a/gdb/testsuite/gdb.base/fork-running-state.exp b/gdb/testsuite/gdb.base/fork-running-state.exp index 4b810a6..c446800 100644 --- a/gdb/testsuite/gdb.base/fork-running-state.exp +++ b/gdb/testsuite/gdb.base/fork-running-state.exp @@ -17,6 +17,8 @@ # in non-stop). GDB used to miss updating the parent/child running # states after a fork. +require allow_fork_tests + standard_testfile # The test proper. diff --git a/gdb/testsuite/gdb.base/gcore.exp b/gdb/testsuite/gdb.base/gcore.exp index 5251e3f..0a9f099 100644 --- a/gdb/testsuite/gdb.base/gcore.exp +++ b/gdb/testsuite/gdb.base/gcore.exp @@ -16,6 +16,7 @@ # This file was written by Michael Snyder (msnyder@redhat.com) # This is a test for the gdb command "generate-core-file". +require gcore_cmd_available standard_testfile diff --git a/gdb/testsuite/gdb.base/infcall-failure.exp b/gdb/testsuite/gdb.base/infcall-failure.exp index 66bccd1..e7aeac1 100644 --- a/gdb/testsuite/gdb.base/infcall-failure.exp +++ b/gdb/testsuite/gdb.base/infcall-failure.exp @@ -131,7 +131,13 @@ proc_with_prefix run_cond_hits_segfault_test { async_p non_stop_p } { [multi_line \ "Continuing\\." \ "" \ - "Program received signal SIGSEGV, Segmentation fault\\." \ + [string cat \ + [string_to_regexp \ + "Program received signal SIGSEGV, Segmentation fault."] \ + "("] \ + [string cat \ + [string_to_regexp "Address not mapped to object."] \ + ")?"] \ "${::hex} in func_segfault \\(\\) at \[^\r\n\]+:${::segv_line}" \ "${::decimal}\\s+\[^\r\n\]+Segfault here\[^\r\n\]+" \ "Error in testing condition for breakpoint ${bp_1_num}:" \ @@ -161,7 +167,13 @@ proc_with_prefix run_call_hits_segfault_test { async_p non_stop_p } { gdb_test "call func_segfault ()" \ [multi_line \ "" \ - "Program received signal SIGSEGV, Segmentation fault\\." \ + [string cat \ + [string_to_regexp \ + "Program received signal SIGSEGV, Segmentation fault."] \ + "("] \ + [string cat \ + [string_to_regexp "Address not mapped to object."] \ + ")?"] \ "${::hex} in func_segfault \\(\\) at \[^\r\n\]+:${::segv_line}" \ "${::decimal}\\s+\[^\r\n\]+Segfault here\[^\r\n\]+" \ "The program being debugged was signaled while in a function called from GDB\\." \ diff --git a/gdb/testsuite/gdb.base/inferior-died.exp b/gdb/testsuite/gdb.base/inferior-died.exp index 3992561..764a88d 100644 --- a/gdb/testsuite/gdb.base/inferior-died.exp +++ b/gdb/testsuite/gdb.base/inferior-died.exp @@ -13,10 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# Until "set follow-fork-mode" and "catch fork" are implemented on -# other targets... -# -require {istarget "*-*-linux*"} +require allow_fork_tests require support_displaced_stepping diff --git a/gdb/testsuite/gdb.base/info-shared.exp b/gdb/testsuite/gdb.base/info-shared.exp index 6f1b2d6..e81b28e 100644 --- a/gdb/testsuite/gdb.base/info-shared.exp +++ b/gdb/testsuite/gdb.base/info-shared.exp @@ -79,6 +79,9 @@ proc check_info_shared { test expect1 expect2 } { } } +# Check that "info shared" before running doesn't crash. +check_info_shared "info sharedlibrary before running" 0 0 + # Start the inferior, and check neither of the libraries are loaded at # the start. if ![runto_main] { diff --git a/gdb/testsuite/gdb.base/interrupt-daemon.exp b/gdb/testsuite/gdb.base/interrupt-daemon.exp index 161f854..8b8c61d 100644 --- a/gdb/testsuite/gdb.base/interrupt-daemon.exp +++ b/gdb/testsuite/gdb.base/interrupt-daemon.exp @@ -16,6 +16,8 @@ # Make sure that we can interrupt an inferior that forks and moves to # its own session. +require allow_fork_tests + standard_testfile if {[build_executable "failed to build" $testfile $srcfile {debug}]} { diff --git a/gdb/testsuite/gdb.base/jit-elf-fork.exp b/gdb/testsuite/gdb.base/jit-elf-fork.exp index 81d3350..c1fa428 100644 --- a/gdb/testsuite/gdb.base/jit-elf-fork.exp +++ b/gdb/testsuite/gdb.base/jit-elf-fork.exp @@ -15,6 +15,7 @@ # Test fork handling of an inferior that has JIT-ed objfiles. +require allow_fork_tests require allow_shlib_tests load_lib jit-elf-helpers.exp diff --git a/gdb/testsuite/gdb.base/kill-detach-inferiors-cmd.exp b/gdb/testsuite/gdb.base/kill-detach-inferiors-cmd.exp index ef4bb88..57ec330 100644 --- a/gdb/testsuite/gdb.base/kill-detach-inferiors-cmd.exp +++ b/gdb/testsuite/gdb.base/kill-detach-inferiors-cmd.exp @@ -19,6 +19,7 @@ # commands. require can_spawn_for_attach +require allow_multi_inferior_tests standard_testfile set executable $testfile diff --git a/gdb/testsuite/gdb.base/maint.exp b/gdb/testsuite/gdb.base/maint.exp index 52282bc..7936e53 100644 --- a/gdb/testsuite/gdb.base/maint.exp +++ b/gdb/testsuite/gdb.base/maint.exp @@ -52,6 +52,40 @@ if {[prepare_for_testing "failed to prepare" $testfile \ return -1 } +# Check "maint set per-command" warnings. We do this early so that +# the following tests don't need to expect them, as GDB only warns +# once. + +with_test_prefix "warnings" { + # Potential warning given by "maint set per-command time". + set maybe_per_command_warning \ + "(?:warning: per-thread run time information not available on this platform)?" + + # This one should not issue the "per-command time" warning. + with_test_prefix "per-command space" { + gdb_test_no_output "mt set per-command space on" + gdb_test_no_output "mt set per-command space off" + } + + # These might warn. "per-command on" enables all sub commands, so + # might trigger the "per-command time" warning. + foreach cmd {"per-command" "per-command time"} { + with_test_prefix $cmd { + # GDB only warns once, so restart between commands. + clean_restart $binfile + gdb_test "mt set $cmd on" "$maybe_per_command_warning" + gdb_test "mt set $cmd off" "command started" + gdb_test_no_output "mt set $cmd on" \ + "mt set $cmd on, again" + gdb_test "mt set $cmd off" "command started" \ + "mt set $cmd off, again" + } + } + + # We've already warned once above, so the following tests don't + # need to expect the warning. +} + set readnow_p [readnow] # The commands we test here produce many lines of output; disable "press @@ -205,8 +239,8 @@ set re \ "( Number of \"partial\" symbols read: $decimal" \ ")?( Number of psym tables \\(not yet expanded\\): $decimal" \ ")?( Total memory used for psymbol cache: $decimal" \ - ")?( Number of read CUs: $decimal" \ - " Number of unread CUs: $decimal" \ + ")?( Number of read units: $decimal" \ + " Number of unread units: $decimal" \ ")? Total memory used for objfile obstack: $decimal" \ " Total memory used for BFD obstack: $decimal" \ " Total memory used for string cache: $decimal" \ diff --git a/gdb/testsuite/gdb.base/multi-forks.exp b/gdb/testsuite/gdb.base/multi-forks.exp index 61a240f..3facccb 100644 --- a/gdb/testsuite/gdb.base/multi-forks.exp +++ b/gdb/testsuite/gdb.base/multi-forks.exp @@ -13,11 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# Until "set follow-fork-mode" and "catch fork" are implemented on -# other targets... -# -require {istarget "*-*-linux*"} - +require allow_fork_tests standard_testfile .c 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.base/pie-fork.exp b/gdb/testsuite/gdb.base/pie-fork.exp index 48c01d9..86407b4 100644 --- a/gdb/testsuite/gdb.base/pie-fork.exp +++ b/gdb/testsuite/gdb.base/pie-fork.exp @@ -16,6 +16,8 @@ # Test that we can follow forks properly when the executable is # position-independent. +require allow_fork_tests + standard_testfile set opts [list debug pie] diff --git a/gdb/testsuite/gdb.base/print-symbol-loading.exp b/gdb/testsuite/gdb.base/print-symbol-loading.exp index 15f2c19..c9e2480 100644 --- a/gdb/testsuite/gdb.base/print-symbol-loading.exp +++ b/gdb/testsuite/gdb.base/print-symbol-loading.exp @@ -15,7 +15,7 @@ # Test the "print symbol-loading" option. -require allow_shlib_tests +require allow_shlib_tests gcore_cmd_available standard_testfile print-symbol-loading-main.c set libfile print-symbol-loading-lib diff --git a/gdb/testsuite/gdb.base/run-control-while-bg-execution.exp b/gdb/testsuite/gdb.base/run-control-while-bg-execution.exp index 22913ca..eaee010 100644 --- a/gdb/testsuite/gdb.base/run-control-while-bg-execution.exp +++ b/gdb/testsuite/gdb.base/run-control-while-bg-execution.exp @@ -49,6 +49,11 @@ if {[build_executable "failed to prepare" $testfile $srcfile]} { # - run: use the run command # - attach: start a process outside of GDB and attach it proc do_test { action1 action2 } { + + if {$action1 == "add" && ![allow_multi_inferior_tests]} { + return + } + save_vars { ::GDBFLAGS } { append ::GDBFLAGS " -ex \"maintenance set target-non-stop on\"" clean_restart $::binfile diff --git a/gdb/testsuite/gdb.base/sigall.exp b/gdb/testsuite/gdb.base/sigall.exp index b23e3c5..461a92b 100644 --- a/gdb/testsuite/gdb.base/sigall.exp +++ b/gdb/testsuite/gdb.base/sigall.exp @@ -41,13 +41,14 @@ proc test_one_sig {nextsig} { setup_xfail "i*86-pc-linuxoldld-gnu" "i*86-pc-linuxaout-gnu" } # On Linux SPARC64 systems SIGLOST==SIGPWR and gdb identifies - # the raised signal as PWR. - if {$thissig == "LOST" && [istarget "sparc64-*-linux*"]} { + # the raised signal as PWR. Same for Cygwin. + if {$thissig == "LOST" + && ([istarget "sparc64-*-linux*"] || [istarget "*-*-cygwin*"])} { set esig "PWR" } gdb_test "continue" \ - "Continuing.*Program received signal SIG$esig.*" \ + "Continuing.* received signal SIG$esig.*" \ "get signal $esig" } @@ -177,7 +178,7 @@ gdb_test "handle SIGTERM stop print" \ "SIGTERM\[ \t\]*Yes\[ \t\]*Yes\[ \t\]*Yes.*" gdb_test "b handle_TERM" "Breakpoint \[0-9\]+ .*" gdb_test "continue" \ - "Continuing.*Program received signal SIGTERM.*" \ + "Continuing.* received signal SIGTERM.*" \ "get signal TERM" gdb_test "continue" "Breakpoint.*handle_TERM.*" "send signal TERM" gdb_continue_to_end "continue to sigall exit" diff --git a/gdb/testsuite/gdb.base/solib-search.exp b/gdb/testsuite/gdb.base/solib-search.exp index 2efad18..35b0314 100644 --- a/gdb/testsuite/gdb.base/solib-search.exp +++ b/gdb/testsuite/gdb.base/solib-search.exp @@ -16,7 +16,7 @@ # Test solib-search-path, and in the case of solib-svr4.c whether l_addr_p # is properly reset when the path is changed. -require allow_shlib_tests +require allow_shlib_tests gcore_cmd_available require {!is_remote target} # Build "wrong" and "right" versions of the libraries in separate directories. diff --git a/gdb/testsuite/gdb.base/step-over-exit.exp b/gdb/testsuite/gdb.base/step-over-exit.exp index 2370f97..6dfa7bb 100644 --- a/gdb/testsuite/gdb.base/step-over-exit.exp +++ b/gdb/testsuite/gdb.base/step-over-exit.exp @@ -13,11 +13,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -standard_testfile - # Test a thread is doing step-over a syscall instruction which is exit, # and GDBserver should cleanup its state of step-over properly. +# The testcase relies on follow-fork-mode child. +require allow_fork_tests + +standard_testfile + set syscall_insn "" # Define the syscall instruction for each target. diff --git a/gdb/testsuite/gdb.base/style.exp b/gdb/testsuite/gdb.base/style.exp index c10be3b..503671b 100644 --- a/gdb/testsuite/gdb.base/style.exp +++ b/gdb/testsuite/gdb.base/style.exp @@ -751,6 +751,280 @@ proc test_enable_styling_warning { } { } } +# Run an 'apropos' command. Each line of output starts with a +# non-default style (command style). Ensure that pagination triggers +# during the 'apropos' output such that, at the point pagination kicks +# in, a non-default style is in effect. +# +# Then, at the pagination prompt, quit the command. +# +# Next, run a command which switches to a different style, and then +# back to the current style. +# +# At one point, a bug in the pagination code would leave the +# non-default style from the 'apropos' command recorded as the current +# style, such that the second command would switch back to the earlier +# style. +proc test_pagination_cmd_after_quit_styling {} { + with_ansi_styling_terminal { + clean_restart + } + + # We're going to use 'apropos time'. Check that with a height of + # 12 lines, each line starts with a non-default style, and that we + # do see the pagination prompt. This means that there are more + # than 12 lines for this command. + with_test_prefix "validate apropos output" { + gdb_test_no_output "set height 12" + + set saw_pagination_prompt false + gdb_test_multiple "apropos time" "" { + -re "^apropos time\r\n" { + exp_continue + } + -re "^\033\\\[39;49;1;27m\[^\r\n\]+\r\n" { + exp_continue + } + -re "^$::pagination_prompt$" { + set saw_pagination_prompt true + send_gdb "q\n" + exp_continue + } + -re "^q\r\n" { + exp_continue + } + -re "^Quit\r\n" { + exp_continue + } + -re "^$::gdb_prompt $" { + gdb_assert { $saw_pagination_prompt } $gdb_test_name + } + -re "^\[^\r\n\]+\r\n" { + exp_continue + } + } + } + + # Now reduce the height to 10 and re-run 'apropos time'. Based on + # the previous check, we know that this is going to present the + # pagination prompt when a non-default style is in use. + gdb_test_no_output "set height 10" + + set saw_pagination_prompt false + gdb_test_multiple "apropos time" "" { + -re "$::pagination_prompt" { + set saw_pagination_prompt true + send_gdb "q\n" + exp_continue + } + -re "\r\n$::gdb_prompt $" { + gdb_assert { $saw_pagination_prompt } $gdb_test_name + } + } + + # The help output for this maintenance command switches to a + # different style, and then back to the default. If the + # pagination bug still exists, then this would switch back to the + # non-default style that was in use when pagination kicked in + # above. + gdb_test "maintenance time" \ + "^\"\033\\\[39;49;1;27mmaintenance time\033\\\[m\" takes a numeric argument\\." +} + +# Helper for test_pagination_prompt_styling. Return false if STR, a +# line that appears immediately before a pagination prompt, matches +# the pattern for needing a style reset at the end, but does not have +# the style reset. +# +# In all other cases, return true. So lines that don't match the +# known pattern for neededing a style reset will always return true, +# as will lines that match the pattern, and do have the style reset. +proc previous_line_is_ok { str } { + + # Create a copy of STR with all the '\033' characters removed. + # Then compare string lengths to get a count of the '\033' + # charactes present in STR. + regsub -all "\033" $str {} stripped + set count [expr [string length $str] - [string length $stripped]] + + # If STR switched styles, then it _must_ switch back again, + # otherwise the pagination prompt will be in the wrong style. + # This means that there _must_ be an even number of '\033' + # characters in STR. If there is not then we switched style, but + # failed to switch back. + if { [expr $count % 2] != 0 } { + return false + } + + # For lines that don't match this pattern, we cannot comment on + # where the style reset should occur, so lets just claim the line + # is fine. + if { ![regexp "\\s+$::hex - $::hex is \[^\r\n\]+ in " $str] } { + return true + } + + # This line did match the above pattern, so we know that a style + # reset _must_ occur at the end of the line. If it doesn't then + # this line is not OK. + if { ![regexp "\033\\\[m$" $str] } { + return false + } + + # All tests passed, this line looks OK. + return true +} + +# Test that the pagination prompt is displayed unstyled. This is done +# by looking at the 'info files' output and selecting a width that +# will mean we should get a pagination prompt part way through a +# styled filename. +# +# Then, re-run 'info files' and check that for every pagination +# prompt, the previous line disables styling as expected. +proc test_pagination_prompt_styling {} { + with_ansi_styling_terminal { + clean_restart $::binfile + } + + if {![runto_main]} { + return + } + + # Set height so we actually get a pagination prompt. + gdb_test_no_output "set height 3" + + # Scan the 'info files' output and set DESIRED_WIDTH such that it + # will trigger pagination part-way through a styled filename. + set desired_width 0 + gdb_test_multiple "info files" "find good test width" { + -re "^info files\r\n" { + exp_continue + } + + -re "^$::pagination_prompt$" { + send_gdb "\n" + exp_continue + } + + -re "^$::gdb_prompt $" { + } + + -re "^((\\s+$::hex - $::hex is \[^\r\n\]+ in )\[^\r\n\]+)\r\n" { + if { $desired_width == 0 } { + set full_line $expect_out(1,string) + set inner_line $expect_out(2,string) + set desired_width [expr [string length $inner_line] + ([string length $full_line] - [string length $inner_line]) / 2] + } + exp_continue + } + + -re "^\[^\r\n\]*\r\n" { + exp_continue + } + } + + # Now setup the screen width. + gdb_test_no_output "set width $desired_width" + + # Re-run 'info files'. Check that the content before any + # pagination prompt correctly disables styling. + set saw_bad_line false + set prev_line "" + gdb_test_multiple "info files" "check pagination prompt styling" { + -re "^info files\r\n" { + exp_continue + } + + -re "^$::pagination_prompt$" { + if { ![previous_line_is_ok $prev_line] } { + set saw_bad_line true + } + send_gdb "\n" + exp_continue + } + + -re "^(\[^\r\n\]+)$::pagination_prompt$" { + set prev_line $expect_out(1,string) + if { ![previous_line_is_ok $prev_line] } { + set saw_bad_line true + } + send_gdb "\n" + exp_continue + } + + -re "^$::gdb_prompt $" { + gdb_assert { !$saw_bad_line } $gdb_test_name + } + + -re "^(\[^\r\n\]*)\r\n" { + set prev_line $expect_out(1,string) + exp_continue + } + } +} + +# Test that GDB can correctly restore the current style after a +# pagination prompt. +# +# Set the logging file to a garbage string based on LENGTH (is +# actually 2x LENGTH), then 'show logging file'. Press return at the +# pagination prompt, and check that the reset of the filename is +# styled correctly, and that GDB correctly switches back to the +# default style once the logging file has finished. +proc test_pagination_continue_styling_1 { length } { + with_ansi_styling_terminal { + clean_restart $::binfile + } + + set filename [string repeat "ax" $length] + + gdb_test_no_output "set logging file $filename" + + gdb_test_no_output "set height 3" + gdb_test_no_output "set width 80" + + set saw_bad_styling false + gdb_test_multiple "show logging file" "" { + -re "^show logging file\r\n" { + exp_continue + } + + -re "^The current logfile is \"\033\\\[32;49;22;27m(?:ax)+\033\\\[m" { + exp_continue + } + + -re "^\r\n\033\\\[32;49;22;27m(?:ax)+\033\\\[m(?=--)" { + exp_continue + } + + -re "^\r\n\033\\\[32;49;22;27m(?:ax)+(?=--)" { + set saw_bad_styling true + exp_continue + } + + -re "^\r\n\033\\\[32;49;22;27m(?:ax)+\033\\\[m\"\\.\r\n" { + exp_continue + } + + -re "^$::gdb_prompt $" { + gdb_assert { !$saw_bad_styling } $gdb_test_name + } + + -re "^$::pagination_prompt$$" { + send_gdb "\n" + exp_continue + } + } +} + +# Wrapper around test_pagination_continue_styling_1, calls that +# function with different lengths. +proc test_pagination_continue_styling { } { + foreach_with_prefix length { 80 160 } { + test_pagination_continue_styling_1 $length + } +} + # Check to see if the Python styling of disassembler output is # expected or not, this styling requires Python support in GDB, and # the Python pygments module to be available. @@ -793,3 +1067,6 @@ test_colorsupport_truecolor test_colorsupport_truecolor_only test_enable_styling_warning +test_pagination_cmd_after_quit_styling +test_pagination_prompt_styling +test_pagination_continue_styling diff --git a/gdb/testsuite/gdb.base/user-namespace-attach.c b/gdb/testsuite/gdb.base/user-namespace-attach.c new file mode 100644 index 0000000..684ce1c --- /dev/null +++ b/gdb/testsuite/gdb.base/user-namespace-attach.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 <sys/types.h> +#include <unistd.h> +#include <stdio.h> + +volatile int spin_p = 1; + +int +main () +{ + alarm (60); + + printf ("pid = %lld\n", ((long long) getpid ())); + + while (spin_p) + sleep (1); + + return 0; +} diff --git a/gdb/testsuite/gdb.base/user-namespace-attach.exp b/gdb/testsuite/gdb.base/user-namespace-attach.exp new file mode 100644 index 0000000..741093c --- /dev/null +++ b/gdb/testsuite/gdb.base/user-namespace-attach.exp @@ -0,0 +1,148 @@ +# Copyright 2025 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Check that GDB can attach to a process started using 'unshare'. The +# inferior is started in a separate mnt namespace. + +require can_spawn_for_attach + +standard_testfile + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile] == -1} { + return +} + +# This test relies (at least in some parts) on the sysroot being +# 'target:'. Grab the current sysroot now so we can skip those tests +# if the board file has changed the sysroot. +set sysroot "" +set test "show sysroot" +gdb_test_multiple $test $test { + -re -wrap "The current system root is \"(.*)\"\\." { + set sysroot $expect_out(1,string) + } +} + +# Start a process using 'unshare FLAGS', then attach to the process +# from GDB. Check that the attach worked as expected. +proc run_test { flags } { + + # If FLAGS contains '--mount' then a separate mnt namespace will + # be created, in which case the executable will have been read + # from the 'target:'. Otherwise, the executable will have been + # read from the local filesystem, and there will be no prefix. + # + # Of course, this only applies if the sysroot is 'target:', some + # boards change this, so skip these tests on those boards. + if { [lsearch -exact [split $flags " "] "--mount"] != -1 } { + if { $::sysroot ne "target:" } { + return + } + + set prefix "target:" + } else { + set prefix "" + } + + set unshare_cmd "unshare $flags" + + # Run '/bin/true' using UNSHARE_CMD. If the flags in UNSHARE_CMD + # aren't supported then this will fail, this means we shouldn't + # spawn the command with our test executable and try attaching. + # + # This will also fail if /bin/true isn't present, or doesn't work + # as we expect. But this should be fine for many targets. + set res [remote_exec target "$unshare_cmd /bin/true"] + if { [lindex $res 0] != 0 } { + unsupported "unshare flags not supported" + return + } + + set inferior_spawn_id \ + [spawn_wait_for_attach [list "$unshare_cmd $::binfile"]] + if { $inferior_spawn_id == -1 } { + unsupported "failed to spawn for attach" + return + } + + set inferior_pid [spawn_id_get_pid $inferior_spawn_id] + + clean_restart + + set saw_bad_warning false + gdb_test_multiple "attach $inferior_pid" "attach to inferior" { + -re "^attach $::decimal\r\n" { + exp_continue + } + + -re "^warning: \[^\r\n\]+: could not open as an executable file: \[^\r\n\]+\r\n" { + set saw_bad_warning true + exp_continue + } + + -re "^warning: \[^\r\n\]+: can't open to read symbols: \[^\r\n\]+\r\n" { + set saw_bad_warning true + exp_continue + } + + -re "^warning: Could not load vsyscall page because no executable was specified\r\n" { + # This warning is a secondary consequence of the above bad + # warnings, so don't count this as a bad warnings, ignore + # it instead. + exp_continue + } + + -re "^warning:\\s+$::decimal\\s*\[^\r\n\]+: No such file or directory\r\n" { + # This unrelated warning is seen when GDB stops in libc, + # and the source code for libc is not available. + exp_continue + } + + -re "^warning: \[^\r\n\]+\r\n" { + # If we ignore "other" warnings then, should the above + # warnings strings change we'll start ignoring the bad + # warnings, and the test will appear to pass. + # + # If you are seeing a warning here that really has nothing + # to do with the test failing, then the correct solution + # is to add a new regexp to specifically match _that_ + # warning, and ignore it. + set saw_bad_warning true + exp_continue + } + + -re "^$::gdb_prompt $" { + gdb_assert { !$saw_bad_warning } $gdb_test_name + } + + -re "^\[^\r\n\]*\r\n" { + exp_continue + } + } + + # Ensure GDB could access the executable. + set binfile_re [string_to_regexp $::binfile] + gdb_test "info inferiors" \ + "\r\n\\*\\s+$::decimal\\s+\[^\r\n\]+\\s+${prefix}${binfile_re}\\s*" +} + +set test_flags [list \ + "--mount --map-root-user" \ + "--user" \ + "--user --map-root-user"] + +foreach_with_prefix flags $test_flags { + run_test $flags +} diff --git a/gdb/testsuite/gdb.base/vfork-follow-parent.exp b/gdb/testsuite/gdb.base/vfork-follow-parent.exp index fca2993..8cb785d 100644 --- a/gdb/testsuite/gdb.base/vfork-follow-parent.exp +++ b/gdb/testsuite/gdb.base/vfork-follow-parent.exp @@ -19,6 +19,8 @@ # schedule-multiple on" or "set detach-on-fork on". Test these two resolution # methods. +require allow_fork_tests + standard_testfile .c vforked-prog.c set binfile ${testfile}-exit diff --git a/gdb/testsuite/gdb.base/watch-before-fork.exp b/gdb/testsuite/gdb.base/watch-before-fork.exp index 074cfbd..509561e 100644 --- a/gdb/testsuite/gdb.base/watch-before-fork.exp +++ b/gdb/testsuite/gdb.base/watch-before-fork.exp @@ -20,6 +20,8 @@ # This test uses "awatch". require allow_hw_watchpoint_access_tests +require allow_fork_tests + standard_testfile if {[build_executable "failed to prepare" $testfile $srcfile debug]} { diff --git a/gdb/testsuite/gdb.base/watch-vfork.exp b/gdb/testsuite/gdb.base/watch-vfork.exp index 1bc61bc..503727d 100644 --- a/gdb/testsuite/gdb.base/watch-vfork.exp +++ b/gdb/testsuite/gdb.base/watch-vfork.exp @@ -17,6 +17,8 @@ standard_testfile .c +require allow_fork_tests + if { [build_executable ${testfile}.exp ${testfile} $srcfile {debug}] } { untested "failed to compile" return -1 diff --git a/gdb/testsuite/gdb.base/watchpoint-hw-attach.exp b/gdb/testsuite/gdb.base/watchpoint-hw-attach.exp index b3892f3..fa63edb 100644 --- a/gdb/testsuite/gdb.base/watchpoint-hw-attach.exp +++ b/gdb/testsuite/gdb.base/watchpoint-hw-attach.exp @@ -26,29 +26,18 @@ if {[prepare_for_testing "failed to prepare" $testfile $srcfile debug]} { return -1 } -if ![runto_main] { - return -1 -} +set test_spawn_id [spawn_wait_for_attach $binfile] +set testpid [spawn_id_get_pid $test_spawn_id] -# Run to the point where mypid in the test program has been -# populated. -gdb_breakpoint [gdb_get_line_number "pidacquired"] -gdb_continue_to_breakpoint "pidacquired" - -# Get the PID of the test process. -set testpid [get_integer_valueof "mypid" 0] +gdb_test "attach $testpid" "Attaching to program: .*, process $testpid.*" "attach once" gdb_test "detach" "Detaching from program: .*, process $testpid\r\n\\\[Inferior $decimal \\(process $testpid\\) detached\\\]" -if {$testpid == ""} { - return -1 -} - # A clean restart is needed to force the hardware watchpoint setup # logic to run post attach rather than post inferior launch. clean_restart $binfile -gdb_test "attach $testpid" "Attaching to program: .*, process $testpid.*" "attach" +gdb_test "attach $testpid" "Attaching to program: .*, process $testpid.*" "attach twice" # Ensure the test program is in the top frame so the required # variables are in scope. @@ -62,3 +51,5 @@ gdb_test "watch watched_variable" \ gdb_test "continue" \ "continue.*Continuing.*\.Hardware watchpoint $decimal: watched_variable.*Old value = 0.*New value = 4.*watched_variable\\);" + +kill_wait_spawned_process $test_spawn_id |