Skip to content

Instantly share code, notes, and snippets.

@zb3
Created December 27, 2017 14:47
Show Gist options
  • Save zb3/29300d9b1450fc66caf463271a5ff1af to your computer and use it in GitHub Desktop.
Save zb3/29300d9b1450fc66caf463271a5ff1af to your computer and use it in GitHub Desktop.
A wrapper program for linux to find out why segfault happend
#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, &regs);
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