## АССЕМБЛЕРНЫЕ ФИЧИ

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

### СОДЕРЖАНИЕ

- НЕМНОГО АРИФМЕТИКИ
  - CDQ
  - NOT
  - NEG
  - TEST
  - CMP
  - SBB
- УСЛОВНОЕ ВЫПОЛНЕНИЕ БЕЗ ПЕРЕДАЧИ УПРАВЛЕНИЯ
  - Пример 1
  - Пример 2 (нахождение абсолютного значения)
  - Пример 3 (нахождение минимального значения)
  - Пример 4
- BCD-ОПЕРАЦИИ
  - Пара слов о BCD
  - Действие BCD-команд
  - О применимости BCD-чисел
  - Перевод чисел в BCD и обратно
  - Пример использования BCD-арифметики (подпрограмма hexword)
- КОМАНДЫ MUL И DIV
  - Умножение на константу
  - Использование MUL вместо DIV (генератор случайных чисел)
- Сравнение ASCIIZ-строк с использованием хэш-функций
- Вывод десятичных чисел на экран (itoa)

### НЕМНОГО ВСЕМ ИЗВЕСТНОЙ АРИФМЕТИКИ

TOP

#### CDQ (convert double-word to quad-word)

Преобразование двойного слова (в EAX) в учетверенное слово (в EDX:EAX)
Более понятный эквивалент:

    cdq

    mov     edx, eax
    sar     edx, 31

#### NOT

Инверсия всех битов операнда.

    not     eax

    xor     eax, 0FFFFFFFFh

#### NEG

Изменение знака операнда.

    neg     eax

    xor     eax, 0FFFFFFFFh
    inc     eax

    eax' = ~eax+1 = -eax

#### TEST

То же что и AND, но без изменения содержимого операндов. (Изменяются только флаги)

    test    eax, 123

    push    eax
    and     eax, 123
    pop     eax

#### CMP

Понятно, все слышали про CMP. ;-) Вот альтернативная точка зрения.
То же, что и SUB, но без изменения содержимого операндов. (Изменяются только флаги).

    cmp     eax, 123

    push    eax
    sub     eax, 123
    pop     eax

#### SBB (subtract with borrow)

Вычитание с заемом. Кроме второго операнда, из первого вычитается еще и CF (Carry Flag, флаг переноса).
Наиболее интересна форма команды с одинаковыми операндами. В этом случае в каждый бит регистра загружается значение CF, то есть регистр=0 если CF=0 и регистр=0xFFFFFFFF если CF=1.

    sbb     eax, eax

    if (cf == 0)
      eax = 0;
    else
      eax = -1;

### УСЛОВНОЕ ВЫПОЛНЕНИЕ БЕЗ ПЕРЕДАЧИ УПРАВЛЕНИЯ

TOP

А вот несколько приводящих в ужас примеров с использованием вышеперечисленных команд. Ассемблерный код примеров взят из какой-то доки по оптимизации для пня.

#### Пример 1

    cmp     eax, ebx
    sbb     ecx, ecx

    if (eax >= ebx)
      ecx = 0;
    else
      ecx = -1;

#### Пример 2
(нахождение абсолютного значения)

| 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)

#### Пример 3
(нахождение минимального значения)

| 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);

#### Пример 4

| 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

BCD -- (binary coded decimal) -- двоично-десятичные числа. Выдумавшие их разработчики были лентяи и мазерфакеры, поэтому BCD-числа числа бывают двух видов -- упакованые и неупакованные.

- Packed BCD -- это байты, каждая тетрада (4 бита) которых изменяется от 0 до 9, то есть 00h,01h,...,09h,10h,11h,...,99h.
- Unpacked BCD -- это просто байты, изменяющиеся от 0 до 9, а старшая их тетрада равна 0.

Понятно, что нормальные арифметические (ADD/SUB/MUL/DIV) операции с такими числами невозможны, поэтому вместо того чтобы их выкинуть на помойку люди занимаются с ними мазохизмом в виде команд AAA, AAS, AAM, AAD, DAA и DAS.

Команды DAA и DAS предназначены для работы с Packed BCD. Команды AAA, AAS, AAM и AAD предназначены для Unpacked BCD. Команда AAD -- это коррекция ПЕРЕД делением, все остальные -- коррекции ПОСЛЕ соответствующих операций.

#### Действие BCD-команд

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-чисел

Это может показаться странным, но BCD иногда используются в реальной жизни.

Например самый типичный вирмэйкерский объект -- таймер компьютера, то бишь время и дата, хранящиеся в CMOS, находятся в Packed BCD формате.

Флаг AF (Auxilary Flag) -- флаг вспомогательного переноса -- существует как раз для BCD арифметики. Работает так же, как и CF, но для бита 3 а не 7.

И даже в наборе команд сопроцессора присутствуют команды для работы с BCD: FBLD -- загрузить и FBSTP -- сохранить число в BCD формате.

#### Перевод чисел в BCD и обратно

BCD --> Binary | Binary --> BCD
--- | ---

    aam   16
    aad   10

    aam   10
    aad   16

#### Пример использования BCD-арифметики

Когда-то один знакомый (написавший как-то весьма крутой вирус) задал мне интересную задачку -- написать подпрограмму выдачи шестнадцатиричного числа на экран (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

#### Использование MUL вместо DIV

Рассмотрим интересный случай. Генератор случайных чисел, алгоритм - почти как у ТурбоПаскаля 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