如何在gdb中为open(2)SysCall返回-1设置断点?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (285)

操作系统:GNU/Linux

发行版:opensuse 13.1

ARCH:x86-64

GDB版本:7.6.50.20130731-CVS

程序语言:主要是C语言,包含少量的程序集

想象一下,我有一个相当大的程序,有时无法打开一个文件。 是否可以在GDB中设置断点,以便在open(2)系统调用返回-1之后停止?

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

我试图使用“catch系统调用打开”,然后“条件N,如果$ rax == - 1”,但显然它没有被击中。

是否有可能在GDB中调用系统调用(例如open(2))和从系统调用返回(例如open(2))?

提问于
用户回答回答于

在gdb中设置断点是否可能使其在open(2)SysCall返回-1之后停止?

我认为这个问题的提出是错误的。

当然,我可以通过源代码实现grep,并找到所有open(2)调用。

这是你的困惑的一部分:当你在C程序中调用open时,你实际上并没有执行open(2)系统调用。 而是从你的libc调用一个open(3)“stub”,并且这个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.

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

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

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

(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,该存根将转换为将errno设置为ENOENT(在此系统上为2)并返回-1。

如果open(2)成功,则$ rax <0的条件将为false,并且GDB将继续执行。

这正是GDB在寻找其中一个失败的系统调用时通常需要的行为。

用户回答回答于

这个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

扫码关注云+社区

领取腾讯云代金券