Как я могу установить точку останова в GDB для системного вызова open (2), возвращающего -1

ОС: GNU / Linux
Дистрибутив: OpenSuSe 13.1
Arch: x86-64
Версия GDB: 7.6.50.20130731-cvs
Язык программы: в основном C с небольшими фрагментами сборки

Представьте, что у меня есть довольно большая программа, которая иногда не может открыть файл. Можно ли установить точку останова в GDB таким образом, чтобы она останавливалась после того, как системный вызов open(2) вернет -1?

Конечно, я могу просмотреть исходный код с помощью grep и найти все open(2) вызовы и сузить open() вызов, вызывающий ошибку, но, возможно, есть способ получше.

Я попытался использовать "catch syscall open", затем "condition N if $rax==-1", но, очевидно, ничего не вышло.
Кстати, возможно ли различить вызов системного вызова (например, open(2)) и возврат из системного вызова (например, open(2)) в GDB?

В качестве текущего обходного пути я делаю следующее:

  1. Запустите рассматриваемую программу под GDB
  2. Из другого терминала запустите скрипт systemtap:

    stap -g -v -e 'probe process("PATH to the program run under GDB").syscall.return { if( $syscall == 2 && $return <0) raise(%{ SIGSTOP %}) }'
    
  3. После того, как open(2) вернет -1, я получаю SIGSTOP в сеансе GDB и могу отладить проблему.

TIA.

С уважением,
alexz.

UPD: хотя я пробовал подход, предложенный n.m, раньше и не смог заставить его работать, я решил попробовать еще раз. Через 2 часа он работает как задумано. Но есть странный обходной путь:

  1. Я все еще не могу различить вызов и возврат из системного вызова
  2. Если я использую finish в comm, я не могу использовать continue, что нормально в соответствии с документами GDB
    , т.е. при каждом перерыве в приглашении GDB появляется следующее:

    gdb> comm
    gdb> finish
    gdb> printf "rax is %d\n",$rax
    gdb> cont
    gdb> end
    
  3. На самом деле я могу не использовать finish и проверять% rax в commands, но в этом случае я должен проверять -errno, а не -1, например. если это «В доступе отказано», то я должен проверить на «-13», а если «Нет такого файла или каталога» - то на -2. Это просто неправильно

  4. Поэтому единственный способ заставить его работать для меня - это определить пользовательскую функцию и использовать ее следующим образом:

    (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
    

person Alex Z    schedule 22.09.2014    source источник
comment
Вряд ли какая-либо программа будет напрямую реализовывать системный вызов - большинство из них будет проходить через функцию-оболочку библиотеки C. Так что у меня возникнет соблазн поставить условную точку останова в пути возврата оболочки библиотеки.   -  person Chris Stratton    schedule 22.09.2014
comment
Другой возможностью может быть strace (1) ваше приложение.   -  person Basile Starynkevitch    schedule 22.09.2014
comment
Кстати, компиляция недавнего (7.8) gdb из исходного кода, с расширяемостью Python и Guile, могла бы быть полезной.   -  person Basile Starynkevitch    schedule 22.09.2014
comment
Без проверки возвращаемого значения для записи: stackoverflow.com/questions/8235436/   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 28.07.2015
comment
на 32-битной версии используйте eax, а не rax.   -  person Matt McHenry    schedule 19.02.2017


Ответы (2)


Можно ли установить точку останова в GDB таким образом, чтобы он останавливался после того, как системный вызов open (2) вернул -1?

На этот узкий вопрос трудно дать лучший ответ, чем ответ n.m.s, но я бы сказал, что вопрос поставлен неправильно.

Конечно, я могу просмотреть исходный код и найти все вызовы 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 продолжит работу.

Это именно то поведение, которое обычно требуется от GDB при поиске одного сбойного системного вызова среди множества последующих.

Обновление:

Как указывает Крис Додд, существует два системных вызова, но в случае ошибки они оба переходят к одному и тому же коду обработки ошибок (коду, который устанавливает errno). Таким образом, мы можем установить безусловную точку останова на *0x00007ffff7b01d49, и эта точка останова сработает только при ошибке.

Это намного лучше, потому что условные точки останова довольно сильно замедляют выполнение, когда условие ложно (GDB должен остановить подчиненное, оценить условие и возобновить подчиненное, если условие ложно).

person Employed Russian    schedule 24.09.2014
comment
@ChrisDodd Вы совершенно правы. Ответ обновлен. Спасибо! - person Employed Russian; 25.09.2014

Этот сценарий 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 только при выходе.

person n. 1.8e9-where's-my-share m.    schedule 22.09.2014
comment
Гораздо более простое решение - установить точку останова внутри заглушки libc open, а не в самом системном вызове. Вам больше не придется играть во внутреннюю / внешнюю игру. Это может пропустить прямые системные вызовы, которые не проходят через заглушку libc, но, учитывая, что OP имеет программу на C, и один из его вызовов open не работает, мы можем предположить, что взлома заглушки достаточно. - person Employed Russian; 23.09.2014
comment
@ n.m: Спасибо. Ваше решение элегантнее моего. - person Alex Z; 23.09.2014
comment
@Chris Stratton, @ трудоустроенный русский: Спасибо, ребята. Я должен еще немного изучить ваше предложение. Поясним: вы предлагаете b open? Правильно? - person Alex Z; 23.09.2014
comment
@Basile Starynkevitch: Спасибо. Не могли бы вы объяснить, как именно использование GDB 7.8 помогает в этом случае? - person Alex Z; 23.09.2014
comment
@AlexZ Нет, b open остановится независимо от open успеха или нет, что не отвечает на ваш вопрос. Я дал полный ответ. - person Employed Russian; 24.09.2014