Printing in SIGALRM handler - c

While messing around with system calls for a class, I ran into trouble with the following code. For whatever reason, when the print statement in the signal handler has a newline at the end of it, it behaves as intended, with the signal being received and handled and the message being displayed. However, when the newline is not present, no output is shown at all.
I'm at a loss as to why this might be the case, and was hoping someone could shed some light on the issue.
Further, when it does print something, the signal only seems to be being sent four times? All sorts of strange things with this code.
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void alarm_handler(int signo) {
printf("pid : %d\n", getpid());
}
int main(int argc, char* argv[]) {
pid_t pid;
signal(SIGALRM, alarm_handler);
pid = fork();
if(pid == 0)
while(1) { }
else
{
int i;
for(i = 0; i < 5; i++)
{
sleep(1);
kill(pid, SIGALRM);
}
kill(pid, SIGKILL);
}
}
GCC Version information
gcc -v
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer//usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.0.0
Thread model: posix

If you for whatever reason want to display something printed without end of line it will most likely help to do fflush(stdout); as stdout is buffered and normally flushes at each end of line.

As pointed out by Henrik Carlqvist in his answer, you observer the effect of "buffered output".
Also SCC mentions in a comment that printf() isn't async signal safe and shall not be called form a signal handler.
To get around 1. and fullfil 2. just write your message using the signal safe function write(), which moreover use unbuffered I/O, so no flushing is needed.
void alarm_handler(int signo)
{
char msg[64] = "alarm handler called";
/* snprintf(msg, sizeof msg, "pid : %d\n", getpid()); */ /* sprintf also isn't async signal safe */
write(fileno(stdout), msg, strlen(msg));
}

Related

Robust graceful shutdown of an application

To ensure that all destructors are properly called if the program is terminated from keyboard (Ctrl+C), the approach with signals are used:
a handler, which sets an exit flag, is set for SIGINT
if a blocking call (accept(), read(), connect(), etc) is waiting for completion, it returns -1 and errno is set to EINTR
The problem is that SIGINT can arrive between check for exit flag (while (!finish)) and calling read(). In this case, read() will be blocked until the signal is sent once again.
This is a minimal working example:
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
enum { STDIN, STDOUT, STDERR };
static unsigned char finish=0;
static void handleSignal(int signal) {
finish=1;
}
int main(int argc, char ** e) {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler=handleSignal;
action.sa_flags=0;
sigaction(SIGINT, &action, NULL);
char buffer[256];
puts("<<");
while (!finish) {
sleep(2);
ssize_t n=read(STDIN, buffer, sizeof(buffer));
if (n==0) {
// End of stream
finish=1;
}
else if (n<0) {
// Error or interrupt
if (errno!=EINTR)
perror("read");
}
else {
// Convert data to hexadecimal format
for (size_t i=0; i<n; i++)
printf("%02x", buffer[i]);
}
}
puts(">>\n");
return 0;
}
sleep(2) is added for visibility (a real program may perform some preparational work before reading from file descritor).
If there any way of reliable handling of signals without using non-crossplatform things like signalfd()?
The pselect(2) system call was invented to solve this exact problem. It's POSIX, so hopefully cross-platform enough for you.
The purpose of pselect is to atomically unblock some signals, wait for I/O as select() does, and reblock them. So your loop can look something like the following pseudocode:
sigprocmask(SIG_BLOCK, {SIGINT});
while (1) {
if (finish)
graceful_exit();
int ret = pselect(1, {STDIN}, ..., { /* empty signal set */});
if (ret > 0) {
read(STDIN, buf, size); // will not block
// process data
// If you like you can do
sigprocmask(SIG_UNBLOCK, {SIGINT});
// work work work
if (finish)
graceful_exit();
// work work work
sigprocmask(SIG_BLOCK, {SIGINT});
} else {
// handle timeout or other errors
}
}
There is no race here because SIGINT is blocked for the time in between checking the finish flag and the call to pselect, so it cannot be delivered during that window. But the signal is unblocked while pselect is waiting, so if it arrives during that time (or already arrived while it was blocked), pselect will return without further delay. We only call read when pselect has told us it was ready for reading, so it cannot block.
If your program is multithreaded, use pthread_sigmask instead of sigprocmask.
As was noted in comments, you have to make your finish flag volatile, and for best compatibility it should be of type sig_atomic_t.
There is more discussion and another example in the select_tut(2) man page.

How to restore original signal handling properties in C

Tried my best to figure this out on my own, but I really do not want to continue tampering with things that I do not fully understand. So for a programming assignment I have to do in C, I need to terminate a program upon the user entering CTRL+D key stroke via a terminal. I tried to isolate that functionality in a smaller test function, but now my CTRL+D behaves as my CTRL+C and CTRL+C does not have any effect, even outside of the program when it finishes executing. This is the program that caused this change:
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <signal.h>
#include <stdlib.h>
void ctrlD(int sig){
printf("\n");
signal(SIGINT, SIG_DFL);
exit(0);
}
int main(){
signal(SIGINT, ctrlD);
while(1) {
printf("Hello\n");
sleep(5);
}
}
The line signal(SIGINT, SIG_DFL); was added afterward upon realizing my CTRL+C no longer worked. I thought it would return the keystrokes to their original functionalities, but to no avail. What do I do to get back the original functionalities while also making this program work with CTRL+D?
***EDIT: This question seems to have gone off the rails a bit. I get now that Ctrl+D is not a signal. Nonetheless, I no longer have the functionality of Ctrl+C anymore when attempting to use it in my MAC OS terminal, and instead Ctrl+D seems to have that exact functionality. HOW exactly can I return each to have the functionality that they had before I went on this haphazard journey?
If your intention is to restore signal's default behavior after executing handler then, pass SA_RESETHAND flag to sa_flags while registering signal action. For example.
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_RESETHAND;
act.sa_handler = some_handler;
sigaction(SIGINT, &act, NULL);
From sigaction() man
SA_RESETHAND
Restore the signal action to the default upon entry to the signal handler. This flag is meaningful only when
establishing a signal handler.
If you write a program to explore signals, it is much better to write it carefully, using proper POSIX interfaces (sigaction() instead of signal()), and avoiding undefined behaviour (using non-async-signal safe functions in a signal handler).
Consider, for example, the following program:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
static volatile sig_atomic_t sigint_count = 0;
static void catch_sigint(int signum)
{
if (signum == SIGINT)
sigint_count++;
}
static int install_sigint(void)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = catch_sigint;
act.sa_flags = 0;
if (sigaction(SIGINT, &act, NULL) == -1)
return errno;
return 0;
}
static int install_default(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_DFL;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
struct timespec duration;
int result;
if (install_sigint()) {
fprintf(stderr, "Cannot install SIGINT handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
duration.tv_sec = 5;
duration.tv_nsec = 0; /* 1/1000000000ths of a second. Nine zeroes. */
printf("Sleeping for %d seconds.\n", (int)duration.tv_sec);
fflush(stdout);
while (1) {
result = nanosleep(&duration, &duration);
if (!result)
break;
if (errno != EINTR) {
fprintf(stderr, "nanosleep() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* nanosleep was interrupted by a delivery of a signal. */
if (sigint_count >= 3) {
/* Ctrl+C pressed three or more times. */
if (install_default(SIGINT) == -1) {
fprintf(stderr, "Cannot revert SIGINT to the default handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("SIGINT has been reverted to the default handler.\n");
fflush(stderr);
}
}
if (sigint_count > 0)
printf("You pressed Ctrl+C %d time%s.\n", (int)sigint_count, (sigint_count > 1) ? "s" : "");
else
printf("You did not press Ctrl+C at all.\n");
return EXIT_SUCCESS;
}
The #define tells your C library (glibc in particular) that you want POSIX.1-2008 (and later) features from it.
The INT signal handler only increments a volatile sig_atomic_t counter. Note that this type may have a very small range it can represent; 0 to 127, inclusive, should be safe.
The main program waits using the POSIX nanosleep() function. On some systems, sleep() may be implemented via the SIGALRM function, so it is better avoided when using signals otherwise; nanosleep() does not interfere with signals like that at all. Plus, nanosleep() can return the amount of time remaining, if it is interrupted by a signal delivery.
In the main loop, nanosleep() will return 0, if it has slept the entire interval (but note that it may not update the remaining time to 0 in this case). If it is interrupted by the delivery of a signal, it will return -1 with errno == EINTR, and the remaining time updated. (The first pointer is to the duration of the sleep, and the second is to where the remaining time should be stored. You can use the same structure for both.)
Normally, the main loop does only one iteration. It can do more than one iteration, if it is interrupted by the delivery of a signal.
When the main loop detects that sigint_count is at least three, i.e. it has received at least three INT signals, it resets the signal handler back to default.
(Note that both the memset() and the sigemptyset() are important when clearing the struct sigaction structure. The memset() ensures that future code is backwards compatible with older code, by ensuring even padding fields are cleared. And sigemptyset() is the safe way to clear the signal mask (set of signals blocked while the handler runs).)
(In theory, memset() is not async-signal-safe, while both sigemptyset() and sigaction() are. This is why I reset the signal handler in the main program, and not in the signal handler.)
If you want to print from a signal handler, you need to use low-level I/O, because <stdio.h> functions are not async-signal safe. For example, you can use the following function to print strings to standard output:
static int wrerr(const char *p)
{
const int saved_errno = errno;
int retval = 0;
if (p) {
const char *q = p;
ssize_t n;
while (*q)
q++;
while (p < q) {
n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1) {
retval = EIO;
break;
} else
if (errno != EINTR) {
retval = errno;
break;
}
}
}
errno = saved_errno;
return retval;
}
The above wrerr() function is async-signal safe (because it only uses async-signal safe functions itself), and it even keeps errno unchanged. (Many guides forget to mention that it is quite important for a signal handler to keep errno unchanged. Otherwise, when a function is interrupted by a signal handler, and that signal handler modifies errno, the original function will return -1 to indicate an error, but then errno is no longer EINTR!)
You can just use wrerr("INT signal!\n") if you want. The return value from wrerr() is zero if the write was successful, and an errno error code otherwise. It ignores interrupts itself.
Do note that you should not mix stderr output via fprintf() or other <stdio.h> functions with the above (except perhaps for printing error messages when the program aborts). Mixing them is not undefined behaviour, it just may yield surprising results, like wrerr() output appearing in the midst of a fprintf(stderr,...) output.
Its because of exit(0) statement in the handler, when SIGINT is raised, handler strlD gets called and you might thinking why signal(SIGINT,SIG_DFL) didn't work ? Actually it works. But your main process a.out get terminated successfully there itself by calling exit(0). remove exit(0) if you want to restore the behavior of SIGINT.
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <signal.h>
#include <stdlib.h>
void ctrlD(int sig){
//printf("CTRL+C pressed\n");/* just to observe I added one printf
statement, Ideally there shouldn't be any printf here */
signal(SIGINT, SIG_DFL);/*restoring back to original action */
}
int main(){
signal(SIGINT, ctrlD);/*1st time when CTRL+C pressed, handler ctrlD gets called */
while(1) {
printf("Hello\n");
sleep(5);
}
return 0;
}
Also its advisable to use sigaction() instead of signal() as told here What is the difference between sigaction and signal? . Read man 2 sigaction and man 2 exit to check what exit(0) means.
Also this How to avoid using printf in a signal handler?
Edit :
void ctrlD(int sig){
/* printf("CTRL+C pressed \n"); */
signal(SIGINT, SIG_DFL); /* only one time CTRL+C works
after that SIG_DFL will terminate whole process */
}
int main(){
signal(SIGINT, ctrlD); /* if you press CTRL+C then it will go to handler
and terminate */
int ch;
while( ((ch = getchar())!=EOF) ) { /* wait or read char until CTrl+D is not pressed */
printf("Hello : %d \n",ch);/* ASCII equivalent of char */
}
return 0;
}
Thank you everyone who contributed to this question. The resources provided/linked were tremendously helpful in learning more about signals (and that EOF isn't a signal), among the other wealth of information provided.
After some more research, I found out that somehow, either through some accidental bash command gone awry, or perhaps the program posted in my original question itself, I had altered the key mappings for my terminal's stty settings. If anyone finds themselves in this oddly specific situation in the future, I hope this can be of help, as it is what fixed my problem:
Enter the command $ stty -a to see all of your terminals settings, specifically the "cchars" section.
I then saw the reversal, and fixed it like so:
$ stty intr ^C
$ stty eof ^D
Then you can run $ stty -a once again to see that the changes have properly taken effect. Once again, thanks everyone.

Catching Signals and Pausing with Character Device Drivers

I'm a student new to C Programming and am not fully understanding how signal catching works, and in line with character device drivers. I'd appreciate some help but I need to state that this is for a project that is due in my first C Programming class. So I have not posted any direct code, only an example of my initial approach.
My project needs to accept a signal input and set that signal to a variable to pass to my character device driver. Another program I've written will need to access that variable's value such that when read, it performs a certain outcome. I've tried to run my control program (<name> &) but it quits immediately. I double check by entering ps into my command prompt and the process is gone.
Basically I need my control program to pause and wait for the signal to be received. Once received, if the signal matches it will set a variable to its value. Otherwise, if it is SIGTERM it will either end or pause(), where it will wait until another signal is received that meets another condition. Currently, when I compile and run it with & it simply runs and quits. Here is an example of my code:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
static int file_state; //variable to pass to the driver for recording
void sig_handler(int sig);
void sig_handler(int sig){
while(1){
if(sig == SIGRTMIN){
printf("SIG = SIGRTMIN\n");
file_state = 0;
}else if(sig == SIGRTMIN+1){
printf("SIG = SIGRTMIN1\n");
file_state = 1;
}else if(sig == SIGTERM){
printf("Exiting\n");
exit(0); //exit
}else{
printf("SIG = %i\n", sig);
pause(); //doesn't match, pause for next signal
}
}
}
int main(){
signal(SIGINT, sig_handler);
//return 0; //tried with and without
}
I'm waiting until this daemon receives a signal to put the device driver into a particular mode. I haven't entered any write() methods yet because I'm trying to take this one step at a time where I send a signal with kill() and the proper response is returned with printf().
My problem is that I can't seem to keep this in pause() mode while I'm waiting for a signal that breaks the if loop. What's worse (other than my lack of knowledge and programming) is that I can't even keep this daemon open long enough to attempt a signal send. Once I can get this to pause and receive the signal, I plan to use the system write() method to write my file_state variable to the /dev/<filename>, which will be cross-referenced in my executable.
How far off am I? This is the final part that (I believe) I'm stuck on and I can't figure out how this should be approached. I've looked online and about 95% of the examples that delve into this contain methods we haven't learned yet. And if not, the examples are more simplistic where they do not include passing a value to a character device driver for use when another program is using the driver.
Any help is greatly appreciated.
ETA
I've updated my code so now it stays open until a signal is received. Problem is that I want this to pause() and remain open until the SIGTERM signal is received, breaking the loop and ending the program. I can't seem to get the loop correct. Even entering a conditional int variable into the while() loop still is broken when any signal is received. Here is my updated code below:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
static int file_state; //variable to pass to the driver for recording
int keep_alive = 1; //added for conditional checking to keep the while
//loop open to receive more than one signal
void sig_handler(int sig);
void sig_handler(int sig){
if(sig == SIGRTMIN){
printf("SIG = SIGRTMIN\n");
file_state = 0;
}else if(sig == SIGRTMIN+1){
printf("SIG = SIGRTMIN1\n");
file_state = 1;
}else if(sig == SIGTERM){
keep_alive = 0;
}else{
}
}
int main(){
do{
signal(SIGINT, sig_handler);
pause(); //thought pausing here would help with waiting for a new signal
}while(keep_alive == 1); //keep looping until false
return 0; //tried with and without
}
I'm trying to figure out a method to keep this process and signal catching loop alive until a specific signal is received. I can't figure it out for the life of me.
ETA 2
Discovered my issue. I wasn't paying attention and fully understanding the signal() method. The first argument requires the exact signal you are attempting to catch. I was using SIGINT which I was understanding it to be a "class" of interrupts that you wanted to catch. And then in the signal_handler() function, you would specify which type of interrupt you were catching. But, it is actually looking to catch the exact signal you are interested in. So in my example code, I should have been using:
int main(){
if(signal(SIGRMIN, sig_handler) == SIG_ERR){
printf("can't catch SIGRMIN Signal.\n")
}
...
}
I'm going to update with my new script as an answer and if anyone thinks it should be done differently or have any constructive criticisms please let me know. Thanks again!
So I found my issue, and it is working now. Below is my fixed code that produces the correct response back to the terminal when caught. I've added a for() loop to catch any other signals I'm not worried about didn't stop my process, only SIGTERM will. Look forward to getting critiqued and why I would never want to do my approach.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
static int file_state; //variable to pass to the driver for recording
void sig_handler(int sig);
void sig_handler(int sig){
if(sig == SIGRTMIN){
printf("SIG = SIGRTMIN\n");
file_state = 0;
}else if(sig == SIGRTMIN+1){
printf("SIG = SIGRTMIN1\n");
file_state = 1;
}else if(sig == SIGTERM){
exit(0);
EXIT_SUCCESS;
}else{
printf("SIGNAL CAUGHT #%d\n", sig);
}
}
int main(){
if(signal(SIGRTMIN, sig_handler)==SIG_ERR){
printf("Unable to catch SIGRTMIN\n");
}
if(signal(SIGRTMIN+1, sig_handler)==SIG_ERR){
printf("Unable to catch SIGRTMIN+1\n");
}
if(signal(SIGTERM, sig_handler)==SIG_ERR){
printf("Unable to terminate process.\n");
}
//This for loop will catch all other signals except the un-catchable and
//other user-specified above signal #31.
int s;
for(s = 0; s < 32; s++){
signal(s, sig_handler);
}
while(1);
pause();
return 0;
}
There are 2 parts, 1st user space where generation and catching of signals occurs. This has nothing to do with kernel driver. Your code seems okay about it.
2nd is interacting with driver when signal has been caught. For char driver have a look at this link. You can simply write a value write(fd, 1, &buf); from user space program and implement corresponding write() in char driver.

Why signal handling is malfunctioning?

I have a signal handling snippet but it is somehow malfunctioning on my Mac and virtual Linux box at koding.com but on my office Linux PC it is working..Can someone please tell me why..
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void my_isr(int n){
printf("Hello World");
signal(SIGINT, SIG_DFL);
}
int main(){
signal(SIGINT, my_isr);
printf("pid = %d\n", getpid());
while(1);
return 0;
}
When I am pressing Ctrl+C it is not printing Hello World on the first time but it is re-modifying the SIGINT signal action & hence it is exiting the program when I press Ctrl+C second time. Can someone explain me why?
You are not allowed to call every function in a signal handler.
Read signal(7). Only async signal safe functions can be called (directly or indirectly) from a signal handler, and printf is not such a function. If you really want to reliably "print" something from inside a signal handler (which I don't recommend), you can only use the low-level write(2) syscall (it is async signal safe).
So you've got undefined behavior. This explains why it is so bad.
The recommended way is to set a volatile sigatomic_t flag in your signal handler, and to test it outside of it (e.g. in your while loop...).
And you forgot to call fflush(3). You might be more lucky by ending your printf format string with \n since stdout is line-buffered!
Of course, changing your printf inside your signal handler is still UB, even with a \n, but very often it would appear to work.
Here is a conforming version of your program....
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
volatile sig_atomic_t got_signal;
void my_sigint_handler (int signum) {
if (signum == SIGINT) // this is always true!
got_signal = 1;
#define INTERRUPT_MESSAGE "Interrupted!\n"
write(STDOUT_FILENO, INTERRUPT_MESSAGE, strlen(INTERRUPT_MESSAGE));
};
int main(int argc, char**argv) {
struct sigaction act_int;
memset (&act_int, 0, sizeof(act_int));
act_int.sa_handler = my_sigint_handler;
if (sigaction(SIGINT, &act_int, NULL)) {
perror("sigaction"); exit(EXIT_FAILURE);
};
printf ("start %s pid %d\n", argv[0], (int)getpid());
while (!got_signal) {
};
printf ("ended %s after signal\n", argv[0]);
return 0;
}
A useful (and permissible) trick could be to write(2) a single byte -inside your signal handler- on a pipe(7) to self (you set up that pipe using pipe(2) early at program initialization), and in your event loop poll(2) the read end of that pipe.
printf is the culprit just use counter in handler and print outside handler its value it will work.
use sigaction instead of signal

calling ptrace inside a ptraced Linux process

Someone added to the Wikipedia "ptrace" article claiming that, on Linux, a ptraced process couldn't itself ptrace another process. I'm trying to determine if (and if so why) that's the case. Below is a simple program I contrived to test this. My program fails (the sub sub process doesn't run properly) but I'm pretty convinced it's my error and not something fundamental.
In essence the initial process A forks process B which in turn forks C. A ptraces its child B, B ptraces its child C. Once they're set up, all three processes are written to just print A,B, or C to stdout once every second.
In practice what happens is that A and B work fine, but C prints only once and then gets stuck. Checking with ps -eo pid,cmd,wchan shows C stuck in kernel function ptrace_stop while the rest are in hrtimer_nanosleep where I'd expect all three to be.
Very occasionally all three do work (so the program prints Cs as well as As and Bs), which leads me to believe there's some race condition in the initial setup.
My guesses as to what might be wrong are:
something to do with A seeing a SIGCHLD related to B seeing a SIGCHLD to do with a signal to C, and wait(2) reporting both as coming from B (but a hacky call of PTRACE_CONT to both pids doesn't fix things)?
C should be ptraced by B - has C inherited the ptrace by A instead (and B's call to ptrace neither errored nor overwrote this)?
Can anyone figure out what I'm doing wrong? Thanks.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
static void a(){
while(1){
printf ("A\n");
fflush(stdout);
sleep(1);
}
}
static void b(){
while(1){
printf ("B\n");
fflush(stdout);
sleep(1);
}
}
static void c(){
while(1){
printf ("C\n");
fflush(stdout);
sleep(1);
}
}
static void sigchld_handler(int sig){
int result;
pid_t child_pid = wait(NULL); // find who send us this SIGCHLD
printf("SIGCHLD on %d\n", child_pid);
result=ptrace(PTRACE_CONT, child_pid, sig, NULL);
if(result) {
perror("continuing after SIGCHLD");
}
}
int main(int argc,
char **argv){
pid_t mychild_pid;
int result;
printf("pidA = %d\n", getpid());
signal(SIGCHLD, sigchld_handler);
mychild_pid = fork();
if (mychild_pid) {
printf("pidB = %d\n", mychild_pid);
result = ptrace(PTRACE_ATTACH, mychild_pid, NULL, NULL);
if(result==-1){
perror("outer ptrace");
}
a();
}
else {
mychild_pid = fork();
if (mychild_pid) {
printf("pidC = %d\n", mychild_pid);
result = ptrace(PTRACE_ATTACH, mychild_pid, NULL, NULL);
if(result==-1){
perror("inner ptrace");
}
b();
}
else {
c();
}
}
return 0;
}
You are indeed seeing a race condition. You can cause it to happen repeatably by putting sleep(1); immediately before the second fork() call.
The race condition is caused because process A is not correctly passing signals on to process B. That means that if process B starts tracing process C after process A has started tracing process B, process B never gets the SIGCHLD signal indicating that process C has stopped, so it can never continue it.
To fix the problem, you just need to fix your SIGCHLD handler:
static void sigchld_handler(int sig){
int result, status;
pid_t child_pid = wait(&status); // find who send us this SIGCHLD
printf("%d received SIGCHLD on %d\n", getpid(), child_pid);
if (WIFSTOPPED(status))
{
result=ptrace(PTRACE_CONT, child_pid, 0, WSTOPSIG(status));
if(result) {
perror("continuing after SIGCHLD");
}
}
}
It is "possible" to perform some ptrace functionalities on a child process that invokes ptrace itself. The real difficulty is that a tracer process becomes the parent of the tracee when attached to the latter. And if your tracer process wants to trace all behaviors from all (direct and indirect) child processes (i.e. like when a debugger program needs to debug a multi-threaded program), it naturally breaks the original process hierarchy, and all inter-process/inter-thread communications (i.e. thread synchronization, signal sending / receiving, ...) among all child processes needs to be emulated / multiplexed by the tracer process. It is still "possible", but much more difficult and inefficient.

Resources