![]() |
Оптимальный кодинг на Fasm
СТАТЬЯ ПО ОПТИМАЛЬНОМУ КОДИНГУ В FASM При использовании компилятора FASM можно достичь малых размеров программы, а при использовании дополнительных вещиц еще больше уменьшить размер программы.1. Переменные в функциях: При использовании переменных в функциях желательно не допускать следующих описаний: Код:
proc myprogКод:
proc myprogdata_1 dw ? data_2 dd ? data_3 db 10 dup (?) т.е. мы даем знать компилятору, что нас не волнует первоначальное значение данных переменных и => он не будет задавать им значение. 2. Структура секций: Чаще всего структура программы выглядит так: Код:
format PE GUIКаждая секция физически имеет выравнение на 512 байт. => потери будут возникать как раз при выравнении, а т.к. у нас 3 секции, то это будет наблюдаться в трех местах программы. Исправляется это следующим образом: Код:
format PE GUIНедостатки данного способа в том, что секция кода имеет права на запись + нельзя совместить вместе секцию импорта, экспорта, ресурсов. Если присутствует хотя бы 2 из них, то необходимо так склеивать наименую из них. 3. Глобальные переменные: Как было видно из вышеописанной структуры, секция данных находится в конце файла. Допустим у нас есть множество переменных. Если эти переменные были описаны в самом конце программы как data_1 dw ? data_2 dd ? data_3 db 10 dup (?) то физически они не попадают в исполняемый файл и располагаются только в оперативной памяти. по этому структура данных должна быть следующей ; инициализированные данные xxx1 db 0 xxx2 dd 2 ; неинициализированные данные xxx3 db ? xxx4 dd ? 4. Текст используемый в коде: Допустим у нас есть строка: invoke MessageBox,0,"Error","TITLE",0 После компиляции данный код будет выглядеть примерно так(но не всегда!) Код:
jmp _nextНа реале получается еще более страшный код. Именно по этой причине лучше всего такие текстовые строки описывать как переменные. т.е. Код:
5. Оптимизация команд: Не секрет, что существуют команды которые при определенных условиях выполняют одну и туже операцию, но занимают меньше места Среди таких команд можно перечислить часто используемые: 1. add/sub reg,1 лучше заменить на inc/dec reg 2. умножение/деление на степень двойки. - shl/shr reg,1 ; где 1,2,3 - степень 3. При записи в регистр какого либо числа командой mov, лучше всего использовать по возможности регистр eax т.к. mov eax,xxxxxxxxh имеет более короткий опкод, нежили mov ecx,xxxxxxxxh 4. mov reg,0 заменяется на xor reg,reg 5. cmp reg,0 заменяться на test reg,reg 6. Для циклов использовать лучше использовать регистр ecx с командами loop и jzecx 7. Адресация данных. Допустим у нас есть массив и значения некоторых элементов нам нужно поместить в стек для этого используем адресацию через регистр. lea eax,[mas] push [eax+4] push [eax+8] push [eax+12] дело втом что адресация через регистры имеет более короткий опкод. 8. При использовании процедур желательно в начале и в конце использовать pushad/popad а не по отдельности сохранять значения изменяемых регистров. 6. Таблица импорта: При написании программ с большим числом API функций лучше всего использовать динамический импорт. Это связанно с тем, что можно более оптимально распределить данные. В итоге таблица импорта должна будет состоять только из 2-х функций - GetProcAddress и LoadLibraryA и тогда код будет строиться следующим образом: Код:
invoke LoadLibraryA,lib_1Плюсы данного метода - при первичном осмотре файла средствами типа PE_edit будут незаметны используемые функции. Также для большей скрытности имена функций и библиотек могут быть зашифрованные. Дополнение: Также можно использовать вместо GetProccAddress другие алгоритмы нахождения адресов функций через таблицу экспорта библиотеки. А при использовании метода импорта по хешам, это значительно сократит размер программы в ненадобности хранения полного имени API функции Также удобно использовать собственное построение таблицы импорта т.к. оно занимает чуть меньше места а именно: Код:
dd 0,0,0,IT_lib-IMAGE_BASE,IT-IMAGE_BASEНекоторые Api функции созданы только для удобства получения некоторых данных. При этом на код самой функции может быть потрачено меньше байт, чем на поиск её адреса и вызова. Примером таких функции являются: 1. invoke GetModuleHandleA,0 Её можно заменить на: mov eax,[fs:18h] mov eax,[eax+30h] mov eax,[eax+3h] 2. invoke GetProcessHeap Можно заменить на: mov eax,[fs:18h] mov eax,[eax+30h] mov eax,[eax+18h] 3. invoke GetLastError заменяем на mov eax,[fs:18h] mov eax,[eax+34h] 4. invoke lstrlen,my_str в данном случаи код на ручной подсчет длинны строки будет меньше чем затраты на импорт функции Таким образом можно оптимизировать использование и некоторых других функций. 8. Разбиение кода на функции и "кеширование": Порой часто встречается, что необходимо выполнять похожие операции, для этого лучше разбивать код на функции которые будут делать данную операцию Примером таких функций может быть функция отсыла запроса на http сервер. В случаях, когда код чуть отличается, то можно делать специальные флаги которые будут указывать какой вариант использовать. т.е. логика будет такая: Код:
cmp param_2,0К примеру для сетевых приложений желательно кешировать IP адрес сервера, а не резолвить его каждый раз при подключении. Тоже самое можно делать и с некоторыми API функциями. Которые используются в разное время, но возвращают одно и тоже. Примером может быть: Код:
invoke GetModuleHandleA,09. Использование альтернативного MZ заголовка: Стандартный MZ заголовок у FASM имеет размер 128 байт. ПРи использовании альтернативного заголовка этот размер может быть уменьшен до 64 байт, без прибегания к разным хитростям. делается это с помощью команды: format PE GUI on 'stub.inc' примером такого стаба может быть следующий код (переведен в HEX) Код:
4D 5A 00 00 01 00 00 00 ЗАКЛЮЧЕНИЕ Конечно это не все способы которые помогут уменьшить размер программы, но всё же в совокупности они могут дать уменьшение размера программы на несколько килобайт, что немало важно бывает в некоторые специфических областях программирования. (С) SLESH 2008 |
Цитата:
а со стандартным стабом делает его в 1024байта. |
Это глюки новой версии компилятора. сам заметил её. Под Win при версии 1.65.14 всё нормально работает.
P.S. фактически привинчивание нового стаба не приведет к уменьшению программы, это просто уберет левые данные, такие как This program cannot be run in DOS mode. |
стаб, который занимает на 4байта меньше стаба слеша :D
Код:
4D 5A 3C 00 01 00 00 00 02 00 00 01 FF FF 02 00 MZ<.........яя..format PE GUI on 'stub' |
Stub 0Ch (c) wasm.ru
Код:
00000000 4D 5A 00 00 01 00 00 00 01 00 00 00 50 45 00 00 MZ..........PE.. |
slesh, вы б хоть указале, где по размеру, а где по скорости оптимизация. насчет апи - так можно вобще хеши зашить, исчо меньше места занимать будет.
вобщем как вам, так и остальным рекомендую почитать маны от Intel'a касающиеся оптимизации, а также посмотреть формат РЕ более детально и провереть все, что вы здесь напесале, а то только сбиваете с толку юнных одептов. |
Там где оптимизация идет по размеру, зачастую она идет и по скорости.
Цитата:
А на счет PE - тут уже вы переборщили. Как ни крути его, но PE он и остается PE. В статье я только раскрыл те моменты, которые люди могут сами(без особых проблем) реализовать. Если дело на то пошло, то можно и самому собираться MZ и PE заголовок. И оставить только одну таблицу для единственной секции, сразу за которой пойдет код этой секции. Но собирать всё вручную - это геморно, и уже выходит за рамки данной статьи. |
| Время: 12:54 |