web-dev-qa-db-ja.com

関数が戻るGDBにブレークポイントを設定するにはどうすればよいですか?

さまざまな場所に多くのreturnステートメントがあるC++関数があります。関数が実際に返すreturnステートメントにブレークポイントを設定するにはどうすればよいですか?

そして、引数なしの「break」コマンドはどういう意味ですか?

23
avd

引数なしでブレークすると、現在選択されているスタックフレームの次の命令で実行が停止します。 frameまたはupおよびdownコマンドを使用してストラックフレームを選択します。現在の関数を離れて実際にはのポイントをデバッグする場合は、次の外側のフレームを選択してそこで中断します。

7
Jim Brissom

これまでの回答とは異なり、ほとんどのコンパイラは、関数内のreturnステートメントの数に関係なく、単一の戻りアセンブリ命令を作成します(コンパイラがそれを行うのは便利なので、実行する場所は1つだけです。すべてのスタックフレームクリーンアップを実行します)。

その命令を停止したい場合は、disasretq(またはプロセッサの戻り命令)を探して、ブレークポイントを設定するだけです。例えば:

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
22

逆デバッグ を使用して、関数が実際に戻る場所を見つけることができます。現在のフレームの実行を終了し、reverse-stepを実行すると、返されたステートメントで停止する必要があります。

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

現在の関数のすべての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

rrリバースデバッグ

https://stackoverflow.com/a/3649698/895245 で言及されているGDB recordに似ていますが、GDB7.11とrr4.1.0の時点ではるかに機能的です。 Ubuntu16.04で。

特に、AVXを正しく処理します。

これにより、デフォルトの標準ライブラリ呼び出しで機能しなくなります。

Ubuntu16.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;

現在、正しいリターンラインにいます。

引数なしのブレークは、現在の行にブレークポイントを設定します。

単一のブレークポイントがすべてのリターンパスをキャッチする方法はありません。呼び出し元が戻った直後にブレークポイントを設定するか、すべてのreturnステートメントでブレークポイントを設定します。

これはC++なので、ローカルの歩哨オブジェクトを作成して、そのデストラクタを壊すことができると思います。

2
Potatoswatter

ソースコードを変更できる場合は、プリプロセッサでいくつかの汚いトリックを使用する可能性があります。

_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_に設定し、1フレーム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を返すため、これは有効です。

2番目のマクロが変更されます

_return -1;
_

_return on_return(), -1;
_

これはon_return()を呼び出してから、-1を返します(_,_-演算子に感謝します)。

これは非常に汚いトリックですが、後方ステップを使用しているにもかかわらず、マルチスレッド環境やインライン関数でも機能します。

1
urzeit