315
правок
Изменения
Новая страница: «Регистры -- основная память в ассемблере. Регистры общего назначения: eax (accumulator) ebx (base) ecx (...»
Регистры -- основная память в ассемблере.
Регистры общего назначения:
eax (accumulator)
ebx (base)
ecx (counter)
edx (data)
ebp ...
esp (stack pointer) трогать нежелательно, стек ПЫЩЬ
esi ...
edi ...
То, что вторые буквы -- первые четыре буквы лат. алфавита -- случайность. Это видно в том числе и по тому, что их естественный порядок виден при переносе на стек: eax, edx, ecx, ebx...
Ещё регистры:
eip (instruction pointer)
eflags: куча битов, которые означают флаги. пример:
zf -- 1, если последняя операция вернула 0, 1 otherwise
cf -- знак переноса (если результат последней операции не влезает в нужное кол-во битов и один переносится)
sf -- знаковый бит. 0 -- полож., 1 -- отриц.
(на вышеперечисленные флаги влияет результат выполнения ТОЛЬКО арифметических операций!)
df -- направление выполнения строковых операций.
Регистры 32-битны. На 64-битных системах действует следующие наименования:
e*x
____^____
/ \
32 | 16 | 8 | 8
*h *l
\______ ______/
v
r*x
Команды
Команды загрузки
mov -- команда загрузки.
mov al, 5 -- загрузить в al 5
Обычно первый аргумент команды -- то, что меняется.
mov cx, di -- скопировать значения cx в di. Размер копируемых данных должен совпадать.
movzx -- размер игнорируется; то, что осталось, заполняется нулями
movcx -- размер игнорируется; то, что осталось, заполняется знаковым (старшем) битом
mov(cc): cc -- условие. если cc, то mov, ничего otherwise
xchg -- обмен значений регистров
bswap -- меняет порядок байт в регистре на обратный (little в big, big в little)
//little-endian:
//байты загружаются в память в порядке:
//0...7...15...31
//
//big-endian:
//31...15...7...0
//
//процессоры: x86 -- little-endian, при обмене данными по сети -- big-endian
Обращаться можно и к памяти:
mov eax,[ebx] -- загрузка в eax того, что находится по адресу ebx. (в C выглядело бы как eax = *ebx. [] - косвенная адресация. не более одних скобок за раз, ** недопустимы. может использоваться как для левого, так и правого аргумента mov. по правилам в команде <= 1 обращения к памяти. сказать, что "mov [eax],[ebx]" разрешено == всё равно, что выгнать себя с экзамена)
Оперативную память попортить нельзя, она разделена между процессами.
//Си -- высокоуровневый ассемблер, многие вещи в Си пошли от ассемблера
В квадратных скобках при использовании 16-битных регистров можно писать три опциональные части:
bx (bp) + si (di) +(-) offset (16-битное число)
В квадратных скобках при использовании 32-битных регистров позволено писать довольно много разных вещей:
любой из регистров общего назначения + (любой из регистров общего назначения, за исключением esp)*(1|2|4|8) +(-) offset (32-битное число)
push eax -- положить eax на стек.
Сишный псевдокод:
| esp -= sizeof(eax) (минус потому, что стек "растёт вниз", чем он больше, тем на меньшее число указывает esp (и наоборот))
| *esp = eax
Вроде, эквивалентный код на ассемблере:
| sub esp, 4
| mov [esp], eax
pop eax -- запихать вершину стека в eax
Как это выглядит на ассемблере:
| mov eax, [esp]
| add esp, 4
Стек-пойнтеру (sp) в 16-битном режиме довольно желательно бы быть чётным, в 32-битном -- кратным четырём: всё будет работать существенно быстрее.
Стек -- хорошее быстрое временное хранилище. не слишком большое (1-2 мб)
//malloc и free -- системные функции, их можно свободно использовать в любом языке (и в Си, и здесь, и в дельфи)
pusha(d) -- сохранить в стек все 8 регистров общего назначения
popa(d) -- вытащить их из стека
(push|pop)ad работают с 32-битными регистрами, (push|pop)a - c 16-битными
при восстановлении не меняется esp (логично, дабы с esp не произошло трешака)
//"Вас никто не заставляет делать разумные вещи. Если вы хотите делать безумные вещи, вы можете это делать совершенно свободно!"
*Начало второй лекции. Начало пропущено:*
Арифм. команды:
add eax, ebx (eax += ebx)
adc (???)
sub / sbb
mul / div
imul / idiv
mul OP -- после этой команды в edx:eax 64 бита -- результат умножения eax на аргумент
div OP -- после этой команды в edx edx:eax / ebx
в eax же eds:eax % ebx
как при делении на ноль, так и переполнении при делении вылетает исключение и программу убивает система. последнее может лечиться обнулением edx'а.
//в документации указана обработка всего этого в 8- и 16-битных случаях
imul и idiv -- аналогично mul и div, НО: работают со знаковыми числами
кроме того, у imul есть такие формы записи:
imul ecx, ebx == ecx *= ebx (верхние биты обнуляются)
imul ecx, ebx, 5 == ecx = ebx * 5
inc и dec. принимают один операнд, увеличивают (уменьшают) его на один; не трогают знак переноса
Команда, которая неизвестно для чего изначально нужна.
lea (load effective adress):
lea eax, [ebx+ecx] -- фактически, трёхоперандное сложение. eax = abx + ecx
lea eax, [ebx+ebx*4] -- eax = ebx * 5 (yasm поддерживает запись вида lea eax, [ebx*5], но по стандарту так нельзя)
Как быстрее всего умножить число на 10?
lea eax, [ebx*5]
add eax, eax
Команды логики:
and, or, xor, not. Все, кроме not -- унарные.
xor eax, eax -- быстрое обнуление регистра
команда mov anything, 0 имеет смысл, но редко. когда:
1) когда нужно сохранить флаги. логика их меняет
2) когда нужно обнулять память. ксорить память нельзя (её можно указать лишь один раз)
Команды cmp/test аналогичны командам (sub/and). результаты не пишут никуда (!!!), но ставят флаги.
test eax,eax -- самый короткий способ проверить, 0 ли регистр
Команды сдвига:
Во всех сдвигах последний сдвигаемый бит идёт в cf
shr/shl -- сишные сдвиги (>>, <<). второй аргумент команды -- либо константа, либо cl. деление на степени двойки -- самое быстрые, ибо сдвиги, которые гораздо более быстрее, нежели "реальное" деление.
sar -- сдвиг справо со знаком. Сишное >> компилируется в зависимости от "знаковости" переменной в shr/sar. shl, очевидно, == sal.
shrd/shld принимают три аргумента.
shrd eax,ebx,3:
ebx,eax двигаются на 3 вправо (О_О). первые k (где k -- третий аргумент) битов того, что указано в первом аргументе == последние k того, что указано во втором.
Команды вращения (о_О):
rol, ror: тупое вращение по кругу, ничего ниоткуда не берётся, не теряется и не придумывается.
rcl, rcr:
rcl:
cf <- 31...<первый аргумент>...0-^
| |
v-------------------------------->
Аналогично rol/ror'у, но флаг cf -- часть того, что сдвигается (т.о., сдвигаются 33 байта)
Команды передачи управления:
jmp метка (jmp eax)
фактически -- mov eip, smth
Так же есть условия, зависящие от флагов:
j(cc)
ex.: jz -- условный переход, если флаг нуля установлен.
jnz -- переход, если флаг нуля не установлен.
jc, jnc -- аналогично для флага переноса, и так далее... тысячи их!
ja, jb, jae, jbe, jl, jg, jle, jge (к каждой можно устроить отрицание(
ja -- above (если больше, без знака)
jb -- below (если меньше, без знака)
jae -- above/equal (больше или равно без знака)
jbe -- below/equal (меньше или равно без знака)
jg \
jl |} -- аналогично вышеуказанным,
jge |} -- но с учётом знаков.
jle /
к каждым командам существуют отрицания. j(n)cc
логично, что, например, jb == jnae
те же самые условия используеются в командах условной загрузки (mov(cc))
call -- аналогично jmp, НО.
call x ~=
| push eip
| mov eip, x
ret ~=
| pop eip
У ret есть опциональный параметр -- число.
ret x ~=
pop eip
add esp, x (выкинуть со стека x байт)
//пара команд, которые все любят
nop -- Команда, Которая Ничего Не Делает
ud2 -- Команда, Которой Не Существует. Если она выполняется, программа падает с ошибкой. И БУДЕТ ПАДАТЬ. ВСЕГДА. И НЫНЕ. И ПРИСНО. ВО ВЕКИ. ВЕКОВ
Регистры общего назначения:
eax (accumulator)
ebx (base)
ecx (counter)
edx (data)
ebp ...
esp (stack pointer) трогать нежелательно, стек ПЫЩЬ
esi ...
edi ...
То, что вторые буквы -- первые четыре буквы лат. алфавита -- случайность. Это видно в том числе и по тому, что их естественный порядок виден при переносе на стек: eax, edx, ecx, ebx...
Ещё регистры:
eip (instruction pointer)
eflags: куча битов, которые означают флаги. пример:
zf -- 1, если последняя операция вернула 0, 1 otherwise
cf -- знак переноса (если результат последней операции не влезает в нужное кол-во битов и один переносится)
sf -- знаковый бит. 0 -- полож., 1 -- отриц.
(на вышеперечисленные флаги влияет результат выполнения ТОЛЬКО арифметических операций!)
df -- направление выполнения строковых операций.
Регистры 32-битны. На 64-битных системах действует следующие наименования:
e*x
____^____
/ \
32 | 16 | 8 | 8
*h *l
\______ ______/
v
r*x
Команды
Команды загрузки
mov -- команда загрузки.
mov al, 5 -- загрузить в al 5
Обычно первый аргумент команды -- то, что меняется.
mov cx, di -- скопировать значения cx в di. Размер копируемых данных должен совпадать.
movzx -- размер игнорируется; то, что осталось, заполняется нулями
movcx -- размер игнорируется; то, что осталось, заполняется знаковым (старшем) битом
mov(cc): cc -- условие. если cc, то mov, ничего otherwise
xchg -- обмен значений регистров
bswap -- меняет порядок байт в регистре на обратный (little в big, big в little)
//little-endian:
//байты загружаются в память в порядке:
//0...7...15...31
//
//big-endian:
//31...15...7...0
//
//процессоры: x86 -- little-endian, при обмене данными по сети -- big-endian
Обращаться можно и к памяти:
mov eax,[ebx] -- загрузка в eax того, что находится по адресу ebx. (в C выглядело бы как eax = *ebx. [] - косвенная адресация. не более одних скобок за раз, ** недопустимы. может использоваться как для левого, так и правого аргумента mov. по правилам в команде <= 1 обращения к памяти. сказать, что "mov [eax],[ebx]" разрешено == всё равно, что выгнать себя с экзамена)
Оперативную память попортить нельзя, она разделена между процессами.
//Си -- высокоуровневый ассемблер, многие вещи в Си пошли от ассемблера
В квадратных скобках при использовании 16-битных регистров можно писать три опциональные части:
bx (bp) + si (di) +(-) offset (16-битное число)
В квадратных скобках при использовании 32-битных регистров позволено писать довольно много разных вещей:
любой из регистров общего назначения + (любой из регистров общего назначения, за исключением esp)*(1|2|4|8) +(-) offset (32-битное число)
push eax -- положить eax на стек.
Сишный псевдокод:
| esp -= sizeof(eax) (минус потому, что стек "растёт вниз", чем он больше, тем на меньшее число указывает esp (и наоборот))
| *esp = eax
Вроде, эквивалентный код на ассемблере:
| sub esp, 4
| mov [esp], eax
pop eax -- запихать вершину стека в eax
Как это выглядит на ассемблере:
| mov eax, [esp]
| add esp, 4
Стек-пойнтеру (sp) в 16-битном режиме довольно желательно бы быть чётным, в 32-битном -- кратным четырём: всё будет работать существенно быстрее.
Стек -- хорошее быстрое временное хранилище. не слишком большое (1-2 мб)
//malloc и free -- системные функции, их можно свободно использовать в любом языке (и в Си, и здесь, и в дельфи)
pusha(d) -- сохранить в стек все 8 регистров общего назначения
popa(d) -- вытащить их из стека
(push|pop)ad работают с 32-битными регистрами, (push|pop)a - c 16-битными
при восстановлении не меняется esp (логично, дабы с esp не произошло трешака)
//"Вас никто не заставляет делать разумные вещи. Если вы хотите делать безумные вещи, вы можете это делать совершенно свободно!"
*Начало второй лекции. Начало пропущено:*
Арифм. команды:
add eax, ebx (eax += ebx)
adc (???)
sub / sbb
mul / div
imul / idiv
mul OP -- после этой команды в edx:eax 64 бита -- результат умножения eax на аргумент
div OP -- после этой команды в edx edx:eax / ebx
в eax же eds:eax % ebx
как при делении на ноль, так и переполнении при делении вылетает исключение и программу убивает система. последнее может лечиться обнулением edx'а.
//в документации указана обработка всего этого в 8- и 16-битных случаях
imul и idiv -- аналогично mul и div, НО: работают со знаковыми числами
кроме того, у imul есть такие формы записи:
imul ecx, ebx == ecx *= ebx (верхние биты обнуляются)
imul ecx, ebx, 5 == ecx = ebx * 5
inc и dec. принимают один операнд, увеличивают (уменьшают) его на один; не трогают знак переноса
Команда, которая неизвестно для чего изначально нужна.
lea (load effective adress):
lea eax, [ebx+ecx] -- фактически, трёхоперандное сложение. eax = abx + ecx
lea eax, [ebx+ebx*4] -- eax = ebx * 5 (yasm поддерживает запись вида lea eax, [ebx*5], но по стандарту так нельзя)
Как быстрее всего умножить число на 10?
lea eax, [ebx*5]
add eax, eax
Команды логики:
and, or, xor, not. Все, кроме not -- унарные.
xor eax, eax -- быстрое обнуление регистра
команда mov anything, 0 имеет смысл, но редко. когда:
1) когда нужно сохранить флаги. логика их меняет
2) когда нужно обнулять память. ксорить память нельзя (её можно указать лишь один раз)
Команды cmp/test аналогичны командам (sub/and). результаты не пишут никуда (!!!), но ставят флаги.
test eax,eax -- самый короткий способ проверить, 0 ли регистр
Команды сдвига:
Во всех сдвигах последний сдвигаемый бит идёт в cf
shr/shl -- сишные сдвиги (>>, <<). второй аргумент команды -- либо константа, либо cl. деление на степени двойки -- самое быстрые, ибо сдвиги, которые гораздо более быстрее, нежели "реальное" деление.
sar -- сдвиг справо со знаком. Сишное >> компилируется в зависимости от "знаковости" переменной в shr/sar. shl, очевидно, == sal.
shrd/shld принимают три аргумента.
shrd eax,ebx,3:
ebx,eax двигаются на 3 вправо (О_О). первые k (где k -- третий аргумент) битов того, что указано в первом аргументе == последние k того, что указано во втором.
Команды вращения (о_О):
rol, ror: тупое вращение по кругу, ничего ниоткуда не берётся, не теряется и не придумывается.
rcl, rcr:
rcl:
cf <- 31...<первый аргумент>...0-^
| |
v-------------------------------->
Аналогично rol/ror'у, но флаг cf -- часть того, что сдвигается (т.о., сдвигаются 33 байта)
Команды передачи управления:
jmp метка (jmp eax)
фактически -- mov eip, smth
Так же есть условия, зависящие от флагов:
j(cc)
ex.: jz -- условный переход, если флаг нуля установлен.
jnz -- переход, если флаг нуля не установлен.
jc, jnc -- аналогично для флага переноса, и так далее... тысячи их!
ja, jb, jae, jbe, jl, jg, jle, jge (к каждой можно устроить отрицание(
ja -- above (если больше, без знака)
jb -- below (если меньше, без знака)
jae -- above/equal (больше или равно без знака)
jbe -- below/equal (меньше или равно без знака)
jg \
jl |} -- аналогично вышеуказанным,
jge |} -- но с учётом знаков.
jle /
к каждым командам существуют отрицания. j(n)cc
логично, что, например, jb == jnae
те же самые условия используеются в командах условной загрузки (mov(cc))
call -- аналогично jmp, НО.
call x ~=
| push eip
| mov eip, x
ret ~=
| pop eip
У ret есть опциональный параметр -- число.
ret x ~=
pop eip
add esp, x (выкинуть со стека x байт)
//пара команд, которые все любят
nop -- Команда, Которая Ничего Не Делает
ud2 -- Команда, Которой Не Существует. Если она выполняется, программа падает с ошибкой. И БУДЕТ ПАДАТЬ. ВСЕГДА. И НЫНЕ. И ПРИСНО. ВО ВЕКИ. ВЕКОВ