操作系统:GNU/Linux发行版
:OpenSuSe 13.1
Arch:x86-64
GDB版本:7.6.50.20130731-cvs
程序语言:主要是带有少量汇编的C语言
想象一下,我有一个相当大的程序,有时无法打开文件.是否可以在GDB中设置断点,使其在open(2)syscall返回-1 后停止?
当然,我可以通过源代码查找并查找所有open(2)调用并缩小错误open()调用,但也许有更好的方法.
我试图用"catch syscall open"然后"condition N if $rax==-1"但显然它并没有被击中.
顺便说一句,是否可以区分对系统调用(例如open(2))的调用和从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 %}) }'
Run Code Online (Sandbox Code Playgroud)open(2)返回-1我收到SIGSTOP在GDB会话,我可以调试问题.TIA.
最好的问候,
亚历克斯.
UPD:即使我之前尝试过nm建议的方法并且无法使其工作,我还是决定再试一次.2小时后,它现在按预期工作.但有一些奇怪的解决方法:
如果我使用finishin comm我无法使用continue,根据GDB文档即可,
即以下内容确实会在每次中断时降至gdb提示符:
gdb> comm
gdb> finish
gdb> printf "rax is %d\n",$rax
gdb> cont
gdb> end
Run Code Online (Sandbox Code Playgroud)实际上我可以避免使用finish和检查%rax commands但在这种情况下我必须检查-errno而不是-1例如,如果它是"权限被拒绝"那么我必须检查"-13"并且如果它是"没有这样的文件或direcory" - 然后-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
Run Code Online (Sandbox Code Playgroud)n. *_* m. 10
这个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
Run Code Online (Sandbox Code Playgroud)
该$outside变量是必需的,因为gdb在syscall enter和syscall exit都停止.我们需要忽略输入事件并$rax仅在退出时检查.
是否可以在打开(2)系统调用返回-1后停止在GDB中设置断点?
n.m.对于这个狭隘的问题,很难做得比答案更好,但我认为这个问题是错误的.
当然,我可以通过源代码grep并找到所有open(2)调用
这是你困惑的一部分:当你打电话open给C程序时,你实际上并没有执行open(2)系统调用.相反,您正在open(3)从libc 调用"存根",该存根将为您执行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.
Run Code Online (Sandbox Code Playgroud)
在这里,我们已经达到了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.
Run Code Online (Sandbox Code Playgroud)
在这里,您可以看到存根的行为有所不同,具体取决于程序是否具有多个线程.这与异步取消有关.
有两个系统调用指令,在一般情况下,我们需要在每个指令之后设置一个断点(但见下文).
但是这个例子是单线程的,所以我可以设置一个条件断点:
(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
Run Code Online (Sandbox Code Playgroud)
Voila,open(2)系统调用返回-2,存根将转换为设置errno为ENOENT(在此系统上为2)并返回-1.
如果open(2)成功,则条件$rax < 0为false,GDB将继续运行.
这正是GDB在许多后续系统中寻找一个失败的系统调用时通常需要的行为.
更新:
正如Chris Dodd指出的那样,有两个系统调用,但是在出错时它们都会转移到相同的错误处理代码(设置的代码errno).因此,我们可以设置一个非条件断点*0x00007ffff7b01d49,并且该断点仅在失败时触发.
这要好得多,因为当条件为假时,条件断点会大大减慢执行速度(GDB必须停止下级,评估条件,如果条件为假则恢复劣势).