2010-09-06 215 views
15

我有一個C++函數,它在各個地方有很多返回語句。如何在函數實際返回的return語句處設置斷點?在功能返回的GDB中設置斷點

什麼沒有參數意味着「打破」命令?

+0

您是否探索過rbr? – Jack 2010-09-06 06:54:15

回答

8

不帶參數的break在當前所選棧幀的下一條指令處停止執行。您可以通過frameupdown命令選擇散架。如果你想調試點實際上離開當前功能,選擇下一個外框並在那裏打破。

+3

斷點在當前指令處設置,而不是下一個。如果你正在執行一個命令,執行已經停止。當前函數處於活動狀態時,調用函數中的任何斷點都不會發生,除非它是遞歸的,在這種情況下,此類調試會變得令人困惑。 – Potatoswatter 2010-09-06 06:48:09

+0

你如何「選擇下一個外框並在那裏打破」?你能澄清嗎? (請注意,目標是在函數內部有一個斷點*(例如能夠看到它的本地語言),但在它返回之前)。 – ShreevatsaR 2017-09-06 23:13:54

2

無參數斷點在當前行設置斷點。

單個斷點無法捕獲所有返回路徑。在返回後立即在調用者處設置斷點,或在所有return語句處中斷。因爲這是C++,我想你可以創建一個本地的崗位對象,並打破它的析構函數,儘管如此。

15

您可以使用reverse debugging找出函數實際返回的位置。完成執行當前幀,然後執行反向步驟,然後您應該停止返回語句。

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

根據該頁面,這需要Linux-x86,它可能有一個強大的表現懲罰。無論如何+1,因爲它太酷了。 – Potatoswatter 2010-09-07 03:13:40

+2

http://rr-project.org/上的'rr'工具可以通過Linux上的重放進行反向調試,而只會產生大約1.2倍的放緩(根據其網站,至少)。它使更酷的事情變得更酷。 :) – pnkfelix 2015-08-05 10:41:49

+0

@Potatoswatter最重要的是,它在7.11中完全崩潰,如果你因爲缺乏AVX支持而進行大多數庫調用... https://stackoverflow.com/questions/2528918/gdb-reverse-debugging-fails -with-process-record-does-not-support-instruction-0x/46113472 – 2017-09-08 09:49:21

15

相反的答案,到目前爲止,大多數編譯器會創建一個單一的回報彙編指令,不管return陳述有多少的功能(方便編譯器要做到這一點,所以只有一個地方執行所有的堆棧幀清理)。

如果你想停止該指令,你所要做的就是disas並尋找retq(或任何你的處理器的返回指令),並在其上設置一個斷點。例如:

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

我對此投了票,因爲它是一個有用的消息,但OP可以告訴代碼中哪個「返回」被調用。 – dmckee 2010-11-11 21:35:16

+1

這與@ ks1322的反向步驟是非常寶貴的。你需要做兩個反向步驟,這就是爲什麼。 – falstro 2014-12-13 14:26:38

+1

有趣!我製作了一個Python命令,它可以找到'retq'並自動放置一個斷點:http://stackoverflow.com/a/31264709/895245 – 2015-07-07 12:29:17

4

中斷當前函數的所有retq

這段Python命令把一個斷點當前函數的每retq指令:

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

與來源是:

source gdb.py 

和請使用以下命令:

break-return 
continue 

您現在應該在retq

步驟,直到retq

只是爲了好玩,當retq發現(效率較低,因爲沒有硬件支持),該站另一種實現方式:

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

這會忽略你的其他斷點。 TODO:可以避免嗎?不知道是否比reverse-step更快或更慢。

停在一個給定的操作碼的版本,可以發現在:https://stackoverflow.com/a/31249378/895245

+0

不知何故,使用多次調用的遞歸函數,干擾,並且應該在返回時運行的每個斷點都稱爲多次。 (實際上還沒有用一個簡單的函數嘗試過它......)(另一方面,這實際上起作用,即使多次調用斷點,所以謝謝。) – ShreevatsaR 2017-08-24 14:12:57

+0

@ShreevatsaR很奇怪。如果可以的話,鏈接到一個最小可重現的例子。 – 2017-08-24 15:51:51

1

如果你可以改變的源代碼,你可以使用一些骯髒的伎倆與預處理:

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 

然後設置一個斷點到on_return並去一幀up

注意:如果函數不通過return語句返回,則這不起作用。所以請確保它的最後一行是return

實施例(無恥地從C代碼複製,但也將工作在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); 
} 

第一個宏將改變

return; 

return on_return(); 

哪個是有效,因爲on_return也返回void

第二個宏將改變

return -1; 

return on_return(), -1; 

它將調用on_return(),然後返回-1(感謝, - 運算符)。

這是一個非常髒的技巧,但儘管使用向後步進,它也可以在多線程環境和內聯函數中工作。

1

rr反向調試

類似於在https://stackoverflow.com/a/3649698/895245提到GDB record,但更實用的GDB 7.11的VS rr 4.1.0在Ubuntu 16.04。

值得注意的是,它涉及AVX正確:

它禁止使用默認標準庫調用工作。

安裝Ubuntu 16.04。

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

但也考慮從源代碼編譯來獲取最新的更新,這並不難。

測試程序:

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

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

編譯和運行:

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

現在你留下了GDB會話裏面,可以適當反向調試:

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

我們現在在正確的回程線上。