Ответ 1
Причиной "странных" адресов, таких как main+0
, main+1
, main+3
, main+6
и т.д., является то, что каждая команда принимает переменное количество байтов. Например:
main+0: push %ebp
является однобайтовой инструкцией, поэтому следующая инструкция находится в main+1
. С другой стороны,
main+3: and $0xfffffff0,%esp
является трехбайтовой инструкцией, поэтому следующая инструкция после этого находится в main+6
.
И, поскольку вы спрашиваете в комментариях, почему movl
, кажется, принимает переменное количество байтов, объяснение этого выглядит следующим образом.
Длина команды зависит не только от кода операции (например, movl
), но также и режимов адресации для операндов (то, что работает операционный код). Я специально не проверял ваш код, но я подозреваю, что
movl $0x1,(%esp)
инструкция, вероятно, короче, потому что нет никакого смещения - она просто использует esp
в качестве адреса. Если что-то вроде:
movl $0x2,0x4(%esp)
требует все, что movl $0x1,(%esp)
, плюс дополнительный байт для смещения 0x4
.
Фактически, здесь сеанс отладки, показывающий, что я имею в виду:
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
c:\pax> debug
-a
0B52:0100 mov word ptr [di],7
0B52:0104 mov word ptr [di+2],8
0B52:0109 mov word ptr [di+0],7
0B52:010E
-u100,10d
0B52:0100 C7050700 MOV WORD PTR [DI],0007
0B52:0104 C745020800 MOV WORD PTR [DI+02],0008
0B52:0109 C745000700 MOV WORD PTR [DI+00],0007
-q
c:\pax> _
Вы можете видеть, что вторая команда со смещением фактически отличается от первой без нее. Он один байт длиннее (5 байт вместо 4, чтобы удерживать смещение) и фактически имеет другое кодирование c745
вместо c705
.
Вы также можете увидеть, что вы можете кодировать первую и третью инструкцию двумя разными способами, но они в основном делают то же самое.
Инструкция and $0xfffffff0,%esp
- это способ заставить esp
находиться на определенной границе. Это используется для обеспечения правильного выравнивания переменных. Многие обращения к памяти на современных процессорах будут более эффективными, если они будут следовать правилам выравнивания (например, 4-байтовое значение должно быть выровнено с 4-байтной границей). Некоторые современные процессоры даже вызовут ошибку, если вы не будете следовать этим правилам.
После этой инструкции вам гарантировано, что esp
меньше или равно его предыдущему значению и выравнивается с 16-байтовой границей.
Префикс gs:
просто означает использовать регистр сегмента gs
для доступа к памяти, а не по умолчанию.
Инструкция mov %eax,-0xc(%ebp)
означает взять содержимое регистра ebp
, вычесть 12 (0xc
), а затем поместить значение eax
в эту ячейку памяти.
Повторите объяснение кода. Ваша функция function
в основном одна большая не-операционная. Сгенерированная сборка ограничивается установкой и удалением кадров стека, а также некоторой проверкой повреждения кадров стека, которая использует вышеупомянутую ячейку памяти %gs:14
.
Он загружает значение из этого местоположения (возможно, что-то вроде 0xdeadbeef
) в фрейм стека, выполняет его работу, затем проверяет стек, чтобы убедиться, что он не был поврежден.
Его работа, в данном случае, ничто. Итак, все, что вы видите, это элемент управления функциями.
Настройка стека происходит между function+0
и function+12
. Все после этого настраивает код возврата в eax
и срывает фрейм стека, включая проверку коррупции.
Аналогично, main
состоит из установки фрейма стека, нажимает параметры для function
, вызывая function
, срывая фрейм стека и выходя из него.
Комментарии добавлены в код ниже:
0x08048428 <main+0>: push %ebp ; save previous value.
0x08048429 <main+1>: mov %esp,%ebp ; create new stack frame.
0x0804842b <main+3>: and $0xfffffff0,%esp ; align to boundary.
0x0804842e <main+6>: sub $0x10,%esp ; make space on stack.
0x08048431 <main+9>: movl $0x3,0x8(%esp) ; push values for function.
0x08048439 <main+17>: movl $0x2,0x4(%esp)
0x08048441 <main+25>: movl $0x1,(%esp)
0x08048448 <main+32>: call 0x8048404 <function> ; and call it.
0x0804844d <main+37>: leave ; tear down frame.
0x0804844e <main+38>: ret ; and exit.
0x08048404 <func+0>: push %ebp ; save previous value.
0x08048405 <func+1>: mov %esp,%ebp ; create new stack frame.
0x08048407 <func+3>: sub $0x28,%esp ; make space on stack.
0x0804840a <func+6>: mov %gs:0x14,%eax ; get sentinel value.
0x08048410 <func+12>: mov %eax,-0xc(%ebp) ; put on stack.
0x08048413 <func+15>: xor %eax,%eax ; set return code 0.
0x08048415 <func+17>: mov -0xc(%ebp),%eax ; get sentinel from stack.
0x08048418 <func+20>: xor %gs:0x14,%eax ; compare with actual.
0x0804841f <func+27>: je <func+34> ; jump if okay.
0x08048421 <func+29>: call <_stk_chk_fl> ; otherwise corrupted stack.
0x08048426 <func+34>: leave ; tear down frame.
0x08048427 <func+35>: ret ; and exit.
Я думаю, что причина для %gs:0x14
может быть очевидна сверху, но на всякий случай я расскажу здесь.
Он использует это значение (дозорный) для размещения в текущем фрейме стека, чтобы, если что-то в функции делало что-то глупое, как писать 1024 байта в 20-байтовый массив, созданный в стеке, или в вашем случае:
char buffer1[5];
strcpy (buffer1, "Hello there, my name is Pax.");
тогда контролер будет перезаписан, и проверка в конце функции обнаружит это, вызвав функцию отказа, чтобы вы знали, а затем, вероятно, прерывается, чтобы избежать других проблем.
Если он помещал 0xdeadbeef
в стек, и это было изменено на что-то еще, тогда xor
с 0xdeadbeef
создаст ненулевое значение, которое будет обнаружено в коде с инструкцией je
.
Соответствующий бит перефразируется здесь:
mov %gs:0x14,%eax ; get sentinel value.
mov %eax,-0xc(%ebp) ; put on stack.
;; Weave your function
;; magic here.
mov -0xc(%ebp),%eax ; get sentinel back from stack.
xor %gs:0x14,%eax ; compare with original value.
je stack_ok ; zero/equal means no corruption.
call stack_bad ; otherwise corrupted stack.
stack_ok: leave ; tear down frame.