C ptrace attach causing interrupt

I'm trying to use ptrace attach, but I'm seeing an interrupt in the attachee. Here's some kdump output

Code:
 81892 128803 vgdb     1655156785.278917 CALL  ptrace(PT_ATTACH,0x13fe3,0,0)
 81892 128803 vgdb     1655156785.278972 RET   ptrace 0
 81891 101368 none-amd64-freebsd 1655156785.278980 RET   select -1 errno 4 Interrupted system call
 81892 128803 vgdb     1655156785.279000 CALL  write(0x2,0x7fffdfffd630,0x10)
 81892 128803 vgdb     1655156785.279009 GIO   fd 2 wrote 16 bytes
       "23:46:25.278991 "
 81892 128803 vgdb     1655156785.279014 RET   write 16/0x10
 81892 128803 vgdb     1655156785.279021 CALL  write(0x2,0x7fffdfffd630,0x3e)
 81892 128803 vgdb     1655156785.279026 GIO   fd 2 wrote 62 bytes
       "waitstopped attach main pid before waitpid signal_expected 17

Here pid 81892 'vgdb' is attaching to pid 81891 'none-amd64-freebsd'

I don't see ptrace attach doing this on Linux.

Is this something specific to FreeBSD?
 
A well-behaved app must always expect and properly deal with interrupted system calls. But I don't want to know how many broken apps are out there :what:
 
I kind of hope that the act of attaching a debugger doesn't change the behaviour of the debuggee. I'll do some tests with lldb and gdb to see if they have similar behaviour.
 
Could you share the code, or skeleton of the code where you observe a problem? On Linux SIGSTOP is sent to tracee, tracer has to wait for it. Same is true for FreeBSD I saw the same behavior in my test code.
Test code:
tracee:
Code:
#include <stdio.h>
#include <unistd.h>

#define BUFSZ    64

int main(void) {
    char buf[BUFSZ] = { 0 };

    setvbuf(stdout, NULL, _IONBF, 0);
    printf("pid: %d, input? ", getpid());

    if ((read(fileno(stdin), buf, BUFSZ)) == -1) {
        perror("something went wrong");
        return -1;
    }

    puts("done.");
    return 0;
}
tracer:
Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char** argv) {

    if (argc != 2) {
        printf("%s <pid>\n", argv[0]);
        return 1;
    }
    pid_t pid;
    if ((pid = atoi(argv[1])) <= 0) {
        printf("+ incorrect pid: %d\n", pid);
        return 2;
    }

    printf("+ attaching to %d\n", pid);
    if ((ptrace(PT_ATTACH, pid, NULL, 0)) != 0 ) {
        perror("attach failed");
        return 3;
    }

    int status;
    wait(&status);

    if(! WIFSTOPPED(status)) {
        printf("+ tracee not stopped: %x\n", status);
        return 4;   // XXX: try to detach or determine other possibilities
    }
    printf("READY (%x)\n", status);
    // do whatever with the process
    //for(;;);

    printf("+ done.\n");
    ptrace(PT_DETACH, pid, NULL, 0);

    return 0;
}
And I do see the expected behavior.
 
I kind of hope that the act of attaching a debugger doesn't change the behaviour of the debuggee.
In general, this is impossible, see also heisenbug.

Whether it must interrupt a running system call, I don't know :-/
 

Attachments

  • 1655209697892.png
    1655209697892.png
    551.9 KB · Views: 76
Could you share the code, or skeleton of the code where you observe a problem? On Linux SIGSTOP is sent to tracee, tracer has to wait for it. I'd assume the same for FreeBSD, I saw the same behavior in my test code.

I'll attach the code this evening.

This is an implementation of "vgdb invoker" for Valgrind. vgdb acts as a relay between gdb and Valgrind gdbserver via pipes. If Valgrind is "stuck" either in blocking syscalls or in a CPU-bound loop there's no way for vgdb to re-assert control. That's where the invoker comes in. This handles signals and uses ptrace to activate the Valgrind gdbserver. Basically that will poll for any gdb commands.

Roughly the sequence of events should be
  • ptrace attach
  • ptrace getregs, and save a copy
  • setup stack
  • put a magic number in RDI
  • ptrace write set a 0 return address - it won't be used
  • set RIP to the invoke_gdbserver function
  • ptrace setregs
  • ptrace continue
  • invoke_gdbserver does its stuff and sends SIGSTOP to itself
  • wait for the stop
  • ptrace setregs with the the saved regs
  • ptrace cont if there are any pending signals
  • ptrace detach
There is 1 testcase that is failing. It should be blocking in select(), ptrace to get back control and gdb printing the thread state, setting the select timeout to zero, ptrace again and printing the thread state.

The ktrace in my initial post shows the initial ptrace attach and the valgrind+guest having select interrupted.

On Linux, the select() calls do not get interrupted by ptrace.

I haven't yet tried the i386 version to see if it is any different. Probably not.

I've attached the source. It won't build alone, it needs the rest of the Valgrind source plus this diff

Code:
diff --git a/coregrind/Makefile.am b/coregrind/Makefile.am
index 76c0aebc9..151f5c2f0 100644
--- a/coregrind/Makefile.am
+++ b/coregrind/Makefile.am
@@ -98,10 +98,7 @@ if VGCONF_OS_IS_SOLARIS
 vgdb_SOURCES += vgdb-invoker-solaris.c
 endif
 if VGCONF_OS_IS_FREEBSD
-# As with Darwin, we don't have ptrace PTRACE_PEEKTEXT
-# so we can't use vgdb-invoker-ptrace.c
-# Need to find an alternative, like Solaris
-vgdb_SOURCES += vgdb-invoker-none.c
+vgdb_SOURCES += vgdb-invoker-freebsd.c
 endif


The two main functions are

Bool invoker_invoke_gdbserver (pid_t pid)

and

Bool attach (pid_t pid, const char *msg)



I also did some tests with gdb

I ran the guest testcase on its own, so there will be main+3 threads in select() with 1000s timeouts
Attached gdb
info threads
set sleepms=0
detached
attached again
info threads
kill

Code:
[Switching to LWP 102171 of process 1625]
_umtx_op_err () at /usr/src/lib/libthr/arch/amd64/amd64/_umtx_op_err.S:40
warning: Source file is more recent than executable.
40      RSYSCALL_ERR(_umtx_op)
(gdb) info threads
  Id   Target Id                  Frame
* 1    LWP 102171 of process 1625 _umtx_op_err () at /usr/src/lib/libthr/arch/amd64/amd64/_umtx_op_err.S:40
  2    LWP 103509 of process 1625 _select () at _select.S:4
  3    LWP 103510 of process 1625 _select () at _select.S:4
  4    LWP 103511 of process 1625 _select () at _select.S:4

and ktrace says

1650 102911 sleepers 1655235847.708993 CALL select(0,0,0,0,0x204e60)
1650 102911 sleepers 1655235863.708730 RET select -1 errno 4 Interrupted system call

lldb crashed when I tried "thread info all". And "bt".

In any case, it looks like this is how FreeBSD ptrace works and we'll have to live with it.
 

Attachments

  • vgdb-invoker-freebsd.c.txt
    20.8 KB · Views: 69
In general, this is impossible, see also heisenbug.

Whether it must interrupt a running system call, I don't know :-/

So what do you do when a function that should never return EINTR does so?

 
Can you please share the test case too? Both Linux and FreeBSD deliver SIGSTOP to a tracee. So even if select() was being executed it should get interrupted (you can't mask it). select() man page on Linux and FreeBSD differ (e.g. Linux does modify the timeout, FreeBSD doesn't). Maybe there's an issue around the logic there.
 
I've pushed the source to Valgrind, you can get everything by following the instructions at


The testcase is gdbserver_tests/sleepers.c

Otherwise the test file is

Code:
#define _GNU_SOURCE
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#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_sys()
{
#ifdef __NR_gettid
   return syscall(__NR_gettid);
#else
   return getpid();
#endif
}
// will be invoked from gdb.
void whoami(char *msg)
{
   fprintf(stderr, "pid %ld Thread %ld %s\n", (long) getpid(), (long) gettid_sys(),
           msg);
   fflush(stderr);
}


static void do_burn ()
{
   int i;
   int loopnr = 0;
   // one single line for the below, to ensure interrupt on this line.
   for (i = 0; i < burn; i++) loopnr++;
}

static int thread_ready = 0;
static pthread_cond_t ready = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t ready_mutex = PTHREAD_MUTEX_INITIALIZER;
static void signal_ready (void)
{
   int rc;
   rc = pthread_mutex_lock(&ready_mutex);
   if (rc != 0)
      fprintf(stderr, "signal_ready lock error %d_n", rc);
   thread_ready = 1;
   rc = pthread_cond_signal(&ready);
   if (rc != 0)
      fprintf(stderr, "signal_ready signal error %d_n", rc);
   rc = pthread_mutex_unlock(&ready_mutex);
   if (rc != 0)
      fprintf(stderr, "signal_ready unlock error %d_n", rc);
}

struct spec {
   char *name;
   int sleep;
   int burn;
   int t;
};
static struct timeval t[4];
static int nr_sleeper_or_burner = 0;
static volatile int report_finished = 1;
// set to 0 to have no finish msg (as order is non-deterministic)
static void *sleeper_or_burner(void *v)
{
   int i = 0;
   struct spec* s = (struct spec*)v;
   int ret;
   fprintf(stderr, "%s ready to sleep and/or burn\n", s->name);
   fflush (stderr);
   signal_ready();
   nr_sleeper_or_burner++;

   for (i = 0; i < loops; i++) {
      if (sleepms > 0 && s->sleep) {
         t[s->t].tv_sec = sleepms / 1000;
         t[s->t].tv_usec = (sleepms % 1000) * 1000;
         ret = select (0, NULL, NULL, NULL, &t[s->t]);
         /* We only expect a timeout result or EINTR from the above. */
         if (ret != 0 && errno != EINTR)
            perror("unexpected result from select");
      }
      if (burn > 0 && s->burn)
         do_burn();
   }
   if (report_finished) {
      fprintf(stderr, "%s finished to sleep and/or burn\n", s->name);
      fflush (stderr);
   }
   return NULL;
}

// wait till a thread signals it is ready
static void wait_ready(void)
{
   int rc;
   rc = pthread_mutex_lock(&ready_mutex);
   if (rc != 0)
      fprintf(stderr, "wait_ready lock error %d_n", rc);
   while (! thread_ready && rc == 0) {
      rc = pthread_cond_wait(&ready, &ready_mutex);
      if (rc != 0)
         fprintf(stderr, "wait_ready wait error %d_n", rc);
   }
   thread_ready = 0;
   rc = pthread_mutex_unlock(&ready_mutex);
   if (rc != 0)
      fprintf(stderr, "wait_ready unlock error %d_n", rc);
}

// We will lock ourselves on one single cpu.
// This bypasses the unfairness of the Valgrind scheduler
// when a multi-cpu machine has enough cpu to run all the
// threads wanting to burn cpu.
static void setaffinity(void)
{
#ifdef VGO_linux
   cpu_set_t single_cpu;
   CPU_ZERO(&single_cpu);
   CPU_SET(1, &single_cpu);
   (void) sched_setaffinity(0, sizeof(single_cpu), &single_cpu);
#endif
   // GDBTD: equivalent for Darwin ?
}

int main (int argc, char *argv[])
{
  char *threads_spec;
  pthread_t ebbr, egll, zzzz;
  struct spec b, l, p, m;
  char *some_mem __attribute__((unused)) = malloc(100);
  if (argc > 5 && atoi(argv[5])) setaffinity();
  setup_sigusr_handler();
  if (argc > 1)
     loops = atoi(argv[1]);

  if (argc > 2)
     sleepms = atoi(argv[2]);

  if (argc > 3)
     burn = atoll(argv[3]);

  if (argc > 4)
     threads_spec = argv[4];
  else
     threads_spec = "BSBSBSBS";
 
  fprintf(stderr, "loops/sleep_ms/burn/threads_spec/affinity:  %d %d %d %s %d\n",
          loops, sleepms, burn, threads_spec, argc > 5 && atoi(argv[5]));
  fflush(stderr);

  b.name = "Brussels";
  b.burn = *threads_spec++ == 'B';
  b.sleep = *threads_spec++ == 'S';
  b.t = -1;
  if (b.burn || b.sleep) {
     b.t = 1;
     pthread_create(&ebbr, NULL, sleeper_or_burner, &b);
     wait_ready();
  }
 
  l.name = "London";
  l.burn = *threads_spec++ == 'B';
  l.sleep = *threads_spec++ == 'S';
  l.t = -1;
  if (l.burn || l.sleep) {
     l.t = 2;
     pthread_create(&egll, NULL, sleeper_or_burner, &l);
     wait_ready();
  }

  p.name = "Petaouchnok";
  p.burn = *threads_spec++ == 'B';
  p.sleep = *threads_spec++ == 'S';
  p.t = -1;
  if (p.burn || p.sleep) {
     p.t = 3;
     pthread_create(&zzzz, NULL, sleeper_or_burner, &p);
     wait_ready();
  }

  m.name = "main";
  m.burn = *threads_spec++ == 'B';
  m.sleep = *threads_spec++ == 'S';
  m.t = 0;
  sleeper_or_burner(&m);

  if (b.t != -1) pthread_join(ebbr, NULL);
  if (l.t != -1) pthread_join(egll, NULL);
  if (p.t != -1) pthread_join(zzzz, NULL);

  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");
}


And the testcase control file is

nlcontrolc.vgtest

specifically

prog: sleepers
args: 1000000000 0 100000 BSBSBSBS 1
 
I downloaded the sleepers code, compiled it and executed ./sleepers 1000000000 0 100000 BSBSBSBS 1. In the other terminal I used gdb -p to attach to it and executed gdb commands you mentioned above (info threads ; set sleepms=0), detached, attached again and did rest of the gdb commands (info threads; kill) and got the expected results - killed process without issue.

I did clone the repo but it chokes on make during
Code:
$ make
echo "# This is a generated file, composed of the following suppression rules:" > default.supp
echo "# " ./xfree-3.supp ./xfree-4.supp ./freebsd.supp ./freebsd-helgrind.supp ./freebsd-drd.supp >> default.supp
cat  >> default.supp

^Cmake: *** default.supp removed
*** Signal 2
I let it be for a while to see i it does something but it didn't so I stopped it.
 
Ok. As I mentioned though I was able to attach with gdb to sleepers without problem. I compiled the repo and sleepers test and run it as ./vg-in-place ./gdbserver_tests/sleepers with the results:
Code:
==18930== Memcheck, a memory error detector
==18930== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==18930== Using Valgrind-3.20.0.GIT and LibVEX; rerun with -h for copyright info
==18930== Command: ./gdbserver_tests/sleepers
==18930==
loops/sleep_ms/burn/threads_spec/affinity:  15 1000 0 BSBSBSBS 0
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
main finished to sleep and/or burn
Petaouchnok finished to sleep and/or burn
Brussels finished to sleep and/or burn
London finished to sleep and/or burn
==18930==
==18930== HEAP SUMMARY:
==18930==     in use at exit: 7,032 bytes in 10 blocks
==18930==   total heap usage: 10 allocs, 0 frees, 7,032 bytes allocated
==18930==
==18930== LEAK SUMMARY:
==18930==    definitely lost: 100 bytes in 1 blocks
==18930==    indirectly lost: 0 bytes in 0 blocks
==18930==      possibly lost: 0 bytes in 0 blocks
==18930==    still reachable: 6,932 bytes in 9 blocks
==18930==         suppressed: 0 bytes in 0 blocks
==18930== Rerun with --leak-check=full to see details of leaked memory
==18930==
==18930== For lists of detected and suppressed errors, rerun with: -s
==18930== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
So even this way I don't see any problem. How exactly are you triggering the issue?
 
`gmake regtest` is the long way

perl tests/vg_regtest gdbserver_tests/nlcontrolc is the best way

ktrace will also work

If you have the very latest source from git then the testcase will pass as I've added an expected with the current FreeBSD output.

Delete gdbserver_tests/nlcontrolc.stdoutB.exp-freebsd to get the failing behaviour.

It is possible to run the test manually. You'll need 2 terminals. Assuming that you are in the gdbserver_tests directory.

In the first
vg-in-place --tool=none --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-nlcontrolc sleepers 1000000000 0 100000 BSBSBSBS 1
note the vgdb command

In the second
gdb sleepers
run the vgdb command from the 1st terminal

You can now replay the contents of gdbserver_tests/nlcontrolc.stdinB.gdb
 
I did this:
Code:
term1:(/local/forums/ptrace/valgrind)$ find . -name nlcontrolc.stdoutB.exp-freebsd
term1:(/local/forums/ptrace/valgrind)$

term1:(/local/forums/ptrace/valgrind/gdbserver_tests)$ ../vg-in-place --tool=none --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-nlcontrolc ./sleepers 1000000000 0 100000 BSBSBSBS 1
==19260== Nulgrind, the minimal Valgrind tool
==19260== Copyright (C) 2002-2017, and GNU GPL'd, by Nicholas Nethercote.
==19260== Using Valgrind-3.20.0.GIT and LibVEX; rerun with -h for copyright info
==19260== Command: ./sleepers 1000000000 0 100000 BSBSBSBS 1
==19260==
==19260== (action at startup) vgdb me ...
==19260==
==19260== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==19260==   /path/to/gdb ./sleepers
==19260== and then give GDB the following command
==19260==   target remote | /local/forums/ptrace/valgrind/gdbserver_tests/../.in_place/../../bin/vgdb --vgdb-prefix=./vgdb-prefix-nlcontrolc --pid=19260
==19260== --pid is optional if only one valgrind process is running
==19260==
On second terminal I ran gdb:
Code:
term2:(/local/forums/ptrace/valgrind/gdbserver_tests)$ find /local/forums/ptrace/ -name vgdb
/local/forums/ptrace/valgrind/coregrind/vgdb
term2:(/local/forums/ptrace/valgrind/gdbserver_tests)$

term2:(/local/forums/ptrace/valgrind/gdbserver_tests)$ gdb ./sleepers
Reading symbols from ./sleepers...
(gdb) target remote | /local/forums/ptrace/valgrind/coregrind/vgdb --vgdb-prefix=./vgdb-prefix-nlcontrolc --pid=19260
Remote debugging using | /local/forums/ptrace/valgrind/coregrind/vgdb --vgdb-prefix=./vgdb-prefix-nlcontrolc --pid=19260
relaying data between gdb and process 19260
warning: remote target does not support file transfer, attempting to access files from local filesystem.
Reading symbols from /libexec/ld-elf.so.1...
(No debugging symbols found in /libexec/ld-elf.so.1)
=> 0x4006f70:    xor    rbp,rbp
   0x4006f73:    sub    rsp,0x18
   0x4006f77:    mov    r12,rdi
....
(gdb)c
On terminal1 I saw progress
Code:
loops/sleep_ms/burn/threads_spec/affinity:  1000000000 0 100000 BSBSBSBS 1
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
Back on term2
Code:
(gdb) info threads
  Id   Target Id                           Frame
  1    Thread 100275 (tid 1 VgTs_Yielding) 0x0000000000202952 in do_burn () at sleepers.c:40
* 2    Thread 101250 (tid 4 VgTs_Runnable) 0x0000000000202952 in do_burn () at sleepers.c:40
  3    Thread 101248 (tid 2 VgTs_Yielding) 0x0000000000202952 in do_burn () at sleepers.c:40
  4    Thread 101249 (tid 3 VgTs_Yielding) 0x0000000000202952 in do_burn () at sleepers.c:40
(gdb) set sleepms=0
(gdb) c
Continuing.
I never run into issue.
 
The testcase does 3 attaches, the first in the burn phase with 1 thread runnable and 3 yielding.
gdb then turns off the burn counter and sets sleepms to 1000000
Then there is the second attach, all 4 threads should be waiting. Even if the next ptrace interrupts one of these selects() it will just loop once and resume waiting.
gdb turns off sleepms and there is a third attach.
It's this third attach after setting sleepms to 0 that is problematical. The select() is interrupted but now next time round the loop both sleepms and burn are 0 so the third 'info threads' shows 3 threads waiting and 1 running.

On Linux the 3rd attach does not interrupt the select() calls so it shows 4 threads waiting,

Here more precisely is the problem on FreeBSD

Code:
(gdb) Continuing.

Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x000000000020265b in sleeper_or_burner (v=0x7fc000250) at sleepers.c:81
81         for (i = 0; i < loops; i++) {
(gdb) (gdb)   Id   Target Id                           Frame
* 1    Thread 104289 (tid 1 VgTs_Runnable) 0x000000000020265b in sleeper_or_burner (v=0x7fc000250) at sleepers.c:81
  2    Thread 106118 (tid 2 VgTs_WaitSys)  _select () at _select.S:4
  3    Thread 106119 (tid 3 VgTs_WaitSys)  _select () at _select.S:4
  4    Thread 106120 (tid 4 VgTs_WaitSys)  _select () at _select.S:4
(gdb)  > > > >

Thread 1 should also be in the VgTs_WaitSys state in _select.S:4
 
I was not able to reproduce this. I tried gmake regtest but it got hung similarly as before. I don't want to dig through the repo code. But that's not that important ; you did mention what the problem is.
I did have a look at the sleepers and the loop it does. The select() there is actually just a fancy sleep. I did modify my tracee to verify the issue:
Code:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#define BUFSZ    16

int main(void) {
    unsigned char buf[BUFSZ] = { 0 };
    int i,j,r;
    struct timeval t = { .tv_sec = 10, .tv_usec = 0 };

    printf("pid: %d\n", getpid());

    for (i =0 ; i < 12; i++) {
        r = select(0, NULL, NULL, NULL, &t);

        if (r != -1) {
            printf("%i: select ready: %d (%d)\n", i, r,errno);
            read(0, buf, BUFSZ);

            printf("\nbuf: ");
            for (j =0 ; j < BUFSZ; j++) {
                printf("%.02x ", buf[j]);
            }
            puts("");
        }
        else {
            printf("%i: select returned: %d (%d)\n", i, r, errno);
        }
    }

    puts("done.");
    return 0;
}
And I attach to it with the gdb -p in second terminal. Surely I do get -1 from select as expected.

I don't have the answer for this problem right now. But as select is really used only as sleep you could use usleep() or nanosleep() in FreeBSD instead.
 
Thanks for all your efforts so far.

I'll keep the select as the code has to run on Linux FreeBSD and with fingers crossed Solaris.
 
Ok. But I'll point out what Zirias mentioned above and maybe got lost:
A well-behaved app must always expect and properly deal with interrupted system calls.
It's not a problem to catch this condition (r==-1 && errno == EINTR) and restart the select. You should change the code to deal with it.
Also this is not specific to ptrace but rather signals in general.
 
I don't agree. In general applications should handle interrupts but it should not be necessary to need to second-guess debugger attach side effects.

In this case, gdb will have changed sleepms so if the code does detect EINTR it will restart with a zero timeout, timeout straight away and then loop without entering the 'if' for select. So I'd expect the testcase to still fail.
 
Back
Top