Что здесь делает subl? - PullRequest
2 голосов
/ 17 марта 2010

Итак ... Я собираю в ассемблер, с gcc -S -O2 -m32:

void h(int y){int x; x=y+1; f(y); f(2); }

И это дает мне следующее:

.file   "sample.c"
.text
.p2align 4,,15
.globl h
.type   h, @function
 h:
pushl   %ebp
movl    %esp, %ebp
subl    $24, %esp
movl    8(%ebp), %eax
movl    %eax, (%esp)
call    f
movl    $2, 8(%ebp)
leave
jmp f
.size   h, .-h
.ident  "GCC: (GNU) 4.4.3 20100127 (Red Hat 4.4.3-4)"
.section    .note.GNU-stack,"",@progbits

Теперь я знаю, что такое pushl и movel: они сохраняют текущий указатель кадра на стек, а затем устанавливают значение регистра указателя кадра на значение указателя стека.

  1. Но я понятия не имею, что такое subl $ 24,% esp . Я понял, что он перемещает указатель стека вниз на 24 байта. Правильно?
  2. Что между прочим?
  3. Почему movl 8 (% ebp),% eax использует 8? Это 8 байтов? Это для размещения возвращаемого значения + аргумент y в h? Или я здесь совсем. Значит, это означает, что вы оглянулись назад на 8 байт из указателя стека?
  4. Что делает movl $ 2,8 (% ebp) ? Он копирует константу 2 в местоположение 8 байтов перед указателем кадра. Изменился ли указатель кадра, когда мы вызвали f? Если да - тогда 8 (% ebp) указывает на местоположение аргумента для f.
  5. Что делает уход? Как он может «удалить» кадр стека? Я имею в виду, вы не можете просто удалить часть памяти. В документе написано, что он делает mov (esp, ebp), pop ebp .

Спасибо!

Ответы [ 2 ]

5 голосов
/ 22 января 2013

Чтобы ответить на эти пронумерованные вопросы:

1) subl $24,%esp

означает esp = esp - 24

GNU AS использует синтаксис AT & T, противоположный синтаксису Intel. У AT & T пункт назначения справа, у Intel пункт назначения слева. Также AT & T явно указывает размер аргументов. Intel пытается вывести это или заставляет вас быть явным.

Стек увеличивается в памяти, память в и после esp - содержимое стека, адреса ниже, чем esp - неиспользуемое пространство стека. esp указывает на последнюю вещь, помещенную в стек.

2) Кодировка команд x86 в основном позволяет следующее:

movl rm,r   ' move value from register or memory to a register
movl r,rm   ' move a value from a register to a register or memory
movl imm,rm ' Move immediate value.

Нет формата инструкции «память-в-память». (Строго говоря, вы можете выполнять операции с памятью в память с помощью movs или push mem, pop mem, но ни один из них не использует два операнда памяти для одной и той же инструкции)

«Немедленно» означает, что значение закодировано прямо в инструкции. Например, хранить 15 по адресу в ebx:

movl $15,(%ebx)

15 является «немедленным» значением.

Скобки позволяют использовать регистр в качестве указателя на память.

3) movl 8(%ebp),%eax

означает,

  • принять значение ebp
  • добавить 8 к нему (но не изменяет ebp),
  • использовать его как адрес (скобки),
  • читать 32-битное значение с этого адреса,
  • и сохранить значение в eax

esp - указатель стека. В 32-битном режиме каждое нажатие и выталкивание в стеке имеет ширину 4 байта. Как правило, большинство переменных в любом случае занимают 4 байта. Таким образом, вы могли бы сказать, что 8 (% ebp) означает, начиная с вершины стека, дать мне значение 2 (4 x 2 = 8) целых чисел в стеке.

Как правило, 32-битный код использует ebp для указания на начало локальных переменных в функции. В 16-битном коде x86 не было способа использовать указатель стека в качестве указателя (трудно поверить, правда?). Поэтому люди скопировали sp в bp и использовали bp в качестве локального указателя кадра. Это стало совершенно ненужным, когда вышел 32-битный режим (80386), у него был способ просто использовать указатель стека напрямую. К сожалению, ebp облегчает отладку, поэтому мы продолжили использовать ebp в 32-битном коде (сделать дамп стека тривиально легко, если используется ebp).

К счастью, amd64 дал нам новый ABI, который не использует ebp в качестве указателя кадра, 64-битный код обычно использует esp для доступа к локальным переменным, ebp доступен для хранения переменной.

4) Объяснено выше

5) leave - старая инструкция, которая просто выполняет movl %ebp,%esp и popl %ebp и сохраняет несколько байтов кода. На самом деле он отменяет изменения в стеке и восстанавливает ebp вызывающего. Вызываемая функция должна сохранять ebp в ABI x86.

При входе в функцию компилятор выполнил subl $ 24,% esp, чтобы освободить место для локальных переменных и иногда временного хранилища, для которого не хватало регистров для хранения.

Лучший способ «представить» кадр стека в вашем уме - это увидеть его как структуру, лежащую в стеке. Первые члены воображаемой структуры - это самые последние «выдвинутые» ценности. Поэтому, когда вы перемещаетесь в стек, представьте, что вы вставляете новый элемент в начале структуры, пока ни один из других элементов не переместился. Когда вы «выталкиваете» из стека, вы получаете значение первого члена воображаемой структуры, и эта (первая) строка структуры исчезает из существования.

Управление кадрами стека - это в основном просто перемещение указателя стека, чтобы освободить место в той воображаемой структуре, которую мы называем кадром стека. Вычитание из указателя стека просто помещает несколько воображаемых членов в начало структуры за один шаг. Добавление указателя в стек приводит к исчезновению первых многих элементов.

Конец отправленного вами кода не является типичным. Это jmp, как правило, ret. Компилятор был сообразителен и выполнил «оптимизацию хвостового вызова», то есть он просто очищает то, что он сделал со стеком, и переходит к f. Когда f(2) вернется, он на самом деле вернется прямо к вызывающей стороне (не обратно в код, который вы отправили)

4 голосов
/ 17 марта 2010

Компилятор резервирует место в стеке для локальных и любых других нужд, которые могут у него возникнуть. Я не уверен, почему он резервирует 24 байта (кажется, он не нуждается или не использует все это).

При вызове функции f() вместо использования команды push для помещения параметра в стек используется простой movl для последнего зарезервированного местоположения:

movl    8(%ebp), %eax    ; get the value of `y` passed in to `h()`
movl    %eax, (%esp)     ; put that value on the stack for call to `f()`

Более интересная (на мой взгляд) вещь, происходящая здесь, заключается в том, как компилятор обрабатывает вызов f(2):

movl    $2, 8(%ebp)      ; store 2 in the `y` argument passed to `h()`
                         ;     since `h()` won't be using `y` anymore
leave                    ; get rid of the stackframe for `h()`
jmp f                    ; jump to `f()` instead of calling it - it'll return
                         ;     directly to whatever called `h()`

Чтобы ответить на ваш вопрос, "immed кстати?" - это то, что ссылка на инструкцию использует, чтобы указать, что значение закодировано в коде операции инструкции вместо того, чтобы приходить куда-то еще, например в регистр или ячейку памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...