Skip to content

Instantly share code, notes, and snippets.

@abma
Created March 29, 2014 05:42
Show Gist options
  • Save abma/9849178 to your computer and use it in GitHub Desktop.
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
/**
*
* 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