Created
December 27, 2017 14:47
-
-
Save zb3/29300d9b1450fc66caf463271a5ff1af to your computer and use it in GitHub Desktop.
A wrapper program for linux to find out why segfault happend
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
#include <string.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <wait.h> | |
#include <sys/ptrace.h> | |
#include <sys/user.h> | |
#include <stdlib.h> | |
#include <linux/ptrace.h> | |
//Grandis Spiritus Diavolos! | |
/* | |
This program uses ptrace to catch SIGSEGV and print its cause including IP, addr, maps and recent code addresses on stack (usually backtrace+noise which is not the same as a real backtrace). | |
There are better methods to do this, but if they fail, this tool can help. | |
--- | |
gcc grandis.c -o grandis | |
./grandis COMMAND [ARGS]... | |
to set environment variables, use env: | |
./grandis env [NAME=VALUE]... COMMAND [ARGS]... | |
*/ | |
#define MAX_ACTIVE_PIDS 64 | |
#define MAX_REGIONS 128 | |
#define MAX_LIBNAME 63 | |
#define SCAN_STACK_SIZE 16384 | |
#define STACKMEM_ALIGN 4 | |
#ifdef __i386__ | |
#define IP eip | |
#define SP esp | |
#endif | |
#ifdef __x86_64__ | |
#define IP rip | |
#define SP rsp | |
#endif | |
#ifdef __arm__ | |
#define IP ARM_pc | |
#define SP ARM_sp | |
#endif | |
#ifdef __aarch64__ | |
#define IP pc | |
#define SP sp | |
#endif | |
#define STR_HELPER(x) #x | |
#define STR(x) STR_HELPER(x) | |
#define BUF_LIBNAME MAX_LIBNAME +1 | |
#define BUF_LIBNAME_FMT "%" STR(MAX_LIBNAME) "s" | |
struct region_data | |
{ | |
unsigned long min_start, max_end; | |
unsigned long stack_end; | |
int num_regions; | |
unsigned long start[MAX_REGIONS], end[MAX_REGIONS], off[MAX_REGIONS]; | |
char libname[MAX_REGIONS][BUF_LIBNAME]; | |
}; | |
struct region_data exec_region_data; | |
pid_t active[MAX_ACTIVE_PIDS] = {0}; | |
int active_pid_num = 0; | |
void add_active_pid(pid_t pid) | |
{ | |
if (active_pid_num >= MAX_ACTIVE_PIDS) | |
return; | |
active[active_pid_num] = pid; | |
active_pid_num++; | |
} | |
int should_exit(pid_t exit_pid) | |
{ | |
int t; | |
for(t=0;t<active_pid_num;t++) | |
if (active[t] == exit_pid) | |
break; | |
for(;t<active_pid_num-1;t++) | |
active[t] = active[t+1]; | |
active_pid_num--; | |
return active_pid_num == 0; | |
} | |
void parse_pid_maps(pid_t pid, struct region_data *data) | |
{ | |
memset(data, 0, sizeof(*data)); | |
char buf[64]; | |
snprintf(buf, 64, "/proc/%d/maps", pid); | |
FILE *fp = fopen(buf, "r"); | |
if (!fp) | |
{ | |
printf("Failed to open maps file!"); | |
return; | |
} | |
unsigned long start, end, off; | |
int n, cur; | |
char x, c; | |
char fb[BUF_LIBNAME]; | |
char line_buf[128+BUF_LIBNAME]; | |
char *line; | |
while(line = fgets(line_buf, 128+BUF_LIBNAME, fp)) | |
{ | |
printf("map: %s", line); | |
n = sscanf(line, "%lx-%lx %*c%*c%c%*c %lx %*s %*lu "BUF_LIBNAME_FMT, &start, &end, &x, &off, fb); | |
//read the rest | |
while (strlen(line_buf) == sizeof(line_buf)-1 && line_buf[sizeof(line_buf)-1] != '\n' && fgets(line_buf, 128+BUF_LIBNAME, fp)) | |
printf("reading rest"); | |
if (n<5) | |
continue; | |
if (x == 'x' && fb[0] != '[') | |
{ | |
if (!data->min_start) | |
data->min_start = start; | |
data->max_end = end; | |
int cur = data->num_regions; | |
data->start[cur] = start; | |
data->end[cur] = end; | |
data->off[cur] = off; | |
strcpy(data->libname[cur], fb); | |
data->num_regions++; | |
} | |
else if (!strcmp(fb, "[stack]")) | |
{ | |
data->stack_end = end; | |
} | |
} | |
} | |
int print_resolved_addr(unsigned long val, struct region_data *data) | |
{ | |
if (val >= data->min_start && val < data->max_end) | |
{ | |
int t; | |
for(t=0;t<data->num_regions;t++) | |
{ | |
if (val >= data->start[t] && val < data->end[t]) | |
{ | |
printf("%s: 0x%lx", data->libname[t], val-data->start[t]); | |
if (data->off[t]) | |
printf(" (0x%lx in file)", val-data->start[t]+data->off[t]); | |
printf("\n"); | |
return 1; | |
} | |
} | |
} | |
return 0; | |
} | |
/* | |
Note this is not a real backtrace, it will report many false positives | |
*/ | |
void try_print_backtrace(pid_t pid, unsigned long sp, struct region_data *data) | |
{ | |
unsigned long end_addr = sp+SCAN_STACK_SIZE; | |
if (data->stack_end < end_addr) | |
end_addr = data->stack_end; | |
unsigned long val; | |
unsigned long addr; | |
for(addr=sp;addr<end_addr;addr+=STACKMEM_ALIGN) | |
{ | |
val = ptrace(PTRACE_PEEKDATA, pid, addr); | |
print_resolved_addr(val, data); | |
} | |
} | |
int main(int argc, char **argv) | |
{ | |
if (argc<2) | |
{ | |
printf("%s COMMAND [ARGS]...\n", argv[0]); | |
return -1; | |
} | |
pid_t child_main; | |
int wstatus, signum; | |
struct pt_regs regs; | |
child_main = fork(); | |
if (child_main == 0) | |
{ | |
ptrace(PTRACE_TRACEME, 0, NULL, NULL); | |
kill(getpid(), SIGSTOP); | |
execvp(argv[1], argv+1); | |
return -1; | |
} | |
printf("child main pid: %d\n", child_main); | |
add_active_pid(child_main); | |
waitpid(child_main, &wstatus, 0); | |
ptrace(PTRACE_SETOPTIONS, child_main, NULL, PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK|PTRACE_O_TRACECLONE); | |
ptrace(PTRACE_CONT, child_main, NULL, 0); | |
while(1) | |
{ | |
pid_t child = waitpid(-1, &wstatus, 0); | |
signum = WSTOPSIG(wstatus); | |
if (signum == SIGTRAP) | |
{ | |
int event = (wstatus >> 16) & 0xffff; | |
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK || event == PTRACE_EVENT_CLONE) | |
{ | |
pid_t newpid; | |
if (ptrace(PTRACE_GETEVENTMSG, child, NULL, (long) &newpid) != -1) | |
{ | |
add_active_pid(newpid); | |
printf("adding pid %d\n", newpid); | |
} | |
} | |
signum = 0; //currently we never forward this signal... theoretically we could know if this trap was not generated by ptrace | |
goto cont; | |
} | |
if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) | |
{ | |
if (should_exit(child)) | |
break; | |
} | |
if (signum == SIGSEGV) | |
{ | |
ptrace(PTRACE_GETREGS, child, NULL, ®s); | |
printf("SEGFAULT in %d, IP: 0x%lx\n", child, regs.IP); | |
parse_pid_maps(child, &exec_region_data); | |
printf("IP: "); | |
int ip_resolved = print_resolved_addr(regs.IP, &exec_region_data); | |
if (!ip_resolved) | |
printf("0x%lx (invalid) \n", regs.IP); | |
siginfo_t siginfo; | |
ptrace(PTRACE_GETSIGINFO, child, 0, &siginfo); | |
printf("siginfo.si_addr: 0x%08lx\n", siginfo.si_addr); | |
try_print_backtrace(child, regs.SP, &exec_region_data); | |
break; | |
} | |
cont: | |
ptrace(PTRACE_CONT, child, NULL, signum); | |
} | |
return wstatus; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment