Blog. Just Blog

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

Версия для печати Добавить в Избранное Отправить на E-Mail 21.05.2011 | Категория: Образ мышления: 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
Аналог функции 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
Получение каталога запуска программы. В отличие от 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|
Продолжение следует, так что следите за обновлениями. Если у вас тоже есть какие-нибудь свои наработки, то можете поделиться ими в комментариях, самые интересные примеры я обязательно добавлю в статью.

Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на Я.ру Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Поделиться ссылкой на Friendfeed Добавить в Яндекс.Закладки Добавить в закладки Memori Добавить в закладки Google
Просмотров: 2130 | Комментариев: 18

Комментарии

Отзывы посетителей сайта о статье
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-2012
При использовании материалов ссылка на сайт обязательна
Время генерации: 0,11 сек. / MySQL: 2 (0,0006 сек.) / Память: 3,5 Mb
Наверх