Как установить точку останова в GDB для системного вызова open(2), возвращающего -1
ОС: GNU / Linux
Distro: OpenSuSe 13.1
Арка: x86-64
Версия GDB: 7.6.50.20130731-cvs
Язык программы: в основном C с незначительными битами сборки
Представьте, что у меня довольно большая программа, которая иногда не может открыть файл. Можно ли установить точку останова в GDB таким образом, чтобы она остановилась после open(2)
системный вызов возвращает -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 %}) }'
- После
open(2)
возвращает -1 Я получаю SIGSTOP в сеансе GDB и могу решить проблему.
ТИА.
С наилучшими пожеланиями,
AlexZ.
UPD: Несмотря на то, что я попробовал подход, предложенный ранее, и не смог заставить его работать, я решил попробовать еще раз. Через 2 часа он теперь работает как задумано. Но с некоторым странным обходным путем:
- Я до сих пор не могу различить вызов и возврат из системного вызова
Если я использую
finish
вcomm
Я не могу использоватьcontinue
, что в порядке в соответствии с документами GDB
то есть следующее переходит к приглашению GDB при каждом перерыве:gdb> comm gdb> finish gdb> printf "rax is %d\n",$rax gdb> cont gdb> end
На самом деле я могу избежать использования
finish
и проверьте%rax вcommands
но в этом случае я должен проверить для -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
2 ответа
Можно ли установить точку останова в GDB таким образом, чтобы она остановилась после того, как системный вызов open(2) вернул -1?
Трудно сделать лучше, чем n.m.
Ответ на этот узкий вопрос, но я бы сказал, что вопрос поставлен неправильно.
Конечно, я могу просмотреть исходный код и найти все вызовы 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.
Здесь мы достигли заглушки системного вызова 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
будет ложным, и ГБД будет продолжать идти.
Именно такое поведение обычно требуется от GDB при поиске одного сбойного системного вызова среди множества последующих.
Обновить:
Как указывает Крис Додд, существует два системных вызова, но в случае ошибки они оба переходят на один и тот же код обработки ошибок (код, который устанавливает errno
). Таким образом, мы можем установить безусловную точку останова на *0x00007ffff7b01d49
и эта точка останова сработает только при неудаче.
Это намного лучше, потому что условные точки останова значительно замедляют выполнение, когда условие ложно (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
$outside
переменная нужна потому что gdb
останавливается как при входе системного вызова, так и при выходе системного вызова. Нам нужно игнорировать ввод событий и проверять $rax
только на выходе.