GDB в Windows: другое поведение при отладке скомпилированного кода C и C ++

0

Я заметил странное поведение GDB 7.5 в Windows. Рассмотрим следующую программу C:

int foo(void){
    int i = 5;
    return i;
}

int main(int argc, char** argv){
    foo();
    return 0;
}

При компиляции как классического C, так и C++ команда GDB disass foo дает один и тот же код сборки:

Dump of assembler code for function foo:
0x00401954 <+0>:     push   %ebp
0x00401955 <+1>:     mov    %esp,%ebp
0x00401957 <+3>:     sub    $0x10,%esp
0x0040195a <+6>:     movl   $0x5,-0x4(%ebp)
0x00401961 <+13>:    mov    -0x4(%ebp),%eax
0x00401964 <+16>:    leave
0x00401965 <+17>:    ret
End of assembler dump.

Однако после вставки точки останова в команду "оставить", например: br *0x00401964, запустив код до этой строки и пытаясь распечатать переменную i, выполняются исполняемые файлы, созданные путем компиляции его как C и C++ иначе. Исполняемый файл C работает так, как ожидалось, и выводит $i = 5, а при выполнении исполняемого GDB C++ - "нет символа я в текущем контексте".

Так что просто из любопытства я хотел бы знать, является ли это ошибкой или функцией GDB? Или компилятор (GCC) делает что-то тонко другое, чтобы что-то происходило между строк? Благодарю.

EDIT: Ну, я не думаю, что это правда, что компилятор полностью удалил функцию, потому что нарушение в строке перед "уходом" и печать значения я действительно работает.

Теги:
x86
gdb

2 ответа

1
Лучший ответ

Это не ошибка или функция, а побочный эффект оптимизации компилятора. Разборки - это вывод неинтерминированной сборки (i записывается в стек в foo+6 и перечитывается из стека на один шаг позже в foo+13).

Хотя вывод сборки C и C++ в этом случае одинаковый, выход символа отладки, однако, немного отличается. Объем i более ограничен в C++. Я могу только рассуждать о причинах. Я бы предположил, что это связано с тем, что область обзора более сложна в C++ (подумайте о конструкторах, деструкторах, исключении), и поэтому C++ часть gcc более жесткая по областям, чем C-часть gcc.

Детали

(Я проверил все на 32-битной сборке, но на 64-разрядной Linux с gcc 4.8 и gdb 7.6. Хотя некоторые детали будут отличаться в Windows, я ожидаю, что общая механика будет одинаковой)

Обратите внимание, что адреса различаются в моем случае.

(gdb) disas foo
Dump of assembler code for function foo:
   0x080483ed <+0>:     push   %ebp
   0x080483ee <+1>:     mov    %esp,%ebp
   0x080483f0 <+3>:     sub    $0x10,%esp
   0x080483f3 <+6>:     movl   $0x5,-0x4(%ebp)
   0x080483fa <+13>:    mov    -0x4(%ebp),%eax
   0x080483fd <+16>:    leave  
   0x080483fe <+17>:    ret    
End of assembler dump.

Технически foo+0 и foo+1 - функция пролога, foo+3 - foo+13 - тело функции, а foo+16 и foo+17 - эпилог функции. Таким образом, только foo+3 to foo+13 представляет код между { и }. Я бы сказал, что версия C++ более правильна, говоря, что i входит в сферу действия до и после тела функции.

Чтобы убедиться, что это действительно вопрос отладочных символов, вы можете сбросить внутренние maintenance print symbols output_file_on_disk gdb структур отладки с maintenance print symbols output_file_on_disk. Для C это выглядит так:

block #000, object at 0x1847710, 1 syms/buckets in 0x80483ed..0x804840e
 int foo(); block object 0x18470d0, 0x80483ed..0x80483ff
 int main(int, char **); block object 0x18475d0, 0x80483ff..0x804840e section .text
  block #001, object at 0x18476a0 under 0x1847710, 1 syms/buckets in 0x80483ed..0x804840e
   typedef int int; 
   typedef char char; 
    block #002, object at 0x18470d0 under 0x18476a0, 1 syms/buckets in 0x80483ed..0x80483ff, function foo
     int i; computed at runtime
    block #003, object at 0x18475d0 under 0x18476a0, 2 syms/buckets in 0x80483ff..0x804840e, function main
     int argc; computed at runtime
     char **argv; computed at runtime

Хотя это C++

block #000, object at 0x1a3c790, 1 syms/buckets in 0x80483ed..0x804840e
 int foo(); block object 0x1a3c0c0, 0x80483ed..0x80483ff
 int main(int, char**); block object 0x1a3c640, 0x80483ff..0x804840e section .text
  block #001, object at 0x1a3c720 under 0x1a3c790, 1 syms/buckets in 0x80483ed..0x804840e
   typedef int int; 
   typedef char char; 
    block #002, object at 0x1a3c0c0 under 0x1a3c720, 0 syms/buckets in 0x80483ed..0x80483ff, function foo()
      block #003, object at 0x1a3c050 under 0x1a3c0c0, 1 syms/buckets in 0x80483f3..0x80483fd
       int i; computed at runtime
    block #004, object at 0x1a3c640 under 0x1a3c720, 2 syms/buckets in 0x80483ff..0x804840e, function main(int, char**)
     int argc; computed at runtime
     char **argv; computed at runtime

Таким образом, символы отладки для кода C++ различают всю функцию (блок № 002) и область действия тела функции (блок # 003). Это приводит к вашим наблюдениям.

(И чтобы увидеть, что это действительно не gdb, просто что-то не так, вы можете даже анализировать двоичный файл с помощью objdump на Linux или dumpbin в Windows. Я сделал это на Linux, и действительно это символы отладки DWARF, которые отличаются друг от друга :-))

0

Это не ошибка или особенность. Компилятору разрешено заменять функционально эквивалентный код и обычно делает это, если он может найти лучший способ сделать что-то. Пример кода эквивалентен тому, что он ничего не делает, поэтому компилятор может удалить его. Это позволяет отладчику ничего не отлаживать, что хорошо, поскольку отладочный код, который ничего не делает, будет пустой тратой времени.

  • 0
    Спасибо за ответ, но я сомневаюсь, что компилятор полностью избавился от функции, так как добавление точки останова перед инструкцией leave работает
  • 0
    Если disass foo генерирует одинаковый ассемблерный код как на C, так и на C ++, то не означает ли это, что foo не был скомпилирован? Это может быть не проблема GDB вообще. В зависимости от параметров командной строки C / C ++, C ++ может не производить отладочную информацию.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню