Cómo obtener un "backtrace"(como gdb) usando solo ptrace(linux, x86/x86_64)
x86-64 (2)
Puede ser que la fuente de la pstack(1)
me ayude: (git en línea de Debian ). Lamentablemente, esto es solo x86 de 32 bits
547 static int crawl(int pid)
548 {
549 unsigned long pc, fp, nextfp, nargs, i, arg;
550 int error_occured = 0;
551
552 errno = 0;
553 fp = -1;
554
555 pc = ptrace(PTRACE_PEEKUSER, pid, EIP * 4, 0);
556 if (pc != -1 || !errno)
557 fp = ptrace(PTRACE_PEEKUSER, pid, EBP * 4, 0);
558
559 if ((pc != -1 && fp != -1) || !errno) {
560 print_pc(pc);
561 for ( ; !errno && fp; ) {
562 nextfp = ptrace(PTRACE_PEEKDATA, pid, fp, 0);
563 if (nextfp == (unsigned) -1 && errno) break;
564
565 nargs = (nextfp - fp - 8) / 4;
566 if (nargs > MAXARGS) nargs = MAXARGS;
567 if (nargs > 0) {
568 fputs(" (", stdout);
569 for (i = 1; i <= nargs; i++) {
570 arg = ptrace(PTRACE_PEEKDATA, pid, fp + 4 * (i + 1), 0);
571 if (arg == (unsigned) -1 && errno) break;
572 printf("%lx", arg);
573 if (i < nargs) fputs(", ", stdout);
574 }
575 fputc('')'', stdout);
576 nargs = nextfp - fp - 8 - (4 * nargs);
577 if (!errno && nargs > 0) printf(" + %lx/n", nargs);
578 else fputc(''/n'', stdout);
579 } else fputc(''/n'', stdout);
580
581 if (errno || !nextfp) break;
582 pc = ptrace(PTRACE_PEEKDATA, pid, fp + 4, 0);
583 if (pc == (unsigned) -1 && errno) break;
584 fp = nextfp;
585 print_pc(pc);
586 }
587 if (fp) error_occured = 1;
588 } else error_occured = 1;
589
590 if (error_occured) perror("crawl");
591 else errno = 0;
592 return errno;
593 }
594
Además, la prueba rápida dice que no es muy confiable, pero a veces puede imprimir algo.
Quiero obtener una salida de tipo backtrace
como lo hace gdb. Pero quiero hacer esto a través de ptrace()
directamente. Mi plataforma es Linux, x86; y, más tarde x86_64.
Ahora solo quiero leer las direcciones de devolución de la pila, sin convertirlas en nombres de símbolos.
Entonces, para el programa de prueba, compilado en modo -O0
por gcc-4.5
:
int g() {
kill(getpid(),SIGALRM);
}
int f() {
int a;
int b;
a = g();
b = a;
return a+b;
}
int e() {
int c;
c = f();
}
main() {
return e();
}
Comenzaré mi programa y me conectaré con ptrace
para probar el programa al principio. Luego, haré PTRACE_CONT y esperaré la señal. Cuando el programa de prueba hará una auto-matanza; la señal será entregada a mi programa. En este momento quiero leer las direcciones de retorno, serán como (porque la función de kill
está activa en este momento):
0x00_some_address_in_g
0x00_some_address_in_f
0x00_some_address_in_e
0x00_some_address_in_main
0x00_some_address_in__libc_start_main
¿Cómo puedo encontrar las direcciones de retorno del proceso de prueba actualmente detenido con ptrace
? Habrá un ciclo sobre los cuadros? ¿Cuándo debería detener ese ciclo?
PD: sí, esto también es muy parecido a backtrace(3)
función libc en idea, pero quiero hacerlo externamente a través de ptrace.
El ejemplo publicado por osgx solo funcionará con código que usa punteros de marco. x86_64
código x86_64
producido por GCC con optimizaciones no lo hace. El código kernel vdso
en x86
no usa punteros de marco en al menos algunos procesadores. GCC 4.6 (con optimización) tampoco usa punteros de cuadro en el modo x86
.
Todo lo anterior se combina para hacer que el "rastreo de la pila a través de los indicadores de cuadro" sea muy poco confiable.
Puede usar libunwind
(que admite el libunwind
local (en proceso) y global (fuera de proceso a través de ptrace)).
O tendrá que volver a implementar una gran parte de libunwind
.
Ejemplo de obtener retroceso a través de ptrace
utilizando libunwind
.