Несколько полезных функций на Ассемблере

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

Начну с функции очень быстрого копирования одного участка памяти в другой. Фишка в том, что основная часть строки копируется по 4 байта DWORD'ами, а остаток дописывается побайтно. Это дает нехилый выигрыш в скорости.
;-----------------------------------------------------
; Функция быстрого копирования участка памяти
;-----------------------------------------------------
; lpDst - указатель на приемник
; lpSrc - указатель на источник
; dSize - размер копируемого блока
;-----------------------------------------------------
proc    _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
        pusha
 
        ; Установить указатели на источник и приемник
        cld
        mov     edi,[lpDst]
        mov     esi,[lpSrc]
 
        mov     ecx,[dSize]
        push    ecx
        ; Разделить на 4 и получить длину в DWORD
        shr     ecx,2
        ; Скопировать основную часть строки DWORD'ами
        rep     movsd
        pop     ecx
        ; Получить остаток от деления на 4
        and     ecx,3
        ; Скопировать остаток строки байтами
        rep     movsb
 
        popa
        ret
endp

Функция копирует любые данные, в том числе и нулевые байты, поэтому для нее обязательно указывать размер копируемого блока.

Еще один вариант копирования. Он более красивый по реализации, но менее эффективный по производительности за счет использования переходов и, соответственно, невозможности использования конвейера команд процессора.
;-----------------------------------------------------
; Функция быстрого копирования участка памяти
;-----------------------------------------------------
; lpDst - указатель на приемник
; lpSrc - указатель на источник
; dSize - размер копируемого блока
;-----------------------------------------------------
proc    _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
        pusha
 
        ; Установить указатели на источник и приемник
        cld
        mov     edi,[lpDst]
        mov     esi,[lpSrc]
        mov     ecx,[dSize]
 
        ; Если есть остаток от деления на 2
        shr     ecx,1
        jnc     @f
        ; Скопировать один байт
        movsb
@@:
        ; Если есть остаток от деления на 4
        shr     ecx,1
        jnc     @f
        ; Скопировать слово
        movsw
@@:
        ; Остаток строки скопировать двойными словами
        rep     movsd
 
        popa
        ret
endp

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

Аналог функции lstrlen - получение длины строки. На входе адрес строки в формате ASCIIZ, на выходе EAX - длина строки.
;-----------------------------------------------------
; Функция получения длины строки
;-----------------------------------------------------
; lpStr - указатель на строку ASCIIZ
; На выходе: EAX - длина строки без учета завершающего
; нулевого байта
;-----------------------------------------------------
proc    _lstrlen lpStr:DWORD
        push    edi ecx
 
        cld
        mov     edi,[lpStr]
        xor     ecx,ecx
        dec     ecx
        xor     eax,eax
        repne   scasb
        not     ecx
        dec     ecx
        mov     eax,ecx
 
        pop     ecx edi
        ret
endp

Еще одна функция получения длины строки. По размеру получается немного больше, но по скорости работы почти в два раза превосходит как стандартную функцию, так и предыдущую. Выбирайте сами: размер получаемого кода или производительность.
;-----------------------------------------------------
; Функция получения длины строки (Fast)
;-----------------------------------------------------
; lpStr - указатель на строку ASCIIZ
; На выходе: EAX - длина строки без учета завершающего
; нулевого байта
;-----------------------------------------------------
proc    _lstrlen lpStr:DWORD
        mov     eax, [lpStr]
        sub     eax, 4
@@:
        add     eax, 4
        cmp     byte [eax], 0
        je      .szlen_lb1
        cmp     byte [eax+1], 0
        je      .szlen_lb2
        cmp     byte [eax+2], 0
        je      .szlen_lb3
        cmp     byte [eax+3], 0
        jne     @b
        sub     eax, [lpStr]
        add     eax, 3
        ret
.szlen_lb3:
        sub     eax, [lpStr]
        add     eax, 2
        ret
.szlen_lb2:
        sub     eax, [lpStr]
        add     eax, 1
        ret
.szlen_lb1:
        sub     eax, [lpStr]
        ret
endp

Аналог функции lstrcpy - копирование строки в формате ASCIIZ. На входе адреса приемника и источника. Функция ничего не возвращает. Используются описанные выше функции _lstrlen и_memcopy.
;-----------------------------------------------------
; Функция быстрого копирования строки
; используются функции _lstrlen, _memcopy
;-----------------------------------------------------
; lpDst - указатель на приемник
; lpSrc - указатель на строку ASCIIZ
;-----------------------------------------------------
proc    _lstrcpy lpDst:DWORD, lpSrc:DWORD
        pusha
 
        stdcall _lstrlen,[lpSrc]
        inc     eax
        stdcall _memcopy,[lpDst],[lpSrc],eax
 
        popa
        ret
endp

Аналог функции lstrcat - конкатенация (слияние) двух строк в формате ASCIIZ. Параметры: адрес основной строки и адрес второй строки, которая будет дописана к основной. Функция ничего не возвращает. Используются описанные выше функции _lstrlen и _lstrcpy.
;-----------------------------------------------------
; Функция быстрого слияния двух строк
; используются функции _lstrlen, _lstrcpy
;-----------------------------------------------------
; lpDst - указатель на исходную строку ASCIIZ
; lpSrc - указатель на добавляемую строку ASCIIZ
;-----------------------------------------------------
proc    _lstrcat lpDst:DWORD, lpSrc:DWORD
        pusha
 
        stdcall _lstrlen,[lpDst]
        add     eax,[lpDst]
 
        stdcall _lstrcpy,eax,[lpSrc]
 
        popa
        ret
endp

Аналог функции lstrcmp - сравнение двух строк. На входе адреса двух строк в формате ASCIIZ, на выходе EAX=0 - строки равны, EAX=1 - строки различаются по длине и/или содержимому. Используется описанная выше функция _lstrlen.
;-----------------------------------------------------
; Функция быстрого сравнения двух строк
; используется функция _lstrlen
;-----------------------------------------------------
; lpStr1 - указатель на первую строку ASCIIZ
; lpStr2 - указатель на вторую строку ASCIIZ
; На выходе: EAX=0 - строки совпадают
;            EAX=1 - строки различаются
;-----------------------------------------------------
proc    _lstrcmp lpStr1:DWORD, lpStr2:DWORD
        push    ecx esi edi
 
        mov     esi,[lpStr1]
        mov     edi,[lpStr2]
 
        stdcall _lstrlen,esi
        mov     ecx,eax
        stdcall _lstrlen,edi
        cmp     ecx,eax
 
        ; Длина строк не совпадает
        jnz     @f
 
        cld
        repe    cmpsb
@@:
        setnz   al
        movsx   eax,al
 
        pop     edi esi ecx
        ret
endp

Еще одна быстрая функция сравнения двух строк. Входные и выходные параметры аналогичны предыдущей функции, никакие дополнительные функции не используются.
;-----------------------------------------------------
; Функция быстрого сравнения двух строк
;-----------------------------------------------------
; lpStr1 - указатель на первую строку ASCIIZ
; lpStr2 - указатель на вторую строку ASCIIZ
; На выходе: EAX=0 - строки совпадают
;            EAX=1 - строки различаются
;-----------------------------------------------------
proc    _lstrcmp lpStr1:DWORD,lpStr2:DWORD
        push    esi edi
 
        mov     esi,[lpStr1]    ; Указатели на строки
        mov     edi,[lpStr2]
        xor     eax,eax         ; Предположим, что строки равны
@@:
        lodsb                   ; Сравнить следующие символы
        mov     ah,[edi]
        inc     edi
 
        or      al,al           ; Первая строка закончилась?
        jz      @f              ; Да
        cmp     al,ah           ; Символы совпадают?
        je      @b              ; Да, проверить следующий символ
@@:
        or      ah,ah           ; Вторая строка закончилась?
        je      @f              ; Да, строки равны
 
        xor     eax,eax         ; Строки не совпадают
        inc     eax
@@:
        pop     edi esi
        ret
endp

Функция быстрого регистронезависимого (только для букв английского алфавита) сравнения строк, аналог функции API lstrcmpi. На входе адреса двух строк в формате ASCIIZ, на выходе EAX=0 - строки равны, EAX=1 - строки различаются по длине и/или содержимому. Никакие дополнительные функции не используются.
;-----------------------------------------------------
; Функция быстрого регистронезависимого сравнения
; двух строк
;-----------------------------------------------------
; lpStr1 - указатель на первую строку ASCIIZ
; lpStr2 - указатель на вторую строку ASCIIZ
; На выходе: EAX=0 - строки совпадают
;            EAX=1 - строки различаются
;-----------------------------------------------------
proc    _lstrcmpi lpStr1:DWORD,lpStr2:DWORD
        push    esi edi
 
        mov     esi,[lpStr1]    ; Указатели на строки
        mov     edi,[lpStr2]
        xor     eax,eax         ; Предположим, что строки равны
.loc_compare:
        lodsb                   ; Сравнить следующие символы
        mov     ah,[edi]
        inc     edi
 
        cmp     al,ah           ; Символы совпадают?
        jne     @f              ; Нет
 
        or      al,al           ; Строки закончились?
        jz      .loc_ret        ; Да, строки совпадают
        jmp     .loc_compare    ; Проверить следующий символ
@@:
        and     eax,0DFDFh      ; Оба символа в верхний регистр
        cmp     al,'A'
        jb      .loc_not_equal
        cmp     al,'Z'
        ja      .loc_not_equal
 
        cmp     al,ah           ; Разница была только в регистре?
        je      .loc_compare    ; Да, следующий символ
.loc_not_equal:
        xor     eax,eax         ; Строки не совпадают
        inc     eax    
.loc_ret:
        pop     edi esi
        ret
endp

Функция быстрого заполнения фрагмента памяти указанными значениями (байтами). Может применяться для очистки памяти.
;-----------------------------------------------------
; Функция быстрого заполнения блока памяти значениями
;-----------------------------------------------------
; lpDst - указатель на приемник
; dVal  - заполнитель (берется младший байт)
; dSize - размер заполняемого блока
;-----------------------------------------------------
proc    _fillmem lpDst:DWORD, dVal:DWORD, dSize:DWORD
        pusha
 
        cld
        mov     edi,[lpDst]
        mov     eax,[dVal]
        ; Оставить только младший байт
        movzx   eax,al
        mov     edx,01010101h
        ; Теперь в EAX будет DWORD-заполнитель
        mul     edx
 
        mov     ecx,[dSize]
        push    ecx
        ; Разделить на 4 и получить длину в DWORD
        shr     ecx,2
        ; Заполнить основную часть строки DWORD'ами
        rep     stosd
        pop     ecx
        ; Получить остаток от деления на 4
        and     ecx,3
        ; Заполнить остаток строки байтами
        rep     stosb
 
        popa
        ret
endp

Получение каталога запуска программы. В отличие от GetCurrentDirectory получает именно тот каталог, в котором находится запущенный исполняемый файл. Этот код удобно применять для формирования полных путей к файлам конфигурации, каким-либо дополнительным ресурсам, хранящимся в папке с программой и т.п. Можно, конечно, использовать GetFullPathName, но так получается быстрее и короче.
        ; my_path описан в данных как буфер размером MAX_PATH+1
        mov     edi,my_path
        invoke  GetModuleFileName,NULL,edi,MAX_PATH
        add     edi,eax
        std
        mov     ecx,eax
        mov     al,'\'
        repne   scasb
        mov     byte [edi+2],0
        cld
        ; Теперь в my_path содержится каталог запуска с завершающим '\'

Два варианта нахождения модуля числа, записанного в EAX:
        ; Вариант №1
@@:     neg     eax
        js      @b
        ; EAX = |EAX|
;------------------------------------
        ; Вариант №2
        mov     edx, eax
        sar     edx, 31
        xor     eax, edx
        sub     eax, edx
        ; EAX = |EAX|

Как поменять местами значения двух регистров без использования дополнительных переменных и других регистров.
        ; Идеальный вариант с использованием штатной команды XCHG
        xchg    eax,ebx
 
        ; Классический вариант с использованием стека
        push    eax
        push    ebx
        pop     eax
        pop     ebx
 
        ; Хитрый вариант, иногда встречается на олимпиадах по программированию
        xor     eax,ebx 
        xor     ebx,eax 
        xor     eax,ebx 

Поиск наибольшего общего делителя (НОД) двух натуральных чисел. Если помните из курса школьной математики, то наибольший общий делитель - это наибольшее из чисел, на которые делится каждое из данных чисел без остатка. Очень красивый и высокооптимизированный алгоритм.
        ; EAX = первое число
        ; EDX = второе число
 
        neg     eax
        jz      loc_3
loc_1:
        neg     eax
        xchg    edx,eax
loc_2:
        sub     eax,edx
        jg      loc_2
        jnz     loc_1
loc_3:
        add     eax,edx
        jnz     loc_4
        inc     eax
loc_4:
        ; EAX = найденное значение НОД

Поиск наименьшего общего кратного (НОК) двух натуральных чисел. Наименьшим общим кратным называется наименьшее натуральное число, которое само делится на каждое из данных чисел без остатка.
        ; EAX = первое число
        ; EBX = второе число
 
        cmp     eax,ebx
        je      loc_3
        jb      loc_1
 
        xchg    eax,ebx
loc_1:
        mov     ecx,eax
loc_2:
        add     eax,ecx
        push    eax
        xor     edx,edx
        div     ebx
        pop     eax
        or      edx,edx
        jnz     loc_2
loc_3:
        ; EAX = найденное значение НОК

Продолжение следует, так что следите за обновлениями. Если у вас тоже есть какие-нибудь свои наработки, то можете поделиться ими в комментариях, самые интересные примеры я обязательно добавлю в статью.
Теги:
функции
Добавлено: 08 Апреля 2018 06:36:46 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...