2010-09-06 21 views

Respuesta

8

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

+3

El punto de interrupción se establece en la instrucción actual, no en la siguiente. La ejecución ya está detenida si está ejecutando un comando. Cualquier punto de interrupción en la función de llamada no ocurrirá cuando la función actual esté activa, a menos que sea una recursión, en cuyo caso dicha depuración se vuelve confusa. – Potatoswatter

+0

¿Cómo se "selecciona el siguiente marco externo y se rompe allí"? ¿Puedes aclarar? (Tenga en cuenta que el objetivo es tener un punto de interrupción * dentro de la función (por ejemplo, poder ver sus locales), pero justo antes de que regrese). – ShreevatsaR

2

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

No hay forma de que un solo punto de interrupción capture 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 return.

Dado que se trata de C++, supongo que podría crear un objeto centinela local y romper su destructor.

15

Puede usar reverse debugging para averiguar dónde devuelve realmente la función. Finalice la ejecución de la trama actual, haga paso inverso y luego debe detenerse en la declaración que acaba de devolver.

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

Según esa página, esto requiere Linux-x86, y probablemente tenga una pena de rendimiento formidable. +1 de todos modos, ya que es genial. – Potatoswatter

+2

La herramienta 'rr' en http://rr-project.org/ habilita la depuración inversa a través de la reproducción en Linux mientras incurre solo en una desaceleración de 1.2x (según su sitio web, al menos). Hace una cosa genial incluso más fresca. :) – pnkfelix

+0

@Potatoswatter y, además, se divide completamente en 7.11 si realiza la mayoría de las llamadas a la biblioteca debido a la falta de soporte AVX ... https://stackoverflow.com/questions/2528918/gdb-reverse-debugging-fails -with-process-record-do-not-support-instruction-0x/46113472 # 46113472 –

15

Al contrario de respuestas hasta el momento, la mayoría de los compiladores creará una sola instrucción de montaje de retorno, independientemente del número de return declaraciones están en la función (que es conveniente para el compilador para hacer eso, por lo que sólo hay un único lugar para realizar toda la limpieza del marco de la pila).

Si desea detenerse en esa instrucción, todo lo que tiene que hacer es disas y busque 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 <foo+27> 
    0x000000000040045b <+19>: cmpl $0x2,-0xc(%rbp) 
    0x000000000040045f <+23>: je  0x40046c <foo+36> 
    0x0000000000400461 <+25>: jmp 0x400475 <foo+45> 
    0x0000000000400463 <+27>: movl $0x2,-0x8(%rbp) 
    0x000000000040046a <+34>: jmp 0x40047c <foo+52> 
    0x000000000040046c <+36>: movl $0x3,-0x8(%rbp) 
    0x0000000000400473 <+43>: jmp 0x40047c <foo+52> 
    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 
+0

. Estoy votando por esto porque es un tidbit útil, pero ¿el OP puede decir qué 'return' se llamó en el código. – dmckee

+1

esto, junto con el paso inverso de @ ks1322 es invaluable. Debes hacer dos pasos inversos, y esta es la razón. – falstro

+1

¡Interesante! He hecho un comando de Python que encuentra el 'retq' y pone un punto de interrupción allí automáticamente: http://stackoverflow.com/a/31264709/895245 –

4

Detener en todos los retq de la función actual

Este comando Python pone un punto de interrupción en cada retq la instrucción 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() 

Fuente con:

source gdb.py 

y utilizar el comando como:

break-return 
continue 

Ahora debería estar en retq.

paso hasta retq

Sólo por diversión, otra aplicación que se detiene cuando un retq se encuentra (menos eficiente de porque ningún soporte de 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 hará caso de sus 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 dada se puede encontrar en: https://stackoverflow.com/a/31249378/895245

+0

De alguna manera, con una función recursiva que se llama varias veces, esto parece ir Haywire, y cada punto de interrupción que se supone que se ejecuta en la devolución se llama varias veces. (No lo he intentado con una función más simple aún ...) (Por otro lado, esto realmente funciona, incluso si el punto de interrupción se llama varias veces, así que gracias.) – ShreevatsaR

+0

@ShreevatsaR extraño. Enlace a un ejemplo reproducible mínimo si puede. –

1

Si puede cambiar el código fuente, puede utilizar 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 

continuación, ajuste una punto de interrupción a on_return y vaya a un marco up.

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

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

#include <stdio.h> 

/* 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(); 

que es válido , desde on_return también devuelve void.

La segunda macro cambiará

return -1; 

a

return on_return(), -1; 

que llamará on_return() y luego regresar -1 (gracias a la , -operator).

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

1

rr depuración inversa

similares a BGF record mencionado al https://stackoverflow.com/a/3649698/895245, pero mucho más funcional a partir del BGF 7,11 vs rr 4.1.0 en Ubuntu 16.04.

En particular, se trata de AVX correctamente:

que le impide trabajar con las llamadas por defecto de la biblioteca estándar.

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 compilar desde la fuente para obtener las últimas actualizaciones, no fue difícil.

Programa de prueba:

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

int main(void) { 
    where_return(0); 
    where_return(1); 
} 

compilar y ejecutar:

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

Ahora lo que queda dentro de una sesión de GDB, y depurar puede invertir adecuadamente:

(rr) break main 
Breakpoint 1 at 0x56057c458619: file a.c, line 9. 
(rr) continue 
Continuing. 

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

Somos ahora en la línea de retorno correcta.

Cuestiones relacionadas