2010-09-06 8 views
15

Ich habe eine C++ Funktion, die viele Rückgabeanweisungen an verschiedenen Stellen hat. Wie setze ich einen Haltepunkt in der return-Anweisung, wo die Funktion tatsächlich zurückgibt?Setzen von Haltepunkt in GDB, wo die Funktion gibt

Und was bedeutet "brechen" Befehl ohne Argument bedeutet?

+0

Haben Sie RBR erforscht? – Jack

Antwort

8

break ohne Argumente stoppt die Ausführung bei der nächsten Anweisung im aktuell ausgewählten Stapelrahmen. Sie wählen Versatzrahmen über die Befehle frame oder up und down aus. Wenn Sie den Punkt debuggen möchten, an dem Sie tatsächlich die aktuelle Funktion verlassen, wählen Sie den nächsten äußeren Rahmen und brechen Sie dort.

+3

Der Haltepunkt wird auf die aktuelle Anweisung gesetzt, nicht auf die nächste Anweisung. Die Ausführung wird bereits beendet, wenn Sie überhaupt einen Befehl ausführen. Jeder Haltepunkt in der aufrufenden Funktion wird nicht auftreten, wenn die aktuelle Funktion aktiv ist, es sei denn, es handelt sich um eine Rekursion. In diesem Fall wird ein solches Debugging verwirrend. – Potatoswatter

+0

Wie wählst du "den nächsten äußeren Rahmen aus und brichst dort"? Könntest Du das erläutern? (Beachten Sie, dass das Ziel darin besteht, einen Haltepunkt * innerhalb * der Funktion zu haben (zB in der Lage zu sein, seine Einheimischen zu sehen), aber kurz bevor es zurückkehrt.) – ShreevatsaR

2

Bruch ohne Argument setzt einen Haltepunkt in der aktuellen Zeile.

Es gibt keine Möglichkeit, dass ein einzelner Haltepunkt alle Rückkanäle erfasst. Setzen Sie entweder unmittelbar nach der Rückgabe einen Haltepunkt beim Aufrufer oder brechen Sie alle return Anweisungen ab.

Da dies C++ ist, könnten Sie vermutlich ein lokales Sentry-Objekt erstellen und dessen Destruktor unterbrechen.

15

Sie können reverse debugging verwenden, um herauszufinden, wo die Funktion tatsächlich zurückkehrt. Beenden Sie die Ausführung des aktuellen Frames, machen Sie reverse-step und dann sollten Sie bei der zurückgegebenen Anweisung stoppen.

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

Laut dieser Seite erfordert dies Linux-x86, und es hat wahrscheinlich eine beeindruckende Leistung Strafe. +1 sowieso, da es so cool ist. – Potatoswatter

+2

Das Tool 'rr' unter http://rr-project.org/ ermöglicht das Reverse-Debugging über Replay unter Linux, wobei es nur zu einer 1,2-fachen Verlangsamung kommt (zumindest auf der Website). Es macht eine coole Sache noch cooler. :) – pnkfelix

+0

@Potatoswatter und obendrein, bricht es vollständig in 7.11, wenn Sie die meisten Bibliotheksaufrufe wegen fehlender AVX-Unterstützung ... tun https://StackOverflow.com/Questions/2528918/Gdb-Reverse-debugging-fails -mit-Prozess-Aufzeichnung-tut-nicht-Unterstützung-Anweisung-0x/46113472 # 46113472 –

15

Im Gegensatz zu den bisherigen Antworten, die meisten Compiler eine einzige Rückkehr Montageanleitung erstellen, unabhängig davon, wie viele return Aussagen sind in der Funktion (es ist bequem für den Compiler, das zu tun, so gibt es nur eine einzige Ort, um die gesamte Stapelrahmenbereinigung durchzuführen).

Wenn Sie diese Anweisung stoppen möchten, müssen Sie nur disas eingeben und nach retq suchen (oder was auch immer die Rückgabeanweisung für Ihren Prozessor ist) und einen Haltepunkt darauf setzen. Zum Beispiel:

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

Ich stimme dafür, weil es ein nützlicher Leckerbissen ist, aber kann die OP sagen, welche 'return' im Code aufgerufen wurde. – dmckee

+1

Dies zusammen mit dem Rückwärtsschritt von @ ks1322 ist von unschätzbarem Wert. Sie müssen zwei umgekehrte Schritte ausführen, und das ist der Grund. – falstro

+1

Interessant! Ich habe einen Python-Befehl gemacht, der 'retq' findet und dort automatisch einen Haltepunkt setzt: http://Stackoverflow.com/a/31264709/895245 –

4

Pause auf allen retq der aktuellen Funktion

Dieser Python-Befehl setzt einen Breakpoint auf jede retq Anweisung der aktuellen Funktion:

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() 

Quelle mit:

und verwenden Sie den Befehl wie:

break-return 
continue 

Sie jetzt bei retq sein sollte.

Schritt bis retq

Just for fun, eine andere Implementierung, wenn stoppt ein retq (weniger effizient von da keine Hardware-Unterstützung) zu finden ist:

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() 

Dies wird Ihre anderen Haltepunkte ignorieren. TODO: Kann vermieden werden?

Nicht sicher, ob es schneller oder langsamer als reverse-step ist.

Eine Version, die bei zu einem gegebenen Opcode hält zu finden: https://stackoverflow.com/a/31249378/895245

+0

Irgendwie scheint das mit einer rekursiven Funktion, die mehrfach aufgerufen wird, zu gehen haywire, und jeder Haltepunkt, der bei der Rückkehr laufen soll, wird mehrfach aufgerufen. (Habe es eigentlich noch nicht mit einer einfacheren Funktion probiert ...) (Auf der anderen Seite funktioniert das auch, auch wenn der Breakpoint mehrfach aufgerufen wird, also danke.) – ShreevatsaR

+0

@ShreevatsaR komisch. Verknüpfen Sie ein minimales reproduzierbares Beispiel, wenn Sie können. –

1

Wenn Sie den Quellcode ändern können, Sie etwas schmutzigen Trick mit dem Prä-Prozessor verwendet werden könnten:

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 

Dann stellte einen Haltepunkt zu on_return und gehe einen Rahmen up.

Achtung: Dies funktioniert nicht, wenn eine Funktion nicht über eine return-Anweisung zurückkehrt. Stellen Sie also sicher, dass es sich bei der letzten Zeile um eine return handelt.

Beispiel (schamlos von C-Code kopiert, sondern auch in C++ arbeiten):

#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); 
} 

Das erste Makro

return; 

zu

return on_return(); 

der gültigen ändern ist , da on_return auch void zurückgibt.

wird Der zweite Makro

ändern
return -1; 

zu

return on_return(), -1; 

Welche on_return() und dann wieder anrufen wird -1 (dank der , -Operator).

Dies ist ein sehr schmutziger Trick, aber trotz der Verwendung von Rückwärtsschritten funktioniert es auch in Multithreading-Umgebungen und inline-Funktionen.

1

rr Reverse Debugging

ähnlich wie GDB record bei https://stackoverflow.com/a/3649698/895245 erwähnt, aber viel funktioneller wie von GDB 7,11 vs rr 4.1.0 in Ubuntu 16.04.

Insbesondere beschäftigt sie sich mit AVX richtig:

, die sie von der Arbeit mit den Standard-Standard-Bibliothek Anrufe verhindert.

Installieren Sie Ubuntu 16.04.

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

Aber auch in Erwägung ziehen, aus der Quelle zu kompilieren, um die neuesten Updates zu erhalten, war es nicht schwer.

Testprogramm:

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

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

kompilieren und ausführen:

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

Jetzt sind Sie in einer GDB Sitzung verlassen, und Sie können richtig Debug-Reverse:

(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; 

Wir sind jetzt auf der richtigen Rückleitung.

Verwandte Themen