Created
November 21, 2012 03:16
-
-
Save crimsonwoods/4122796 to your computer and use it in GitHub Desktop.
A sample code for ARM processor to handle the abnormal termination and display backtrace.
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 <stdio.h> | |
#include <stdlib.h> | |
#include <errno.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <sys/ptrace.h> | |
#include <sys/wait.h> | |
#include <endian.h> | |
#include <libunwind.h> | |
#include <libunwind-arm.h> | |
#include <libunwind-ptrace.h> | |
#ifndef __deprecated | |
#define __deprecated | |
#endif | |
#include <asm/user.h> | |
typedef struct app_args_t { | |
pid_t pid; | |
char const *out; | |
char const *exec; | |
} app_args_t; | |
typedef struct exec_args_t { | |
char *path; | |
char **args; | |
} exec_args_t; | |
static void show_usage(); | |
static int parse_args(app_args_t *args, int argc, char **argv); | |
static char const *sigstr(int sig); | |
static void dump_siginfo(siginfo_t const *info, FILE *outfp); | |
static void dump_regs(struct user const *regs, FILE *outfp); | |
static void build_exec_args(char const *cmd, exec_args_t *args); | |
static void backtrace(unw_addr_space_t *as, pid_t pid, FILE *outfp); | |
int main(int argc, char **argv) | |
{ | |
app_args_t args; | |
siginfo_t sif; | |
int status = 0; | |
int failed = 0; | |
int stopsig = 0; | |
int i; | |
FILE *outfp = NULL; | |
int ret = 0; | |
struct user regs; | |
unw_addr_space_t unw_as; | |
parse_args(&args, argc, argv); | |
if (!args.pid && (NULL == args.exec)) { | |
show_usage(); | |
return 0; | |
} | |
if (!strcmp(args.out, "-")) { | |
outfp = stdout; | |
} else { | |
outfp = fopen(args.out, "w"); | |
if (NULL == outfp) { | |
ret = errno; | |
fprintf(stderr, "failed to open output file (%s).\n", strerror(errno)); | |
goto __cleanup; | |
} | |
} | |
if (args.exec) { | |
exec_args_t exec_args = { NULL, NULL }; | |
build_exec_args(args.exec, &exec_args); | |
pid_t pid = fork(); | |
if (0 == pid) { | |
if (0 != ptrace(PTRACE_TRACEME, 0, NULL, NULL)) { | |
fprintf(stderr, "failed to start trace (%s).\n", strerror(errno)); | |
exit(1); | |
} | |
usleep(100); | |
execv(exec_args.path, exec_args.args); | |
exit(2); | |
} else { | |
args.pid = pid; | |
} | |
} else { | |
if (0 != ptrace(PTRACE_ATTACH, args.pid, NULL, NULL)) { | |
ret = errno; | |
fprintf(stderr, "ERROR: failed to attach the process (pid=%d) (%s)\n", args.pid, strerror(errno)); | |
goto __cleanup; | |
} | |
} | |
unw_as = unw_create_addr_space(&_UPT_accessors, 0); | |
if (!unw_as) { | |
fprintf(stderr, "ERROR: unw_create_addr_space failed.\n"); | |
goto __detach; | |
} | |
if (-1 == wait(&status)) { | |
ret = errno; | |
fprintf(stderr, "WARNING: failed to wait events (%s).\n", strerror(errno)); | |
goto __detach; | |
} | |
for (;;) { | |
if (0 != ptrace(PTRACE_CONT, args.pid, NULL, stopsig)) { | |
ret = errno; | |
fprintf(stderr, "ERROR: failed to continue the process (%s).\n", strerror(errno)); | |
failed = 1; | |
break; | |
} | |
if (-1 == wait(&status)) { | |
fprintf(stderr, "WARNING: failed to wait events (%s).\n", strerror(errno)); | |
continue; | |
} | |
fprintf(outfp, "INFO: status = %d\n", status); | |
if (WIFEXITED(status)) { | |
fprintf(outfp, "INFO: process exited (ret=%d)\n", WEXITSTATUS(status)); | |
break; | |
} | |
if (!WIFSTOPPED(status)) { | |
continue; | |
} | |
stopsig = WSTOPSIG(status); | |
fprintf(outfp, "INFO: stop signal = %s(%d)\n", sigstr(stopsig), stopsig); | |
if (stopsig != SIGSTOP) { | |
if (0 != ptrace(PTRACE_GETSIGINFO, args.pid, NULL, &sif)) { | |
ret = errno; | |
fprintf(stderr, "ERROR: failed to get signal information (%s).\n", strerror(errno)); | |
failed = 1; | |
break; | |
} | |
dump_siginfo(&sif, outfp); | |
} | |
if (stopsig == SIGSEGV || stopsig == SIGILL) { | |
char cat_cmd[48]; | |
memset(®s, 0, sizeof(regs)); | |
if (0 != ptrace(PTRACE_GETREGS, args.pid, NULL, ®s)) { | |
fprintf(stderr, "WARNING: failed to get registers information (%s).\n", strerror(errno)); | |
} else { | |
dump_regs(®s, outfp); | |
} | |
backtrace(&unw_as, args.pid, outfp); | |
snprintf(cat_cmd, sizeof(cat_cmd), "cat /proc/%d/maps", args.pid); | |
system(cat_cmd); | |
} | |
} | |
__detach: | |
unw_destroy_addr_space (unw_as); | |
if (args.exec) { | |
ptrace(PTRACE_KILL, args.pid, NULL, NULL); | |
waitpid(args.pid, NULL, 0); | |
} else { | |
ptrace(PTRACE_DETACH, args.pid, NULL, NULL); | |
} | |
__cleanup: | |
if (outfp && (stdout != outfp)) { | |
fclose(outfp); | |
} | |
return ret; | |
} | |
static void show_usage() | |
{ | |
printf("Usage: debug (-p pid | -c cmd) [-o out]\n"); | |
} | |
static int parse_args(app_args_t *args, int argc, char **argv) | |
{ | |
args->pid = 0; | |
args->out = "-"; | |
args->exec = NULL; | |
int opt; | |
while (-1 != (opt = getopt(argc, argv, "p:c:o:"))) { | |
switch (opt) { | |
case 'p': | |
args->pid = atoi(optarg); | |
break; | |
case 'c': | |
args->exec = optarg; | |
break; | |
case 'o': | |
args->out = optarg; | |
break; | |
} | |
} | |
return 0; | |
} | |
static char const *sigstr(int sig) | |
{ | |
static char signum[4]; | |
#define CASESTR(x) case x: return #x; | |
switch (sig) { | |
CASESTR(SIGINT); | |
CASESTR(SIGHUP); | |
CASESTR(SIGILL); | |
CASESTR(SIGTRAP); | |
CASESTR(SIGABRT); | |
CASESTR(SIGSEGV); | |
CASESTR(SIGTERM); | |
CASESTR(SIGCHLD); | |
default: snprintf (signum, sizeof(signum), "%d", sig); return signum; | |
} | |
} | |
static void dump_siginfo(siginfo_t const *info, FILE *outfp) | |
{ | |
fprintf(outfp, "SIGNAL: %s(%d), ERRNO: %d, CODE: %d\n", | |
sigstr(info->si_signo), info->si_signo, info->si_errno, info->si_code); | |
switch (info->si_signo) { | |
case SIGCHLD: | |
fprintf(outfp, "pid = %d\n", info->si_pid); | |
fprintf(outfp, "uid = %d\n", info->si_uid); | |
fprintf(outfp, "status = %x\n", info->si_status); | |
fprintf(outfp, "utime = %u\n", info->si_utime); | |
fprintf(outfp, "stime = %u\n", info->si_stime); | |
break; | |
case SIGILL: | |
case SIGFPE: | |
case SIGSEGV: | |
case SIGBUS: | |
fprintf(outfp, "addr = %p\n", info->si_addr); | |
#ifdef __ARCH_SI_TRAPNO | |
fprintf(outfp, "trapno = %d\n", info->si_trapno); | |
#endif | |
break; | |
} | |
} | |
static void dump_regs(struct user const *regs, FILE *outfp) | |
{ | |
fprintf(outfp, "cpsr = 0x%08x, pc = 0x%08x\n", regs->regs.ARM_cpsr, regs->regs.ARM_pc); | |
fprintf(outfp, "lr = 0x%08x, sp = 0x%08x\n", regs->regs.ARM_lr, regs->regs.ARM_sp); | |
fprintf(outfp, "ip = 0x%08x, fp = 0x%08x\n", regs->regs.ARM_ip, regs->regs.ARM_fp); | |
fprintf(outfp, "r0 = 0x%08x, r1 = 0x%08x\n", regs->regs.ARM_r0, regs->regs.ARM_r1); | |
fprintf(outfp, "r2 = 0x%08x, r3 = 0x%08x\n", regs->regs.ARM_r2, regs->regs.ARM_r3); | |
fprintf(outfp, "r4 = 0x%08x, r5 = 0x%08x\n", regs->regs.ARM_r4, regs->regs.ARM_r5); | |
fprintf(outfp, "r6 = 0x%08x, r7 = 0x%08x\n", regs->regs.ARM_r6, regs->regs.ARM_r7); | |
fprintf(outfp, "r8 = 0x%08x, r9 = 0x%08x\n", regs->regs.ARM_r8, regs->regs.ARM_r9); | |
} | |
static void build_exec_args(char const *cmd, exec_args_t *args) | |
{ | |
char const delim[] = " \t"; | |
char *token; | |
char *cur_ptr; | |
char *cmd_dup = strdup(cmd); | |
int err = 0; | |
int i; | |
args->path = NULL; | |
args->args = NULL; | |
if (NULL == cmd_dup) { | |
goto __cleanup; | |
} | |
token = strtok_r(cmd_dup, delim, &cur_ptr); | |
if (!token) { | |
goto __cleanup; | |
} | |
args->path = strdup(token); | |
if (!args->path) { | |
err = errno; | |
goto __cleanup; | |
} | |
args->args = malloc(sizeof(char *) * 66); | |
if (!args->args) { | |
err = errno; | |
goto __cleanup; | |
} | |
memset(args->args, 0, sizeof(char *) * 66); | |
args->args[0] = strdup(token); | |
if (!args->args[0]) { | |
err = errno; | |
goto __cleanup; | |
} | |
for (i = 1; i < 65; ++i) { | |
token = strtok_r(NULL, delim, &cur_ptr); | |
if (!token) { | |
break; | |
} | |
args->args[i] = strdup(token); | |
if (!args->args[i]) { | |
err = errno; | |
break; | |
} | |
} | |
args->args[65] = NULL; | |
__cleanup: | |
if (cmd_dup) { | |
free(cmd_dup); | |
} | |
if (err) { | |
if (args->path) { | |
free(args->path); | |
} | |
if (args->args) { | |
for (i = 0; args->args[i]; ++i) { | |
free(args->args[i]); | |
} | |
free(args->args); | |
} | |
args->path = NULL; | |
args->args = NULL; | |
} | |
} | |
static void backtrace(unw_addr_space_t *as, pid_t pid, FILE *outfp) | |
{ | |
unw_word_t ip, sp, start_ip = 0, off; | |
int n = 0; | |
unw_proc_info_t pi; | |
char buf[512]; | |
size_t len; | |
unw_cursor_t unw_c; | |
int ret; | |
void *ui; | |
ui = _UPT_create(pid); | |
fprintf(outfp, "\n---- [ backtrace ] ----------\n"); | |
ret = unw_init_remote(&unw_c, *as, ui); | |
if (0 > ret) { | |
fprintf(stderr, "unw_init_remote failed (ret=%d).\n", ret); | |
goto __cleanup; | |
} | |
do { | |
if ((ret = unw_get_reg(&unw_c, UNW_REG_IP, &ip)) < 0 || | |
(ret = unw_get_reg(&unw_c, UNW_REG_SP, &sp)) < 0) { | |
fprintf(stderr, "unw_get_reg failed (ret=%d).\n", ret); | |
break; | |
} | |
if (0 == n) { | |
start_ip = ip; | |
} | |
buf[0] = '\0'; | |
if ((ret = unw_get_proc_name(&unw_c, buf, sizeof(buf), &off)) == 0) { | |
fprintf(outfp, "%p <%s + 0x%x>\n", (void*)ip, buf, off); | |
} else { | |
//fprintf(stderr, "unw_get_proc_name failed (ret = %d).\n", ret); | |
break; | |
} | |
if ((ret = unw_get_proc_info(&unw_c, &pi)) < 0) { | |
//fprintf(stderr, "unw_get_proc_info failed (ret=%d).\n", ret); | |
break; | |
} | |
ret = unw_step(&unw_c); | |
if (ret < 0) { | |
unw_get_reg(&unw_c, UNW_REG_IP, &ip); | |
} | |
if (++n > 64) { | |
break; | |
} | |
} while (ret > 0); | |
__cleanup: | |
_UPT_destroy(ui); | |
fprintf(outfp ,"-----------------------------\n\n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment