web-dev-qa-db-ja.com

-1を返すopen(2)システムコールのGDBにブレークポイントを設定するにはどうすればよいですか?

OS:GNU/Linux
ディストリビューション:OpenSuSe 13.1
アーチ:x86-64
GDBバージョン:7.6.50.20130731-cvs
プログラム言語:ほとんどがCで、アセンブリが少しあります

ファイルを開かないことがあるかなり大きなプログラムがあると想像してみてください。 open(2) syscallが-1を返した後に停止するようにGDBにブレークポイントを設定することは可能ですか?

もちろん、ソースコードをgrepして、すべてのopen(2)呼び出しを見つけ、障害のあるopen()呼び出しを絞り込むことはできますが、もっと良い方法があるかもしれません。

_"catch syscall open"_、次に_"condition N if $rax==-1"_を使おうとしましたが、明らかにヒットしませんでした。
BTW、GDBでsyscallの呼び出し(例:open(2))とsyscallからの戻り(例:open(2))を区別することはできますか?

現在の回避策として、次のことを行います。

  1. GDBの下で問題のプログラムを実行します
  2. 別のターミナル起動systemtapスクリプトから:

    _stap -g -v -e 'probe process("PATH to the program run under GDB").syscall.return { if( $syscall == 2 && $return <0) raise(%{ SIGSTOP %}) }'
    _
  3. open(2)が-1を返した後、GDBセッションでSIGSTOPを受け取り、問題をデバッグできます。

TIA。

宜しくお願いします、
alexz。

PD:以前にn.mで提案されたアプローチを試したが、それを機能させることができなかったにもかかわらず、もう一度試してみることにしました。 2時間後、意図したとおりに機能するようになりました。しかし、いくつかの奇妙な回避策があります:

  1. 私はまだシステムコールからの呼び出しとリターンを区別することはできません
  2. finishcommを使用すると、continueを使用できなくなります。これは、GDBのドキュメントによると問題ありません。
    つまり以下は、ブレークごとにgdbプロンプトにドロップします。

    _gdb> comm
    gdb> finish
    gdb> printf "rax is %d\n",$rax
    gdb> cont
    gdb> end
    _
  3. 実際には、finishの使用を避け、commandsで%raxをチェックできますが、この場合、-1ではなく-errnoをチェックする必要があります。 =例「アクセスが拒否されました」の場合は「-13」をチェックし、「そのようなファイルまたはディレクトリはありません」の場合は-2をチェックする必要があります。それは単に正しくありません

  4. したがって、それを機能させる唯一の方法は、カスタム関数を定義し、それを次のように使用することでした。

    _(gdb) catch syscall open
    Catchpoint 1 (syscall 'open' [2]
    (gdb) define mycheck
    Type commands for definition of "mycheck".
    End with a line saying just "end".
    >finish
    >finish
    >if ($rax != -1)
     >cont
     >end
    >printf "rax is %d\n",$rax
    >end
    (gdb) comm
    Type commands for breakpoint(s) 1, one per line.
    End with a line saying just "end".
    >mycheck
    >end
    (gdb) r
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /home/alexz/gdb_syscall_test/main
    .....
    Catchpoint 1 (returned from syscall open), 0x00007ffff7b093f0 in __open_nocancel () from /lib64/libc.so.6
    0x0000000000400756 in main (argc=1, argv=0x7fffffffdb18) at main.c:24
    24                      fd = open(filenames[i], O_RDONLY);
    Opening test1
    fd = 3 (0x3)
    Successfully opened test1
    
    Catchpoint 1 (call to syscall open), 0x00007ffff7b093f0 in __open_nocancel () from /lib64/libc.so.6
    rax is -38
    
    Catchpoint 1 (returned from syscall open), 0x00007ffff7b093f0 in __open_nocancel () from /lib64/libc.so.6
    0x0000000000400756 in main (argc=1, argv=0x7fffffffdb18) at main.c:24
    ---Type <return> to continue, or q <return> to quit---
    24                      fd = open(filenames[i], O_RDONLY);
    rax is -1
    (gdb) bt
    #0  0x0000000000400756 in main (argc=1, argv=0x7fffffffdb18) at main.c:24
    (gdb) step
    26                      printf("Opening %s\n", filenames[i]);
    (gdb) info locals
    i = 1
    fd = -1
    _
20
Alex Z

Open(2)syscallが-1を返した後に停止するようにGDBにブレークポイントを設定することは可能ですか?

このnarrowの質問に対するn.m.sの回答よりもうまくいくのは難しいですが、質問の提起が間違っていると私は主張します。

もちろん、ソースコードをgrepして、すべてのopen(2)呼び出しを見つけることができます

これは混乱の一部です。Cプログラムでopenを呼び出すと、実際にはopen(2)を実行していませんではありませんシステムコール。むしろ、libcからopen(3) "stub"を呼び出しているので、そのスタブはopen(2)システムコールを実行します。

また、スタブが-1を返そうとしているときにブレークポイントを設定したい場合、それは非常に簡単です。

例:

/* t.c */
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  int fd = open("/no/such/file", O_RDONLY);
  return fd == -1 ? 0 : 1;
}

$ gcc -g t.c; gdb -q ./a.out
(gdb) start
Temporary breakpoint 1 at 0x4004fc: file t.c, line 6.
Starting program: /tmp/a.out

Temporary breakpoint 1, main () at t.c:6
6     int fd = open("/no/such/file", O_RDONLY);
(gdb) s
open64 () at ../sysdeps/unix/syscall-template.S:82
82  ../sysdeps/unix/syscall-template.S: No such file or directory.

ここで、glibcシステムコールスタブに到達しました。それを分解しましょう:

(gdb) disas
Dump of assembler code for function open64:
=> 0x00007ffff7b01d00 <+0>: cmpl   $0x0,0x2d74ad(%rip)        # 0x7ffff7dd91b4 <__libc_multiple_threads>
   0x00007ffff7b01d07 <+7>: jne    0x7ffff7b01d19 <open64+25>
   0x00007ffff7b01d09 <+0>: mov    $0x2,%eax
   0x00007ffff7b01d0e <+5>: syscall
   0x00007ffff7b01d10 <+7>: cmp    $0xfffffffffffff001,%rax
   0x00007ffff7b01d16 <+13>:    jae    0x7ffff7b01d49 <open64+73>
   0x00007ffff7b01d18 <+15>:    retq
   0x00007ffff7b01d19 <+25>:    sub    $0x8,%rsp
   0x00007ffff7b01d1d <+29>:    callq  0x7ffff7b1d050 <__libc_enable_asynccancel>
   0x00007ffff7b01d22 <+34>:    mov    %rax,(%rsp)
   0x00007ffff7b01d26 <+38>:    mov    $0x2,%eax
   0x00007ffff7b01d2b <+43>:    syscall
   0x00007ffff7b01d2d <+45>:    mov    (%rsp),%rdi
   0x00007ffff7b01d31 <+49>:    mov    %rax,%rdx
   0x00007ffff7b01d34 <+52>:    callq  0x7ffff7b1d0b0 <__libc_disable_asynccancel>
   0x00007ffff7b01d39 <+57>:    mov    %rdx,%rax
   0x00007ffff7b01d3c <+60>:    add    $0x8,%rsp
   0x00007ffff7b01d40 <+64>:    cmp    $0xfffffffffffff001,%rax
   0x00007ffff7b01d46 <+70>:    jae    0x7ffff7b01d49 <open64+73>
   0x00007ffff7b01d48 <+72>:    retq
   0x00007ffff7b01d49 <+73>:    mov    0x2d10d0(%rip),%rcx        # 0x7ffff7dd2e20
   0x00007ffff7b01d50 <+80>:    xor    %edx,%edx
   0x00007ffff7b01d52 <+82>:    sub    %rax,%rdx
   0x00007ffff7b01d55 <+85>:    mov    %edx,%fs:(%rcx)
   0x00007ffff7b01d58 <+88>:    or     $0xffffffffffffffff,%rax
   0x00007ffff7b01d5c <+92>:    jmp    0x7ffff7b01d48 <open64+72>
End of assembler dump.

ここでは、プログラムに複数のスレッドがあるかどうかによって、スタブの動作が異なることがわかります。これは非同期キャンセルと関係があります。

2つのsyscall命令があり、一般的な場合、それぞれの後にブレークポイントを設定する必要があります(ただし、以下を参照)。

ただし、この例はシングルスレッドであるため、単一の条件付きブレークポイントを設定できます。

(gdb) b *0x00007ffff7b01d10 if $rax < 0
Breakpoint 2 at 0x7ffff7b01d10: file ../sysdeps/unix/syscall-template.S, line 82.
(gdb) c
Continuing.

Breakpoint 2, 0x00007ffff7b01d10 in __open_nocancel () at ../sysdeps/unix/syscall-template.S:82
82  in ../sysdeps/unix/syscall-template.S
(gdb) p $rax
$1 = -2

出来上がり、open(2)システムコールは-2を返しました。これは、スタブがerrnoENOENT(このシステムでは2)に設定し、-1を返すように変換します。

open(2)が成功した場合、条件$rax < 0はfalseになり、GDBは続行します。

これはまさに、多くの成功したシステムコールの中から失敗したシステムコールを探すときにGDBに通常求められる動作です。

更新:

Chris Doddが指摘しているように、2つのシステムコールがありますが、エラーが発生すると、両方とも同じエラー処理コード(errnoを設定するコード)に分岐します。したがって、*0x00007ffff7b01d49un-conditionalブレークポイントを設定でき、そのブレークポイントは失敗した場合にのみ起動します。

条件付きブレークポイントは、条件がfalseの場合に実行を大幅に遅くするため、これははるかに優れています(GDBは下位を停止し、条件を評価し、条件がfalseの場合は下位を再開する必要があります)。

6

このgdbスクリプトは、要求されたことを実行します。

set $outside = 1
catch syscall open
commands
  silent
  set $outside = ! $outside
  if ( $outside && $rax >= 0)
    continue
  end
  if ( !$outside )
    continue
  end
  echo `open' returned a negative value\n
end

gdbはsyscallenterとsyscallexitの両方で停止するため、$outside変数が必要です。入力イベントを無視し、終了時にのみ$raxをチェックする必要があります。

10
n.m.