Причина «странных» адресов, таких как 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
в эту ячейку памяти.
Re объяснение кода,Ваша 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.