¿Cómo establecer un punto de interrupción en GDB donde la función retorna?

Tengo una función de C ++ que tiene muchas declaraciones de retorno en varios lugares. ¿Cómo establecer un punto de interrupción en la statement de devolución donde la función realmente regresa?

¿Y qué significa “romper” el comando sin argumento?

romper sin argumentos detiene la ejecución en la siguiente instrucción en el marco de stack seleccionado actualmente. Usted selecciona cuadros de marco a través de los comandos de frame o up y down . Si desea depurar el punto en el que realmente está abandonando la función actual, seleccione el siguiente marco externo y divídalo allí.

Contrariamente a las respuestas hasta ahora, la mayoría de los comstackdores crearán una única instrucción de ensamblaje de retorno, independientemente de cuántas declaraciones de return contengan en la función (es conveniente para el comstackdor hacer eso, por lo que solo hay un lugar para realizar todo el marco de stack limpiar).

Si desea detenerse en esa instrucción, todo lo que tiene que hacer es disas y buscar retq (o cualquiera que sea la instrucción de retorno para su procesador), y establecer un punto de interrupción en él. Por ejemplo:

 int foo(int x) { switch(x) { case 1: return 2; case 2: return 3; default: return 42; } } int main() { return foo(0); } (gdb) disas foo Dump of assembler code for function foo: 0x0000000000400448 <+0>: push %rbp 0x0000000000400449 <+1>: mov %rsp,%rbp 0x000000000040044c <+4>: mov %edi,-0x4(%rbp) 0x000000000040044f <+7>: mov -0x4(%rbp),%eax 0x0000000000400452 <+10>: mov %eax,-0xc(%rbp) 0x0000000000400455 <+13>: cmpl $0x1,-0xc(%rbp) 0x0000000000400459 <+17>: je 0x400463  0x000000000040045b <+19>: cmpl $0x2,-0xc(%rbp) 0x000000000040045f <+23>: je 0x40046c  0x0000000000400461 <+25>: jmp 0x400475  0x0000000000400463 <+27>: movl $0x2,-0x8(%rbp) 0x000000000040046a <+34>: jmp 0x40047c  0x000000000040046c <+36>: movl $0x3,-0x8(%rbp) 0x0000000000400473 <+43>: jmp 0x40047c  0x0000000000400475 <+45>: movl $0x2a,-0x8(%rbp) 0x000000000040047c <+52>: mov -0x8(%rbp),%eax 0x000000000040047f <+55>: leaveq 0x0000000000400480 <+56>: retq End of assembler dump. (gdb) b *0x0000000000400480 Breakpoint 1 at 0x400480 (gdb) r Breakpoint 1, 0x0000000000400480 in foo () (gdb) p $rax $1 = 42 

Puede usar la depuración inversa para averiguar dónde devuelve realmente la función. Finaliza la ejecución del fotogtwig actual, realiza el paso inverso y luego debes detenerse en la statement que acaba de devolver.

 (gdb) record (gdb) fin (gdb) reverse-step 

Rompe todos los retq de la función actual

Este comando de Python pone un punto de interrupción en cada instrucción de retq de la función actual:

 class BreakReturn(gdb.Command): def __init__(self): super().__init__( 'break-return', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE, False ) def invoke(self, arg, from_tty): frame = gdb.selected_frame() # TODO make this work if there is no debugging information, where .block() fails. block = frame.block() # Find the function block in case we are in an inner block. while block: if block.function: break block = block.superblock start = block.start end = block.end arch = frame.architecture() pc = gdb.selected_frame().pc() instructions = arch.disassemble(start, end - 1) for instruction in instructions: if instruction['asm'].startswith('retq '): gdb.Breakpoint('*{}'.format(instruction['addr'])) BreakReturn() 

Fuentealo con:

 source gdb.py 

y usa el comando como:

 break-return continue 

Ahora debería estar en retq .

Paso hasta retq

Solo por diversión, otra implementación que se detiene cuando se encuentra un retq (menos eficiente porque no admite hardware):

 class ContinueReturn(gdb.Command): def __init__(self): super().__init__( 'continue-return', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE, False ) def invoke(self, arg, from_tty): thread = gdb.inferiors()[0].threads()[0] while thread.is_valid(): gdb.execute('ni', to_string=True) frame = gdb.selected_frame() arch = frame.architecture() pc = gdb.selected_frame().pc() instruction = arch.disassemble(pc)[0]['asm'] if instruction.startswith('retq '): break ContinueReturn() 

Esto ignorará tus otros puntos de interrupción. TODO: ¿se puede evitar?

No estoy seguro de si es más rápido o más lento que reverse-step .

Una versión que se detiene en un código de operación dado se puede encontrar en: https://stackoverflow.com/a/31249378/895245

rr depuración inversa

Similar al record GDB mencionado en https://stackoverflow.com/a/3649698/895245 , pero mucho más funcional a partir de GDB 7.11 vs rr 4.1.0 en Ubuntu 16.04.

Notablemente, trata con AVX correctamente:

  • La depuración inversa de gdb falla con “El registro de proceso no admite la instrucción 0xf0d en la dirección”
  • “target record-full” en gdb hace que el comando “n” falle en printf con “¿El registro del proceso no es compatible con la instrucción 0xc5 en la dirección 0x7ffff7dee6e7”?

lo que le impide trabajar con las llamadas de biblioteca estándar predeterminadas.

Instalar Ubuntu 16.04.

 sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic sudo cpupower frequency-set -g performance 

Pero también considere comstackr desde el origen para obtener las últimas actualizaciones, no fue difícil.

Progtwig de prueba:

 int where_return(int i) { if (i) return 1; else return 0; } int main(void) { where_return(0); where_return(1); } 

comstackr y ejecutar:

 gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c rr record ./reverse.out rr replay 

Ahora se queda dentro de una sesión de GDB, y puede revertir adecuadamente la depuración:

 (rr) break main Breakpoint 1 at 0x56057c458619: file ac, line 9. (rr) continue Continuing. Breakpoint 1, main () at ac:9 9 where_return(0); (rr) step where_return (i=0) at ac:2 2 if (i) (rr) finish Run till exit from #0 where_return (i=0) at ac:2 main () at ac:10 10 where_return(1); Value returned is $1 = 0 (rr) reverse-step where_return (i=0) at ac:6 6 } (rr) reverse-step 5 return 0; 

Ahora estamos en la línea de retorno correcta.

Break sin argumento establece un punto de interrupción en la línea actual.

No hay forma de que un único punto de interrupción atrape todas las rutas de retorno. Establezca un punto de interrupción en la persona que llama inmediatamente después de que vuelva, o corte en todas las declaraciones de return .

Dado que esto es C ++, supongo que podría crear un objeto centinela local y romper su destructor.

Si puede cambiar el código fuente, puede usar algún truco sucio con el preprocesador:

 void on_return() { } #define return return on_return(), /* If the function has a return value != void */ #define return return on_return() /* If the function has a return value == void */ /* <<<-- Insert your function here -->>> */ #undef return 

Luego configura un punto de interrupción para on_return y on_return un cuadro.

Atención: Esto no funcionará, si una función no regresa a través de una statement de return . Así que asegúrese de que su última línea sea una return .

Ejemplo (copiado descaradamente del código C, pero también funcionará en C ++):

 #include  /* Dummy function to place the breakpoint */ void on_return(void) { } #define return return on_return() void myfun1(int a) { if (a > 10) return; printf("<10\n"); return; } #undef return #define return return on_return(), int myfun2(int a) { if (a < 0) return -1; if (a > 0) return 1; return 0; } #undef return int main(void) { myfun1(1); myfun2(2); } 

La primera macro cambiará

 return; 

a

 return on_return(); 

Lo cual es válido, ya que on_return también devuelve void .

La segunda macro cambiará

 return -1; 

a

 return on_return(), -1; 

Que llamará a on_return() y luego devolverá -1 (gracias a , -operator).

Este es un truco muy sucio, pero a pesar de usar pasos hacia atrás, funcionará también en entornos de subprocesos múltiples y funciones en línea.