From b8ed3a5b65c9db4c9cda089c2a1e8888bb2661cf Mon Sep 17 00:00:00 2001 From: Philippe Waroquiers Date: Sun, 1 Dec 2013 14:56:28 +0000 Subject: [PATCH] Fix 326462 Refactor vgdb to isolate invoker stuff into separate module vgdb.c contained (conditionally compiled) "invoker" code to have ptrace syscalls used to allow gdb/vgdb to connect to a valgrind process blocked in a syscall. This "invoker" code is ptrace based. Not all platforms are using ptrace. => refactor vgdb so as allow invoker code to be added more cleanly for non ptrace based platforms (e.g. Darwin, Solaris). * add file vgdb.h for: - definition of the vgdb-invoker interface - common declarations between vgdb.c and vgdb-invoker implementations * move ptrace related code from vgdb.c to new file vgdb-invoker-ptrace.c * new file vgdb-invoker-none.c containing an empty invoker implementation used on platforms that do not (yet) have a invoker implementation (e.g. android and darwin). * modified Makefile.am to use one of the vgdb-invoker-*.c file depending on the platform. * small changes related to changing ptraceinvoker to invoker in various files: gdbserver_tests/make_local_links, gdbserver_tests/nlcontrolc.vgtest, gdbserver_tests/mcinvokeRU.vgtest, gdbserver_tests/nlsigvgdb.vgtest gdbserver_tests/mcinvokeWS.vgtest, coregrind/m_gdbserver/README_DEVELOPERS Patch from Ivo Raisr, slightly modified git-svn-id: svn://svn.valgrind.org/valgrind/trunk@13743 --- NEWS | 2 +- coregrind/Makefile.am | 15 + coregrind/m_gdbserver/README_DEVELOPERS | 11 +- coregrind/vgdb-invoker-none.c | 56 ++ coregrind/vgdb-invoker-ptrace.c | 917 ++++++++++++++++++++ coregrind/vgdb.c | 1023 +---------------------- coregrind/vgdb.h | 103 +++ gdbserver_tests/make_local_links | 6 +- gdbserver_tests/mcinvokeRU.vgtest | 4 +- gdbserver_tests/mcinvokeWS.vgtest | 2 +- gdbserver_tests/nlcontrolc.vgtest | 2 +- gdbserver_tests/nlsigvgdb.vgtest | 2 +- 12 files changed, 1134 insertions(+), 1009 deletions(-) create mode 100644 coregrind/vgdb-invoker-none.c create mode 100644 coregrind/vgdb-invoker-ptrace.c create mode 100644 coregrind/vgdb.h diff --git a/NEWS b/NEWS index d10919f68..01f47d7da 100644 --- a/NEWS +++ b/NEWS @@ -24,7 +24,7 @@ To see details of a given bug, visit https://bugs.kde.org/show_bug.cgi?id=XXXXXX where XXXXXX is the bug number as listed below. - +326462 Refactor vgdb to isolate invoker stuff into separate module 326983 Clear direction flag after tests on amd64. 327238 Callgrind Assertion 'passed <= last_bb->cjmp_count' failed 327837 dwz compressed alternate .debug_info and .debug_str not read correctly diff --git a/coregrind/Makefile.am b/coregrind/Makefile.am index fd84ebc18..792d94a82 100644 --- a/coregrind/Makefile.am +++ b/coregrind/Makefile.am @@ -59,7 +59,22 @@ valgrind_CFLAGS += -static valgrind_LDFLAGS += -Wl,-z,noexecstack endif + vgdb_SOURCES = vgdb.c +if VGCONF_OS_IS_LINUX +if VGCONF_PLATVARIANT_IS_ANDROID +vgdb_SOURCES += vgdb-invoker-none.c +else +vgdb_SOURCES += vgdb-invoker-ptrace.c +endif +endif +if VGCONF_OS_IS_DARWIN +# Some darwin specific stuff is needed as ptrace is not +# fully supported on MacOS. Till we find someone courageous +# having access to Darwin, 'none' implementation is used. +vgdb_SOURCES += vgdb-invoker-none.c +endif + vgdb_CPPFLAGS = $(AM_CPPFLAGS_PRI) vgdb_CFLAGS = $(AM_CFLAGS_PRI) vgdb_CCASFLAGS = $(AM_CCASFLAGS_PRI) diff --git a/coregrind/m_gdbserver/README_DEVELOPERS b/coregrind/m_gdbserver/README_DEVELOPERS index 3cf740f49..58c9269e0 100644 --- a/coregrind/m_gdbserver/README_DEVELOPERS +++ b/coregrind/m_gdbserver/README_DEVELOPERS @@ -165,7 +165,7 @@ will check after 100ms if the characters it has written have been read by valgrind. If not, vgdb will force the invocation of the gdbserver code inside the valgrind process. -This forced invocation is implemented using the ptrace system call: +On Linux, this forced invocation is implemented using the ptrace system call: using ptrace, vgdb will cause the valgrind process to call the gdbserver code. @@ -272,10 +272,12 @@ Then adapt the set of functions needed to initialize the structure Optional but heavily recommended: To have a proper wake up of a Valgrind process with all threads blocked in a system call, some architecture specific code -has to be done in vgdb.c : search for PTRACEINVOKER processor symbol -to see what has to be completed. +has to be done in vgdb-invoker-*.c. +Typically, for a linux system supporting ptrace, you have to modify +vgdb-invoker-ptrace.c. -For Linux based platforms, all the ptrace calls should be ok. +For Linux based platforms, all the ptrace calls in vgdb-invoker-ptrace.c +should be ok. The only thing needed is the code needed to "push a dummy call" on the stack, i.e. assign the relevant registers in the struct user_regs_struct, and push values on the stack according to the ABI. @@ -337,7 +339,6 @@ TODO and/or additional nice things to have (such as search leaks)? - * currently jump(s) and inferior call(s) are somewhat dangerous when called from a block not yet instrumented : instead of continuing till the next Imark, where there will be a diff --git a/coregrind/vgdb-invoker-none.c b/coregrind/vgdb-invoker-none.c new file mode 100644 index 000000000..9b6b90c1a --- /dev/null +++ b/coregrind/vgdb-invoker-none.c @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------*/ +/*--- Empty implementation of vgdb invoker subsystem. ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2011-2013 Philippe Waroquiers + + 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 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +#include "vgdb.h" + +#include +#include + +void invoker_restrictions_msg(void) +{ + fprintf(stderr, + "Note: vgdb invoker not implemented on this platform.\n" + "For more info: read user manual section" + " 'Limitations of the Valgrind gdbserver'.\n"); +} + +void invoker_cleanup_restore_and_detach(void *v_pid) +{ + DEBUG(1, "invoker_cleanup_restore_and_detach"); +} + +Bool invoker_invoke_gdbserver(pid_t pid) +{ + DEBUG(2, "invoker_invoke_gdbserver not implemented\n"); + /* Returning True signals to not retry (too soon) to invoke. */ + return True; +} + +void invoker_valgrind_dying(void) +{ +} diff --git a/coregrind/vgdb-invoker-ptrace.c b/coregrind/vgdb-invoker-ptrace.c new file mode 100644 index 000000000..71f88b73e --- /dev/null +++ b/coregrind/vgdb-invoker-ptrace.c @@ -0,0 +1,917 @@ +/*--------------------------------------------------------------------*/ +/*--- Implementation of vgdb invoker subsystem via ptrace() calls. ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2011-2013 Philippe Waroquiers + + 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 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +#include "config.h" + +#include "vgdb.h" +#include "pub_core_threadstate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if VEX_HOST_WORDSIZE == 8 +typedef Addr64 CORE_ADDR; +#elif VEX_HOST_WORDSIZE == 4 +typedef Addr32 CORE_ADDR; +#else +# error "unexpected wordsize" +#endif + +#if VEX_HOST_WORDSIZE == 8 +typedef Addr64 PTRACE_XFER_TYPE; +typedef void* PTRACE_ARG3_TYPE; +#elif VEX_HOST_WORDSIZE == 4 +typedef Addr32 PTRACE_XFER_TYPE; +typedef void* PTRACE_ARG3_TYPE; +#else +# error "unexpected wordsize" +#endif + +/* True if we have continued pid_of_save_regs after PTRACE_ATTACH. */ +static Bool pid_of_save_regs_continued = False; + +/* True when loss of connection indicating that the Valgrind + process is dying. */ +static Bool dying = False; + +/* ptrace_(read|write)_memory are modified extracts of linux-low.c + from gdb 6.6. Copyrighted FSF */ +/* Copy LEN bytes from valgrind memory starting at MEMADDR + to vgdb memory starting at MYADDR. */ +static +int ptrace_read_memory (pid_t inferior_pid, CORE_ADDR memaddr, + void *myaddr, size_t len) +{ + register int i; + /* Round starting address down to longword boundary. */ + register CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_XFER_TYPE); + /* Round ending address up; get number of longwords that makes. */ + register int count + = (((memaddr + len) - addr) + sizeof (PTRACE_XFER_TYPE) - 1) + / sizeof (PTRACE_XFER_TYPE); + /* Allocate buffer of that many longwords. */ + register PTRACE_XFER_TYPE *buffer + = (PTRACE_XFER_TYPE *) alloca (count * sizeof (PTRACE_XFER_TYPE)); + + /* Read all the longwords */ + for (i = 0; i < count; i++, addr += sizeof (PTRACE_XFER_TYPE)) { + errno = 0; + buffer[i] = ptrace (PTRACE_PEEKTEXT, inferior_pid, + (PTRACE_ARG3_TYPE) addr, 0); + if (errno) + return errno; + } + + /* Copy appropriate bytes out of the buffer. */ + memcpy (myaddr, + (char *) buffer + (memaddr & (sizeof (PTRACE_XFER_TYPE) - 1)), len); + + return 0; +} + +/* Copy LEN bytes of data from vgdb memory at MYADDR + to valgrind memory at MEMADDR. + On failure (cannot write the valgrind memory) + returns the value of errno. */ +__attribute__((unused)) /* not used on all platforms */ +static +int ptrace_write_memory (pid_t inferior_pid, CORE_ADDR memaddr, + const void *myaddr, size_t len) +{ + register int i; + /* Round starting address down to longword boundary. */ + register CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_XFER_TYPE); + /* Round ending address up; get number of longwords that makes. */ + register int count + = (((memaddr + len) - addr) + sizeof (PTRACE_XFER_TYPE) - 1) + / sizeof (PTRACE_XFER_TYPE); + /* Allocate buffer of that many longwords. */ + register PTRACE_XFER_TYPE *buffer + = (PTRACE_XFER_TYPE *) alloca (count * sizeof (PTRACE_XFER_TYPE)); + + if (debuglevel >= 1) { + DEBUG (1, "Writing "); + for (i = 0; i < len; i++) + PDEBUG (1, "%02x", ((const unsigned char*)myaddr)[i]); + PDEBUG(1, " to %p\n", (void *) memaddr); + } + + /* Fill start and end extra bytes of buffer with existing memory data. */ + + buffer[0] = ptrace (PTRACE_PEEKTEXT, inferior_pid, + (PTRACE_ARG3_TYPE) addr, 0); + + if (count > 1) { + buffer[count - 1] + = ptrace (PTRACE_PEEKTEXT, inferior_pid, + (PTRACE_ARG3_TYPE) (addr + (count - 1) + * sizeof (PTRACE_XFER_TYPE)), + 0); + } + + /* Copy data to be written over corresponding part of buffer */ + + memcpy ((char *) buffer + (memaddr & (sizeof (PTRACE_XFER_TYPE) - 1)), + myaddr, len); + + /* Write the entire buffer. */ + + for (i = 0; i < count; i++, addr += sizeof (PTRACE_XFER_TYPE)) { + errno = 0; + ptrace (PTRACE_POKETEXT, inferior_pid, + (PTRACE_ARG3_TYPE) addr, buffer[i]); + if (errno) + return errno; + } + + return 0; +} + +/* subset of VG_(threads) needed for vgdb ptrace. + This is initialized when process is attached. */ +typedef struct { + ThreadStatus status; + Int lwpid; +} +VgdbThreadState; +static VgdbThreadState vgdb_threads[VG_N_THREADS]; + +static const +HChar* name_of_ThreadStatus ( ThreadStatus status ) +{ + switch (status) { + case VgTs_Empty: return "VgTs_Empty"; + case VgTs_Init: return "VgTs_Init"; + case VgTs_Runnable: return "VgTs_Runnable"; + case VgTs_WaitSys: return "VgTs_WaitSys"; + case VgTs_Yielding: return "VgTs_Yielding"; + case VgTs_Zombie: return "VgTs_Zombie"; + default: return "VgTs_???"; + } +} + +static +char *status_image (int status) +{ + static char result[256]; + int sz = 0; +#define APPEND(...) sz += snprintf (result+sz, 256 - sz - 1, __VA_ARGS__) + + result[0] = 0; + + if (WIFEXITED(status)) + APPEND ("WIFEXITED %d ", WEXITSTATUS(status)); + + if (WIFSIGNALED(status)) { + APPEND ("WIFSIGNALED %d ", WTERMSIG(status)); + if (WCOREDUMP(status)) APPEND ("WCOREDUMP "); + } + + if (WIFSTOPPED(status)) + APPEND ("WIFSTOPPED %d ", WSTOPSIG(status)); + +#ifdef WIFCONTINUED + if (WIFCONTINUED(status)) + APPEND ("WIFCONTINUED "); +#endif + + return result; +#undef APPEND +} + +/* Wait till the process pid is reported as stopped with signal_expected. + If other signal(s) than signal_expected are received, waitstopped + will pass them to pid, waiting for signal_expected to stop pid. + Returns True when process is in stopped state with signal_expected. + Returns False if a problem was encountered while waiting for pid + to be stopped. + + If pid is reported as being dead/exited, waitstopped will return False. +*/ +static +Bool waitstopped (pid_t pid, int signal_expected, const char *msg) +{ + pid_t p; + int status = 0; + int signal_received; + int res; + + while (1) { + DEBUG(1, "waitstopped %s before waitpid signal_expected %d\n", + msg, signal_expected); + p = waitpid(pid, &status, __WALL); + DEBUG(1, "after waitpid pid %d p %d status 0x%x %s\n", pid, p, + status, status_image (status)); + if (p != pid) { + ERROR(errno, "%s waitpid pid %d in waitstopped %d status 0x%x %s\n", + msg, pid, p, status, status_image (status)); + return False; + } + + if (WIFEXITED(status)) { + shutting_down = True; + return False; + } + + assert (WIFSTOPPED(status)); + signal_received = WSTOPSIG(status); + if (signal_received == signal_expected) + break; + + /* pid received a signal which is not the signal we are waiting for. + We continue pid, transmitting this signal. */ + DEBUG(1, "waitstopped PTRACE_CONT with signal %d\n", signal_received); + res = ptrace (PTRACE_CONT, pid, NULL, signal_received); + if (res != 0) { + ERROR(errno, "waitstopped PTRACE_CONT\n"); + return False; + } + } + + return True; +} + +/* Stops the given pid, wait for the process to be stopped. + Returns True if succesful, False otherwise. + msg is used in tracing and error reporting. */ +static +Bool stop (pid_t pid, const char *msg) +{ + long res; + + DEBUG(1, "%s SIGSTOP pid %d\n", msg, pid); + res = kill (pid, SIGSTOP); + if (res != 0) { + ERROR(errno, "%s SIGSTOP pid %d %ld\n", msg, pid, res); + return False; + } + + return waitstopped (pid, SIGSTOP, msg); + +} + +/* Attaches to given pid, wait for the process to be stopped. + Returns True if succesful, False otherwise. + msg is used in tracing and error reporting. */ +static +Bool attach (pid_t pid, const char *msg) +{ + long res; + static Bool output_error = True; + static Bool initial_attach = True; + // For a ptrace_scope protected system, we do not want to output + // repetitively attach error. We will output once an error + // for the initial_attach. Once the 1st attach has succeeded, we + // again show all errors. + + DEBUG(1, "%s PTRACE_ATTACH pid %d\n", msg, pid); + res = ptrace (PTRACE_ATTACH, pid, NULL, NULL); + if (res != 0) { + if (output_error || debuglevel > 0) { + ERROR(errno, "%s PTRACE_ATTACH pid %d %ld\n", msg, pid, res); + if (initial_attach) + output_error = False; + } + return False; + } + + initial_attach = False; + output_error = True; + return waitstopped(pid, SIGSTOP, msg); +} + +/* once we are attached to the pid, get the list of threads and stop + them all. + Returns True if all threads properly suspended, False otherwise. */ +static +Bool acquire_and_suspend_threads (pid_t pid) +{ + int i; + int rw; + Bool pid_found = False; + Addr vgt; + int sz_tst; + int off_status; + int off_lwpid; + int nr_live_threads = 0; + + if (shared32 != NULL) { + vgt = shared32->threads; + sz_tst = shared32->sizeof_ThreadState; + off_status = shared32->offset_status; + off_lwpid = shared32->offset_lwpid; + } + else if (shared64 != NULL) { + vgt = shared64->threads; + sz_tst = shared64->sizeof_ThreadState; + off_status = shared64->offset_status; + off_lwpid = shared64->offset_lwpid; + } else { + assert (0); + } + + /* note: the entry 0 is unused */ + for (i = 1; i < VG_N_THREADS; i++) { + vgt += sz_tst; + rw = ptrace_read_memory(pid, vgt+off_status, + &(vgdb_threads[i].status), + sizeof(ThreadStatus)); + if (rw != 0) { + ERROR(rw, "status ptrace_read_memory\n"); + return False; + } + + rw = ptrace_read_memory(pid, vgt+off_lwpid, + &(vgdb_threads[i].lwpid), + sizeof(Int)); + if (rw != 0) { + ERROR(rw, "lwpid ptrace_read_memory\n"); + return False; + } + + if (vgdb_threads[i].status != VgTs_Empty) { + DEBUG(1, "found tid %d status %s lwpid %d\n", + i, name_of_ThreadStatus(vgdb_threads[i].status), + vgdb_threads[i].lwpid); + nr_live_threads++; + if (vgdb_threads[i].lwpid <= 1) { + if (vgdb_threads[i].lwpid == 0 + && vgdb_threads[i].status == VgTs_Init) { + DEBUG(1, "not set lwpid tid %d status %s lwpid %d\n", + i, name_of_ThreadStatus(vgdb_threads[i].status), + vgdb_threads[i].lwpid); + } else { + ERROR(1, "unexpected lwpid tid %d status %s lwpid %d\n", + i, name_of_ThreadStatus(vgdb_threads[i].status), + vgdb_threads[i].lwpid); + } + /* in case we have a VtTs_Init thread with lwpid not yet set, + we try again later. */ + return False; + } + if (vgdb_threads[i].lwpid == pid) { + assert (!pid_found); + assert (i == 1); + pid_found = True; + } else { + if (!attach(vgdb_threads[i].lwpid, "attach_thread")) { + ERROR(0, "ERROR attach pid %d tid %d\n", + vgdb_threads[i].lwpid, i); + return False; + } + } + } + } + /* If we found no thread, it means the process is stopping, and + we better do not force anything to happen during that. */ + if (nr_live_threads > 0) + return True; + else + return False; +} + +static +void detach_from_all_threads (pid_t pid) +{ + int i; + long res; + Bool pid_found = False; + + /* detach from all the threads */ + for (i = 1; i < VG_N_THREADS; i++) { + if (vgdb_threads[i].status != VgTs_Empty) { + if (vgdb_threads[i].status == VgTs_Init + && vgdb_threads[i].lwpid == 0) { + DEBUG(1, "skipping PTRACE_DETACH pid %d tid %d status %s\n", + vgdb_threads[i].lwpid, i, + name_of_ThreadStatus (vgdb_threads[i].status)); + } else { + if (vgdb_threads[i].lwpid == pid) { + assert (!pid_found); + pid_found = True; + } + DEBUG(1, "PTRACE_DETACH pid %d tid %d status %s\n", + vgdb_threads[i].lwpid, i, + name_of_ThreadStatus (vgdb_threads[i].status)); + res = ptrace (PTRACE_DETACH, vgdb_threads[i].lwpid, NULL, NULL); + if (res != 0) { + ERROR(errno, "PTRACE_DETACH pid %d tid %d status %s res %ld\n", + vgdb_threads[i].lwpid, i, + name_of_ThreadStatus (vgdb_threads[i].status), + res); + } + } + } + } + + if (!pid_found && pid) { + /* No threads are live. Process is busy stopping. + We need to detach from pid explicitely. */ + DEBUG(1, "no thread live => PTRACE_DETACH pid %d\n", pid); + res = ptrace (PTRACE_DETACH, pid, NULL, NULL); + if (res != 0) + ERROR(errno, "PTRACE_DETACH pid %d res %ld\n", pid, res); + } +} + +// if > 0, pid for which registers have to be restored. +static int pid_of_save_regs = 0; +static struct user user_save; + +// The below indicates if ptrace_getregs (and ptrace_setregs) can be used. +// Note that some linux versions are defining PTRACE_GETREGS but using +// it gives back EIO. +// has_working_ptrace_getregs can take the following values: +// -1 : PTRACE_GETREGS is defined +// runtime check not yet done. +// 0 : PTRACE_GETREGS runtime check has failed. +// 1 : PTRACE_GETREGS defined and runtime check ok. +#ifdef HAVE_PTRACE_GETREGS +static int has_working_ptrace_getregs = -1; +#endif + +/* Get the registers from pid into regs. + regs_bsz value gives the length of *regs. + Returns True if all ok, otherwise False. */ +static +Bool getregs (pid_t pid, void *regs, long regs_bsz) +{ + DEBUG(1, "getregs regs_bsz %ld\n", regs_bsz); +# ifdef HAVE_PTRACE_GETREGS + if (has_working_ptrace_getregs) { + // Platforms having GETREGS + long res; + DEBUG(1, "getregs PTRACE_GETREGS\n"); + res = ptrace (PTRACE_GETREGS, pid, NULL, regs); + if (res == 0) { + if (has_working_ptrace_getregs == -1) { + // First call to PTRACE_GETREGS succesful => + has_working_ptrace_getregs = 1; + DEBUG(1, "detected a working PTRACE_GETREGS\n"); + } + assert (has_working_ptrace_getregs == 1); + return True; + } + else if (has_working_ptrace_getregs == 1) { + // We had a working call, but now it fails. + // This is unexpected. + ERROR(errno, "PTRACE_GETREGS %ld\n", res); + return False; + } else { + // Check this is the first call: + assert (has_working_ptrace_getregs == -1); + if (errno == EIO) { + DEBUG(1, "detected a broken PTRACE_GETREGS with EIO\n"); + has_working_ptrace_getregs = 0; + // Fall over to the PTRACE_PEEKUSER case. + } else { + ERROR(errno, "broken PTRACE_GETREGS unexpected errno %ld\n", res); + return False; + } + } + } +# endif + + // We assume PTRACE_PEEKUSER is defined everywhere. + { +# ifdef PT_ENDREGS + long peek_bsz = PT_ENDREGS; + assert (peek_bsz <= regs_bsz); +# else + long peek_bsz = regs_bsz-1; +# endif + char *pregs = (char *) regs; + long offset; + errno = 0; + DEBUG(1, "getregs PTRACE_PEEKUSER(s) peek_bsz %ld\n", peek_bsz); + for (offset = 0; offset < peek_bsz; offset = offset + sizeof(long)) { + *(long *)(pregs+offset) = ptrace(PTRACE_PEEKUSER, pid, offset, NULL); + if (errno != 0) { + ERROR(errno, "PTRACE_PEEKUSER offset %ld\n", offset); + return False; + } + } + return True; + } + + // If neither PTRACE_GETREGS not PTRACE_PEEKUSER have returned, + // then we are in serious trouble. + assert (0); +} + +/* Set the registers of pid to regs. + regs_bsz value gives the length of *regs. + Returns True if all ok, otherwise False. */ +static +Bool setregs (pid_t pid, void *regs, long regs_bsz) +{ + DEBUG(1, "setregs regs_bsz %ld\n", regs_bsz); +// Note : the below is checking for GETREGS, not SETREGS +// as if one is defined and working, the other one should also work. +# ifdef HAVE_PTRACE_GETREGS + if (has_working_ptrace_getregs) { + // Platforms having SETREGS + long res; + // setregs can never be called before getregs has done a runtime check. + assert (has_working_ptrace_getregs == 1); + DEBUG(1, "setregs PTRACE_SETREGS\n"); + res = ptrace (PTRACE_SETREGS, pid, NULL, regs); + if (res != 0) { + ERROR(errno, "PTRACE_SETREGS %ld\n", res); + return False; + } + return True; + } +# endif + + { + char *pregs = (char *) regs; + long offset; + long res; +# ifdef PT_ENDREGS + long peek_bsz = PT_ENDREGS; + assert (peek_bsz <= regs_bsz); +# else + long peek_bsz = regs_bsz-1; +# endif + errno = 0; + DEBUG(1, "setregs PTRACE_POKEUSER(s) %ld\n", peek_bsz); + for (offset = 0; offset < peek_bsz; offset = offset + sizeof(long)) { + res = ptrace(PTRACE_POKEUSER, pid, offset, *(long*)(pregs+offset)); + if (errno != 0) { + ERROR(errno, "PTRACE_POKEUSER offset %ld res %ld\n", offset, res); + return False; + } + } + return True; + } + + // If neither PTRACE_SETREGS not PTRACE_POKEUSER have returned, + // then we are in serious trouble. + assert (0); +} + +/* Restore the registers to the saved value, then detaches from all threads */ +static +void restore_and_detach (pid_t pid) +{ + if (pid_of_save_regs) { + /* In case the 'main pid' has been continued, we need to stop it + before resetting the registers. */ + if (pid_of_save_regs_continued) { + pid_of_save_regs_continued = False; + if (!stop(pid_of_save_regs, "sigstop before reset regs")) + DEBUG(0, "Could not sigstop before reset"); + } + + DEBUG(1, "setregs restore registers pid %d\n", pid_of_save_regs); + if (!setregs(pid_of_save_regs, &user_save.regs, sizeof(user_save.regs))) { + ERROR(errno, "setregs restore registers pid %d after cont\n", + pid_of_save_regs); + } + pid_of_save_regs = 0; + } else { + DEBUG(1, "PTRACE_SETREGS restore registers: no pid\n"); + } + detach_from_all_threads(pid); +} + +Bool invoker_invoke_gdbserver (pid_t pid) +{ + long res; + Bool stopped; + struct user user_mod; + Addr sp; + /* A specific int value is passed to invoke_gdbserver, to check + everything goes according to the plan. */ + const int check = 0x8BADF00D; // ate bad food. + + const Addr bad_return = 0; + // A bad return address will be pushed on the stack. + // The function invoke_gdbserver cannot return. If ever it returns, a NULL + // address pushed on the stack should ensure this is detected. + + /* Not yet attached. If problem, vgdb can abort, + no cleanup needed. */ + + DEBUG(1, "attach to 'main' pid %d\n", pid); + if (!attach(pid, "attach main pid")) { + ERROR(0, "error attach main pid %d\n", pid); + return False; + } + + /* Now, we are attached. If problem, detach and return. */ + + if (!acquire_and_suspend_threads(pid)) { + detach_from_all_threads(pid); + /* if the pid does not exist anymore, we better stop */ + if (kill(pid, 0) != 0) + XERROR (errno, "invoke_gdbserver: check for pid %d existence failed\n", + pid); + return False; + } + + if (!getregs(pid, &user_mod.regs, sizeof(user_mod.regs))) { + detach_from_all_threads(pid); + return False; + } + user_save = user_mod; + +#if defined(VGA_x86) + sp = user_mod.regs.esp; +#elif defined(VGA_amd64) + sp = user_mod.regs.rsp; + if (shared32 != NULL) { + /* 64bit vgdb speaking with a 32bit executable. + To have system call restart properly, we need to sign extend rax. + For more info: + web search '[patch] Fix syscall restarts for amd64->i386 biarch' + e.g. http://sourceware.org/ml/gdb-patches/2009-11/msg00592.html */ + *(long *)&user_save.regs.rax = *(int*)&user_save.regs.rax; + DEBUG(1, "Sign extending %8.8lx to %8.8lx\n", + user_mod.regs.rax, user_save.regs.rax); + } +#elif defined(VGA_arm) + sp = user_mod.regs.uregs[13]; +#elif defined(VGA_ppc32) + sp = user_mod.regs.gpr[1]; +#elif defined(VGA_ppc64) + sp = user_mod.regs.gpr[1]; +#elif defined(VGA_s390x) + sp = user_mod.regs.gprs[15]; +#elif defined(VGA_mips32) + long long *p = (long long *)user_mod.regs; + sp = p[29]; +#elif defined(VGA_mips64) + sp = user_mod.regs[29]; +#else + I_die_here : (sp) architecture missing in vgdb.c +#endif + + + // the magic below is derived from spying what gdb sends to + // the (classical) gdbserver when invoking a C function. + if (shared32 != NULL) { + // vgdb speaking with a 32bit executable. +#if defined(VGA_x86) || defined(VGA_amd64) + const int regsize = 4; + int rw; + /* push check arg on the stack */ + sp = sp - regsize; + DEBUG(1, "push check arg ptrace_write_memory\n"); + assert(regsize == sizeof(check)); + rw = ptrace_write_memory(pid, sp, + &check, + regsize); + if (rw != 0) { + ERROR(rw, "push check arg ptrace_write_memory"); + detach_from_all_threads(pid); + return False; + } + + sp = sp - regsize; + DEBUG(1, "push bad_return return address ptrace_write_memory\n"); + // Note that for a 64 bits vgdb, only 4 bytes of NULL bad_return + // are written. + rw = ptrace_write_memory(pid, sp, + &bad_return, + regsize); + if (rw != 0) { + ERROR(rw, "push bad_return return address ptrace_write_memory"); + detach_from_all_threads(pid); + return False; + } +#if defined(VGA_x86) + /* set ebp, esp, eip and orig_eax to invoke gdbserver */ + // compiled in 32bits, speaking with a 32bits exe + user_mod.regs.ebp = sp; // bp set to sp + user_mod.regs.esp = sp; + user_mod.regs.eip = shared32->invoke_gdbserver; + user_mod.regs.orig_eax = -1L; +#elif defined(VGA_amd64) + /* set ebp, esp, eip and orig_eax to invoke gdbserver */ + // compiled in 64bits, speaking with a 32bits exe + user_mod.regs.rbp = sp; // bp set to sp + user_mod.regs.rsp = sp; + user_mod.regs.rip = shared32->invoke_gdbserver; + user_mod.regs.orig_rax = -1L; +#else + I_die_here : not x86 or amd64 in x86/amd64 section/ +#endif + +#elif defined(VGA_ppc32) || defined(VGA_ppc64) + user_mod.regs.nip = shared32->invoke_gdbserver; + user_mod.regs.trap = -1L; + /* put check arg in register 3 */ + user_mod.regs.gpr[3] = check; + /* put NULL return address in Link Register */ + user_mod.regs.link = bad_return; + +#elif defined(VGA_arm) + /* put check arg in register 0 */ + user_mod.regs.uregs[0] = check; + /* put NULL return address in Link Register */ + user_mod.regs.uregs[14] = bad_return; + user_mod.regs.uregs[15] = shared32->invoke_gdbserver; + +#elif defined(VGA_s390x) + XERROR(0, "(fn32) s390x has no 32bits implementation"); +#elif defined(VGA_mips32) + /* put check arg in register 4 */ + p[4] = check; + /* put NULL return address in ra */ + p[31] = bad_return; + p[34] = shared32->invoke_gdbserver; + p[25] = shared32->invoke_gdbserver; + /* make stack space for args */ + p[29] = sp - 32; + +#elif defined(VGA_mips64) + assert(0); // cannot vgdb a 32 bits executable with a 64 bits exe +#else + I_die_here : architecture missing in vgdb.c +#endif + } + + else if (shared64 != NULL) { +#if defined(VGA_x86) + assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe +#elif defined(VGA_amd64) + // vgdb speaking with a 64 bit executable. + const int regsize = 8; + int rw; + + /* give check arg in rdi */ + user_mod.regs.rdi = check; + + /* push return address on stack : return to breakaddr */ + sp = sp - regsize; + DEBUG(1, "push bad_return return address ptrace_write_memory\n"); + rw = ptrace_write_memory(pid, sp, + &bad_return, + sizeof(bad_return)); + if (rw != 0) { + ERROR(rw, "push bad_return return address ptrace_write_memory"); + detach_from_all_threads(pid); + return False; + } + + /* set rbp, rsp, rip and orig_rax to invoke gdbserver */ + user_mod.regs.rbp = sp; // bp set to sp + user_mod.regs.rsp = sp; + user_mod.regs.rip = shared64->invoke_gdbserver; + user_mod.regs.orig_rax = -1L; + +#elif defined(VGA_arm) + assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe +#elif defined(VGA_ppc32) + assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe +#elif defined(VGA_ppc64) + Addr64 func_addr; + Addr64 toc_addr; + int rw; + rw = ptrace_read_memory(pid, shared64->invoke_gdbserver, + &func_addr, + sizeof(Addr64)); + if (rw != 0) { + ERROR(rw, "ppc64 read func_addr\n"); + detach_from_all_threads(pid); + return False; + } + rw = ptrace_read_memory(pid, shared64->invoke_gdbserver+8, + &toc_addr, + sizeof(Addr64)); + if (rw != 0) { + ERROR(rw, "ppc64 read toc_addr\n"); + detach_from_all_threads(pid); + return False; + } + // We are not pushing anything on the stack, so it is not + // very clear why the sp has to be decreased, but it seems + // needed. The ppc64 ABI might give some lights on this ? + user_mod.regs.gpr[1] = sp - 220; + user_mod.regs.gpr[2] = toc_addr; + user_mod.regs.nip = func_addr; + user_mod.regs.trap = -1L; + /* put check arg in register 3 */ + user_mod.regs.gpr[3] = check; + /* put bad_return return address in Link Register */ + user_mod.regs.link = bad_return; +#elif defined(VGA_s390x) + /* put check arg in register r2 */ + user_mod.regs.gprs[2] = check; + /* bad_return Return address is in r14 */ + user_mod.regs.gprs[14] = bad_return; + /* minimum stack frame */ + sp = sp - 160; + user_mod.regs.gprs[15] = sp; + /* set program counter */ + user_mod.regs.psw.addr = shared64->invoke_gdbserver; +#elif defined(VGA_mips32) + assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe +#elif defined(VGA_mips64) + /* put check arg in register 4 */ + user_mod.regs[4] = check; + /* put NULL return address in ra */ + user_mod.regs[31] = bad_return; + user_mod.regs[34] = shared64->invoke_gdbserver; + user_mod.regs[25] = shared64->invoke_gdbserver; +#else + I_die_here: architecture missing in vgdb.c +#endif + } + else { + assert(0); + } + + if (!setregs(pid, &user_mod.regs, sizeof(user_mod.regs))) { + detach_from_all_threads(pid); + return False; + } + /* Now that we have modified the registers, we set + pid_of_save_regs to indicate that restore_and_detach + must restore the registers in case of cleanup. */ + pid_of_save_regs = pid; + pid_of_save_regs_continued = False; + + + /* We PTRACE_CONT-inue pid. + Either gdbserver will be invoked directly (if all + threads are interruptible) or gdbserver will be + called soon by the scheduler. In the first case, + pid will stop on the break inserted above when + gdbserver returns. In the 2nd case, the break will + be encountered directly. */ + DEBUG(1, "PTRACE_CONT to invoke\n"); + res = ptrace (PTRACE_CONT, pid, NULL, NULL); + if (res != 0) { + ERROR(errno, "PTRACE_CONT\n"); + restore_and_detach(pid); + return False; + } + pid_of_save_regs_continued = True; + /* Wait for SIGSTOP generated by m_gdbserver.c give_control_back_to_vgdb */ + stopped = waitstopped (pid, SIGSTOP, + "waitpid status after PTRACE_CONT to invoke"); + if (stopped) { + /* Here pid has properly stopped on the break. */ + pid_of_save_regs_continued = False; + restore_and_detach(pid); + return True; + } else { + /* Whatever kind of problem happened. We shutdown. */ + shutting_down = True; + return False; + } +} + +void invoker_cleanup_restore_and_detach(void *v_pid) +{ + DEBUG(1, "invoker_cleanup_restore_and_detach dying: %d\n", dying); + if (!dying) + restore_and_detach(*(int*)v_pid); +} + +void invoker_restrictions_msg(void) +{ +} + +void invoker_valgrind_dying(void) +{ + /* Avoid messing up with registers of valgrind when it is dying. */ + pid_of_save_regs_continued = False; + dying = True; +} diff --git a/coregrind/vgdb.c b/coregrind/vgdb.c index 485ad5d1a..08633bbc4 100644 --- a/coregrind/vgdb.c +++ b/coregrind/vgdb.c @@ -26,34 +26,28 @@ The GNU General Public License is contained in the file COPYING. */ -#include "pub_core_basics.h" -#include "pub_core_vki.h" -#include "pub_core_libcsetjmp.h" -#include "pub_core_threadstate.h" -#include "pub_core_gdbserver.h" +#include "vgdb.h" + #include "config.h" +#include +#include +#include +#include #include -#include -#include #include #include +#include #include #include -#include -#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + /* vgdb has two usages: 1. relay application between gdb and the gdbserver embedded in valgrind. 2. standalone to send monitor commands to a running valgrind-ified process @@ -72,75 +66,18 @@ As a standalone utility, vgdb builds command packets to write to valgrind, sends it and reads the reply. The same two threads are used to write/read. Once all the commands are sent and their replies received, vgdb will exit. - */ -/* define PTRACEINVOKER to compile the ptrace related code - which ensures a valgrind process blocked in a system call - can be "waken up". PTRACEINVOKER implies some architecture - specific code and/or some OS specific code. */ -#if defined(VGA_arm) || defined(VGA_x86) || defined(VGA_amd64) \ - || defined(VGA_ppc32) || defined(VGA_ppc64) || defined(VGA_s390x) \ - || defined(VGA_mips32) || defined(VGA_mips64) -#define PTRACEINVOKER -#else -I_die_here : (PTRACEINVOKER) architecture missing in vgdb.c -#endif - -/* Some darwin specific stuff is needed as ptrace is not - fully supported on MacOS. Till we find someone courageous - having access to Darwin, there is no PTRACEINVOKER. */ -#if defined(VGO_darwin) -#undef PTRACEINVOKER -#endif - -#if defined(VGPV_arm_linux_android) || defined(VGPV_x86_linux_android) -#undef PTRACEINVOKER -#endif - -#if defined(PTRACEINVOKER) -#include -#endif - - -// Outputs information for the user about ptrace not working. -static void ptrace_restrictions_msg(void); - -static int debuglevel; -static struct timeval dbgtv; -/* if level <= debuglevel, print timestamp, then print provided by debug info */ -#define DEBUG(level, ...) (level <= debuglevel ? \ - gettimeofday(&dbgtv, NULL), \ - fprintf(stderr, "%ld.%6.6ld ", \ - (long int)dbgtv.tv_sec, \ - (long int)dbgtv.tv_usec), \ - fprintf(stderr, __VA_ARGS__),fflush(stderr) \ - : 0) - -/* same as DEBUG but does not print time stamp info */ -#define PDEBUG(level, ...) (level <= debuglevel ? \ - fprintf(stderr, __VA_ARGS__),fflush(stderr) \ - : 0) - -/* if errno != 0, - report the errno and fprintf the ... varargs on stderr. */ -#define ERROR(errno, ...) ((errno == 0 ? 0 : perror("syscall failed")), \ - fprintf(stderr, __VA_ARGS__), \ - fflush(stderr)) -/* same as ERROR, but also exits with status 1 */ -#define XERROR(errno, ...) ((errno == 0 ? 0 : perror("syscall failed")), \ - fprintf(stderr, __VA_ARGS__), \ - fflush(stderr), \ - exit(1)) - +int debuglevel; +struct timeval dbgtv; static char *vgdb_prefix = NULL; /* Will be set to True when any condition indicating we have to shutdown is encountered. */ -static Bool shutting_down = False; +Bool shutting_down = False; -static VgdbShared32 *shared32; -static VgdbShared64 *shared64; +VgdbShared32 *shared32; +VgdbShared64 *shared64; #define VS_written_by_vgdb (shared32 != NULL ? \ shared32->written_by_vgdb \ : shared64->written_by_vgdb) @@ -266,898 +203,11 @@ void map_vgdbshared (char* shared_mem) } -#if VEX_HOST_WORDSIZE == 8 -typedef Addr64 CORE_ADDR; -typedef Addr64 PTRACE_XFER_TYPE; -typedef void* PTRACE_ARG3_TYPE; -#elif VEX_HOST_WORDSIZE == 4 -typedef Addr32 CORE_ADDR; -typedef Addr32 PTRACE_XFER_TYPE; -typedef void* PTRACE_ARG3_TYPE; -#else -# error "unexpected wordsize" -#endif - -static Bool pid_of_save_regs_continued = False; -// True if we have continued pid_of_save_regs after PTRACE_ATTACH - -static Bool dying = False; -// Set to True when loss of connection indicating that the Valgrind -// process is dying. - -/* To be called when connection with valgrind is lost. In case we -have lost the connection, it means that Valgrind has closed the -connection and is busy exiting. We can't and don't have to stop it in -this case. */ -static -void valgrind_dying(void) -{ - pid_of_save_regs_continued = False; - dying = True; -} - - -#ifdef PTRACEINVOKER -/* ptrace_(read|write)_memory are modified extracts of linux-low.c - from gdb 6.6. Copyrighted FSF */ -/* Copy LEN bytes from valgrind memory starting at MEMADDR - to vgdb memory starting at MYADDR. */ - -static -int ptrace_read_memory (pid_t inferior_pid, CORE_ADDR memaddr, - void *myaddr, int len) -{ - register int i; - /* Round starting address down to longword boundary. */ - register CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_XFER_TYPE); - /* Round ending address up; get number of longwords that makes. */ - register int count - = (((memaddr + len) - addr) + sizeof (PTRACE_XFER_TYPE) - 1) - / sizeof (PTRACE_XFER_TYPE); - /* Allocate buffer of that many longwords. */ - register PTRACE_XFER_TYPE *buffer - = (PTRACE_XFER_TYPE *) alloca (count * sizeof (PTRACE_XFER_TYPE)); - - /* Read all the longwords */ - for (i = 0; i < count; i++, addr += sizeof (PTRACE_XFER_TYPE)) { - errno = 0; - buffer[i] = ptrace (PTRACE_PEEKTEXT, inferior_pid, - (PTRACE_ARG3_TYPE) addr, 0); - if (errno) - return errno; - } - - /* Copy appropriate bytes out of the buffer. */ - memcpy (myaddr, - (char *) buffer + (memaddr & (sizeof (PTRACE_XFER_TYPE) - 1)), len); - - return 0; -} - -/* Copy LEN bytes of data from vgdb memory at MYADDR - to valgrind memory at MEMADDR. - On failure (cannot write the valgrind memory) - returns the value of errno. */ -__attribute__((unused)) /* not used on all platforms */ -static -int ptrace_write_memory (pid_t inferior_pid, CORE_ADDR memaddr, - const void *myaddr, int len) -{ - register int i; - /* Round starting address down to longword boundary. */ - register CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_XFER_TYPE); - /* Round ending address up; get number of longwords that makes. */ - register int count - = (((memaddr + len) - addr) + sizeof (PTRACE_XFER_TYPE) - 1) - / sizeof (PTRACE_XFER_TYPE); - /* Allocate buffer of that many longwords. */ - register PTRACE_XFER_TYPE *buffer - = (PTRACE_XFER_TYPE *) alloca (count * sizeof (PTRACE_XFER_TYPE)); - - if (debuglevel >= 1) { - DEBUG (1, "Writing "); - for (i = 0; i < len; i++) - PDEBUG (1, "%02x", ((const unsigned char*)myaddr)[i]); - PDEBUG(1, " to %p\n", (void *) memaddr); - } - - /* Fill start and end extra bytes of buffer with existing memory data. */ - - buffer[0] = ptrace (PTRACE_PEEKTEXT, inferior_pid, - (PTRACE_ARG3_TYPE) addr, 0); - - if (count > 1) { - buffer[count - 1] - = ptrace (PTRACE_PEEKTEXT, inferior_pid, - (PTRACE_ARG3_TYPE) (addr + (count - 1) - * sizeof (PTRACE_XFER_TYPE)), - 0); - } - - /* Copy data to be written over corresponding part of buffer */ - - memcpy ((char *) buffer + (memaddr & (sizeof (PTRACE_XFER_TYPE) - 1)), - myaddr, len); - - /* Write the entire buffer. */ - - for (i = 0; i < count; i++, addr += sizeof (PTRACE_XFER_TYPE)) { - errno = 0; - ptrace (PTRACE_POKETEXT, inferior_pid, - (PTRACE_ARG3_TYPE) addr, buffer[i]); - if (errno) - return errno; - } - - return 0; -} - -/* subset of VG_(threads) needed for vgdb ptrace. - This is initialized when process is attached. */ -typedef struct { - ThreadStatus status; - Int lwpid; -} -VgdbThreadState; -static VgdbThreadState vgdb_threads[VG_N_THREADS]; - -static const -HChar* name_of_ThreadStatus ( ThreadStatus status ) -{ - switch (status) { - case VgTs_Empty: return "VgTs_Empty"; - case VgTs_Init: return "VgTs_Init"; - case VgTs_Runnable: return "VgTs_Runnable"; - case VgTs_WaitSys: return "VgTs_WaitSys"; - case VgTs_Yielding: return "VgTs_Yielding"; - case VgTs_Zombie: return "VgTs_Zombie"; - default: return "VgTs_???"; - } -} - -static -char *status_image (int status) -{ - static char result[256]; - int sz = 0; -#define APPEND(...) sz += snprintf (result+sz, 256 - sz - 1, __VA_ARGS__) - - result[0] = 0; - - if (WIFEXITED(status)) - APPEND ("WIFEXITED %d ", WEXITSTATUS(status)); - - if (WIFSIGNALED(status)) { - APPEND ("WIFSIGNALED %d ", WTERMSIG(status)); - if (WCOREDUMP(status)) APPEND ("WCOREDUMP "); - } - - if (WIFSTOPPED(status)) - APPEND ("WIFSTOPPED %d ", WSTOPSIG(status)); - -#ifdef WIFCONTINUED - if (WIFCONTINUED(status)) - APPEND ("WIFCONTINUED "); -#endif - - return result; -#undef APPEND -} - -/* Wait till the process pid is reported as stopped with signal_expected. - If other signal(s) than signal_expected are received, waitstopped - will pass them to pid, waiting for signal_expected to stop pid. - Returns True when process is in stopped state with signal_expected. - Returns False if a problem was encountered while waiting for pid - to be stopped. - - If pid is reported as being dead/exited, waitstopped will return False. -*/ -static -Bool waitstopped (int pid, int signal_expected, const char *msg) -{ - pid_t p; - int status = 0; - int signal_received; - int res; - - while (1) { - DEBUG(1, "waitstopped %s before waitpid signal_expected %d\n", - msg, signal_expected); - p = waitpid(pid, &status, __WALL); - DEBUG(1, "after waitpid pid %d p %d status 0x%x %s\n", pid, p, - status, status_image (status)); - if (p != pid) { - ERROR(errno, "%s waitpid pid %d in waitstopped %d status 0x%x %s\n", - msg, pid, p, status, status_image (status)); - return False; - } - - if (WIFEXITED(status)) { - shutting_down = True; - return False; - } - - assert (WIFSTOPPED(status)); - signal_received = WSTOPSIG(status); - if (signal_received == signal_expected) - break; - - /* pid received a signal which is not the signal we are waiting for. - We continue pid, transmitting this signal. */ - DEBUG(1, "waitstopped PTRACE_CONT with signal %d\n", signal_received); - res = ptrace (PTRACE_CONT, pid, NULL, signal_received); - if (res != 0) { - ERROR(errno, "waitstopped PTRACE_CONT\n"); - return False; - } - } - - return True; -} - -/* Stops the given pid, wait for the process to be stopped. - Returns True if succesful, False otherwise. - msg is used in tracing and error reporting. */ -static -Bool stop (int pid, const char *msg) -{ - long res; - - DEBUG(1, "%s SIGSTOP pid %d\n", msg, pid); - res = kill (pid, SIGSTOP); - if (res != 0) { - ERROR(errno, "%s SIGSTOP pid %d %ld\n", msg, pid, res); - return False; - } - - return waitstopped (pid, SIGSTOP, msg); - -} - -/* Attaches to given pid, wait for the process to be stopped. - Returns True if succesful, False otherwise. - msg is used in tracing and error reporting. */ -static -Bool attach (int pid, const char *msg) -{ - long res; - static Bool output_error = True; - static Bool initial_attach = True; - // For a ptrace_scope protected system, we do not want to output - // repetitively attach error. We will output once an error - // for the initial_attach. Once the 1st attach has succeeded, we - // again show all errors. - - DEBUG(1, "%s PTRACE_ATTACH pid %d\n", msg, pid); - res = ptrace (PTRACE_ATTACH, pid, NULL, NULL); - if (res != 0) { - if (output_error || debuglevel > 0) { - ERROR(errno, "%s PTRACE_ATTACH pid %d %ld\n", msg, pid, res); - if (initial_attach) - output_error = False; - } - return False; - } - - initial_attach = False; - output_error = True; - return waitstopped(pid, SIGSTOP, msg); -} - -/* once we are attached to the pid, get the list of threads and stop - them all. - Returns True if all threads properly suspended, False otherwise. */ -static -Bool acquire_and_suspend_threads(int pid) -{ - int i; - int rw; - Bool pid_found = False; - Addr vgt; - int sz_tst; - int off_status; - int off_lwpid; - int nr_live_threads = 0; - - if (shared32 != NULL) { - vgt = shared32->threads; - sz_tst = shared32->sizeof_ThreadState; - off_status = shared32->offset_status; - off_lwpid = shared32->offset_lwpid; - } - else if (shared64 != NULL) { - vgt = shared64->threads; - sz_tst = shared64->sizeof_ThreadState; - off_status = shared64->offset_status; - off_lwpid = shared64->offset_lwpid; - } else { - assert (0); - } - - /* note: the entry 0 is unused */ - for (i = 1; i < VG_N_THREADS; i++) { - vgt += sz_tst; - rw = ptrace_read_memory(pid, vgt+off_status, - &(vgdb_threads[i].status), - sizeof(ThreadStatus)); - if (rw != 0) { - ERROR(rw, "status ptrace_read_memory\n"); - return False; - } - - rw = ptrace_read_memory(pid, vgt+off_lwpid, - &(vgdb_threads[i].lwpid), - sizeof(Int)); - if (rw != 0) { - ERROR(rw, "lwpid ptrace_read_memory\n"); - return False; - } - - if (vgdb_threads[i].status != VgTs_Empty) { - DEBUG(1, "found tid %d status %s lwpid %d\n", - i, name_of_ThreadStatus(vgdb_threads[i].status), - vgdb_threads[i].lwpid); - nr_live_threads++; - if (vgdb_threads[i].lwpid <= 1) { - if (vgdb_threads[i].lwpid == 0 - && vgdb_threads[i].status == VgTs_Init) { - DEBUG(1, "not set lwpid tid %d status %s lwpid %d\n", - i, name_of_ThreadStatus(vgdb_threads[i].status), - vgdb_threads[i].lwpid); - } else { - ERROR(1, "unexpected lwpid tid %d status %s lwpid %d\n", - i, name_of_ThreadStatus(vgdb_threads[i].status), - vgdb_threads[i].lwpid); - } - /* in case we have a VtTs_Init thread with lwpid not yet set, - we try again later. */ - return False; - } - if (vgdb_threads[i].lwpid == pid) { - assert (!pid_found); - assert (i == 1); - pid_found = True; - } else { - if (!attach(vgdb_threads[i].lwpid, "attach_thread")) { - ERROR(0, "ERROR attach pid %d tid %d\n", - vgdb_threads[i].lwpid, i); - return False; - } - } - } - } - /* If we found no thread, it means the process is stopping, and - we better do not force anything to happen during that. */ - if (nr_live_threads > 0) - return True; - else - return False; -} - -static -void detach_from_all_threads(int pid) -{ - int i; - long res; - Bool pid_found = False; - - /* detach from all the threads */ - for (i = 1; i < VG_N_THREADS; i++) { - if (vgdb_threads[i].status != VgTs_Empty) { - if (vgdb_threads[i].status == VgTs_Init - && vgdb_threads[i].lwpid == 0) { - DEBUG(1, "skipping PTRACE_DETACH pid %d tid %d status %s\n", - vgdb_threads[i].lwpid, i, - name_of_ThreadStatus (vgdb_threads[i].status)); - } else { - if (vgdb_threads[i].lwpid == pid) { - assert (!pid_found); - pid_found = True; - } - DEBUG(1, "PTRACE_DETACH pid %d tid %d status %s\n", - vgdb_threads[i].lwpid, i, - name_of_ThreadStatus (vgdb_threads[i].status)); - res = ptrace (PTRACE_DETACH, vgdb_threads[i].lwpid, NULL, NULL); - if (res != 0) { - ERROR(errno, "PTRACE_DETACH pid %d tid %d status %s res %ld\n", - vgdb_threads[i].lwpid, i, - name_of_ThreadStatus (vgdb_threads[i].status), - res); - } - } - } - } - - if (!pid_found && pid) { - /* No threads are live. Process is busy stopping. - We need to detach from pid explicitely. */ - DEBUG(1, "no thread live => PTRACE_DETACH pid %d\n", pid); - res = ptrace (PTRACE_DETACH, pid, NULL, NULL); - if (res != 0) - ERROR(errno, "PTRACE_DETACH pid %d res %ld\n", pid, res); - } -} - -// if > 0, pid for which registers have to be restored. -static int pid_of_save_regs = 0; -static struct user user_save; - -// The below indicates if ptrace_getregs (and ptrace_setregs) can be used. -// Note that some linux versions are defining PTRACE_GETREGS but using -// it gives back EIO. -// has_working_ptrace_getregs can take the following values: -// -1 : PTRACE_GETREGS is defined -// runtime check not yet done. -// 0 : PTRACE_GETREGS runtime check has failed. -// 1 : PTRACE_GETREGS defined and runtime check ok. -#ifdef HAVE_PTRACE_GETREGS -static int has_working_ptrace_getregs = -1; -#endif - -/* Get the registers from pid into regs. - regs_bsz value gives the length of *regs. - Returns True if all ok, otherwise False. */ -static -Bool getregs (int pid, void *regs, long regs_bsz) -{ - DEBUG(1, "getregs regs_bsz %ld\n", regs_bsz); -# ifdef HAVE_PTRACE_GETREGS - if (has_working_ptrace_getregs) { - // Platforms having GETREGS - long res; - DEBUG(1, "getregs PTRACE_GETREGS\n"); - res = ptrace (PTRACE_GETREGS, pid, NULL, regs); - if (res == 0) { - if (has_working_ptrace_getregs == -1) { - // First call to PTRACE_GETREGS succesful => - has_working_ptrace_getregs = 1; - DEBUG(1, "detected a working PTRACE_GETREGS\n"); - } - assert (has_working_ptrace_getregs == 1); - return True; - } - else if (has_working_ptrace_getregs == 1) { - // We had a working call, but now it fails. - // This is unexpected. - ERROR(errno, "PTRACE_GETREGS %ld\n", res); - return False; - } else { - // Check this is the first call: - assert (has_working_ptrace_getregs == -1); - if (errno == EIO) { - DEBUG(1, "detected a broken PTRACE_GETREGS with EIO\n"); - has_working_ptrace_getregs = 0; - // Fall over to the PTRACE_PEEKUSER case. - } else { - ERROR(errno, "broken PTRACE_GETREGS unexpected errno %ld\n", res); - return False; - } - } - } -# endif - - // We assume PTRACE_PEEKUSER is defined everywhere. - { -# ifdef PT_ENDREGS - long peek_bsz = PT_ENDREGS; - assert (peek_bsz <= regs_bsz); -# else - long peek_bsz = regs_bsz-1; -# endif - char *pregs = (char *) regs; - long offset; - errno = 0; - DEBUG(1, "getregs PTRACE_PEEKUSER(s) peek_bsz %ld\n", peek_bsz); - for (offset = 0; offset < peek_bsz; offset = offset + sizeof(long)) { - *(long *)(pregs+offset) = ptrace(PTRACE_PEEKUSER, pid, offset, NULL); - if (errno != 0) { - ERROR(errno, "PTRACE_PEEKUSER offset %ld\n", offset); - return False; - } - } - return True; - } - - // If neither PTRACE_GETREGS not PTRACE_PEEKUSER have returned, - // then we are in serious trouble. - assert (0); -} - -/* Set the registers of pid to regs. - regs_bsz value gives the length of *regs. - Returns True if all ok, otherwise False. */ -static -Bool setregs (int pid, void *regs, long regs_bsz) -{ - DEBUG(1, "setregs regs_bsz %ld\n", regs_bsz); -// Note : the below is checking for GETREGS, not SETREGS -// as if one is defined and working, the other one should also work. -# ifdef HAVE_PTRACE_GETREGS - if (has_working_ptrace_getregs) { - // Platforms having SETREGS - long res; - // setregs can never be called before getregs has done a runtime check. - assert (has_working_ptrace_getregs == 1); - DEBUG(1, "setregs PTRACE_SETREGS\n"); - res = ptrace (PTRACE_SETREGS, pid, NULL, regs); - if (res != 0) { - ERROR(errno, "PTRACE_SETREGS %ld\n", res); - return False; - } - return True; - } -# endif - - { - char *pregs = (char *) regs; - long offset; - long res; -# ifdef PT_ENDREGS - long peek_bsz = PT_ENDREGS; - assert (peek_bsz <= regs_bsz); -# else - long peek_bsz = regs_bsz-1; -# endif - errno = 0; - DEBUG(1, "setregs PTRACE_POKEUSER(s) %ld\n", peek_bsz); - for (offset = 0; offset < peek_bsz; offset = offset + sizeof(long)) { - res = ptrace(PTRACE_POKEUSER, pid, offset, *(long*)(pregs+offset)); - if (errno != 0) { - ERROR(errno, "PTRACE_POKEUSER offset %ld res %ld\n", offset, res); - return False; - } - } - return True; - } - - // If neither PTRACE_SETREGS not PTRACE_POKEUSER have returned, - // then we are in serious trouble. - assert (0); -} - -/* Restore the registers to the saved value, then detaches from all threads */ -static -void restore_and_detach(int pid) -{ - if (pid_of_save_regs) { - /* In case the 'main pid' has been continued, we need to stop it - before resetting the registers. */ - if (pid_of_save_regs_continued) { - pid_of_save_regs_continued = False; - if (!stop(pid_of_save_regs, "sigstop before reset regs")) - DEBUG(0, "Could not sigstop before reset"); - } - - DEBUG(1, "setregs restore registers pid %d\n", pid_of_save_regs); - if (!setregs(pid_of_save_regs, &user_save.regs, sizeof(user_save.regs))) { - ERROR(errno, "setregs restore registers pid %d after cont\n", - pid_of_save_regs); - } - pid_of_save_regs = 0; - } else { - DEBUG(1, "PTRACE_SETREGS restore registers: no pid\n"); - } - detach_from_all_threads(pid); -} - -/* Ensures that the gdbserver code is invoked by pid. - If an error occurs, resets to the valgrind process - to the state it has before being ptrace-d. - Returns True if invoke successful, False otherwise. -*/ -static -Bool invoke_gdbserver (int pid) -{ - static Bool ptrace_restrictions_msg_given = False; - long res; - Bool stopped; - struct user user_mod; - Addr sp; - /* A specific int value is passed to invoke_gdbserver, to check - everything goes according to the plan. */ - const int check = 0x8BADF00D; // ate bad food. - - const Addr bad_return = 0; - // A bad return address will be pushed on the stack. - // The function invoke_gdbserver cannot return. If ever it returns, a NULL - // address pushed on the stack should ensure this is detected. - - /* Not yet attached. If problem, vgdb can abort, - no cleanup needed. */ - - DEBUG(1, "attach to 'main' pid %d\n", pid); - if (!attach(pid, "attach main pid")) { - if (!ptrace_restrictions_msg_given) { - ptrace_restrictions_msg_given = True; - ERROR(0, "error attach main pid %d\n", pid); - ptrace_restrictions_msg(); - } - return False; - } - - /* Now, we are attached. If problem, detach and return. */ - - if (!acquire_and_suspend_threads(pid)) { - detach_from_all_threads(pid); - /* if the pid does not exist anymore, we better stop */ - if (kill(pid, 0) != 0) - XERROR (errno, "invoke_gdbserver: check for pid %d existence failed\n", - pid); - return False; - } - - if (!getregs(pid, &user_mod.regs, sizeof(user_mod.regs))) { - detach_from_all_threads(pid); - return False; - } - user_save = user_mod; - -#if defined(VGA_x86) - sp = user_mod.regs.esp; -#elif defined(VGA_amd64) - sp = user_mod.regs.rsp; - if (shared32 != NULL) { - /* 64bit vgdb speaking with a 32bit executable. - To have system call restart properly, we need to sign extend rax. - For more info: - web search '[patch] Fix syscall restarts for amd64->i386 biarch' - e.g. http://sourceware.org/ml/gdb-patches/2009-11/msg00592.html */ - *(long *)&user_save.regs.rax = *(int*)&user_save.regs.rax; - DEBUG(1, "Sign extending %8.8lx to %8.8lx\n", - user_mod.regs.rax, user_save.regs.rax); - } -#elif defined(VGA_arm) - sp = user_mod.regs.uregs[13]; -#elif defined(VGA_ppc32) - sp = user_mod.regs.gpr[1]; -#elif defined(VGA_ppc64) - sp = user_mod.regs.gpr[1]; -#elif defined(VGA_s390x) - sp = user_mod.regs.gprs[15]; -#elif defined(VGA_mips32) - long long *p = (long long *)user_mod.regs; - sp = p[29]; -#elif defined(VGA_mips64) - sp = user_mod.regs[29]; -#else - I_die_here : (sp) architecture missing in vgdb.c -#endif - - - // the magic below is derived from spying what gdb sends to - // the (classical) gdbserver when invoking a C function. - if (shared32 != NULL) { - // vgdb speaking with a 32bit executable. -#if defined(VGA_x86) || defined(VGA_amd64) - const int regsize = 4; - int rw; - /* push check arg on the stack */ - sp = sp - regsize; - DEBUG(1, "push check arg ptrace_write_memory\n"); - assert(regsize == sizeof(check)); - rw = ptrace_write_memory(pid, sp, - &check, - regsize); - if (rw != 0) { - ERROR(rw, "push check arg ptrace_write_memory"); - detach_from_all_threads(pid); - return False; - } - - sp = sp - regsize; - DEBUG(1, "push bad_return return address ptrace_write_memory\n"); - // Note that for a 64 bits vgdb, only 4 bytes of NULL bad_return - // are written. - rw = ptrace_write_memory(pid, sp, - &bad_return, - regsize); - if (rw != 0) { - ERROR(rw, "push bad_return return address ptrace_write_memory"); - detach_from_all_threads(pid); - return False; - } -#if defined(VGA_x86) - /* set ebp, esp, eip and orig_eax to invoke gdbserver */ - // compiled in 32bits, speaking with a 32bits exe - user_mod.regs.ebp = sp; // bp set to sp - user_mod.regs.esp = sp; - user_mod.regs.eip = shared32->invoke_gdbserver; - user_mod.regs.orig_eax = -1L; -#elif defined(VGA_amd64) - /* set ebp, esp, eip and orig_eax to invoke gdbserver */ - // compiled in 64bits, speaking with a 32bits exe - user_mod.regs.rbp = sp; // bp set to sp - user_mod.regs.rsp = sp; - user_mod.regs.rip = shared32->invoke_gdbserver; - user_mod.regs.orig_rax = -1L; -#else - I_die_here : not x86 or amd64 in x86/amd64 section/ -#endif - -#elif defined(VGA_ppc32) || defined(VGA_ppc64) - user_mod.regs.nip = shared32->invoke_gdbserver; - user_mod.regs.trap = -1L; - /* put check arg in register 3 */ - user_mod.regs.gpr[3] = check; - /* put NULL return address in Link Register */ - user_mod.regs.link = bad_return; - -#elif defined(VGA_arm) - /* put check arg in register 0 */ - user_mod.regs.uregs[0] = check; - /* put NULL return address in Link Register */ - user_mod.regs.uregs[14] = bad_return; - user_mod.regs.uregs[15] = shared32->invoke_gdbserver; - -#elif defined(VGA_s390x) - XERROR(0, "(fn32) s390x has no 32bits implementation"); -#elif defined(VGA_mips32) - /* put check arg in register 4 */ - p[4] = check; - /* put NULL return address in ra */ - p[31] = bad_return; - p[34] = shared32->invoke_gdbserver; - p[25] = shared32->invoke_gdbserver; - /* make stack space for args */ - p[29] = sp - 32; - -#elif defined(VGA_mips64) - assert(0); // cannot vgdb a 32 bits executable with a 64 bits exe -#else - I_die_here : architecture missing in vgdb.c -#endif - } - - else if (shared64 != NULL) { -#if defined(VGA_x86) - assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe -#elif defined(VGA_amd64) - // vgdb speaking with a 64 bit executable. - const int regsize = 8; - int rw; - - /* give check arg in rdi */ - user_mod.regs.rdi = check; - - /* push return address on stack : return to breakaddr */ - sp = sp - regsize; - DEBUG(1, "push bad_return return address ptrace_write_memory\n"); - rw = ptrace_write_memory(pid, sp, - &bad_return, - sizeof(bad_return)); - if (rw != 0) { - ERROR(rw, "push bad_return return address ptrace_write_memory"); - detach_from_all_threads(pid); - return False; - } - - /* set rbp, rsp, rip and orig_rax to invoke gdbserver */ - user_mod.regs.rbp = sp; // bp set to sp - user_mod.regs.rsp = sp; - user_mod.regs.rip = shared64->invoke_gdbserver; - user_mod.regs.orig_rax = -1L; - -#elif defined(VGA_arm) - assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe -#elif defined(VGA_ppc32) - assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe -#elif defined(VGA_ppc64) - Addr64 func_addr; - Addr64 toc_addr; - int rw; - rw = ptrace_read_memory(pid, shared64->invoke_gdbserver, - &func_addr, - sizeof(Addr64)); - if (rw != 0) { - ERROR(rw, "ppc64 read func_addr\n"); - detach_from_all_threads(pid); - return False; - } - rw = ptrace_read_memory(pid, shared64->invoke_gdbserver+8, - &toc_addr, - sizeof(Addr64)); - if (rw != 0) { - ERROR(rw, "ppc64 read toc_addr\n"); - detach_from_all_threads(pid); - return False; - } - // We are not pushing anything on the stack, so it is not - // very clear why the sp has to be decreased, but it seems - // needed. The ppc64 ABI might give some lights on this ? - user_mod.regs.gpr[1] = sp - 220; - user_mod.regs.gpr[2] = toc_addr; - user_mod.regs.nip = func_addr; - user_mod.regs.trap = -1L; - /* put check arg in register 3 */ - user_mod.regs.gpr[3] = check; - /* put bad_return return address in Link Register */ - user_mod.regs.link = bad_return; -#elif defined(VGA_s390x) - /* put check arg in register r2 */ - user_mod.regs.gprs[2] = check; - /* bad_return Return address is in r14 */ - user_mod.regs.gprs[14] = bad_return; - /* minimum stack frame */ - sp = sp - 160; - user_mod.regs.gprs[15] = sp; - /* set program counter */ - user_mod.regs.psw.addr = shared64->invoke_gdbserver; -#elif defined(VGA_mips32) - assert(0); // cannot vgdb a 64 bits executable with a 32 bits exe -#elif defined(VGA_mips64) - /* put check arg in register 4 */ - user_mod.regs[4] = check; - /* put NULL return address in ra */ - user_mod.regs[31] = bad_return; - user_mod.regs[34] = shared64->invoke_gdbserver; - user_mod.regs[25] = shared64->invoke_gdbserver; -#else - I_die_here: architecture missing in vgdb.c -#endif - } - else { - assert(0); - } - - if (!setregs(pid, &user_mod.regs, sizeof(user_mod.regs))) { - detach_from_all_threads(pid); - return False; - } - /* Now that we have modified the registers, we set - pid_of_save_regs to indicate that restore_and_detach - must restore the registers in case of cleanup. */ - pid_of_save_regs = pid; - pid_of_save_regs_continued = False; - - - /* We PTRACE_CONT-inue pid. - Either gdbserver will be invoked directly (if all - threads are interruptible) or gdbserver will be - called soon by the scheduler. In the first case, - pid will stop on the break inserted above when - gdbserver returns. In the 2nd case, the break will - be encountered directly. */ - DEBUG(1, "PTRACE_CONT to invoke\n"); - res = ptrace (PTRACE_CONT, pid, NULL, NULL); - if (res != 0) { - ERROR(errno, "PTRACE_CONT\n"); - restore_and_detach(pid); - return False; - } - pid_of_save_regs_continued = True; - /* Wait for SIGSTOP generated by m_gdbserver.c give_control_back_to_vgdb */ - stopped = waitstopped (pid, SIGSTOP, - "waitpid status after PTRACE_CONT to invoke"); - if (stopped) { - /* Here pid has properly stopped on the break. */ - pid_of_save_regs_continued = False; - restore_and_detach(pid); - return True; - } else { - /* Whatever kind of problem happened. We shutdown */ - shutting_down = True; - return False; - } -} -#endif - -static -void cleanup_restore_and_detach(void *v_pid) -{ - DEBUG(1, "cleanup_restore_and_detach dying: %d\n", dying); -#ifdef PTRACEINVOKER - if (!dying) - restore_and_detach(*(int*)v_pid); -#endif -} - /* This function loops till shutting_down becomes true. In this loop, it verifies if valgrind process is reading the characters written by vgdb. The verification is done every max_invoke_ms ms. If - valgrind is not reading characters, it will use invoke_gdbserver - (if PTRACE_INVOKER is defined) to ensure that the gdbserver code is - called soon by valgrind. */ + valgrind is not reading characters, it will use invoker_invoke_gdbserver + to ensure that the gdbserver code is called soon by valgrind. */ static int max_invoke_ms = 100; #define NEVER 99999999 static int cmd_time_out = NEVER; @@ -1175,7 +225,7 @@ void *invoke_gdbserver_in_valgrind(void *v_pid) int invoked_written = -1; unsigned int usecs; - pthread_cleanup_push(cleanup_restore_and_detach, v_pid); + pthread_cleanup_push(invoker_cleanup_restore_and_detach, v_pid); while (!shutting_down) { written_by_vgdb_before_sleep = VS_written_by_vgdb; @@ -1240,19 +290,15 @@ void *invoke_gdbserver_in_valgrind(void *v_pid) pid, cmd_time_out); } if (max_invoke_ms > 0 && timercmp (&now, &invoke_time, >=)) { - #if defined(PTRACEINVOKER) /* only need to wake up if the nr written has changed since last invoke. */ if (invoked_written != written_by_vgdb_before_sleep) { - if (invoke_gdbserver(pid)) { + if (invoker_invoke_gdbserver(pid)) { /* If invoke succesful, no need to invoke again for the same value of written_by_vgdb_before_sleep. */ invoked_written = written_by_vgdb_before_sleep; } } - #else - DEBUG(2, "invoke_gdbserver via ptrace not (yet) implemented\n"); - #endif } } else { // Something happened => restart timer check. @@ -1645,7 +691,7 @@ void received_signal (int signum) sigalrm++; #if defined(VGPV_arm_linux_android) || defined(VGPV_x86_linux_android) /* Android has no pthread_cancel. As it also does not have - PTRACE_INVOKER, there is no need for cleanup action. + an invoker implementation, there is no need for cleanup action. So, we just do nothing. */ DEBUG(1, "sigalrm received, no action on android\n"); #else @@ -1849,19 +895,19 @@ void gdb_relay (int pid) if (pollfds[ck].revents & POLLERR) { DEBUG(1, "connection %s fd %d POLLERR error condition\n", ppConnectionKind(ck), pollfds[ck].fd); - valgrind_dying(); + invoker_valgrind_dying(); shutting_down = True; } if (pollfds[ck].revents & POLLHUP) { DEBUG(1, "connection %s fd %d POLLHUP error condition\n", ppConnectionKind(ck), pollfds[ck].fd); - valgrind_dying(); + invoker_valgrind_dying(); shutting_down = True; } if (pollfds[ck].revents & POLLNVAL) { DEBUG(1, "connection %s fd %d POLLNVAL error condition\n", ppConnectionKind(ck), pollfds[ck].fd); - valgrind_dying(); + invoker_valgrind_dying(); shutting_down = True; } } @@ -1950,7 +996,7 @@ void standalone_send_commands(int pid, if (buflen < 0) { ERROR (0, "error reading packet\n"); if (buflen == -2) - valgrind_dying(); + invoker_valgrind_dying(); break; } if (strlen(buf) == 0) { @@ -2033,19 +1079,6 @@ void report_pid (int pid, Bool on_stdout) fflush((on_stdout ? stdout : stderr)); } -/* Possibly produces additional usage information documenting the - ptrace restrictions. */ -static -void ptrace_restrictions_msg(void) -{ -# ifndef PTRACEINVOKER - fprintf(stderr, - "Note: ptrace invoker not implemented\n" - "For more info: read user manual section" - " 'Limitations of the Valgrind gdbserver'\n"); -# endif -} - static void usage(void) { @@ -2083,7 +1116,7 @@ void usage(void) " To get help from the Valgrind gdbserver, use vgdb help\n" "\n", vgdb_prefix_default() ); - ptrace_restrictions_msg(); + invoker_restrictions_msg(); } /* If show_list, outputs on stdout the list of Valgrind processes with gdbserver activated. diff --git a/coregrind/vgdb.h b/coregrind/vgdb.h new file mode 100644 index 000000000..65b96a7ec --- /dev/null +++ b/coregrind/vgdb.h @@ -0,0 +1,103 @@ + +/*--------------------------------------------------------------------*/ +/*--- Declarations common for vgdb and implementations ---*/ +/*--- of vgdb-invoker. vgdb.h ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2011-2013 Philippe Waroquiers + + 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 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +#ifndef __VGDB_H +#define __VGDB_H + +#include "pub_core_basics.h" +#include "pub_core_vki.h" +#include "pub_core_gdbserver.h" + +#include + +extern int debuglevel; +extern struct timeval dbgtv; +/* if level <= debuglevel, print timestamp, then print provided by debug info */ +#define DEBUG(level, ...) (level <= debuglevel ? \ + gettimeofday(&dbgtv, NULL), \ + fprintf(stderr, "%ld.%6.6ld ", \ + (long int)dbgtv.tv_sec, \ + (long int)dbgtv.tv_usec), \ + fprintf(stderr, __VA_ARGS__),fflush(stderr) \ + : 0) + +/* same as DEBUG but does not print time stamp info */ +#define PDEBUG(level, ...) (level <= debuglevel ? \ + fprintf(stderr, __VA_ARGS__),fflush(stderr) \ + : 0) + +/* if errno != 0, + report the errno and fprintf the ... varargs on stderr. */ +#define ERROR(errno, ...) ((errno == 0 ? 0 : perror("syscall failed")), \ + fprintf(stderr, __VA_ARGS__), \ + fflush(stderr)) +/* same as ERROR, but also exits with status 1 */ +#define XERROR(errno, ...) ((errno == 0 ? 0 : perror("syscall failed")), \ + fprintf(stderr, __VA_ARGS__), \ + fflush(stderr), \ + exit(1)) + +/* Will be set to True when any condition indicating we have to shutdown + is encountered. */ +extern Bool shutting_down; + +extern VgdbShared32 *shared32; +extern VgdbShared64 *shared64; + +/*--------------------------------------------------------------------*/ +/*--- Below is vgdb-invoker interface which must be implemented by ---*/ +/*--- all vgdb-invoker implementations. ---*/ +/*--------------------------------------------------------------------*/ + +/* Possibly produces additional usage information documenting the + invoker restrictions. */ +void invoker_restrictions_msg(void); + +/* Restore the registers to the saved value, then detaches from all threads. + Used as a cleanup handler for thread cancellation. */ +void invoker_cleanup_restore_and_detach(void *v_pid); + +/* Ensures that the gdbserver code is invoked by pid. + If an error occurs, resets the valgrind process + to the state it had before being invoked. + Returns True if invoke successful, False otherwise. */ +Bool invoker_invoke_gdbserver(pid_t pid); + +/* Called when connection with valgrind is lost. In case we + have lost the connection, it means that Valgrind has closed the + connection and is busy exiting. We can't and don't have to stop it in + this case. */ +void invoker_valgrind_dying(void); + +#endif // __VGDB_H + +/*--------------------------------------------------------------------*/ +/*--- end ---*/ +/*--------------------------------------------------------------------*/ diff --git a/gdbserver_tests/make_local_links b/gdbserver_tests/make_local_links index 73a2f0017..cbdfef5e4 100755 --- a/gdbserver_tests/make_local_links +++ b/gdbserver_tests/make_local_links @@ -99,11 +99,11 @@ ln -f -s ../coregrind/vgdb gdbserver_tests/vgdb # if ptrace not implemented in vgdb or OS restricts the initial attach, # some tests would block for a loooonnnng time. if gdbserver_tests/vgdb --help 2>&1 | - grep -e 'ptrace invoker not implemented' > /dev/null + grep -e 'invoker not implemented' > /dev/null then - rm -f gdbserver_tests/vgdb.ptraceinvoker + rm -f gdbserver_tests/vgdb.invoker else - touch gdbserver_tests/vgdb.ptraceinvoker + touch gdbserver_tests/vgdb.invoker fi # cleanup the possibly big garbage previously collected output diff --git a/gdbserver_tests/mcinvokeRU.vgtest b/gdbserver_tests/mcinvokeRU.vgtest index 44a9e5f85..1d56b7fdb 100644 --- a/gdbserver_tests/mcinvokeRU.vgtest +++ b/gdbserver_tests/mcinvokeRU.vgtest @@ -4,8 +4,8 @@ prog: sleepers args: 1 0 1000000000 B-B-B-B- vgopts: --tool=memcheck --vgdb=yes --vgdb-prefix=./vgdb-prefix-mcinvokeRU stderr_filter: filter_make_empty -# as the Valgrind process is always busy, we do not need the vgdb.ptraceinvoker prereq. -# We even disable ptrace invoker to avoid spurious attach error message +# as the Valgrind process is always busy, we do not need the vgdb.invoker prereq. +# We even disable invoker to avoid spurious attach error message # on kernels where ptrace is restricted. progB: invoker argsB: 10 --vgdb-prefix=./vgdb-prefix-mcinvokeRU --max-invoke-ms=0 --wait=60 -c v.wait 0 diff --git a/gdbserver_tests/mcinvokeWS.vgtest b/gdbserver_tests/mcinvokeWS.vgtest index 24ecad45f..ced90fa18 100644 --- a/gdbserver_tests/mcinvokeWS.vgtest +++ b/gdbserver_tests/mcinvokeWS.vgtest @@ -4,7 +4,7 @@ prog: sleepers args: 1 10000000 0 -S-S-S-S vgopts: --tool=memcheck --vgdb=yes --vgdb-prefix=./vgdb-prefix-mcinvokeWS stderr_filter: filter_make_empty -prereq: test -f vgdb.ptraceinvoker +prereq: test -f vgdb.invoker progB: invoker argsB: 10 --vgdb-prefix=./vgdb-prefix-mcinvokeWS --wait=60 -c v.wait 0 # if the --wait is not enough, the test will fail or block diff --git a/gdbserver_tests/nlcontrolc.vgtest b/gdbserver_tests/nlcontrolc.vgtest index 64d21e317..8ff83555d 100644 --- a/gdbserver_tests/nlcontrolc.vgtest +++ b/gdbserver_tests/nlcontrolc.vgtest @@ -10,7 +10,7 @@ prog: sleepers args: 1000000000 1000000000 1000000000 BSBSBSBS vgopts: --tool=none --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-nlcontrolc stderr_filter: filter_stderr -prereq: test -e gdb -a -f vgdb.ptraceinvoker +prereq: test -e gdb -a -f vgdb.invoker progB: gdb argsB: --quiet -l 60 --nx ./sleepers stdinB: nlcontrolc.stdinB.gdb diff --git a/gdbserver_tests/nlsigvgdb.vgtest b/gdbserver_tests/nlsigvgdb.vgtest index 596cefabb..f488748e4 100644 --- a/gdbserver_tests/nlsigvgdb.vgtest +++ b/gdbserver_tests/nlsigvgdb.vgtest @@ -7,7 +7,7 @@ prog: sleepers args: 1 10000000 0 -S-S-S-S vgopts: --tool=none --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-nlsigvgdb stderr_filter: filter_stderr -prereq: test -e gdb -a -f vgdb.ptraceinvoker +prereq: test -e gdb -a -f vgdb.invoker progB: gdb argsB: --quiet -l 60 --nx ./sleepers stdinB: nlsigvgdb.stdinB.gdb