如何在GDB中为open(2)syscall返回-1设置断点

操作系统:GNU / Linux
Distro:OpenSuSe 13.1
拱门:x86-64
GDB版本:7.6.50.20130731-cvs
程序语言:大部分是C和少量装配

想像一下,我有一个很大的程序,有时无法打开一个文件。是否可以在GDB中设置断点,使其在打开后停止(2)syscall返回-1?

当然,我可以通过源代码查找所有的open(2)调用,并缩小错误的open()调用,但也许有更好的方法。

我试图使用“catch syscall open”然后“条件N如果$ rax == – 1”,但显然没有被击中。
BTW,在GDB中调用syscall(例如open(2))和从syscall(例如open(2))返回之间是否有区别?

作为当前的解决方法,我执行以下操作:

>在GDB下运行有问题的程序
>从另一个终端启动systemtap脚本:

stap -g -v -e 'probe process("PATH to the program run under GDB").syscall.return { if( $syscall == 2 && $return <0) raise(%{ SIGSTOP %}) }'

>打开后(2)返回-1我在GDB会话中收到SIGSTOP,我可以调试问题。

TIA。

最好的祝福,
 alexz。

UPD:即使我尝试了之前提到的方法,并且无法使其工作,我决定再试一次。 2个小时后,它按照预期工作。但有一些奇怪的解决方法:

>我仍然无法区分调用和从系统调用返回
>如果我使用comm在comm我不能使用继续,这是可以根据GDB文档
即以下内容在每次休息时都会下降到gdb提示符下:

gdb> comm
gdb> finish
gdb> printf "rax is %d\n",$rax
gdb> cont
gdb> end

>其实我可以避免在命令中使用完成并检查%rax,但在这种情况下,我必须检查-errno而不是-1。如果它是“权限被拒绝”,那么我必须检查“-13”,如果它是“没有这样的文件或目录” – 那么对于-2。这只是不对的
>所以使它为我工作的唯一方法是定义自定义函数,并以下列方式使用它:

(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
最佳答案

Is it possible to set breakpoint in GDB in such way that it stops after open(2) syscall returns -1?

对于这个狭隘的问题,很难比n.m.s的答案更好,但我认为这个问题是错误的。

Of course, I can grep through the source code and find all open(2) invocations

这是您的混乱的一部分:当您在C程序中调用open时,实际上并不执行open(2)系统调用。相反,您正在从libc调用一个open(3)“stub”,该存根将为您执行open(2)系统调用。

如果要在stub即将返回-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.

在这里,您可以看到存根的行为有所不同,具体取决于程序是否有多个线程。这与异步取消有关。

有两个系统调用指令,在一般情况下,我们需要在每个指令之后设置一个断点(但见下文)。

但是这个例子是单线程的,所以我可以设置一个单条件的断点:

(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

Voila,open(2)系统调用返回-2,该存根将转换为将errno设置为ENOENT(该系统为2)并返回-1。

如果open(2)成功,条件$ rax< 0将是假的,GDB将继续。 这正是GDB在许多成功的人中寻找一个失败的系统调用时通常想要的行为。 更新: 正如Chris Dodd指出的那样,有两个系统调用,但是错误的是它们分支到相同的错误处理代码(设置errno的代码)。因此,我们可以在* 0x00007ffff7b01d49上设置一个无条件断点,并且该断点仅在失败时触发。 这是好多了,因为条件为false时,条件断点会减慢执行速度(GDB必须停止下级,评估条件,如果条件为false,则恢复下级)。

转载注明原文:如何在GDB中为open(2)syscall返回-1设置断点 - 代码日志