c gdb systemtap

¿Cómo puedo establecer el punto de interrupción en GDB para abrir(2) syscall devolviendo-1



systemtap (2)

SO: GNU / Linux
Distro: OpenSuSe 13.1
Arco: x86-64
Versión GDB: 7.6.50.20130731-cvs
Lenguaje del programa: en su mayoría C con pedacitos de montaje menores.

Imagina que tengo un programa bastante grande que a veces no abre un archivo. ¿Es posible establecer un punto de interrupción en GDB de tal manera que se detenga después open(2) syscall open(2) devuelva -1?

Por supuesto, puedo revisar el código fuente y encontrar todas open(2) invocaciones open(2) y restringir la llamada open() falla, pero quizás haya una mejor manera.

Intenté usar "catch syscall open" luego "condition N if $rax==-1" pero obviamente no fue alcanzado.
Por cierto, ¿es posible distinguir entre una llamada a syscall (por ejemplo, open(2) ) y devolver desde syscall (por ejemplo, open(2) ) en GDB?

Como una solución actual hago lo siguiente:

  1. Ejecutar el programa en cuestión bajo la GDB
  2. Desde otro terminal, ejecute el script systemtap:

    stap -g -v -e ''probe process("PATH to the program run under GDB").syscall.return { if( $syscall == 2 && $return <0) raise(%{ SIGSTOP %}) }''

  3. Después de open(2) devuelve -1 recibo SIGSTOP en la sesión de GDB y puedo solucionar el problema.

TIA.

Atentamente,
alexz

UPD: Aunque probé el enfoque sugerido por nm antes y no pude hacerlo funcionar, decidí intentarlo de nuevo. Después de 2 horas, ahora funciona según lo previsto. Pero con alguna solución extraña:

  1. Todavía no puedo distinguir entre llamar y regresar de syscall
  2. Si utilizo finish in comm no puedo usar continue , lo cual está bien de acuerdo con los documentos de GDB
    es decir, lo siguiente cae al indicador de gdb en cada interrupción:

    gdb> comm gdb> finish gdb> printf "rax is %d/n",$rax gdb> cont gdb> end

  3. En realidad, puedo evitar el uso de finish y verificar% rax en los commands pero en este caso tengo que buscar -errno en lugar de -1, por ejemplo, si es "Permiso denegado", entonces tengo que buscar "-13" y si es "No tal archivo o direcory "- entonces para -2. Simplemente no está bien

  4. Así que la única manera de hacer que funcionara para mí fue definir una función personalizada y usarla de la siguiente manera:

    (gdb) catch syscall open Catchpoint 1 (syscall ''open'' [2] (gdb) define mycheck Type commands for definition of "mycheck". End with a line saying just "end". >finish >finish >if ($rax != -1) >cont >end >printf "rax is %d/n",$rax >end (gdb) comm Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >mycheck >end (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/alexz/gdb_syscall_test/main ..... Catchpoint 1 (returned from syscall open), 0x00007ffff7b093f0 in __open_nocancel () from /lib64/libc.so.6 0x0000000000400756 in main (argc=1, argv=0x7fffffffdb18) at main.c:24 24 fd = open(filenames[i], O_RDONLY); Opening test1 fd = 3 (0x3) Successfully opened test1 Catchpoint 1 (call to syscall open), 0x00007ffff7b093f0 in __open_nocancel () from /lib64/libc.so.6 rax is -38 Catchpoint 1 (returned from syscall open), 0x00007ffff7b093f0 in __open_nocancel () from /lib64/libc.so.6 0x0000000000400756 in main (argc=1, argv=0x7fffffffdb18) at main.c:24 ---Type <return> to continue, or q <return> to quit--- 24 fd = open(filenames[i], O_RDONLY); rax is -1 (gdb) bt #0 0x0000000000400756 in main (argc=1, argv=0x7fffffffdb18) at main.c:24 (gdb) step 26 printf("Opening %s/n", filenames[i]); (gdb) info locals i = 1 fd = -1


¿Es posible establecer un punto de interrupción en GDB de tal manera que se detenga después de que syscall abierto (2) devuelva -1?

Es difícil hacerlo mejor que la respuesta de nm para esta pregunta restringida , pero diría que la pregunta se plantea de manera incorrecta.

Por supuesto, puedo revisar el código fuente y encontrar todas las invocaciones abiertas (2)

Eso es parte de su confusión: cuando llama a open en un programa C, de hecho no está ejecutando la llamada al sistema open(2) . Más bien, está invocando un "código auxiliar" open(3) desde su libc, y ese código auxiliar ejecutará la llamada del sistema open(2) para usted.

Y si desea establecer un punto de interrupción cuando el apéndice está a punto de devolver -1 , es muy fácil.

Ejemplo:

/* t.c */ #include <sys/stat.h> #include <fcntl.h> int main() { int fd = open("/no/such/file", O_RDONLY); return fd == -1 ? 0 : 1; } $ gcc -g t.c; gdb -q ./a.out (gdb) start Temporary breakpoint 1 at 0x4004fc: file t.c, line 6. Starting program: /tmp/a.out Temporary breakpoint 1, main () at t.c:6 6 int fd = open("/no/such/file", O_RDONLY); (gdb) s open64 () at ../sysdeps/unix/syscall-template.S:82 82 ../sysdeps/unix/syscall-template.S: No such file or directory.

Aquí hemos llegado a la llamada del sistema glibc. Vamos a desmontarlo:

(gdb) disas Dump of assembler code for function open64: => 0x00007ffff7b01d00 <+0>: cmpl $0x0,0x2d74ad(%rip) # 0x7ffff7dd91b4 <__libc_multiple_threads> 0x00007ffff7b01d07 <+7>: jne 0x7ffff7b01d19 <open64+25> 0x00007ffff7b01d09 <+0>: mov $0x2,%eax 0x00007ffff7b01d0e <+5>: syscall 0x00007ffff7b01d10 <+7>: cmp $0xfffffffffffff001,%rax 0x00007ffff7b01d16 <+13>: jae 0x7ffff7b01d49 <open64+73> 0x00007ffff7b01d18 <+15>: retq 0x00007ffff7b01d19 <+25>: sub $0x8,%rsp 0x00007ffff7b01d1d <+29>: callq 0x7ffff7b1d050 <__libc_enable_asynccancel> 0x00007ffff7b01d22 <+34>: mov %rax,(%rsp) 0x00007ffff7b01d26 <+38>: mov $0x2,%eax 0x00007ffff7b01d2b <+43>: syscall 0x00007ffff7b01d2d <+45>: mov (%rsp),%rdi 0x00007ffff7b01d31 <+49>: mov %rax,%rdx 0x00007ffff7b01d34 <+52>: callq 0x7ffff7b1d0b0 <__libc_disable_asynccancel> 0x00007ffff7b01d39 <+57>: mov %rdx,%rax 0x00007ffff7b01d3c <+60>: add $0x8,%rsp 0x00007ffff7b01d40 <+64>: cmp $0xfffffffffffff001,%rax 0x00007ffff7b01d46 <+70>: jae 0x7ffff7b01d49 <open64+73> 0x00007ffff7b01d48 <+72>: retq 0x00007ffff7b01d49 <+73>: mov 0x2d10d0(%rip),%rcx # 0x7ffff7dd2e20 0x00007ffff7b01d50 <+80>: xor %edx,%edx 0x00007ffff7b01d52 <+82>: sub %rax,%rdx 0x00007ffff7b01d55 <+85>: mov %edx,%fs:(%rcx) 0x00007ffff7b01d58 <+88>: or $0xffffffffffffffff,%rax 0x00007ffff7b01d5c <+92>: jmp 0x7ffff7b01d48 <open64+72> End of assembler dump.

Aquí puede ver que el código auxiliar se comporta de manera diferente dependiendo de si el programa tiene múltiples subprocesos o no. Esto tiene que ver con la cancelación asíncrona.

Hay dos instrucciones de syscall, y en el caso general deberíamos establecer un punto de interrupción después de cada una (pero ver más abajo).

Pero este ejemplo es de un solo hilo, por lo que puedo establecer un único punto de interrupción condicional:

(gdb) b *0x00007ffff7b01d10 if $rax < 0 Breakpoint 2 at 0x7ffff7b01d10: file ../sysdeps/unix/syscall-template.S, line 82. (gdb) c Continuing. Breakpoint 2, 0x00007ffff7b01d10 in __open_nocancel () at ../sysdeps/unix/syscall-template.S:82 82 in ../sysdeps/unix/syscall-template.S (gdb) p $rax $1 = -2

Voila, la llamada al sistema open(2) devolvió -2 , que el código auxiliar se traducirá en la configuración de errno en ENOENT (que es 2 en este sistema) y devolver -1 .

Si el open(2) tuvo éxito, la condición $rax < 0 sería falsa, y GDB continuará.

Ese es precisamente el comportamiento que generalmente se quiere de GDB cuando se busca una llamada de sistema que falla entre muchas de las que tienen éxito.

Actualizar:

Como señala Chris Dodd, hay dos syscalls, pero en caso de error, ambos se ramifican al mismo código de manejo de errores (el código que establece errno ). Por lo tanto, podemos establecer un punto de interrupción no condicional en *0x00007ffff7b01d49 , y ese punto de interrupción se activará solo en caso de falla.

Esto es mucho mejor, porque los puntos de interrupción condicionales ralentizan bastante la ejecución cuando la condición es falsa (GDB tiene que detener a los inferiores, evaluar la condición y reanudar la inferior si la condición es falsa).


Este script gdb hace lo que se solicita:

set $outside = 1 catch syscall open commands silent set $outside = ! $outside if ( $outside && $rax >= 0) continue end if ( !$outside ) continue end echo `open'' returned a negative value/n end

La variable $outside es necesaria porque gdb detiene tanto en syscall enter como en syscall exit. Necesitamos ignorar ingresar eventos y verificar $rax solo al salir.