- •Nasm help v0.2
- •1. Основы
- •1.1 Секции
- •1.3 _Start
- •1.4 Регистры
- •1.5 Разыменование
- •2. Синтаксис nasm
- •2.1 Базовые команды
- •2.3 Организация подпрограмм
- •2.4 Метки в подпрограммах
- •2.5 Сохранение регистров
- •2.6 Передача аргументов подпрограмме
- •2.2 Вызовы ядра
- •3. Основные логические структуры в переложении на ассемблер.
- •3.1 Условие
- •3.2 Цикл
- •4. Заключение
2.2 Вызовы ядра
|
eax |
ebx |
ecx |
edx |
Выход из программы |
1 |
0 |
- |
- |
Ввод символов с клавиатуры |
3 |
0 |
метка (.bss) |
количество памяти |
Вывод символов на экран |
4 |
1 |
метка (.data или .bss) |
количество памяти |
После помещения соответствующих значений в регистры вызывается ядро:
int 0x80
Нельзя конечно точно утверждать, но скорее всего, размер одного элемента массива для выделенной памяти должен быть равен одному байту. Т.е. Надо использовать только db для секции .data, или только resb для секции .bss
Здесь и далее я буду называть вызов ядра по значению числа помещаемого в eax. То есть «первый вызов ядра», это завершение программы:
mov eax,1
mov ebx,0
int 0x80
«Третий» и «Четвёртый» вызовы это соответственно ввод с клавиатуры и вывод на экран.
2.3 Организация подпрограмм
Первое, что необходимо прояснить: главная программа начинается с метки _start и заканчивается первым вызовом ядра. Как-то так:
section .text
_start:
…
;код главной программы
…
mov eax,1
mov ebx,0
int 0x80
Любая подпрограмма же всегда начинается после первого вызова ядра, и вызывается из главной программы.
Затем — вызов подпрограммы производиться с помощью команды call <имя подпрограммы>.
Чтобы вернутся из подпрограммы в конце её кода пишется
команда ret
Скомпоновав это будет как-то так:
section .text
_start:
…
;код главной программы
…
call subprog1
…
;ещё код…
…
mov eax,1
mov ebx,0
int 0x80
subprog1:
…
;код подпрограммы subprog1
…
ret
2.4 Метки в подпрограммах
Любая метка в подпрограмме должна иметь имя начинающееся с точки! Чтобы «извне» никакая другая подпрограмма не могла перейти на середину этой и застать её «выходящей из душа».
2.5 Сохранение регистров
В подпрограммах часто активно используются регистры, поэтому необходимо сохранять их значение в стеке, чтобы не изменять ничего с точки зрения главной программы. Есть два варианта:
1) Каждый по отдельности.
Если в программе используется всего-то два или три регистра глупо сохранять сразу все, можно в начале, в определенном порядке сохранить эти регистры, а после, в обратном порядке вытащить их. Если мы используем, например, eax, ecx, esi, это будет выглядеть вот так:
push eax
push ecx
push esi
…
;некий код
…
pop esi
pop ecx
pop eax
2) Все сразу.
В случае использования большего количества регистров бывает наглядней и быстрей сохранять и вытаскивать их всех сразу, определёнными командами. Вот так:
pushad
…
;некий код
…
popad
2.6 Передача аргументов подпрограмме
Передавать аргументы подпрограмме можно тоже двумя способами, рассмотрим оба:
1) Через регистры
Если аргументов не так много, то их можно передать через регистры, просто условиться передавать через этот регистр это, а через тот регистр то.
Пример:
Скажем для подпрограммы sp1 в eax и ebx надо положить два числа 23 и 42
section .text
_start:
…
;код главной программы
…
mov eax, 23
mov ebx, 42
call sp1
…
;ещё код…
…
mov eax,1
mov ebx,0
int 0x80
sp1:
…
;код подпрограммы sp1
…
ret
Здесь жирным выделены куски кода на которые стоит обратить внимание.
2) Через стек
Если аргументов намного больше чем регистров, или же для большей простоты обращения к ним, часто прибегают к передаче регистров через стек.
Как работает стек?
В секции .stack находиться память на которую указывает регистр esp, при операции push <операнд1> в четыре байта кладётся этот операнд1 и значение регистра esp уменьшается на четыре. При выполнении pop <операнд1> в операнд1 загружается значение из памяти на которую указывает esp и он же увеличивается на 4.
Как работает call?
В стек помещается значение регистра eip (адрес следующей выполняемой комманды) и происходит переход на метку.
В главной программе:
Аргументы помещаются в стек в обратном порядке и вызывается подпрограмма, после вызова, к регистру esp прибавляется 4*количество_аргументов.
Теперь о подпрограмме:
call помещает в стек eip отдаляя нас от аргументов на 4 байта, затем мы сами сохраняем в стеке некий регистр (обычно ebp), в который потом заносим значение esp => мы отдалились от аргументов на 8 байт. Также каждый аргумент в стеке весит 4 байта => формула расчёта смещения F(n) = (n-1)*4+8. Как пользоваться? Например, нам нужно добраться до 5-ого аргумента в массиве аргументов 4, 8, 15, 16, 23, 42, которые были помещены в стек «правильно»:
push 42
push 23
push 16
push 15
push 8
push 4
Тогда смещение F(5)=(5-1)*4+8=24, то есть
dword [ebp+24] = 23. Почему мы кладём esp именно в ebp, и затем используем только его? Потому что здесь так заведе... Не знаю. Просто так.
Пример кода:
Скажем для подпрограммы sp2 надо положить два числа 23 и 42
section .text
_start:
…
;код главной программы
…
push 42
push 23
call sp2
add esp, 8
…
;ещё код…
…
mov eax,1
mov ebx,0
int 0x80
sp2:
push ebp
mov ebp, esp
…
;код подпрограммы sp2
…
pop ebp
ret
Опять же выделен жирным лишь тот код, который достоин внимания.
