Blog. Just Blog

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

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Несколько полезных функций на Ассемблере
Несколько полезных функций на Ассемблере

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

Начну с функции очень быстрого копирования одного участка памяти в другой. Фишка в том, что основная часть строки копируется по 4 байта DWORD'ами, а остаток дописывается побайтно. Это дает нехилый выигрыш в скорости.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого копирования участка памяти
  3. ;-----------------------------------------------------
  4. ; lpDst - указатель на приемник
  5. ; lpSrc - указатель на источник
  6. ; dSize - размер копируемого блока
  7. ;-----------------------------------------------------
  8. proc    _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
  9.         pusha
  10.  
  11.         ; Установить указатели на источник и приемник
  12.         cld
  13.         mov     edi,[lpDst]
  14.         mov     esi,[lpSrc]
  15.  
  16.         mov     ecx,[dSize]
  17.         push    ecx
  18.         ; Разделить на 4 и получить длину в DWORD
  19.         shr     ecx,2
  20.         ; Скопировать основную часть строки DWORD'ами
  21.         rep     movsd
  22.         pop     ecx
  23.         ; Получить остаток от деления на 4
  24.         and     ecx,3
  25.         ; Скопировать остаток строки байтами
  26.         rep     movsb
  27.  
  28.         popa
  29.         ret
  30. endp
Функция копирует любые данные, в том числе и нулевые байты, поэтому для нее обязательно указывать размер копируемого блока.

Еще один вариант копирования. Он более красивый по реализации, но менее эффективный по производительности за счет использования переходов и, соответственно, невозможности использования конвейера команд процессора.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого копирования участка памяти
  3. ;-----------------------------------------------------
  4. ; lpDst - указатель на приемник
  5. ; lpSrc - указатель на источник
  6. ; dSize - размер копируемого блока
  7. ;-----------------------------------------------------
  8. proc    _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
  9.         pusha
  10.  
  11.         ; Установить указатели на источник и приемник
  12.         cld
  13.         mov     edi,[lpDst]
  14.         mov     esi,[lpSrc]
  15.         mov     ecx,[dSize]
  16.  
  17.         ; Если есть остаток от деления на 2
  18.         shr     ecx,1
  19.         jnc     @f
  20.         ; Скопировать один байт
  21.         movsb
  22. @@:
  23.         ; Если есть остаток от деления на 4
  24.         shr     ecx,1
  25.         jnc     @f
  26.         ; Скопировать слово
  27.         movsw
  28. @@:
  29.         ; Остаток строки скопировать двойными словами
  30.         rep     movsd
  31.  
  32.         popa
  33.         ret
  34. endp
Следующие несколько функций написаны для замены стандартных WinAPI-функций для работы со строками. Во-первых, они оптимизированы, поэтому выполняются быстрее штатных аналогов, а во-вторых нет нужды прописывать их в секцию импорта. Например, если ваша динамическая библиотека в своей работе использует лишь несколько функций работы со строками, то при замене их на приведенные здесь аналоги, секция импорта вообще будет не нужна. Функции полностью самодостаточны, не требуют никаких дополнительных данных. Но обратите внимание, что некоторые из них взаимосвязаны, так что если нужна только какая-нибудь одна, то в нее можно перенести код из вспомогательных функций для сокращения места.

Аналог функции lstrlen - получение длины строки. На входе адрес строки в формате ASCIIZ, на выходе EAX - длина строки.
  1. ;-----------------------------------------------------
  2. ; Функция получения длины строки
  3. ;-----------------------------------------------------
  4. ; lpStr - указатель на строку ASCIIZ
  5. ; На выходе: EAX - длина строки без учета завершающего
  6. ; нулевого байта
  7. ;-----------------------------------------------------
  8. proc    _lstrlen lpStr:DWORD
  9.         push    edi ecx
  10.  
  11.         cld
  12.         mov     edi,[lpStr]
  13.         xor     ecx,ecx
  14.         dec     ecx
  15.         xor     eax,eax
  16.         repne   scasb
  17.         not     ecx
  18.         dec     ecx
  19.         mov     eax,ecx
  20.  
  21.         pop     ecx edi
  22.         ret
  23. endp
Еще одна функция получения длины строки. По размеру получается немного больше, но по скорости работы почти в два раза превосходит как стандартную функцию, так и предыдущую. Выбирайте сами: размер получаемого кода или производительность.
  1. ;-----------------------------------------------------
  2. ; Функция получения длины строки (Fast)
  3. ;-----------------------------------------------------
  4. ; lpStr - указатель на строку ASCIIZ
  5. ; На выходе: EAX - длина строки без учета завершающего
  6. ; нулевого байта
  7. ;-----------------------------------------------------
  8. proc    _lstrlen lpStr:DWORD
  9.         mov     eax, [lpStr]
  10.         sub     eax, 4
  11. @@:
  12.         add     eax, 4
  13.         cmp     byte [eax], 0
  14.         je      .szlen_lb1
  15.         cmp     byte [eax+1], 0
  16.         je      .szlen_lb2
  17.         cmp     byte [eax+2], 0
  18.         je      .szlen_lb3
  19.         cmp     byte [eax+3], 0
  20.         jne     @b
  21.         sub     eax, [lpStr]
  22.         add     eax, 3
  23.         ret
  24. .szlen_lb3:
  25.         sub     eax, [lpStr]
  26.         add     eax, 2
  27.         ret
  28. .szlen_lb2:
  29.         sub     eax, [lpStr]
  30.         add     eax, 1
  31.         ret
  32. .szlen_lb1:
  33.         sub     eax, [lpStr]
  34.         ret
  35. endp
Еще один самый простейший и один из самых компактных вариантов получения длины строки. Строки перебирается по индексу с начала и до первого встретившегося нулевого символа.
  1. ;-----------------------------------------------------
  2. ; Функция получения длины строки
  3. ;-----------------------------------------------------
  4. ; lpStr - указатель на строку ASCIIZ
  5. ; На выходе: EAX - длина строки без учета завершающего
  6. ; нулевого байта
  7. ;-----------------------------------------------------
  8. proc    _lstrlen lpStr:DWORD
  9.         push    ebx
  10.         mov     ebx,[lpStr]
  11.         xor     eax,eax
  12. @@:
  13.         cmp     byte[ebx+eax],0
  14.         je      @f
  15.         inc     eax
  16.         jmp     @b
  17. @@:
  18.         pop     ebx
  19.         ret
  20. endp
Ну и в завершении самые красивые и компактные варианты получения длины строки.
  1. ;-----------------------------------------------------
  2. ; lpStr - указатель на строку ASCIIZ
  3. ; На выходе: EAX - длина строки без учета завершающего
  4. ; нулевого байта
  5. ;-----------------------------------------------------
  6. proc    _lstrlen lpStr:DWORD
  7.         mov     eax, [lpStr]
  8.         dec     eax
  9. @@:
  10.         inc     eax
  11.         cmp     byte [eax],0
  12.         jne     @b
  13.         sub     eax, [lpStr]
  14.         ret
  15. endp
  1. ;-----------------------------------------------------
  2. ; lpStr - указатель на строку ASCIIZ
  3. ; На выходе: EAX - длина строки без учета завершающего
  4. ; нулевого байта
  5. ;-----------------------------------------------------
  6. proc    _lstrlen lpStr:DWORD
  7.         mov     eax, [lpStr]
  8. @@:
  9.         cmp     byte [eax],1
  10.         inc     eax
  11.         jnc     @b
  12.         sbb     eax, [lpStr]
  13.         ret
  14. endp
Аналог функции lstrcpy - копирование строки в формате ASCIIZ. На входе адреса приемника и источника. Функция ничего не возвращает. Используются описанные выше функции _lstrlen и _memcopy.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого копирования строки
  3. ; используются функции _lstrlen, _memcopy
  4. ;-----------------------------------------------------
  5. ; lpDst - указатель на приемник
  6. ; lpSrc - указатель на строку ASCIIZ
  7. ;-----------------------------------------------------
  8. proc    _lstrcpy lpDst:DWORD, lpSrc:DWORD
  9.         pusha
  10.  
  11.         stdcall _lstrlen,[lpSrc]
  12.         inc     eax
  13.         stdcall _memcopy,[lpDst],[lpSrc],eax
  14.  
  15.         popa
  16.         ret
  17. endp
Аналог функции lstrcat - конкатенация (слияние) двух строк в формате ASCIIZ. Параметры: адрес основной строки и адрес второй строки, которая будет дописана к основной. Функция ничего не возвращает. Используются описанные выше функции _lstrlen и _lstrcpy.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого слияния двух строк
  3. ; используются функции _lstrlen, _lstrcpy
  4. ;-----------------------------------------------------
  5. ; lpDst - указатель на исходную строку ASCIIZ
  6. ; lpSrc - указатель на добавляемую строку ASCIIZ
  7. ;-----------------------------------------------------
  8. proc    _lstrcat lpDst:DWORD, lpSrc:DWORD
  9.         pusha
  10.  
  11.         stdcall _lstrlen,[lpDst]
  12.         add     eax,[lpDst]
  13.  
  14.         stdcall _lstrcpy,eax,[lpSrc]
  15.  
  16.         popa
  17.         ret
  18. endp
Аналог функции lstrcmp - сравнение двух строк. На входе адреса двух строк в формате ASCIIZ, на выходе EAX=0 - строки равны, EAX=1 - строки различаются по длине и/или содержимому. Используется описанная выше функция _lstrlen.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого сравнения двух строк
  3. ; используется функция _lstrlen
  4. ;-----------------------------------------------------
  5. ; lpStr1 - указатель на первую строку ASCIIZ
  6. ; lpStr2 - указатель на вторую строку ASCIIZ
  7. ; На выходе: EAX=0 - строки совпадают
  8. ;            EAX=1 - строки различаются
  9. ;-----------------------------------------------------
  10. proc    _lstrcmp lpStr1:DWORD, lpStr2:DWORD
  11.         push    ecx esi edi
  12.  
  13.         mov     esi,[lpStr1]
  14.         mov     edi,[lpStr2]
  15.  
  16.         stdcall _lstrlen,esi
  17.         mov     ecx,eax
  18.         stdcall _lstrlen,edi
  19.         cmp     ecx,eax
  20.  
  21.         ; Длина строк не совпадает
  22.         jnz     @f
  23.  
  24.         cld
  25.         repe    cmpsb
  26. @@:
  27.         setnz   al
  28.         movsx   eax,al
  29.  
  30.         pop     edi esi ecx
  31.         ret
  32. endp
Еще одна быстрая функция сравнения двух строк. Входные и выходные параметры аналогичны предыдущей функции, никакие дополнительные функции не используются.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого сравнения двух строк
  3. ;-----------------------------------------------------
  4. ; lpStr1 - указатель на первую строку ASCIIZ
  5. ; lpStr2 - указатель на вторую строку ASCIIZ
  6. ; На выходе: EAX=0 - строки совпадают
  7. ;            EAX=1 - строки различаются
  8. ;-----------------------------------------------------
  9. proc    _lstrcmp lpStr1:DWORD,lpStr2:DWORD
  10.         push    esi edi
  11.  
  12.         mov     esi,[lpStr1]    ; Указатели на строки
  13.         mov     edi,[lpStr2]
  14.         xor     eax,eax         ; Предположим, что строки равны
  15. @@:
  16.         lodsb                   ; Сравнить следующие символы
  17.         mov     ah,[edi]
  18.         inc     edi
  19.  
  20.         or      al,al           ; Первая строка закончилась?
  21.         jz      @f              ; Да
  22.         cmp     al,ah           ; Символы совпадают?
  23.         je      @b              ; Да, проверить следующий символ
  24. @@:
  25.         or      ah,ah           ; Вторая строка закончилась?
  26.         je      @f              ; Да, строки равны
  27.  
  28.         xor     eax,eax         ; Строки не совпадают
  29.         inc     eax
  30. @@:
  31.         pop     edi esi
  32.         ret
  33. endp
Функция быстрого регистронезависимого (только для букв английского алфавита) сравнения строк, аналог функции API lstrcmpi. На входе адреса двух строк в формате ASCIIZ, на выходе EAX=0 - строки равны, EAX=1 - строки различаются по длине и/или содержимому. Никакие дополнительные функции не используются.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого регистронезависимого сравнения
  3. ; двух строк
  4. ;-----------------------------------------------------
  5. ; lpStr1 - указатель на первую строку ASCIIZ
  6. ; lpStr2 - указатель на вторую строку ASCIIZ
  7. ; На выходе: EAX=0 - строки совпадают
  8. ;            EAX=1 - строки различаются
  9. ;-----------------------------------------------------
  10. proc    _lstrcmpi lpStr1:DWORD,lpStr2:DWORD
  11.         push    esi edi
  12.  
  13.         mov     esi,[lpStr1]    ; Указатели на строки
  14.         mov     edi,[lpStr2]
  15.         xor     eax,eax         ; Предположим, что строки равны
  16. .loc_compare:
  17.         lodsb                   ; Сравнить следующие символы
  18.         mov     ah,[edi]
  19.         inc     edi
  20.  
  21.         cmp     al,ah           ; Символы совпадают?
  22.         jne     @f              ; Нет
  23.  
  24.         or      al,al           ; Строки закончились?
  25.         jz      .loc_ret        ; Да, строки совпадают
  26.         jmp     .loc_compare    ; Проверить следующий символ
  27. @@:
  28.         and     eax,0DFDFh      ; Оба символа в верхний регистр
  29.         cmp     al,'A'
  30.         jb      .loc_not_equal
  31.         cmp     al,'Z'
  32.         ja      .loc_not_equal
  33.  
  34.         cmp     al,ah           ; Разница была только в регистре?
  35.         je      .loc_compare    ; Да, следующий символ
  36. .loc_not_equal:
  37.         xor     eax,eax         ; Строки не совпадают
  38.         inc     eax    
  39. .loc_ret:
  40.         pop     edi esi
  41.         ret
  42. endp
Функция быстрого заполнения фрагмента памяти указанными значениями (байтами). Может применяться для очистки памяти.
  1. ;-----------------------------------------------------
  2. ; Функция быстрого заполнения блока памяти значениями
  3. ;-----------------------------------------------------
  4. ; lpDst - указатель на приемник
  5. ; dVal  - заполнитель (берется младший байт)
  6. ; dSize - размер заполняемого блока
  7. ;-----------------------------------------------------
  8. proc    _fillmem lpDst:DWORD, dVal:DWORD, dSize:DWORD
  9.         pusha
  10.  
  11.         cld
  12.         mov     edi,[lpDst]
  13.         mov     eax,[dVal]
  14.         ; Оставить только младший байт
  15.         movzx   eax,al
  16.         mov     edx,01010101h
  17.         ; Теперь в EAX будет DWORD-заполнитель
  18.         mul     edx
  19.  
  20.         mov     ecx,[dSize]
  21.         push    ecx
  22.         ; Разделить на 4 и получить длину в DWORD
  23.         shr     ecx,2
  24.         ; Заполнить основную часть строки DWORD'ами
  25.         rep     stosd
  26.         pop     ecx
  27.         ; Получить остаток от деления на 4
  28.         and     ecx,3
  29.         ; Заполнить остаток строки байтами
  30.         rep     stosb
  31.  
  32.         popa
  33.         ret
  34. endp
Преобразование строки в нижний регистр. Преобразуются только символы латинского алфавита.
  1. ;----------------------------------------------------
  2. ; Перевести строку ASCIIZ в нижний регистр
  3. ;----------------------------------------------------
  4. ; szStr - указатель на строку
  5. ;----------------------------------------------------
  6. proc lower_case szStr:DWORD
  7.         pusha
  8.  
  9.         mov     esi,[szStr]
  10.         mov     edi,esi
  11.         cld
  12. .loc_loop:
  13.         lodsb
  14.         cmp     al,'A'
  15.         jb      @f
  16.         cmp     al,'Z'
  17.         ja      @f
  18.         or      al,060h
  19. @@:
  20.         stosb
  21.         or      al,al
  22.         jnz     .loc_loop
  23.  
  24.         popa
  25.         ret
  26. endp
Преобразование строки в верхний регистр. Преобразуются только символы латинского алфавита.
  1. ;----------------------------------------------------
  2. ; Перевести строку ASCIIZ в верхний регистр
  3. ;----------------------------------------------------
  4. ; szStr - указатель на строку
  5. ;----------------------------------------------------
  6. proc upper_case szStr:DWORD
  7.         pusha
  8.  
  9.         mov     esi,[szStr]
  10.         mov     edi,esi
  11.         cld
  12. .loc_loop:
  13.         lodsb
  14.         cmp     al,'a'
  15.         jb      @f
  16.         cmp     al,'z'
  17.         ja      @f
  18.         and     al,0DFh
  19. @@:
  20.         stosb
  21.         or      al,al
  22.         jnz     .loc_loop
  23.  
  24.         popa
  25.         ret
  26. endp
Еще парочка сниппетов для приведения символов английского алфавита к верхнему или нижнему регистру, остальные символы не меняются.
  1.         ; Символ из AL в заглавную букву
  2.         mov    dl,al
  3.         sub    dl,'a'
  4.         cmp    dl,26
  5.         sbb    dh,dh
  6.         and    dh,20h
  7.         sub    al,dh
  8.  
  9.         ; Символ из AL в строчную букву
  10.         mov    dl,al
  11.         sub    dl,'A'
  12.         cmp    dl,26
  13.         sbb    dh,dh
  14.         and    dh,20h
  15.         add    al,dh
Очистка строки от символов, кроме заданного диапазона. Например, если надо оставить в строке только цифры. Поменяв начальный символ и длину интервала, можно будет фильтровать по другим правилам.
  1. ;----------------------------------------------------
  2. ; Очистить строку ASCIIZ от всех символов кроме цифр
  3. ;----------------------------------------------------
  4. ; szString - указатель на исходную строку
  5. ; szFiltered - указатель на отфильтрованную строку
  6. ;----------------------------------------------------
  7. proc filter_digits szString:DWORD, szFiltered:DWORD
  8.         pusha
  9.         mov     esi,[szString]   ; Исходная строка
  10.         mov     edi,[szFiltered] ; Отфильтрованная строка
  11. @@:
  12.         lodsb
  13.         or      al,al
  14.         jz      @f
  15.         mov     bl,al
  16.         add     bl,(256-'0')   ; Начальный символ интервала
  17.         sub     bl,10          ; Длина интервала
  18.         jnb     @b
  19.         stosb
  20.         jmp     @b
  21. @@:
  22.         stosb
  23.         popa
  24.         ret
  25. endp
Получение каталога запуска программы. В отличие от GetCurrentDirectory получает именно тот каталог, в котором находится запущенный исполняемый файл. Этот код удобно применять для формирования полных путей к файлам конфигурации, каким-либо дополнительным ресурсам, хранящимся в папке с программой и т.п. Можно, конечно, использовать GetFullPathName, но так получается быстрее и короче.
  1.         ; my_path описан в данных как буфер размером MAX_PATH+1
  2.         mov     edi,my_path
  3.         invoke  GetModuleFileName,NULL,edi,MAX_PATH
  4.         add     edi,eax
  5.         std
  6.         mov     ecx,eax
  7.         mov     al,'\'
  8.         repne   scasb
  9.         mov     byte [edi+2],0
  10.         cld
  11.         ; Теперь в my_path содержится каталог запуска с завершающим '\'
Два варианта нахождения модуля числа, записанного в EAX:
  1.         ; Вариант №1
  2. @@:     neg     eax
  3.         js      @b
  4.         ; EAX = |EAX|
  5. ;------------------------------------
  6.         ; Вариант №2
  7.         mov     edx, eax
  8.         sar     edx, 31
  9.         xor     eax, edx
  10.         sub     eax, edx
  11.         ; EAX = |EAX|
Нахождение минимального из двух значений. Операции сравнения и условные переходы не используются.
  1.         ; EAX = min(EAX,EBX)
  2.         sub     ebx, eax
  3.         sbb     ecx, ecx
  4.         and     ecx, ebx
  5.         add     eax, ecx
Присвоение нужного значения, если регистр равен нулю и если не равен нулю. Условные переходы и операции сравнения также не используются.
  1.         ; Если EAX!=0, то EAX=EBX, иначе EAX=ECX
  2.         cmp     eax, 1
  3.         sbb     eax, eax
  4.         and     ecx, eax
  5.         xor     eax, -1
  6.         and     eax, ebx
  7.         or      eax, ecx
Как поменять местами значения двух регистров без использования дополнительных переменных и других регистров.
  1.         ; Идеальный вариант с использованием штатной команды XCHG
  2.         xchg    eax,ebx
  3.  
  4.         ; Классический вариант с использованием стека
  5.         push    eax
  6.         push    ebx
  7.         pop     eax
  8.         pop     ebx
  9.  
  10.         ; Хитрый вариант, иногда встречается на олимпиадах по программированию
  11.         xor     eax,ebx 
  12.         xor     ebx,eax 
  13.         xor     eax,ebx
  14.  
  15.         ; Еще один хитрый вариант обмена с вычитанием и сложением
  16.         sub     eax,ebx
  17.         add     ebx,eax
  18.         neg     eax
  19.         add     eax,ebx
Два варианта решения задачи по обмену значения регистров, только если выполняется условие сравнения.
  1.         ; Первый вариант
  2.         cmp     ebx,ecx
  3.         sbb     eax,eax  ; ebx<ecx ? -1 : 0
  4.         xor     ebx,ecx
  5.         and     eax,ebx  ; ebx<ecx ? ebx^ecx : 0
  6.         xor     ecx,eax  ; ebx<ecx ? ebx : ecx
  7.         xor     ebx,ecx  ; ebx<ecx ? ecx : ebx
  8.  
  9.         ; Второй вариант
  10.         mov     edx,[y]
  11.         sub     edx,[x]
  12.         sbb     ecx,ecx 
  13.         and     ecx,edx 
  14.         add     [x],ecx ; x=min(x,y) 
  15.         sub     [y],ecx ; y=max(x,y)
Поиск наибольшего общего делителя (НОД) двух натуральных чисел. Если помните из курса школьной математики, то наибольший общий делитель - это наибольшее из чисел, на которые делится каждое из данных чисел без остатка. Очень красивый и высокооптимизированный алгоритм.
  1.         ; EAX = первое число
  2.         ; EDX = второе число
  3.  
  4.         neg     eax
  5.         jz      loc_3
  6. loc_1:
  7.         neg     eax
  8.         xchg    edx,eax
  9. loc_2:
  10.         sub     eax,edx
  11.         jg      loc_2
  12.         jnz     loc_1
  13. loc_3:
  14.         add     eax,edx
  15.         jnz     loc_4
  16.         inc     eax
  17. loc_4:
  18.         ; EAX = найденное значение НОД
Поиск наименьшего общего кратного (НОК) двух натуральных чисел. Наименьшим общим кратным называется наименьшее натуральное число, которое само делится на каждое из данных чисел без остатка.
  1.         ; EAX = первое число
  2.         ; EBX = второе число
  3.  
  4.         cmp     eax,ebx
  5.         je      loc_3
  6.         jb      loc_1
  7.  
  8.         xchg    eax,ebx
  9. loc_1:
  10.         mov     ecx,eax
  11. loc_2:
  12.         add     eax,ecx
  13.         push    eax
  14.         xor     edx,edx
  15.         div     ebx
  16.         pop     eax
  17.         or      edx,edx
  18.         jnz     loc_2
  19. loc_3:
  20.         ; EAX = найденное значение НОК
Преобразование байта в строку. Для этого есть несколько функций, разница только в скорости их выполнения и в компактности. В зависимости от задачи можно выбрать ту, которая вам больше подходит. Самая большая скорость выполнения достигается при использовании следующей функции, но она же самая большая по размеру получаемого кода:
  1. ; value = байт для преобразования
  2. ; result = буфер для получения результата
  3. proc byte2hex value:DWORD, result:DWORD
  4.         pusha
  5.         mov     eax,[value]
  6.         mov     ecx,eax
  7.         shr     eax,4
  8.         and     ecx,15
  9.         mov     al,[.table+eax]
  10.         mov     ah,[.table+ecx]
  11.         mov     edx,[result]
  12.         mov     word [edx],ax
  13.         popa
  14.         ret
  15.  
  16. .table   db '0123456789ABCDEF'
  17.  
  18. endp
Второй вариант, более компактный по коду, но при этом самый медленный в работе.
  1. ; value = байт для преобразования
  2. ; result = буфер для получения результата
  3. proc byte2hex value:DWORD, result:DWORD
  4.         pusha
  5.         mov     eax,[value]
  6.         mov     ebx,.table
  7.         mov     ah,al
  8.         and     al,00001111b
  9.         xlat    byte [ebx]
  10.         xchg    al,ah
  11.         shr     al,4
  12.         xlat    byte [ebx]
  13.         mov     edi,[result]
  14.         stosw
  15.         popa
  16.         ret
  17.  
  18. .table  db '0123456789ABCDEF'
  19.  
  20. endp
И, наконец, самый компактный вариант без использования таблицы, а по скорости выполнения находящийся где-то между первыми двумя функциями.
  1. ; value = байт для преобразования
  2. ; result = буфер для получения результата
  3. proc byte2hex value:DWORD, result:DWORD
  4.         pusha
  5.         mov     eax,[value]
  6.         mov     ah,al
  7.         and     al,15
  8.         cmp     al,10
  9.         sbb     al,69h
  10.         das
  11.         xchg    al,ah
  12.         shr     al,4
  13.         cmp     al,10
  14.         sbb     al,69h
  15.         das
  16.         mov     edi,[result]
  17.         stosw
  18.         popa
  19.         ret
  20. endp
Очень компактная функция перевода двойного слова из регистра EAX в шестнадцатеричную строку. Может использоваться вместо wsprintf для экономии места.
  1.         ; EAX = конвертируемое значение
  2.         ; EDI = указатель на строку-результат
  3.         ; Флаг направления сброшен (CLD)
  4.  
  5.         mov     ecx,8
  6. @@:
  7.         rol     eax,4
  8.         push    eax
  9.         and     al,0Fh
  10.         daa
  11.         add     al,0F0h
  12.         adc     al,40h
  13.         stosb
  14.         pop     eax
  15.         loop    @b
  16.         mov     al,0
  17.         stosb
Универсальная функция для вывода числа в различных системах счисления с основанием от 2 до 10. Чтобы вывести число в двоичной системе, замените в коде число 10 на 2, а чтобы вывести число в восьмеричной системе, замените 10 на 8, ну и так далее по аналогии. Для удобства основание системы счисления можно вынести в параметр функции.
  1. ; EAX = конвертируемое значение
  2. ; EDI = указатель на строку-результат
  3. ; Флаг направления сброшен (CLD)
  4. proc num_to_str
  5.         call    @f
  6.         mov     al,0
  7.         stosb
  8.         ret
  9. @@:
  10.         xor     edx,edx
  11.         ; Основание системы счисления
  12.         mov     ebx,10
  13.         div     ebx
  14.         push    edx
  15.         or      eax,eax
  16.         jz      @f
  17.         call    @b
  18. @@:
  19.         pop     eax
  20.         add     al,'0'
  21.         stosb
  22.         retn
  23. endp
Быстрое возведение в степень при помощи команд математического сопроцессора (FPU). Используется математическое утверждение, что для равенства x=a^b справедливо x=exp(b*ln(a)).
  1. ; В сегменте данных
  2. power   dd  9
  3. x       dd  3
  4. result  dd  ?
  5.  
  6. ; В коде (для примера считаем 3^9)
  7.         finit
  8.         fild    [power]
  9.         fild    [x]
  10.         fyl2x
  11.         fld1
  12.         fld     st1
  13.         fprem
  14.         f2xm1
  15.         faddp
  16.         fscale
  17.         fxch    st1
  18.         fstp    st0
  19.         fistp   [result]
  20.         ; result = 3^9
Целочисленное деление с округлением результата. Используется формула (A+A+B)/(B+B):
  1.         ; Делимое A
  2.         mov     eax,8
  3.         ; Делитель B
  4.         mov     ecx,9
  5.  
  6.         ; (A+A+B)/(B+B)
  7.         add     eax,eax
  8.         add     eax,ecx
  9.         add     ecx,ecx
  10.         xor     edx,edx
  11.         div     ecx
  12.         ; EAX - округленное частное
Компактная проверка на одновременное вхождение двух значений в диапазоны с единственным условным переходом. Такие решения надо отливать золотом в граните :)
  1.         ; ECX = X, должно быть в диапазоне [X_MIN..X_MAX]
  2.         ; EDX = Y, должно быть в диапазоне [Y_MIN..Y_MAX]
  3.         sub    ecx,X_MIN
  4.         cmp    ecx,X_MAX-X_MIN
  5.         sbb    al,al
  6.         sub    edx,Y_MIN
  7.         cmp    edx,Y_MAX-Y_MIN
  8.         adc    al,0
  9.         ; Как минимум одно значение не входит в диапазон
  10.         jae    not_in_ranges
Немного битовой магии. Реверс байтов и битов числа для разных разрядностей. В некоторых случаях задействуется дополнительный регистр.
  1.         ; Реверс битов (8 битов)
  2.         mov     eax,12h
  3.         mov     ah,al
  4.         shr     al,1
  5.         and     al,0x55
  6.         shl     ah,1
  7.         and     ah,0xAA
  8.         or      al,ah
  9.         mov     ah,al
  10.         shr     al,2
  11.         and     al,0x33
  12.         shl     ah,2
  13.         and     ah,0xCC
  14.         or      al,ah
  15.         ; AL = 84h
  16.  
  17.         ; Реверс битов (16 битов)
  18.         mov     eax,1234h
  19.         mov     bx,ax
  20.         shr     ax,1
  21.         and     ax,0x5555
  22.         shl     bx,1
  23.         and     bx,0xAAAA
  24.         or      ax,bx
  25.         mov     bx,ax
  26.         shr     ax,2
  27.         and     ax,0x3333
  28.         shl     bx,2
  29.         and     bx,0xCCCC
  30.         or      ax,bx
  31.         ; AX = 84C2h
  32.  
  33.         ; Реверс битов (32 бита)
  34.         mov     eax,12345678h
  35.         mov     ebx,eax
  36.         shr     eax,1
  37.         and     eax,0x55555555
  38.         shl     ebx,1
  39.         and     ebx,0xAAAAAAAA
  40.         or      eax,ebx
  41.         mov     ebx,eax
  42.         shr     eax,2
  43.         and     eax,0x33333333
  44.         shl     ebx,2
  45.         and     ebx,0xCCCCCCCC
  46.         or      eax,ebx
  47.         ; EAX = 84C2A6E1h
  48.  
  49.         ; Реверс байтов (16 битов)
  50.         mov     eax,1234h
  51.         mov     bx,ax
  52.         shr     ax,8
  53.         and     ax,0x00FF
  54.         shl     bx,8
  55.         and     bx,0xFF00
  56.         or      ax,bx
  57.         ; EAX = 3412h
  58.  
  59.         ; Реверс байтов (32 бита)
  60.         mov     eax,12345678h
  61.         mov     ebx,eax
  62.         shr     eax,8
  63.         and     eax,0x00FF00FF
  64.         shl     ebx,8
  65.         and     ebx,0xFF00FF00
  66.         or      eax,ebx
  67.         mov     ebx,eax
  68.         shr     eax,16
  69.         and     eax,0x0000FFFF
  70.         shl     ebx,16
  71.         and     ebx,0xFFFF0000
  72.         or      eax,ebx
  73.         ; EAX = 78563412h
Продолжение следует, так что следите за обновлениями. Если у вас тоже есть какие-нибудь свои наработки, то можете поделиться ими в комментариях, самые интересные примеры я обязательно добавлю в статью.

Поделиться ссылкой ВКонтакте
Просмотров: 28378 | Комментариев: 34

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Loloshka (28.05.2021 в 17:58):
Спасибо!
ManHunter (28.05.2021 в 07:47):
Да практически все то же самое, только вместо байтовых операций будут операции с WORD'ами.
Loloshka (28.05.2021 в 01:45):
А как лучше будет реализовать для юникодных строк подсчет количества символов?
ManHunter (10.11.2020 в 16:01):
Два последних.
Petya (10.11.2020 в 15:43):
А какой из _lstrlen самый быстрый? 2й?
ManHunter (10.07.2018 в 12:47):
Добавлены функции преобразования байта в hex-строку.
ManHunter (12.06.2018 в 12:44):
IOAN, садись, двойка. Избыточный код.
IOAN (10.06.2018 в 10:07):
proc _lstrlen lpStr:DWORD
        mov     edx, [lpStr]
        xor     ecx,ecx
        xor     eax,eax
        dec     eax
@@: 
        inc     eax
        cmp     [edx+eax],cl
        jne     @b
        ret
endp
ManHunter (23.03.2018 в 11:03):
Ну и?
Константин (23.03.2018 в 10:56):
Привет. Сразу к делу. Есть калькулятор на vb2008. Умножает до 32000000 знаков, после запятой до 10000 знаков. На умножение 1000000 на 1000000 знаков уходит 28 секунд. Проблем в том, что массивы размерностью >22000000 не проходят. Умножение разбито на потоки. Стоит ещё вопрос алгоритма деления
длинных чисел на длинные. Алгоритм деления на числа до 18 знаков работает быстро, а выше есть задумки , на писать на ассемблере.
ManHunter (30.06.2016 в 18:28):
Добавил функции перевода строки в верхний и нижний регистр.
ManHunter (03.12.2013 в 23:03):
Главное, чтобы не в shareware. А в остальном можно как угодно.
Heavyiron (03.12.2013 в 22:56):
Благодарю за добавление в статью, только тег [code] потерялся. Кстати, забыл уточнить про лицензию(в глаза по крайней мере нигде не бросилось): можно ли использовать наработки из этого раздела в GPL-ном проекте?
Heavyiron (01.12.2013 в 19:12):
Коллега по проекту 0CodErr выдал код еще на 3 байта короче и чуть быстрее (из за отсутствия необходимости засовывать регистры в стек):
proc _lstrlen lpStr:DWORD
        mov     eax, [lpStr]
        dec     eax
@@: 
        inc     eax
        cmp     [eax], byte 0
        jne     @b
        sub     eax, [lpStr]
        ret
endp
_http://board.kolibrios.org/viewtopic.php?p=54336#p54336
ManHunter (24.11.2013 в 11:44):
Ну вот, блин, в погоне за наворотами забыл самый простой вариант :)
Добавил, спасибо!
Heavyiron (24.11.2013 в 05:04):
Не мое. По размеру код чуть меньше (4 байта), и чуть быстрее (~6%) чем приведенный здесь вариант №1:
;-----------------------------------------------------
; Функция получения длины строки
;-----------------------------------------------------
; lpStr - указатель на строку ASCIIZ
; На выходе: EAX - длина строки без учета завершающего
; нулевого байта
;-----------------------------------------------------
proc _lstrlen lpStr:DWORD
    push    ebx
    mov    ebx,[lpStr]
    xor    eax,eax
    @@: cmp    byte[ebx+eax],0
    je    @f
    inc    eax
    jmp    @b
    @@: pop    ebx
    ret
endp
ManHunter (26.03.2012 в 20:32):
Красивее - да, быстрее - сомневаюсь. Хотя для небольших проектов очень даже ничего. Добавил в статью, спасибо!
Alexey32 (26.03.2012 в 19:50):
По-моему, так лучше:

proc    _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
        pusha

        ; Установить указатели на источник и приемник
        cld
        mov     edi,[lpDst]
        mov     esi,[lpSrc]
        mov     ecx,[dSize]

           shr ecx,1 ;проверка на чётность и деление на 2
    jnc @f
    movsb ;по необходимости копируем байт
@@:
    shr ecx,1 ;проверка на делимость на 4 и деление ещё на 2
    jnc @f
    movsw ;по необходимости копируем слово
@@:
    rep movsd ;копируем оставшиеся двойные слова

        popa
        ret
endp
ManHunter (22.08.2011 в 16:03):
Зачем городить все в одну кучу? Кому надо - добавят обработку исключений.
DJK (22.08.2011 в 16:01):
ваши идеи хорошы, но при работе с буферами памяти, размер которых не удается достоверно определить в функции, следует использовать SEH иначе могут функции повести себя совсем не так как хотелось бы.
ManHunter (10.07.2011 в 14:35):
Добавил еще один шустрый вариант сравнения строк _lstrcmp и регистронезависимое сравнение строк _lstrcmpi
ManHunter (02.06.2011 в 00:42):
Надо будет позаимствовать оттуда счетчики времени. А так - отличная работа!
disciple27 (01.06.2011 в 00:04):
ManHunter, ещё раз здравствуйте!
Дело было вечером, делать было нечего... http://ifolder.ru/23887012
disciple27 (30.05.2011 в 23:56):
ManHunter, всегда пожалуйста - правда это не моё решение, а всё из того же масма (а точнее masmlib которое, пардон за плагиат), которое я нагло оттуда позаимствовал, обожаю всё на свете портировать под FASM :)
ManHunter (30.05.2011 в 16:28):
disciple27, добавил альтернативную функцию _lstrlen из твоего примера. Спасибо!
disciple27 (29.05.2011 в 23:58):
ManHunter огромное тебе спасибо за функции, особенно понравилась 'получение каталога' оригинально :)
В долгу не останусь, так как еще понравились MASM'овые счётчики по ссылкам, я их тоже немного позаимствовал ;) http://ifolder.ru/23841188
ManHunter (20.05.2011 в 20:14):
Фигасе там народ отжигает :) А я думал что это я извращенец :)
Voffka (20.05.2011 в 19:22):
Тесты разных strLen
http://www.masm32.com/board/in...topic=1807.0
ManHunter (20.05.2011 в 15:58):
Эх, не писал ты вирусов под MS-DOS :))))
zummenix (20.05.2011 в 15:58):
Да, конечно esi. А в оптимизации я нуб :)
ManHunter (20.05.2011 в 15:42):
А разве не mov ESI,[lpStr] ? И зачем xor eax,eax, если можно сравнивать с нулем al? Флаг направления тоже желательно устанавливать. Тогда уж надо что-то типа такого:

  xor     ecx,ecx
  mov     esi,[lpStr]
  cld
@@:
  inc     ecx
  lodsb
  or      al,al
  jnz     @b
  dec     ecx

Плюс команда перехода подавляет конвейер команд процессора, теряется производительность :)
zummenix (20.05.2011 в 15:41):
Для получения длины строки мне нравится такой вот способ :)

        xor     eax,eax
        xor     ecx,ecx
        mov     edi,[lpStr]
    @@:
        inc     ecx
        lodsb
        test    eax,eax
        jne     @R
        dec     ecx
Всеволод (20.05.2011 в 12:14):
Ассемблер — прикольная штука. Все хочу сесть, поучить :)

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.15 сек. / MySQL: 2 (0.0103 сек.) / Память: 4.5 Mb
Наверх