Ответ 1
Можно ли установить точку останова в GDB таким образом, чтобы она останавливалась после того, как open (2) syscall возвращает -1?
Трудно сделать лучше, чем n.m.
ответить на этот узкий вопрос, но я бы сказал, что вопрос задан неправильно.
Конечно, я могу grep через исходный код и найти все открытые (2) invocations
Это часть вашей путаницы: когда вы вызываете 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.
Здесь мы достигли заглушки системного вызова 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.
Здесь вы можете видеть, что заглушка ведет себя по-разному в зависимости от того, имеет ли программа несколько потоков или нет. Это связано с асинхронной отменой.
Есть две команды 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
Voila, системный вызов open(2)
возвращен -2
, который заглушка переведет в настройку errno
в ENOENT
(который равен 2 в этой системе) и возвращает -1
.
Если open(2)
преуспел, условие $rax < 0
будет ложным, а GDB продолжит работу.
Именно такое поведение обычно требуется от GDB при поиске одного неудачного системного вызова среди многих последующих.
Update:
Как указывает Крис Додд, есть два системных вызовов, но при ошибке они оба вступают в один и тот же код обработки ошибок (код, который устанавливает errno
). Таким образом, мы можем установить не условную точку останова на *0x00007ffff7b01d49
, и эта точка останова будет срабатывать только при сбое.
Это намного лучше, потому что условные точки останова замедляют выполнение довольно много, когда условие ложно (GDB должен остановить нижестоящее, оценить условие и возобновить нижестоящее, если условие ложно).