vgdb must not transmit signals when gdbserver has been ptrace-invoked.

Most of the time, Valgrind masks async signals, and polls for such
signals at regular interval.
There is a very narrow range of code (around client syscall logic)
where such signals are unmasked (as they must be able to interrupt
syscalls).
This is the only range of code where Valgrind is expecting to
receive such a signal.

When gdbserver is ptraced invoked, Valgrind is artificially made
to jump out of this code portion. Signals are not masked.
When ptraceing valgrind, vgdb will get these signals but cannot
transmit them immediately, otherwise they arrive in range
of code where they are not expected (causing internal error
in syscall logic) and/or causing gdbserver syscalls to be
interrupted.

3 solutions to solve that were looked at:
1. have the gdbserver code masking signals "as quickly as possible".
 Easy to implement, but this leaves still a small window
 of code where signals are not masked and would cause a problem.
2. have vgdb setting the SIGMASK of valgrind before invoking
  gdbserver.
  This would be easy to implement, but changing the SIGMASK
  of the ptrace-d process is only available on very recent kernels.
3. have vgdb queuing signals, and only transmitting them once
   gdbserver invocation is finished.
This 3rd solution has been implemented.



git-svn-id: svn://svn.valgrind.org/valgrind/trunk@13896
This commit is contained in:
Philippe Waroquiers 2014-04-15 22:35:23 +00:00
parent 958717766b
commit 74fb3e1c3e
12 changed files with 196 additions and 13 deletions

1
NEWS
View File

@ -77,6 +77,7 @@ where XXXXXX is the bug number as listed below.
n-i-bz Fix KVM_CREATE_IRQCHIP ioctl handling
n-i-bz s390x: Fix memory corruption for multithreaded applications
n-i-bz vex arm->IR: allow PC as basereg in some LDRD cases
n-i-bz internal error in Valgrind if vgdb transmit signals when ptrace invoked
Release 3.9.0 (31 October 2013)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -60,8 +60,18 @@ typedef void* PTRACE_ARG3_TYPE;
# error "unexpected wordsize"
#endif
// if > 0, pid for which registers have to be restored.
// if == 0, means we have not yet called setregs (or have already
// restored the registers).
static int pid_of_save_regs = 0;
/* True if we have continued pid_of_save_regs after PTRACE_ATTACH. */
static Bool pid_of_save_regs_continued = False;
// When setregs has been called to change the registers of pid_of_save_regs,
// vgdb cannot transmit the signals intercepted during ptrace.
// So, we queue them, and will deliver them when detaching.
// See function waitstopped for more info.
static int signal_queue_sz = 0;
static siginfo_t *signal_queue;
/* True when loss of connection indicating that the Valgrind
process is dying. */
@ -252,9 +262,44 @@ Bool waitstopped (pid_t pid, int signal_expected, const char *msg)
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 we have not (yet) changed the registers of the inferior
or we have (already) reset them, we can transmit the signal.
If we have already set the registers of the inferior, we cannot
transmit the signal, as this signal would arrive when the
gdbserver code runs. And valgrind only expects signals to
arrive in a small code portion around
client syscall logic, where signal are unmasked (see e.g.
m_syswrap/syscall-x86-linux.S ML_(do_syscall_for_client_WRK).
As ptrace is forcing a call to gdbserver by jumping
'out of this region', signals are not masked, but
will arrive outside of the allowed/expected code region.
So, if we have changed the registers of the inferior, we
rather queue the signal to transmit them when detaching,
after having restored the registers to the initial values. */
if (pid_of_save_regs) {
siginfo_t *newsiginfo;
// realloc a bigger queue, and store new signal at the end.
// This is not very efficient but we assume not many sigs are queued.
signal_queue_sz++;
signal_queue = vrealloc(signal_queue, sizeof(siginfo_t) * signal_queue_sz);
newsiginfo = signal_queue + (signal_queue_sz - 1);
res = ptrace (PTRACE_GETSIGINFO, pid, NULL, newsiginfo);
if (res != 0) {
ERROR(errno, "PTRACE_GETSIGINFO failed: signal lost !!!!\n");
signal_queue_sz--;
} else
DEBUG(1, "waitstopped PTRACE_CONT, queuing signal %d"
" si_signo %d si_pid %d\n",
signal_received, newsiginfo->si_signo, newsiginfo->si_pid);
res = ptrace (PTRACE_CONT, pid, NULL, 0);
} else {
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;
@ -447,8 +492,6 @@ void detach_from_all_threads (pid_t pid)
}
}
// 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.
@ -588,6 +631,11 @@ Bool setregs (pid_t pid, void *regs, long regs_bsz)
static
void restore_and_detach (pid_t pid)
{
int res;
DEBUG(1, "restore_and_detach pid %d pid_of_save_regs %d\n",
pid, pid_of_save_regs);
if (pid_of_save_regs) {
/* In case the 'main pid' has been continued, we need to stop it
before resetting the registers. */
@ -602,10 +650,32 @@ void restore_and_detach (pid_t pid)
ERROR(errno, "setregs restore registers pid %d after cont\n",
pid_of_save_regs);
}
/* Now, we transmit all the signals we have queued. */
if (signal_queue_sz > 0) {
int i;
for (i = 0; i < signal_queue_sz; i++) {
DEBUG(1, "PTRACE_CONT to transmit queued signal %d\n",
signal_queue[i].si_signo);
res = ptrace (PTRACE_CONT, pid_of_save_regs, NULL,
signal_queue[i].si_signo);
if (res != 0)
ERROR(errno, "PTRACE_CONT with signal %d\n",
signal_queue[i].si_signo);
if (!stop(pid_of_save_regs, "sigstop after transmit sig"))
DEBUG(0, "Could not sigstop after transmit sig");
}
free (signal_queue);
signal_queue = NULL;
signal_queue_sz = 0;
}
pid_of_save_regs = 0;
} else {
DEBUG(1, "PTRACE_SETREGS restore registers: no pid\n");
}
if (signal_queue)
ERROR (0, "One or more signals queued were not delivered. "
"First signal: %d\n", signal_queue);
detach_from_all_threads(pid);
}

View File

@ -87,8 +87,6 @@ VgdbShared64 *shared64;
#define VS_vgdb_pid (shared32 != NULL ? shared32->vgdb_pid : shared64->vgdb_pid)
/* Calls malloc (size). Exits if memory can't be allocated. */
static
void *vmalloc(size_t size)
{
void * mem = malloc(size);
@ -97,8 +95,6 @@ void *vmalloc(size_t size)
return mem;
}
/* Calls realloc (size). Exits if memory can't be allocated. */
static
void *vrealloc(void *ptr,size_t size)
{
void * mem = realloc(ptr, size);

View File

@ -64,6 +64,11 @@ extern struct timeval dbgtv;
fflush(stderr), \
exit(1))
/* Calls malloc (size). Exits if memory can't be allocated. */
extern void *vmalloc(size_t size);
/* Calls realloc (size). Exits if memory can't be allocated. */
extern void *vrealloc(void *ptr,size_t size);
/* Will be set to True when any condition indicating we have to shutdown
is encountered. */
extern Bool shutting_down;

View File

@ -119,7 +119,12 @@ EXTRA_DIST = \
nlsigvgdb.vgtest \
nlsigvgdb.stderr.exp \
nlsigvgdb.stderrB.exp \
nlsigvgdb.stdinB.gdb
nlsigvgdb.stdinB.gdb \
nlvgdbsigqueue.vgtest \
nlvgdbsigqueue.stderrB.exp \
nlvgdbsigqueue.stderr.exp \
nlvgdbsigqueue.stdinB.gdb \
nlvgdbsigqueue.stdoutB.exp
check_PROGRAMS = \
clean_after_fork \

View File

@ -0,0 +1,11 @@
Nulgrind, the minimal Valgrind tool
(action at startup) vgdb me ...
loops/sleep_ms/burn/threads_spec: 1000000000 1000000000 1 BSBSBSBS
Brussels ready to sleep and/or burn
London ready to sleep and/or burn
Petaouchnok ready to sleep and/or burn
main ready to sleep and/or burn
Gdb request to kill this process

View File

@ -0,0 +1,29 @@
# connect gdb to Valgrind gdbserver:
target remote | ./vgdb --wait=60 --vgdb-prefix=./vgdb-prefix-nlvgdbsigqueue
echo vgdb launched process attached\n
monitor v.set vgdb-error 999999
#
#
# simulate control-c in a 1 second
shell ./simulate_control_c --vgdb-prefix=./vgdb-prefix-nlvgdbsigqueue 1 grep main nlvgdbsigqueue.stderr.out
#
# send SIGUSR1/SIGUSR1 in a few seconds, when vgdb is attached
shell ./send_signal USR1 --vgdb-prefix=./vgdb-prefix-nlvgdbsigqueue 2
shell ./send_signal USR1 --vgdb-prefix=./vgdb-prefix-nlvgdbsigqueue 3
#
echo continuing to have vgdb interrupted by simulate_control_c\n
continue
#
# Now vgdb should have received the interrupt, and so has
# attached to the sleeping process.
# wait for the USR sig to be sent, that will be queued by vgdb.
shell sleep 4
# continue, so as to have vgdb sending queued signals when PTRACE_DETACHing
echo continuing to receive first SIGUSR1\n
continue
# simulate a control c to afterwards stop the execution
shell ./simulate_control_c --vgdb-prefix=./vgdb-prefix-nlvgdbsigqueue 1
echo continuing to receive second SIGUSR1\n
continue
kill
quit

View File

@ -0,0 +1,14 @@
continuing to have vgdb interrupted by simulate_control_c
Continuing.
Program received signal SIGUSR1, User defined signal 1.
0x........ in syscall ...
continuing to receive first SIGUSR1
Continuing.
[New Thread ....]
Program received signal SIGUSR1, User defined signal 1.
0x........ in syscall ...
continuing to receive second SIGUSR1
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x........ in syscall ...
Kill the program being debugged? (y or n) [answered Y; input not from terminal]

View File

@ -0,0 +1,14 @@
# test :
# sending signal when all threads are in WaitSys
# vgdb must queue these signals and deliver them before PTRACE_DETACHing.
# sleepers is started with argument so that it will mostly sleep.
prog: sleepers
args: 1000000000 1000000000 1 BSBSBSBS
vgopts: --tool=none --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-nlvgdbsigqueue
stderr_filter: filter_stderr
prereq: test -e gdb -a -f vgdb.invoker
progB: gdb
argsB: --quiet -l 60 --nx ./sleepers
stdinB: nlvgdbsigqueue.stdinB.gdb
stdoutB_filter: filter_gdb
stderrB_filter: filter_make_empty

17
gdbserver_tests/send_signal Executable file
View File

@ -0,0 +1,17 @@
#! /bin/sh
# send_signal sends signal $1 to the Valgrind process using prefix $2 in $3 seconds
SIG=$1
shift
PREFIX=$1
shift
SLEEP=$1
shift
VPID=`./vgdb -l $PREFIX 2>&1 | awk '{print $2}' | sed -e 's/--pid=//'`
if [ "$VPID" = "" ]
then
echo "send_signal could not determine the valgrind pid with " $PREFIX
exit 1
fi
(sleep $SLEEP
kill -s $SIG $VPID) &

View File

@ -8,11 +8,11 @@
#include <sys/types.h>
#include <sys/syscall.h>
#include <sched.h>
#include <signal.h>
static int loops = 15; // each thread+main will do this amount of loop
static int sleepms = 1000; // in each loop, will sleep "sleepms" milliseconds
static int burn = 0; // after each sleep, will burn cpu in a tight 'burn' loop
static void setup_sigusr_handler(void); // sigusr1 and 2 sigaction setup.
static pid_t gettid()
{
@ -133,7 +133,7 @@ int main (int argc, char *argv[])
struct spec b, l, p, m;
char *some_mem __attribute__((unused)) = malloc(100);
setaffinity();
setup_sigusr_handler();
if (argc > 1)
loops = atoi(argv[1]);
@ -194,3 +194,24 @@ int main (int argc, char *argv[])
return 0;
}
static int sigusr1_received = 0;
static void sigusr1_handler(int signr)
{
sigusr1_received++;
}
static void setup_sigusr_handler(void)
{
struct sigaction sa;
sa.sa_handler = sigusr1_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction (SIGUSR1, &sa, NULL) != 0)
perror("sigaction SIGUSR1");
sa.sa_handler = SIG_IGN;
if (sigaction (SIGUSR2, &sa, NULL) != 0)
perror("sigaction SIGUSR2");
}