Created
March 29, 2014 05:42
-
-
Save abma/9849178 to your computer and use it in GitHub Desktop.
compiles with: gcc -g -o signaltest signaltest.c -lpthread -lunwind -lunwind-x86_64
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* signaltest.c | |
* | |
* Tests a simple idea for suspending a thread and inspecting its stack using posix thread signals (pthread_kill()). | |
* | |
* 1. Create a simple worker thread that does some (lengthy) work and run it. | |
* 2. Pause the main thread for a while so that we can be fairly certain that the worker's signal handler is installed and it is running. | |
* 3. In the main thread, lock a mutex that the worker will wait on inside its signal handler. | |
* 4. Send SIGUSR1 from the main thread to the worker. The worker's handler tries to lock the mutex. At this point it is suspended. | |
* 5. Inspect associated ucontext_t objects (from both the handler and in the main thread). | |
* | |
*/ | |
#include <time.h> | |
#include <pthread.h> | |
#include <unistd.h> | |
#include <libunwind.h> | |
#include <ucontext.h> | |
#include <signal.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
/* These functions make up the worker thread. Two functions so that it looks like a real "stack". :) */ | |
void worker_signal_handler (int signum, siginfo_t* si, void* ptr); | |
int do_important_work (int count, int total); | |
/* These variables are essentially the worker thread's state, but they are meant to be (indirectly) accessible to the rest of the program. */ | |
ucontext_t workerCtx; | |
pthread_mutex_t workerMutex; | |
/* The worker thread entry point */ | |
void* worker_func (void *ptr) { | |
// Find the worker's context | |
getcontext(&workerCtx); | |
// Install a signal handler for our worker | |
{ | |
printf("[Worker] installing signal handler...\n"); | |
sigset_t sigSet; | |
sigemptyset(&sigSet); | |
sigaddset(&sigSet, SIGUSR1); | |
pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); | |
struct sigaction sa; | |
memset(&sa, 0, sizeof(struct sigaction)); | |
sa.sa_sigaction = worker_signal_handler; | |
sa.sa_flags |= SA_SIGINFO; | |
if (sigaction(SIGUSR1, &sa, NULL)) { | |
perror("sigaction"); exit(-2); | |
} | |
} | |
// Do some important work: | |
{ | |
int i=0; | |
const int cnt = 12; | |
for (i=0; i < cnt; i++) { | |
do_important_work(i+1, cnt); | |
} | |
} | |
return NULL; | |
} | |
/* Some modest work to be done in the worker thread. */ | |
int do_important_work (int count, int total) { | |
struct timespec ts; | |
ts.tv_sec = 0; | |
ts.tv_nsec = 500000000; | |
printf("[Worker] Doing important work [%#d/%d]...\n", count, total); | |
nanosleep(&ts, NULL); | |
} | |
/* The worker thread's signal handler. The kernel will create an extra stack frame on top of the thread's stack to run this. */ | |
void worker_signal_handler (int signal, siginfo_t* si, void* ptr) { | |
char *msg = "[Handler] Caught SIGUSR1!\n"; | |
if (write(0, msg, strlen(msg)) == -1) { | |
perror("write"); | |
return; | |
} | |
ucontext_t curCtx; | |
ucontext_t* pCtx = (ucontext_t*) ptr; | |
/* Get the current context. I am unsure as to whether this structure gives the thread's true stack immediately, | |
or if you need to follow the link pointer to obtain its "successor". */ | |
if (getcontext(&workerCtx)) { | |
perror("getcontext"); | |
} | |
ucontext_t* pSuccessor = curCtx.uc_link; | |
/* Find out which stack pointer is contained in the context. */ | |
/* | |
if (curCtx.uc_stack.ss_sp == workerCtx.uc_stack.ss_sp) { | |
msg = "[Handler] Signal handler getcontext() sp == worker thread stack sp.\n"; | |
write(0, msg, strlen(msg)); | |
} | |
if (pSuccessor != NULL && pSuccessor->uc_stack.ss_sp == workerCtx.uc_stack.ss_sp) { | |
msg = "[Handler] Signal handler successor's sp == worker thread stack sp.\n"; | |
write(0, msg, strlen(msg)); | |
} | |
if (pCtx->uc_stack.ss_sp == workerCtx.uc_stack.ss_sp) { | |
msg = "[Handler] Signal handler context arg sp == worker thread stack sp.\n"; | |
write(0, msg, strlen(msg)); | |
} | |
*/ | |
/* try to obtain a lock on our "suspend" mutex. This will effectively suspend this thread. */ | |
if (pthread_mutex_lock(&workerMutex)) { | |
perror("pthread_mutex_lock"); return; | |
} | |
if (pthread_mutex_unlock(&workerMutex)) { | |
perror("pthread_mutex_unlock"); return; | |
} | |
} | |
/* The main program entry point. */ | |
int main (int argc, char* argv[]) { | |
/* 1. Create a simple worker thread that does some (lengthy) work and run it. */ | |
pthread_t worker; | |
if (pthread_mutex_init(&workerMutex,NULL)) { | |
perror("pthread_mutex_init"); exit(-1); | |
} | |
printf("[Main] Starting worker thread.\n"); | |
if (pthread_create(&worker, NULL, worker_func, &workerMutex)) { | |
perror("pthread_create"); exit(-1); | |
} | |
/* 2. Pause for user input so that we can be fairly certain that the worker's signal handler is installed and it is running. */ | |
sleep(3); | |
/* 3. Lock a mutex that the worker will wait on inside its signal handler. */ | |
if (pthread_mutex_lock(&workerMutex)) { | |
perror("pthread_mutex_lock"); exit(-1); | |
} | |
/* 4. Send SIGUSR1 to the worker in order to suspend it. */ | |
if (pthread_kill(worker, SIGUSR1)) { | |
perror("pthread_kill"); exit(-1); | |
} | |
/* 5. The worker will fill in a ucontext_t structure visible to us. Inspect it to see if we can obtain a full program stack. */ | |
printf("[Main] The worker has been suspended.\n"); | |
sleep(2); | |
printf("[Main] Simple backtrace of worker thread using libunwind:\n"); | |
void *buffer[100]; | |
char names[100][100]; | |
memset(&buffer, 0, sizeof(void*) * 100); | |
unw_cursor_t cursor; | |
unw_word_t ip, sp, offp; | |
unw_init_local(&cursor, &workerCtx); | |
int i=0; | |
int cnt=0; | |
while (unw_step(&cursor) > 0) { | |
unw_get_reg(&cursor, UNW_REG_IP, &ip); | |
unw_get_reg(&cursor, UNW_REG_SP, &sp); | |
unw_get_proc_name(&cursor, names[i], 100, &offp); | |
if (ip) { | |
buffer[i++] = (void*) ip; | |
printf("[Main] ip = 0x%0.10lx, sp = 0x%0.10lx, %s()\n", (long)ip, (long)sp, names[i-1]); | |
} else { | |
break; | |
} | |
} | |
cnt = i; | |
char** ssymb = (char**) (intptr_t) backtrace_symbols(buffer,cnt); | |
printf("[Main] Decoding stack ip values using backtrace_symbols():\n"); | |
for (i=0; i < cnt; i++) { | |
printf("[Main] %s\n", (const char*)ssymb[i]); | |
} | |
sleep(2); | |
printf("[Main] Now resuming the worker.\n"); | |
if (pthread_mutex_unlock(&workerMutex)) { | |
perror("pthread_mutex_unlock"); exit(-1); | |
} | |
if (pthread_join(worker, NULL)) { | |
perror("pthread_join"); exit(-1); | |
} | |
if (pthread_mutex_destroy(&workerMutex)) { | |
perror("pthread_mutex_destroy"); exit(-1); | |
} | |
printf("[Main] Finished.\n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment