Одношаговая отладочная сборка GDB

задняя часть Go Emacs

Когда я раньше смотрел на сборку, я всегда смотрел на результаты GCC -S невооруженным глазом.Недостаток в том, что он очень неинтуитивен и не может видеть значение регистра в реальном времени, поэтому я изучил, как использовать GDB для отладки сборки. Конечно, более важной целью написания этой статьи является то, что я не вел блог уже полгода, а блог вот-вот зарастет травой. ^_^

Есть несколько вещей, которые мне нужны для отладки сборки:

  • Возможность пошаговой отладки сборки.
  • Изменения значений регистров можно увидеть в режиме реального времени.
  • Можно увидеть взаимосвязь между исходным кодом и соответствующей сборкой.

Давайте поделимся реализацией трех вышеуказанных требований с GDB:

Приступайте к отладке сборки

Используйте си и ни. Разница между s и n заключается в следующем: s и n — это одноэтапная отладка на уровне языка C, а si и ni — одноэтапная отладка на уровне сборки.

Изменения значений регистров можно увидеть в режиме реального времени.

Добавьте параметр -tui при использовании gdb, откройте gdb и запуститеlayout regsЗаказ. Учтите, что лучше всего добавить -туй, иначе очень вероятно появление феномена Хуапина.

image

Может видеть взаимосвязь между исходным кодом и соответствующей сборкой

запустить в gdbset disassemble-next-line on, указывающий код, который будет выполняться после автоматической дизассемблирования.

image

можно ясно видетьint c=sum(x,y);Это соответствует инструкции по сборке в красной рамке ниже.

Если вы не хотите использовать такой примитивный метод, вы можете установить плагины для GDB или использовать emacs для достижения вышеуказанной цели.Рекомендуются две статьи:

В заключение небольшой пример:

int sum(int x,int y){
        return x+y;
}

int main(){
        int x=10;
        int y=20;
        int c=sum(x,y);

        return 0;
}

gcc версии 4.4.7, параметры оптимизации по умолчанию.

Пройдемся по сборке, соответствующей этому коду:

установить точку останова

Обратите внимание, что если вы хотите установить точку останова в начале функции уровня сборки, вы должны использоватьb *funвместоb func, здесь мы устанавливаем точку останова наb *main

выделить кадр стека

0x0000000000400489 <main+0>:	 55	push   %rbp
0x000000000040048a <main+1>:	 48 89 e5	mov    %rsp,%rbp
0x000000000040048d <main+4>:	 48 83 ec 10	sub    $0x10,%rsp

%rbp и %rsp представляют нижнюю и верхнюю часть текущего фрейма стека. Где %rbp — это регистр, который должен сохранить вызываемый.sub $0x10,%rspУказывает на выделение пространства кадра стека для основной функции.
Обратите внимание, что здесь выделено 16 байт стекового пространства, а 4 байта использоваться не будут, лично я предполагаю, что оно генерируется сборкой gcc.cfi_def_cfa_offset 16Связано, это не вникал.

int x=10

0x0000000000400491 <main+8>:	 c7 45 f4 0a 00	00 00	movl   $0xa,-0xc(%rbp)

положить значение x в стек

int y=20

0x0000000000400498 <main+15>:         c7 45 f8 14 00	00 00	movl   $0x14,-0x8(%rbp)

положить значение y в стек

Вызов функции суммы

 0x000000000040049f <main+22>:         8b 55 f8	mov    -0x8(%rbp),%edx
 0x00000000004004a2 <main+25>:         8b 45 f4	mov    -0xc(%rbp),%eax
 0x00000000004004a5 <main+28>:         89 d6	mov    %edx,%esi
 0x00000000004004a7 <main+30>:         89 c7	mov    %eax,%edi
 0x00000000004004a9 <main+32>:         e8 c6 ff ff ff	callq  0x400474	<sum>

Присвойте x и y значениям %esi и %edi соответственно, где %edi и %esi указываются для передачи первого и второго параметров функции. (один вопрос, почему не напрямуюmov -0x8(%rbp),%esiШерстяная ткань? )
callq помещает адрес следующей инструкции в стек и переходит к первой инструкции функции суммы.

Введите функцию суммы

0x0000000000400474 <sum+0>:	 55	push   %rbp
0x0000000000400475 <sum+1>:	 48 89 e5	mov    %rsp,%rbp
0x0000000000400478 <sum+4>:	 89 7d fc	mov    %edi,-0x4(%rbp)
0x000000000040047b <sum+7>:	 89 75 f8	mov    %esi,-0x8(%rbp)

Как и в случае с основной функцией, сначала сохраните %rbp, а затем возьмите параметры функции из %edi и %esi.

сумма

0x000000000040047e <sum+10>:	 8b 45 f8	mov    -0x8(%rbp),%eax
0x0000000000400481 <sum+13>:	 8b 55 fc	mov    -0x4(%rbp),%edx
0x0000000000400484 <sum+16>:	 8d 04 02	lea    (%rdx,%rax,1),%eax

Добавьте x и y. Здесь используется инструкция lea. Ознакомление с инструкцией lea см.LEA instruction? , здесь повторяться не буду.
Поместите возвращаемое значение в %eax, а регистр %rax задает возвращаемое значение функции. Как и в языке GO, если функция может иметь несколько возвращаемых значений, возвращаемое значение помещается в стек.

Конец функции суммы

0x0000000000400487 <sum+19>:	 c9	leaveq
0x0000000000400488 <sum+20>:	 c3	retq

Давайте сначала посмотрим на текущий стек:

image
(я не знаю, почему здесь нет саба xx, $rsp, я думаю gcc находит этот последний вызов функции, после этого роста стека не будет, а будет только откат стека, поэтому результаты использования %rsp и %rbp одинаковы. Простая проверка, должно быть так).
По окончании функции сначала необходимо восстановить кадр стека текущей функции, восстановить сохраненные регистры и восстановить значение %rip, то есть адрес возврата.

Инструкция leaveq эквивалентна:

mov  %rbp,%rsp     
pop %rbp

Роль состоит в освобождении (освобождении) кадра стека текущей функции и восстановлении значения сохраненного регистра. Из этого мы также можем видеть роль %rbp: помните, куда %rsp должен отступать, иначе %rsp не будет знать, куда отступать, когда функция завершится.

Инструкция req эквивалентна:

pop %rip

Восстановите адрес следующей инструкции callq, сохраненный выше, в %rip.

Получить возвращаемое значение функции

0x00000000004004ae <main+37>:         89 45 fc	mov    %eax,-0x4(%rbp)

Поместите значение %eax во фрейм стека основной функции.

return 0

0x00000000004004b1 <main+40>:         b8 00 00 00 00	mov    $0x0,%eax

То же, что и функция суммы выше.

конец основной функции

0x00000000004004b6 <main+45>:         c9	leaveq
0x00000000004004b7 <main+46>:         c3	retq

Если приведенные выше %rsp и %rbp указывают на одну и ту же область памяти, это не кажется интуитивно понятным, посмотрите на пространство стека, когда основная функция вот-вот завершится:

image

Так же, как объяснение функции суммы выше, оно не будет повторяться.

Программа успешно запускается и завершает работу.