Здесь собраны некоторые относящиеся к ассемблеру фичи,
не публикуемые в большинстве обычных туториалов.
Не следует этот текст как-либо комментировать, и если содержание Вам покажется
слишком простым - тогда считайте, что это дока для начинающих.
Всю положительную энергию, полученную от прочтения данного текста
надлежит использовать во славу деструктивного вирмэйкинга.
НЕМНОГО ВСЕМ ИЗВЕСТНОЙ АРИФМЕТИКИ | TOP |
Преобразование двойного слова (в EAX) в учетверенное слово (в EDX:EAX)
Более понятный эквивалент:
cdq |
mov edx, eax sar edx, 31 |
Инверсия всех битов операнда.
not eax |
xor eax, 0FFFFFFFFh |
Изменение знака операнда.
neg eax |
xor eax, 0FFFFFFFFh inc eax |
eax' = ~eax+1 = -eax |
То же что и AND, но без изменения содержимого операндов. (Изменяются только флаги)
test eax, 123 |
push eax and eax, 123 pop eax |
Понятно, все слышали про CMP. ;-) Вот альтернативная точка зрения.
То же, что и SUB, но без изменения содержимого операндов.
(Изменяются только флаги).
cmp eax, 123 |
push eax sub eax, 123 pop eax |
Вычитание с заемом. Кроме второго операнда, из первого вычитается еще
и CF (Carry Flag, флаг переноса).
Наиболее интересна форма команды с одинаковыми операндами. В этом случае
в каждый бит регистра загружается значение CF, то есть регистр=0 если CF=0
и регистр=0xFFFFFFFF если CF=1.
sbb eax, eax |
if (cf == 0) eax = 0; else eax = -1; |
УСЛОВНОЕ ВЫПОЛНЕНИЕ БЕЗ ПЕРЕДАЧИ УПРАВЛЕНИЯ | TOP |
А вот несколько приводящих в ужас примеров с использованием вышеперечисленных команд. Ассемблерный код примеров взят из какой-то доки по оптимизации для пня.
cmp eax, ebx sbb ecx, ecx |
if (eax >= ebx) ecx = 0; else ecx = -1; |
| eax >= 0 | eax < 0 | ||
|---|---|---|---|
mov edx, eax sar edx, 31 xor eax, edx sub eax, edx |
edx = eax edx = 0 eax ^= 0 eax -= 0 |
edx = eax
edx = 0xFFFFFFFF=-1
eax' = ~eax
eax'' = eax'-edx =
= ~eax-(-1) =
= ~eax+1 =
= -eax
|
if (a < 0) a = -a; //a = abs(a) |
| ebx >= ebx | ebx < eax | ||
|---|---|---|---|
sub ebx, eax sbb ecx, ecx and ecx, ebx add eax, ecx |
ebx'= ebx-eax ecx = 0 ecx' = 0 eax' += 0 |
ebx' = ebx-eax
ecx = 0xFFFFFFFF
ecx' = ebx' = ebx - eax
eax' = eax + ecx' =
= eax + (ebx - eax) =
= ebx
|
if (a > b) a = b; //a = min(a,b); |
| eax = 0 | eax != 0 | ||
|---|---|---|---|
cmp eax, 1 sbb eax, eax and ecx, eax xor eax, -1 and eax, ebx or eax, ecx |
cf = 1 eax = 0FFFFFFFFh ecx' = ecx eax' = 0 eax'' = 0 eax''' = ecx |
cf = 0
eax = 0
ecx' = 0
eax' = 0FFFFFFFFh
eax'' = ebx
eax''' = eax'' | ecx' =
= ebx | 0 =
= ebx
|
if (a != 0) a = b; else a = c; |
ВСЕМИ ЛЮБИМЫЕ BCD-ОПЕРАЦИИ | TOP |
Замечено, что многие программисты боятся таких команд, как AAA, AAS, ASS ;-) и некоторых других. Причина этого (по Фрэйду) кроется глубоко в детстве и связана с сексуальными переживаниями, не поддающимися психоанализу. ;-))
BCD -- (binary coded decimal) -- двоично-десятичные числа. Выдумавшие их разработчики были лентяи и мазерфакеры, поэтому BCD-числа числа бывают двух видов -- упакованые и неупакованные.
Понятно, что нормальные арифметические (ADD/SUB/MUL/DIV) операции с такими числами невозможны, поэтому вместо того чтобы их выкинуть на помойку люди занимаются с ними мазохизмом в виде команд AAA, AAS, AAM, AAD, DAA и DAS.
Команды DAA и DAS предназначены для работы с Packed BCD. Команды AAA, AAS, AAM и AAD предназначены для Unpacked BCD. Команда AAD -- это коррекция ПЕРЕД делением, все остальные -- коррекции ПОСЛЕ соответствующих операций.
| AAA | AAS |
|---|---|
| Ascii Adjust after Addition | Ascii Adjust after Substraction |
| ASCII-коррекция после сложения | ASCII-коррекция после вычитания |
if ((al & 0x0F) > 9) || (AF == 1)
{
al = (al + 6) & 0x0F
ah = ah + 1
AF = 1
CF = 1
}
else
{
CF = 0
AF = 0
}
|
if ((al & 0x0F) > 9) || (AF == 1)
{
al = al - 6
al = al & 0x0F
ah = ah - 1
AF = 1
CF = 1
}
else
{
CF = 0
AF = 0
}
|
| AAM | AAD |
| Ascii Adjust after Multiplication | Ascii Adjust before Division |
| ASCII-коррекция после умножения | ASCII-коррекция перед делением |
| операнд по умолчанию = 10 | |
AAM xx al' = al mod xx
ah' = al div xx
AAM 0 div 0
AAM 1 mov ah, al
mov al, 0
|
AAD xx add al, ah * xx
mov ah, 0
AAD 0 mov ah, 00
AAD 1 add al, ah
mov ah, 0
|
| DAA | DAS |
| Decimal Adjust after Addition | Decimal Adjust after Substraction |
| десятичная коррекция после сложения | десятичная коррекция после вычитания |
if ((al & 0x0F) > 9) || (AF == 1)
{
al = al + 6
AF = 1
}
else
AF = 0
if (al > 0x9F) || (CF == 1)
{
al = al + 0x60
CF = 1
}
else
CF = 0
|
if ((al & 0x0F) > 9) || (AF == 1)
{
al = al - 6
AF = 1
}
else
AF = 0
if (al > 0x9F) || (CF == 1)
{
al = al - 0x60
CF = 1
}
else
CF = 0
|
Это может показаться странным, но BCD иногда используются в реальной жизни.
Например самый типичный вирмэйкерский объект -- таймер компьютера, то бишь время и дата, хранящиеся в CMOS, находятся в Packed BCD формате.
Флаг AF (Auxilary Flag) -- флаг вспомогательного переноса -- существует как раз для BCD арифметики. Работает так же, как и CF, но для бита 3 а не 7.
И даже в наборе команд сопроцессора присутствуют команды для работы с BCD: FBLD -- загрузить и FBSTP -- сохранить число в BCD формате.
| BCD --> Binary | Binary --> BCD |
|---|---|
aam 16 aad 10 |
aam 10 aad 16 |
Когда-то один знакомый (написавший как-то весьма крутой вирус) задал мне интересную задачку -- написать подпрограмму выдачи шестнадцатиричного числа на экран (DOS, число в AX) -- наименьшей возможной длины.
Вот несколько вариантов решения:
| 20 байт | 21 байт | 21 байт | 21 байт |
|---|---|---|---|
hexword: mov cx, 4 cycle: rol ax, 4 push ax and al,15 daa add al,-10h adc al,'0'+10h int 29h pop ax loop cycle ret |
hexword: mov cx, 4 cycle: rol ax, 4 push ax and al,15 sub al,'A'-'0' aam '9'-'A'+1 add al,'A' int 29h pop ax loop cycle ret |
hexword: mov cx, 4 cycle: rol ax, 4 push ax and al, 15 aam 10 aad 'A'-'0' add al, '0' int 29h pop ax loop cycle ret |
hexword: mov cx, 4 cycle: rol ax, 4 push ax and al, 15 add al, 90h daa adc al, 'A'-1 daa int 29h pop ax loop cycle ret |
НЕСКОЛЬКО СЛОВ О КОМАНДАХ MUL И DIV | TOP |
При умножении чисел на постоянный множитель (для увеличения скорости)
вместо MUL можно использовать серии из SHL и ADD(OR).
Пример: Для многих не секрет, что в демках требуется писать код, выполняющийся с наибольшей скоростью.
Рассмотрим типичный пример -- умножение на 320.
imul eax, 320 |
shl eax, 6 ; *64 mov ebx, eax shl ebx, 2 ; *256 add eax, ebx |
Рассмотрим интересный случай. Генератор случайных чисел, алгоритм - почти как у ТурбоПаскаля 7.0, период 2^32.
randseed dd ?
; void randomize() { randseed = GetTickCount(); }
randomize: call GetTickCount
mov randseed, eax
ret
; DWORD random() { return randseed = randseed * 0x8088405 + 1; }
random: mov eax, randseed
imul eax, 8088405h
inc eax
mov randseed, eax
ret
; DWORD rnd(DWORD range) { return random() % range; }
rnd: xchg ebx, eax ; now EBX=range
call random
xor edx, edx
mul ebx ; (*)
xchg edx, eax
ret
|
Обратим внимание на строчку с (*), то бишь на MUL.
Неочевидно, но факт - здесь его действие почти такое же как и у DIV.
Более того - в этой ситуации MUL работает примерно в 1.5 раза быстрее,
а в случае нулевого параметра -- rnd(0) -- в результате возвращается 0,
тогда как при DIV произошла бы ошибка деления.
Несложно догадаться, что возникает такая ситуация только в конкретном случае, когда операнд для DIV(MUL) -- случайное 16- или 32-битное число.
Сравнение ASCIIZ-строк с использованием хэш-функций | TOP |
Предположим, что нам нужно сравнить две ASCIIZ-строки (обычные заканчивающиеся нулем текстовые строки). Рассматриваемое действие - посчитать от строчек некоторую хэш-функцию (контрольную сумму, etc.), и сравнивать уже эти функции. При известном наборе строчек и/или ограничениях на них, ошибки исключены. Преимущество такого сравнения в случаях:
Вот кусок кода, который позволяет вытаскивать из экспорта адреса функций по хэшам их имен.
calchash macro procname
hash = 0
irpc c, <procname>
; rol 7
hash = ((hash shl 7) and 0FFFFFFFFh) or (hash shr (32-7))
hash = hash xor "&c"
endm
endm
mov_h macro reg, procname
calchash procname
mov reg, hash
endm
j_GetProcAddress dd ?
mov_h edi, GetProcAddress
call findfunc
jz exit
mov j_GetProcAddress, eax
; find function's address in export table
;
; input: EBX=imagebase va, ECX=export table va, EDI=name csum
; modify: EDX, ESI
; output: ZF=1, EAX=0 (function not found)
; ZF=0, EAX=function va
findfunc: xor esi, esi ; current index
search_cycle: lea edx, [esi*4+ebx]
add edx, [ecx].ex_namepointersrva
mov edx, [edx] ; name va
add edx, ebx ; +imagebase
xor eax, eax ; calculate hash
calc_hash: rol eax, 7
xor al, [edx]
inc edx
cmp byte ptr [edx], 0
jne calc_hash
cmp eax, edi ; compare hashs
je name_found
inc esi ; index++
cmp esi, [ecx].ex_numofnamepointers
jb search_cycle
xor eax, eax ; return 0
ret
name_found: mov edx, [ecx].ex_ordinaltablerva
add edx, ebx ; +imagebase
movzx edx, word ptr [edx+esi*2]; edx=current ordinal
; sub edx, [ecx].ex_ordinalbase ; -ordinal base
mov eax, [ecx].ex_addresstablerva
add eax, ebx ; +imagebase
mov eax, [eax+edx*4]; eax=current address
add eax, ebx ; +imagebase
ret ; return address
|
Вывод десятичных чисел на экран (itoa) | TOP |
Достаточно интересная задача, к тому же процедура вывода числа весьма универсальна.
itoa: xor edx, edx
mov ebx, 10 ; основание системы счисления
div ebx
push edx ; запомнить остаток от деления
or eax, eax ; частное == 0 ?
jz @@1
call itoa ; рекурсивный вызов (если есть что делить)
@@1: pop eax
add al, '0'
... ; вывод символа (stosb, INT 29h, etc.)
ret
|