Здесь собраны некоторые относящиеся к ассемблеру фичи,
не публикуемые в большинстве обычных туториалов.
Не следует этот текст как-либо комментировать, и если содержание Вам покажется
слишком простым - тогда считайте, что это дока для начинающих.
Всю положительную энергию, полученную от прочтения данного текста
надлежит использовать во славу деструктивного вирмэйкинга.
НЕМНОГО ВСЕМ ИЗВЕСТНОЙ АРИФМЕТИКИ | 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 |