Замена стандартных макросов fasm

Тема в разделе "WASM.PROJECTS", создана пользователем GoldFinch, 22 сен 2008.

  1. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    На мой взгляд, стандартные макросы fasm'a обладают ненужной избыточностью, плохо читаемы, практически недокументированы, слабо используют возможности препроцессора.
    Мне захотелось их переделать, в результате появилась куча файлов с различными вариантами макросов, которые немного изменяясь перемещались из файла в файл. Несколько раз были попытки засунуть их в один большой универсальный инклуд, что приводило к росту избыточной функциональности и еще большей неразберихе.
    В результате я решил запостить свои наработки на форум, отсортировав их от менее универсальных к более универсальным. В основном макросы предназначены для небольших приложений, где большой уровень вложенности не так страшен.
     
  2. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    1) Импорт функций апи
    Использование стандартных макросов:
    data import
    library kernel32,'KERNEL32.DLL', ...
    import kernel32, ExitProcess,'ExitProcess', ...
    ...
    end data

    Наиболее простая замена
    Код (Text):
    1. macro IMPORTS [dll,funclist] {
    2. common  data import
    3. forward dd 0,0,0,rva a#dll, rva v#dll
    4. common  dd 0,0,0,0,0
    5.         end data
    6. forward v#dll: irp func,funclist \{func dd rva a\#func\}
    7.         dd 0
    8. forward a#dll db `dll#".dll",0
    9.         irp func,funclist \{a\#func db 0,0,\`func,0\} }
    10. ...
    11. IMPORTS KERNEL32,<ExitProcess>, USER32,<wsprintfA,MessageBoxA>, DllName,<APIList>...
    Недостатки - нет импорта по ординалу.

    2) Вызов функций апи
    Стандартный макрос
    invoke funcname,arg1,arg2,...
    Наиболее простая замена
    Код (Text):
    1. macro CALL [line] {
    2. common match  f(x),line \{
    3.           irp arg,x \\{reverse pushd arg\\}
    4.           call [f] \} }
    5. ...
    6. CALL ExitProcess(0)
    Недостатки:
    - только импортируемые функции (косвенный вызов - call [f]),
    - только stdcall,
    - нет поддержки строковых констант f("1")

    3) Создание локальных переменных
    Код (Text):
    1. macro LOCALS [localvar] {
    2. common    frame_size=0
    3. forward define flag 0
    4.     match name[len],localvar \{ frame_size=frame_size+len
    5.                     label name byte at ebp-frame_size
    6.                     define flag 1\}
    7.     match =0,flag \{ frame_size=frame_size+4
    8.              label localvar dword at ebp-frame_size \}
    9. common    push ebp
    10.        mov ebp,esp
    11.        sub esp,(frame_size+3)and 0xFFFFFFFC }
    12. macro ENDL { mov esp,ebp
    13.             pop ebp }
    14. ....
    15. LOCALS dwX,buf[100],...
    16.   lea eax,[buf]
    17.   mov [dwX],eax
    18.   ...
    19. ENDL
    Недостатки:
    - использование регистра ebp
    - нет аналога ADDR
     
  3. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    Следующий этап развития макросов вызова функций:
    Макрос PROTO name,addr создающий макрос name, позволяющий записывать вызовы функций как name(arglist).
    Код (Text):
    1. macro PROTO name,addr { macro name [arg] \{common CALL addr arg \} }
    Cоответствующее изменение макроса IMPORTS:
    Код (Text):
    1. macro IMPORTS [dll,funclist] {
    2. common  data import
    3. forward dd 0,0,0,rva a#dll, rva v#dll
    4. common  dd 0,0,0,0,0
    5.         end data
    6. forward v#dll: irp func,funclist \{p\#func dd rva a\#func ;<<< replaces "func dd rva a\#func"
    7.                                    PROTO func,[p\#func] \}  ;<<< added
    8.         dd 0
    9. forward a#dll db `dll#".dll",0
    10.         irp func,funclist \{a\#func db 0,0,\`func,0\} }
    Преимущества
    + одинаковый вызов как импортируемых, так и не импортируемых функций
    + код полностью идентичный ЯВУ
    Недостатки - только stdcall

    Обновление макроса CALL - замена call [f] на call f, плюс поддержка строковых констант:
    Код (Text):
    1. macro CALL [line] {
    2. common match  f(x),line \{
    3.           irp arg,x \\{reverse
    4.           if arg eqtype ""
    5.              call @f
    6.              db arg,0
    7.              @@:
    8.           else
    9.               pushd arg
    10.           end if \\}
    11.           call f \} } ;<<< replaces "call [f]"
    12. ....
    13. MessageBoxA(0,"111","capt",0)
    Недостатки: нет полной поддержки локальных переменных (ADDR)
     
  4. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    О локальных переменных и соглашениях вызова.
    Я знаю только одну __cdecl функцию api - wsprintfA. Так как она использует буфер, я размещаю его в ebp-фрейме стека, соответсвенно при удаленнии фрейма (mov esp,ebp) удаляется как буфер, так и параметры wsprintfA:
    LOCALS szMsg[1024]
    lea edi,[szMsg]
    wsprintfA(edi,"1=%d",1)
    MessageBoxA(0,esi,"Warning!",0)
    ENDL
    У этого метода есть следующий недостаток - нормальные компиляторы для таких случаев регистр ebp не используют, а отслеживают изменения esp.
    Кроме того, красоту кода нарушает лишняя строчка lea edi,[szMsg], хотело бы сразу писать wsprintfA(szMsg,"1=%d",1), ну или хотябы ADDR szMsg.
     
  5. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    Кроме __stdcall и __cdecl есть еще и делфийский __fastcall. На делфи пишет немало народа, и потому иногда может возникать потребность вызывать __fastcall процедуры.
    Здесь есть следующие моменты:
    1) Аргумент может уже содержаться в нужном регистре, поэтому макрос не должен генерить "mov eax,eax"
    2) Аргумент может быть 1,2 байтовым или нулем, соотв. код отличается от "mov reg,arg"
    3) Возможен случай когда регистры надо заполнять в определенном порядке, например
    mov edx,eax/mov eax,1
    Простейший набор макросов для __fastcall вызова выглядит так:
    Код (Text):
    1. macro __fastcall [line] {
    2. common match f(x),line \{
    3.              __fastcall_args x
    4.              call f \} }
    5. macro __fastcall_args a,d,c,[p] {
    6. common match any,a \{ mov eax,a \}
    7.        match any,d \{ mov edx,d \}
    8.        match any,c \{ mov ecx,c \}
    9.        match any,p \{
    10.              forward pushd p
    11. common \} }
    12. ....
    13. PROTO MySub,MySub,__fastcall
    14. MySub(10,20,30,3,4,5)
    15. MySub1(,,2,3,4) ;для пропуска eax и edx
    К нему должен прилагаться новый макрос PROTO
    macro PROTO name,addr,__xcall {
    match ,__xcall \{macro name [arg] \\{common CALL addr arg \\} \}
    match =__fastcall,__xcall \{macro name [arg] \\{common __fastcall addr arg \\} \} }
     
  6. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    Для полной схожести с ЯВУ нужен оператор присваивания.
    В простейшем случае, только для функций, только для переменных в памяти, пишем макрос
    LET var=func()
    Код (Text):
    1. macro LET [line] {common
    2.     match var==exp,line \{ exp
    3.                            mov [var],eax \} }
    4. ...
    5. LOCALS hFile
    6. LET hFile=CreateFileA(...)
    7. ...
    Однако, если у нас процедуры вызваются без всяких лишних слов типа CALL, по аналогии, от LET надо избавляться. Возникают следующие проблемы:
    - первое слово в строке должно быть макросом
    - имхо навешивать макросы на регистры это перебор
    Пример макросов для локальных переменных:
    Код (Text):
    1. ;общий макрос присваивания
    2. macro LET var,[exp] { common exp
    3.                       mov var,eax }
    4. ;макросы LOCALS/ENDL
    5. macro LOCALS@add name,size,type {
    6.       local m
    7.       frame_size=frame_size+size
    8.       label m type at ebp-frame_size
    9.       name equ m
    10.       match any,locals@list \{ locals@list equ locals@list,name \}
    11.       match ,locals@list \{ locals@list equ name \}
    12.       macro name [line] \{ common match ==exp,line \\{ LET [name],exp \\} \}  }
    13. macro LOCALS [localvar] {
    14. common  frame_size=0
    15.         locals@list equ
    16. forward define flag 0
    17.         match name[len],localvar \{ LOCALS@add name, len, byte
    18.                                     define flag 1\}
    19.         match =0,flag \{ LOCALS@add localvar, 4, dword \}
    20. common  push ebp
    21.         mov ebp,esp
    22.         sub esp,(frame_size+3)and 0xFFFFFFFC }
    23. macro ENDL { mov esp,ebp
    24.              pop ebp
    25.              restore locals@list }
    Проблемы: для глобальных переменных нужен отдельный макрос объявления
     
  7. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    На данный момент готов набор макросов, позволяющих записывать код в следующем виде
    Код (Text):
    1. include "%fasminc%\hl.inc"
    2. PROGRAM o_O
    3. IMPORTS USER32,<MessageBoxA,wsprintfA>
    4. entry $
    5. LOCALS n,buf[1024]
    6.        lea esi,[buf]
    7.        n=wsprintfA(esi,"123")
    8.        wsprintfA(esi,"n1=%d",[n])
    9.        MessageBoxA(0,esi,"capt",0)
    10. ENDL
    11. LOCALS buf[1024],n
    12.        lea esi,[buf]
    13.        n=wsprintfA(esi,"12345")
    14.        wsprintfA(esi,"n2=%d",[n])
    15.        MessageBoxA(0,esi,"capt",0)
    16. ENDL
    17. ret
    Что необходимо сделать в ближайшем будущем:
    1) полноценная поддержка __cdecl и __fastcall
    2) поддержка адресов локальных переменных при вызове функций (наподобие ADDR localvar)
    3) кадры стека без использования ebp
    4) вложенные локальные переменные (LOCALS/.../LOCALS/.../ENDL/ENDL)
    5) доступ к аргументам функций, существующим локальным переменным, пригодится при перехвате функций
    6) контроль повторного использоваиня строковых констант. использование "...\n..." вместо "...",13,10,"...". (через load/store)

    upd: строки "format PE GUI 4.0 / section '.code' code readable executable"
    засунуты в макрос "PROGRAM [any] {...}".
     
  8. FastCall

    FastCall New Member

    Публикаций:
    0
    Регистрация:
    21 фев 2009
    Сообщения:
    1
    GoldFinch
    У меня почему-то получается только так:
    Код (Text):
    1. start:
    2.    __stdcall  ErrMsg("error code: 0x%X")
    3.    call    [ExitProcess]
    4.  
    5.         proc ErrMsg @@szErrMsg:DWORD
    6.         locals
    7.            szMsgBuf db 50 dup(?)
    8.         endl
    9.             lea edi,[szMsgBuf]
    10.             invoke wsprintfA, edi, [@@szErrMsg], eax
    11.             __stdcall [MessageBox](0, edi, "Warning!", 0)
    12.           ret
    13.         endp
    По коду возникли две непонятки:
    1. При вызове MessageBox пришлось брать в скобки []
    2. LOCALS и ENDL не смог использовать, поскольку в этом случае в процедуре два раза подряд генерится:
    Код (Text):
    1. push    ebp
    2. mov     ebp, esp
    Может кто подсказать, как можно сделать код красивее?
     
  9. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    FastCall
    проект несколько заброшен, та последняя версия которую я выкладывал имеет кучу багов и несовместимостей со стандартными заголовочными файлами.
    я написал новую версию, с меньшей функциональностью, и большей совместимостью, на базе WIN32AX.INC

    пример кода
    Код (Text):
    1. include 'win32ax.inc'  ;необходим для hll_simple.inc, должен быть перед hll_simple.inc
    2. include 'hll_simple.inc'
    3.  
    4. .data
    5.         capt db "Warning!",0
    6.  
    7. .code
    8.  
    9. PROTO ErrMsg() ;объявление пользовательской функции, вместо () можно написать что угодно, или не писать их
    10.  
    11. proc Main
    12.      mov eax,0xF00BA12
    13.      ErrMsg("error code: 0x%X")
    14.      xor eax,eax
    15.      ret
    16. endp
    17.  
    18. proc ErrMsg szErrMsg:DWORD
    19.      local szMsgBuf[50]:BYTE
    20.      wsprintfA(szMsgBuf,[szErrMsg], eax)
    21.          ;szMsgBuf - АДРЕС локальной переменной, без []
    22.          ;[szErrMsg] - ЗНАЧЕНИЕ параметра, с []
    23.      MessageBoxA(0, szMsgBuf, capt, 0)
    24.      ret
    25. endp
    26.  
    27. .end Main
    как оно работает:
    1) основной макрос - __hll_push распознает
    32-разрядные значения 111, 1.0
    64-разрядные значения double 1.0, int64 111
    строки "xxx", 'xxx'
    адреса локальных переменных и аргументов функций

    2) макрос создающий макрос имя_функции
    для пользовательских функций - макрос PROTO
    для апи - макрос импортирующий апи по спискам из include/api/*


    макросы неоптимизированы, могут компилиться долго и жрать много памяти (измените настройки компилятора)
     
  10. HeadHunter

    HeadHunter New Member

    Публикаций:
    0
    Регистрация:
    5 авг 2009
    Сообщения:
    30
    А можно гуру макросов попросить написать макрос функции, которая не использует EBP? То есть что бы вместо "mov eax, [ebp+8]" компилятор генерил "mov eax, [esp+??]" и EBP был свободен для использования.

    У меня уже вторая программа за неделю попадается написанная на MASM и использующая EBP как обычный регистр (там есть какой то хитрый макрос для этого). Переписать такую функцию на FASM быстро и просто не получается, приходится изгаляться, писать структуры, лишь бы только повысить удобочитаемость.
     
  11. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    HeadHunter
    можно но сложно. для этого надо отслеживать все изменения стека - заменить все push\pop , может даже заменить add\sub для отлова изменений esp, или применить макрос который позволит явно задать изменение esp
    вобщем это весьма и весьма сложное дело.
    само собой придется переписать стандартные макросы для процедур - proc, locals и т.п.

    возможно проще писать код такой процедуры самому, не пользуясь макросами вообще, чем писать такие макросы.
     
  12. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    опять же отказ от ebp для адресации аргументов и локальных переменных - это не слишком оптимально, т.к. инструкции xxx [esp+yyy] длиннее xxx [ebp+yyy]
     
  13. HeadHunter

    HeadHunter New Member

    Публикаций:
    0
    Регистрация:
    5 авг 2009
    Сообщения:
    30
    Не оптимально, зато не нужно разбираться в работе функции и экономится время на разработку. Вот ведь, а я думал что это пустяковое дело. Вообще странно что нет такого макроса в дистрибутиве фасма, ведь частенько 5 регистров не хватает, а использовать локальные переменные не всегда актуально в плане скорости.
     
  14. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    в плане скорости - юзай fastcall и вообще не трогай стек
     
  15. diamond

    diamond New Member

    Публикаций:
    0
    Регистрация:
    21 май 2004
    Сообщения:
    507
    Адрес:
    Russia
    На форуме фасма это неоднократно обсуждалось, есть несколько вариантов - например, http://board.flatassembler.net/topic.php?t=7764 , http://board.flatassembler.net/topic.php?t=5938 , думаю, если покопаться, можно ещё найти. Есть и проблемы: например, конструкция
    Код (Text):
    1. @@:
    2. push eax
    3. loop @b
    творит с esp вовсе не то же, что один push.
    Подробнее - поиск по форуму board.flatassembler.net.
     
  16. HeadHunter

    HeadHunter New Member

    Публикаций:
    0
    Регистрация:
    5 авг 2009
    Сообщения:
    30
    А вот еще ситуация, у меня имеются "инклуды" от ntdll и msvcrt, в ntdll дублируются некоторые функции из msvcrt, можно конечно их переименовать или вовсе удалить, но это совсем неудобно. Есть ли какой то способ указывать компилятору какую функцию из какой библиотеки мы берём?

    Что-нибудь наподобие - invoke ntdll.strcpy (это и наглядно и очень удобно)
     
  17. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    очередная версия.
    вместо того чтобы делать по макросу для каждой функций, требует писать 'hcall' в начале строки
    поддерживает 1 уровень вложенности, вызовы как апишек, так и локальных функций

    было бы неплохо заменить "hcall" на какое-нибудь более хорошее имя

    пример использования
    Код (Text):
    1. include 'win32ax.inc'
    2. include 'psyn.inc'
    3.  
    4. .code
    5.  
    6. proc main
    7.     local startupInfo:STARTUPINFO
    8.     hcall GetStartupInfo(startupInfo)
    9.     local procInfo:PROCESS_INFORMATION
    10.     hcall CreateProcess("test_target_app.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, startupInfo, procInfo)
    11.  
    12.     local scRemotePtr:DWORD
    13.     hcall [scRemotePtr] = VirtualAllocEx([procInfo.hProcess], NULL, sizeof.teststr, MEM_COMMIT, PAGE_READWRITE)
    14.     hcall WriteProcessMemory([procInfo.hProcess], [scRemotePtr], teststr, sizeof.teststr, NULL)
    15.  
    16.     hcall QueueUserAPC([OutputDebugString], [procInfo.hThread], [scRemotePtr])
    17.     hcall ResumeThread([procInfo.hThread])
    18.     ret
    19. endp
    20.  
    21. teststr db "Remote OutputDebugString call", 0
    22. sizeof.teststr = $ - teststr
    23.  
    24. .end main
     
  18. common_up

    common_up New Member

    Публикаций:
    0
    Регистрация:
    4 июл 2010
    Сообщения:
    85
    А есть какие-то наработки макросов в плане уменьшения листинга ака:
    Код (Text):
    1. ;mov [a],[b]
    2. macro movmem op1,op2
    3. {
    4. lea eax, dword op1
    5. mov eax,dword [eax]
    6. mov dword op2,eax
    7. }
    и подобное. В общем было бы интересно послушать.

    хехе:
    macro movmem [a],
    {
    push [a]
    pop
    }