binutils-gdb/gdb/testsuite/gdb.threads/step-over-exec.c

91 lines
2.2 KiB
C
Raw Normal View History

gdb: clear inferior displaced stepping state and in-line step-over info on exec When a process does an exec, all its program space is replaced with the newly loaded executable. All non-main threads disappear and the main thread starts executing at the entry point of the new executable. Things can go wrong if a displaced step operation is in progress while we process the exec event. If the main thread is the one executing the displaced step: when that thread (now executing in the new executable) stops somewhere (say, at a breakpoint), displaced_step_fixup will run and clear up the state. We will execute the "fixup" phase for the instruction we single-stepped in the old program space. We are now in a completely different context, so doing the fixup may corrupt the state. If it is a non-main thread that is doing the displaced step: while handling the exec event, GDB deletes the thread_info representing that thread (since the thread doesn't exist in the inferior after the exec). But inferior::displaced_step_state::step_thread will still point to it. When handling events later, this condition, in displaced_step_fixup, will likely never be true: /* Was this event for the thread we displaced? */ if (displaced->step_thread != event_thread) return 0; ... since displaced->step_thread points to a deleted thread (unless that storage gets re-used for a new thread_info, but that wouldn't be good either). This effectively makes the displaced stepping buffer occupied for ever. When a thread in the new program space will want to do a displaced step, it will wait for ever. I think we simply need to reset the displaced stepping state of the inferior on exec. Everything execution-related that existed before the exec is now gone. Similarly, if a thread does an in-line step over an exec syscall instruction, nothing clears the in-line step over info when the event is handled. So it the in-line step over info stays there indefinitely, and things hang because we can never start another step over. To fix this, I added a call to clear_step_over_info in infrun_inferior_execd. Add a test with a program with two threads that does an exec. The test includes the following axes: - whether it's the leader thread or the other thread that does the exec. - whether the exec'r and exec'd program have different text segment addresses. This is to hopefully catch cases where the displaced stepping info doesn't get reset, and GDB later tries to restore bytes of the old address space in the new address space. If the mapped addresses are different, we should get some memory error. This happens without the patch applied: $ ./gdb -q -nx --data-directory=data-directory testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-leader-diff-text-segs-true -ex "b main" -ex r -ex "b my_execve_syscall if 0" -ex "set displaced-stepping on" ... Breakpoint 1, main (argc=1, argv=0x7fffffffde38) at /home/simark/src/binutils-gdb/gdb/testsuite/gdb.threads/step-over-exec.c:69 69 argv0 = argv[0]; Breakpoint 2 at 0x60133a: file /home/simark/src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S, line 34. (gdb) c Continuing. [New Thread 0x7ffff7c62640 (LWP 1455423)] Leader going in exec. Exec-ing /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-leader-diff-text-segs-true-execd [Thread 0x7ffff7c62640 (LWP 1455423) exited] process 1455418 is executing new program: /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-leader-diff-text-segs-true-execd Error in re-setting breakpoint 2: Function "my_execve_syscall" not defined. No unwaited-for children left. (gdb) n Single stepping until exit from function _start, which has no line number information. Cannot access memory at address 0x6010d2 (gdb) - Whether displaced stepping is allowed or not, so that we end up testing both displaced stepping and in-line stepping on arches that do support displaced stepping (otherwise, it just tests in-line stepping twice I suppose) To be able to precisely put a breakpoint on the syscall instruction, I added a small assembly file (lib/my-syscalls.S) that contains minimal Linux syscall wrappers. I prefer that to the strategy used in gdb.base/step-over-syscall.exp, which is to stepi into the glibc wrapper until we find something that looks like a syscall instruction, I find that more predictable. gdb/ChangeLog: * infrun.c (infrun_inferior_execd): New function. (_initialize_infrun): Attach inferior_execd observer. gdb/testsuite/ChangeLog: * gdb.threads/step-over-exec.exp: New. * gdb.threads/step-over-exec.c: New. * gdb.threads/step-over-exec-execd.c: New. * lib/my-syscalls.S: New. * lib/my-syscalls.h: New. Change-Id: I1bbc8538e683f53af5b980091849086f4fec5ff9
2020-12-05 05:43:52 +08:00
/* This testcase is part of GDB, the GNU debugger.
Copyright 2020-2021 Free Software Foundation, Inc.
gdb: clear inferior displaced stepping state and in-line step-over info on exec When a process does an exec, all its program space is replaced with the newly loaded executable. All non-main threads disappear and the main thread starts executing at the entry point of the new executable. Things can go wrong if a displaced step operation is in progress while we process the exec event. If the main thread is the one executing the displaced step: when that thread (now executing in the new executable) stops somewhere (say, at a breakpoint), displaced_step_fixup will run and clear up the state. We will execute the "fixup" phase for the instruction we single-stepped in the old program space. We are now in a completely different context, so doing the fixup may corrupt the state. If it is a non-main thread that is doing the displaced step: while handling the exec event, GDB deletes the thread_info representing that thread (since the thread doesn't exist in the inferior after the exec). But inferior::displaced_step_state::step_thread will still point to it. When handling events later, this condition, in displaced_step_fixup, will likely never be true: /* Was this event for the thread we displaced? */ if (displaced->step_thread != event_thread) return 0; ... since displaced->step_thread points to a deleted thread (unless that storage gets re-used for a new thread_info, but that wouldn't be good either). This effectively makes the displaced stepping buffer occupied for ever. When a thread in the new program space will want to do a displaced step, it will wait for ever. I think we simply need to reset the displaced stepping state of the inferior on exec. Everything execution-related that existed before the exec is now gone. Similarly, if a thread does an in-line step over an exec syscall instruction, nothing clears the in-line step over info when the event is handled. So it the in-line step over info stays there indefinitely, and things hang because we can never start another step over. To fix this, I added a call to clear_step_over_info in infrun_inferior_execd. Add a test with a program with two threads that does an exec. The test includes the following axes: - whether it's the leader thread or the other thread that does the exec. - whether the exec'r and exec'd program have different text segment addresses. This is to hopefully catch cases where the displaced stepping info doesn't get reset, and GDB later tries to restore bytes of the old address space in the new address space. If the mapped addresses are different, we should get some memory error. This happens without the patch applied: $ ./gdb -q -nx --data-directory=data-directory testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-leader-diff-text-segs-true -ex "b main" -ex r -ex "b my_execve_syscall if 0" -ex "set displaced-stepping on" ... Breakpoint 1, main (argc=1, argv=0x7fffffffde38) at /home/simark/src/binutils-gdb/gdb/testsuite/gdb.threads/step-over-exec.c:69 69 argv0 = argv[0]; Breakpoint 2 at 0x60133a: file /home/simark/src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S, line 34. (gdb) c Continuing. [New Thread 0x7ffff7c62640 (LWP 1455423)] Leader going in exec. Exec-ing /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-leader-diff-text-segs-true-execd [Thread 0x7ffff7c62640 (LWP 1455423) exited] process 1455418 is executing new program: /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-leader-diff-text-segs-true-execd Error in re-setting breakpoint 2: Function "my_execve_syscall" not defined. No unwaited-for children left. (gdb) n Single stepping until exit from function _start, which has no line number information. Cannot access memory at address 0x6010d2 (gdb) - Whether displaced stepping is allowed or not, so that we end up testing both displaced stepping and in-line stepping on arches that do support displaced stepping (otherwise, it just tests in-line stepping twice I suppose) To be able to precisely put a breakpoint on the syscall instruction, I added a small assembly file (lib/my-syscalls.S) that contains minimal Linux syscall wrappers. I prefer that to the strategy used in gdb.base/step-over-syscall.exp, which is to stepi into the glibc wrapper until we find something that looks like a syscall instruction, I find that more predictable. gdb/ChangeLog: * infrun.c (infrun_inferior_execd): New function. (_initialize_infrun): Attach inferior_execd observer. gdb/testsuite/ChangeLog: * gdb.threads/step-over-exec.exp: New. * gdb.threads/step-over-exec.c: New. * gdb.threads/step-over-exec-execd.c: New. * lib/my-syscalls.S: New. * lib/my-syscalls.h: New. Change-Id: I1bbc8538e683f53af5b980091849086f4fec5ff9
2020-12-05 05:43:52 +08:00
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>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "../lib/my-syscalls.h"
#if (!defined(LEADER_DOES_EXEC) && !defined(OTHER_DOES_EXEC) \
|| defined(LEADER_DOES_EXEC) && defined(OTHER_DOES_EXEC))
# error "Exactly one of LEADER_DOES_EXEC and OTHER_DOES_EXEC must be defined."
#endif
static char *argv0;
static pthread_barrier_t barrier;
static void
do_the_exec (void)
{
char *execd_path = (char *) malloc (strlen (argv0) + sizeof ("-execd"));
sprintf (execd_path, "%s-execd", argv0);
char *argv[] = { execd_path, NULL };
printf ("Exec-ing %s\n", execd_path);
extern char **environ;
my_execve (execd_path, argv, environ);
printf ("Exec failed :(\n");
abort ();
}
static void *
thread_func (void *arg)
{
pthread_barrier_wait (&barrier);
#ifdef OTHER_DOES_EXEC
printf ("Other going in exec.\n");
do_the_exec ();
#endif
/* Just make sure the thread does not exit when the leader does the exec. */
pthread_barrier_wait (&barrier);
return NULL;
}
int
main (int argc, char *argv[])
{
argv0 = argv[0];
int ret = pthread_barrier_init (&barrier, NULL, 2);
if (ret != 0)
abort ();
pthread_t thread;
ret = pthread_create (&thread, NULL, thread_func, argv[0]);
if (ret != 0)
abort ();
pthread_barrier_wait (&barrier);
#ifdef LEADER_DOES_EXEC
printf ("Leader going in exec.\n");
do_the_exec ();
#endif
pthread_join (thread, NULL);
return 0;
}