На мой взгляд, стандартные макросы fasm'a обладают ненужной избыточностью, плохо читаемы, практически недокументированы, слабо используют возможности препроцессора. Мне захотелось их переделать, в результате появилась куча файлов с различными вариантами макросов, которые немного изменяясь перемещались из файла в файл. Несколько раз были попытки засунуть их в один большой универсальный инклуд, что приводило к росту избыточной функциональности и еще большей неразберихе. В результате я решил запостить свои наработки на форум, отсортировав их от менее универсальных к более универсальным. В основном макросы предназначены для небольших приложений, где большой уровень вложенности не так страшен.
1) Импорт функций апи Использование стандартных макросов: data import library kernel32,'KERNEL32.DLL', ... import kernel32, ExitProcess,'ExitProcess', ... ... end data Наиболее простая замена Код (Text): macro IMPORTS [dll,funclist] { common data import forward dd 0,0,0,rva a#dll, rva v#dll common dd 0,0,0,0,0 end data forward v#dll: irp func,funclist \{func dd rva a\#func\} dd 0 forward a#dll db `dll#".dll",0 irp func,funclist \{a\#func db 0,0,\`func,0\} } ... IMPORTS KERNEL32,<ExitProcess>, USER32,<wsprintfA,MessageBoxA>, DllName,<APIList>... Недостатки - нет импорта по ординалу. 2) Вызов функций апи Стандартный макрос invoke funcname,arg1,arg2,... Наиболее простая замена Код (Text): macro CALL [line] { common match f(x),line \{ irp arg,x \\{reverse pushd arg\\} call [f] \} } ... CALL ExitProcess(0) Недостатки: - только импортируемые функции (косвенный вызов - call [f]), - только stdcall, - нет поддержки строковых констант f("1") 3) Создание локальных переменных Код (Text): macro LOCALS [localvar] { common frame_size=0 forward define flag 0 match name[len],localvar \{ frame_size=frame_size+len label name byte at ebp-frame_size define flag 1\} match =0,flag \{ frame_size=frame_size+4 label localvar dword at ebp-frame_size \} common push ebp mov ebp,esp sub esp,(frame_size+3)and 0xFFFFFFFC } macro ENDL { mov esp,ebp pop ebp } .... LOCALS dwX,buf[100],... lea eax,[buf] mov [dwX],eax ... ENDL Недостатки: - использование регистра ebp - нет аналога ADDR
Следующий этап развития макросов вызова функций: Макрос PROTO name,addr создающий макрос name, позволяющий записывать вызовы функций как name(arglist). Код (Text): macro PROTO name,addr { macro name [arg] \{common CALL addr arg \} } Cоответствующее изменение макроса IMPORTS: Код (Text): macro IMPORTS [dll,funclist] { common data import forward dd 0,0,0,rva a#dll, rva v#dll common dd 0,0,0,0,0 end data forward v#dll: irp func,funclist \{p\#func dd rva a\#func ;<<< replaces "func dd rva a\#func" PROTO func,[p\#func] \} ;<<< added dd 0 forward a#dll db `dll#".dll",0 irp func,funclist \{a\#func db 0,0,\`func,0\} } Преимущества + одинаковый вызов как импортируемых, так и не импортируемых функций + код полностью идентичный ЯВУ Недостатки - только stdcall Обновление макроса CALL - замена call [f] на call f, плюс поддержка строковых констант: Код (Text): macro CALL [line] { common match f(x),line \{ irp arg,x \\{reverse if arg eqtype "" call @f db arg,0 @@: else pushd arg end if \\} call f \} } ;<<< replaces "call [f]" .... MessageBoxA(0,"111","capt",0) Недостатки: нет полной поддержки локальных переменных (ADDR)
О локальных переменных и соглашениях вызова. Я знаю только одну __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.
Кроме __stdcall и __cdecl есть еще и делфийский __fastcall. На делфи пишет немало народа, и потому иногда может возникать потребность вызывать __fastcall процедуры. Здесь есть следующие моменты: 1) Аргумент может уже содержаться в нужном регистре, поэтому макрос не должен генерить "mov eax,eax" 2) Аргумент может быть 1,2 байтовым или нулем, соотв. код отличается от "mov reg,arg" 3) Возможен случай когда регистры надо заполнять в определенном порядке, например mov edx,eax/mov eax,1 Простейший набор макросов для __fastcall вызова выглядит так: Код (Text): macro __fastcall [line] { common match f(x),line \{ __fastcall_args x call f \} } macro __fastcall_args a,d,c,[p] { common match any,a \{ mov eax,a \} match any,d \{ mov edx,d \} match any,c \{ mov ecx,c \} match any,p \{ forward pushd p common \} } .... PROTO MySub,MySub,__fastcall MySub(10,20,30,3,4,5) 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 \\} \} }
Для полной схожести с ЯВУ нужен оператор присваивания. В простейшем случае, только для функций, только для переменных в памяти, пишем макрос LET var=func() Код (Text): macro LET [line] {common match var==exp,line \{ exp mov [var],eax \} } ... LOCALS hFile LET hFile=CreateFileA(...) ... Однако, если у нас процедуры вызваются без всяких лишних слов типа CALL, по аналогии, от LET надо избавляться. Возникают следующие проблемы: - первое слово в строке должно быть макросом - имхо навешивать макросы на регистры это перебор Пример макросов для локальных переменных: Код (Text): ;общий макрос присваивания macro LET var,[exp] { common exp mov var,eax } ;макросы LOCALS/ENDL macro LOCALS@add name,size,type { local m frame_size=frame_size+size label m type at ebp-frame_size name equ m match any,locals@list \{ locals@list equ locals@list,name \} match ,locals@list \{ locals@list equ name \} macro name [line] \{ common match ==exp,line \\{ LET [name],exp \\} \} } macro LOCALS [localvar] { common frame_size=0 locals@list equ forward define flag 0 match name[len],localvar \{ LOCALS@add name, len, byte define flag 1\} match =0,flag \{ LOCALS@add localvar, 4, dword \} common push ebp mov ebp,esp sub esp,(frame_size+3)and 0xFFFFFFFC } macro ENDL { mov esp,ebp pop ebp restore locals@list } Проблемы: для глобальных переменных нужен отдельный макрос объявления
На данный момент готов набор макросов, позволяющих записывать код в следующем виде Код (Text): include "%fasminc%\hl.inc" PROGRAM o_O IMPORTS USER32,<MessageBoxA,wsprintfA> entry $ LOCALS n,buf[1024] lea esi,[buf] n=wsprintfA(esi,"123") wsprintfA(esi,"n1=%d",[n]) MessageBoxA(0,esi,"capt",0) ENDL LOCALS buf[1024],n lea esi,[buf] n=wsprintfA(esi,"12345") wsprintfA(esi,"n2=%d",[n]) MessageBoxA(0,esi,"capt",0) ENDL 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] {...}".
GoldFinch У меня почему-то получается только так: Код (Text): start: __stdcall ErrMsg("error code: 0x%X") call [ExitProcess] proc ErrMsg @@szErrMsg:DWORD locals szMsgBuf db 50 dup(?) endl lea edi,[szMsgBuf] invoke wsprintfA, edi, [@@szErrMsg], eax __stdcall [MessageBox](0, edi, "Warning!", 0) ret endp По коду возникли две непонятки: 1. При вызове MessageBox пришлось брать в скобки [] 2. LOCALS и ENDL не смог использовать, поскольку в этом случае в процедуре два раза подряд генерится: Код (Text): push ebp mov ebp, esp Может кто подсказать, как можно сделать код красивее?
FastCall проект несколько заброшен, та последняя версия которую я выкладывал имеет кучу багов и несовместимостей со стандартными заголовочными файлами. я написал новую версию, с меньшей функциональностью, и большей совместимостью, на базе WIN32AX.INC пример кода Код (Text): include 'win32ax.inc' ;необходим для hll_simple.inc, должен быть перед hll_simple.inc include 'hll_simple.inc' .data capt db "Warning!",0 .code PROTO ErrMsg() ;объявление пользовательской функции, вместо () можно написать что угодно, или не писать их proc Main mov eax,0xF00BA12 ErrMsg("error code: 0x%X") xor eax,eax ret endp proc ErrMsg szErrMsg:DWORD local szMsgBuf[50]:BYTE wsprintfA(szMsgBuf,[szErrMsg], eax) ;szMsgBuf - АДРЕС локальной переменной, без [] ;[szErrMsg] - ЗНАЧЕНИЕ параметра, с [] MessageBoxA(0, szMsgBuf, capt, 0) ret endp .end Main как оно работает: 1) основной макрос - __hll_push распознает 32-разрядные значения 111, 1.0 64-разрядные значения double 1.0, int64 111 строки "xxx", 'xxx' адреса локальных переменных и аргументов функций 2) макрос создающий макрос имя_функции для пользовательских функций - макрос PROTO для апи - макрос импортирующий апи по спискам из include/api/* макросы неоптимизированы, могут компилиться долго и жрать много памяти (измените настройки компилятора)
А можно гуру макросов попросить написать макрос функции, которая не использует EBP? То есть что бы вместо "mov eax, [ebp+8]" компилятор генерил "mov eax, [esp+??]" и EBP был свободен для использования. У меня уже вторая программа за неделю попадается написанная на MASM и использующая EBP как обычный регистр (там есть какой то хитрый макрос для этого). Переписать такую функцию на FASM быстро и просто не получается, приходится изгаляться, писать структуры, лишь бы только повысить удобочитаемость.
HeadHunter можно но сложно. для этого надо отслеживать все изменения стека - заменить все push\pop , может даже заменить add\sub для отлова изменений esp, или применить макрос который позволит явно задать изменение esp вобщем это весьма и весьма сложное дело. само собой придется переписать стандартные макросы для процедур - proc, locals и т.п. возможно проще писать код такой процедуры самому, не пользуясь макросами вообще, чем писать такие макросы.
опять же отказ от ebp для адресации аргументов и локальных переменных - это не слишком оптимально, т.к. инструкции xxx [esp+yyy] длиннее xxx [ebp+yyy]
Не оптимально, зато не нужно разбираться в работе функции и экономится время на разработку. Вот ведь, а я думал что это пустяковое дело. Вообще странно что нет такого макроса в дистрибутиве фасма, ведь частенько 5 регистров не хватает, а использовать локальные переменные не всегда актуально в плане скорости.
На форуме фасма это неоднократно обсуждалось, есть несколько вариантов - например, http://board.flatassembler.net/topic.php?t=7764 , http://board.flatassembler.net/topic.php?t=5938 , думаю, если покопаться, можно ещё найти. Есть и проблемы: например, конструкция Код (Text): @@: push eax loop @b творит с esp вовсе не то же, что один push. Подробнее - поиск по форуму board.flatassembler.net.
А вот еще ситуация, у меня имеются "инклуды" от ntdll и msvcrt, в ntdll дублируются некоторые функции из msvcrt, можно конечно их переименовать или вовсе удалить, но это совсем неудобно. Есть ли какой то способ указывать компилятору какую функцию из какой библиотеки мы берём? Что-нибудь наподобие - invoke ntdll.strcpy (это и наглядно и очень удобно)
очередная версия. вместо того чтобы делать по макросу для каждой функций, требует писать 'hcall' в начале строки поддерживает 1 уровень вложенности, вызовы как апишек, так и локальных функций было бы неплохо заменить "hcall" на какое-нибудь более хорошее имя пример использования Код (Text): include 'win32ax.inc' include 'psyn.inc' .code proc main local startupInfo:STARTUPINFO hcall GetStartupInfo(startupInfo) local procInfo:PROCESS_INFORMATION hcall CreateProcess("test_target_app.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, startupInfo, procInfo) local scRemotePtr:DWORD hcall [scRemotePtr] = VirtualAllocEx([procInfo.hProcess], NULL, sizeof.teststr, MEM_COMMIT, PAGE_READWRITE) hcall WriteProcessMemory([procInfo.hProcess], [scRemotePtr], teststr, sizeof.teststr, NULL) hcall QueueUserAPC([OutputDebugString], [procInfo.hThread], [scRemotePtr]) hcall ResumeThread([procInfo.hThread]) ret endp teststr db "Remote OutputDebugString call", 0 sizeof.teststr = $ - teststr .end main
А есть какие-то наработки макросов в плане уменьшения листинга ака: Код (Text): ;mov [a],[b] macro movmem op1,op2 { lea eax, dword op1 mov eax,dword [eax] mov dword op2,eax } и подобное. В общем было бы интересно послушать. хехе: macro movmem [a], { push [a] pop }