]>Ручная компиляция

Ручная компиляция

Аватар пользователя mabu

Как‐то сравнивая HelloWorld на ассемблере и FreeBASIC, обнаружилось, что откомпилированные на FreeBASIC программы всегда получались больше. Так происходит из‐за того, что непосредственно перед нашим кодом идут разные инициализации критических секций, создание командной строки, кучи и так далее. Зачастую в простых программах это не нужно, к тому же иногда хочется удивить коллегу и сделать маленький бинарник или просто покопаться во внутренностях исполняемого файла.

Разработчики FreeBASIC предусмотрели параметр компиляции -v, он показывает все этапы компиляции, какие программы запускаются и какие параметры им передаются.

Цель: сделать простую программу, выводящую строку «Привет, мир!», размером 2048 байт. Или даже меньше.

  1. Что будет, если выкинуть библиотеку времени выполнения
  2. Исходный текст
  3. Компиляция
  4. Чем заменить стандартные функции

Что будет, если выкинуть библиотеку времени выполнения

Придётся отказаться от стандартных возможностей:

Исходный текст

Наша программа будет очень простая, она будет выводить строку на консоль и завершаться. Но так как мы не будем использовать встроенные функции, то для вывода строки придётся использовать WinAPI.

Код FreeBASIC
#define unicode
#include once "windows.bi"

Const HelloWorld = !"Привет, мир!\r\n"
Const HelloWorldLength As Integer = 14

' Это точка входа в программу
' Эту функцию вызовет операционная система при запуске
Function EntryPoint Alias "EntryPoint"()As Integer
&t;' Напечатать HelloWorld
&t;Dim CharsCount As DWORD = Any
&t;WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), @HelloWorld, HelloWorldLength, @CharsCount, 0)

&t;Return 0
End Function

Здесь в имени функции EntryPoint используется оператор Alias, указывающий правильное название функции, иначе без него в объектном файле она будет в верхнем регистре.

Компиляция

Компиляция в 32‐битную программу

По умолчанию будем считать, что FreeBASIC установлен в %ProgramFiles%.

Для начала получим ассемблерный код. Следующая команда заставит создать только ассемблерный код:

Код Batch
"%ProgramFiles%\FreeBASIC\fbc.exe" -r -lib helloworld.bas

Параметр -lib заставляет компилятор создать библиотеку функций, а параметр -r обязывает оттранслировать исходник без компиляции в исполняемый файл.

На этом работа FreeBASIC закончена, отложим его в сторонку, сейчас он нам не понадобится. Все остальные действия по созданию исполняемого файла осуществляются дополнительными утилитами.

Теперь с помощью ассемблера создадим объектный файл:

Код Batch
"%ProgramFiles%\FreeBASIC\bin\win32\as.exe" --32 --strip-local-absolute helloworld.asm -o helloworld.o

Компоновщик из него сделает исполняемый файл:

Код Batch
"%ProgramFiles%\FreeBASIC\bin\win32\ld.exe" -m i386pe -e _EntryPoint@0 -subsystem console -s --stack 1048576,1048576 -L "%programfiles%\freebasic\lib\win32" -L "./" helloworld.o -o helloworld.exe -( -lkernel32 -)

Параметр -e указывает на точку входа _EntryPoint@0. Имя функции искажено: слева добавлен знак подчёркивания, справа через собаку количество принимаемых байт в качестве параметров. Это делается для совместимости с конвенцией вызовов stdcall в WinAPI.

Параметр -subsystem указывает на подсистему. Так как мы пишем консольное приложение, то указываем console, для оконного приложения необходимо будет указать gui.

Параметр -L добавляет пути поиска библиотечных файлов.

Параметр -o назначает имя получившегося исполняемого файла.

В этой программе мы используем пару функций для вывода строки на консоль, все они лежат в библиотеке kernel32.dll. Через параметры в скобках мы сообщаем компоновщику, что нас интресует только библиотека kernel32.dll: -lkernel32. В том случае, когда будут дополнительные библиотеки, то необходимо будет это указать. Например, вот указание на большинство стандартных библиотек: -( -lkernel32 -lgdi32 -lmsimg32 -luser32 -lversion -ladvapi32 -limm32 -lshlwapi -lole32 -loleaut32 -lshell32 -lcomctl32 -lws2_32 -lmsvcrt -).

После работы компоновщика получаем исполняемый файл helloworld.exe размером 2048 байт.

Компиляция в 64‐битную программу 64‐битным компилятором

Компиляция в 64‐битный исполняемый файл принципиально ничем не отличается. Просто в этом случае появляется дополнительный этап: в данном случае FreeBASIC создаёт листинг на Си. Далее этот листинг с помощью компилятора Си транслируется в ассеблерный код, создаются объектные и исполняемый файл. Все вспомогательные утилиты 64‐битные.

Код Batch
"%ProgramFiles%\FreeBASIC\fbc.exe" -r -lib helloworld.bas
"%ProgramFiles%\FreeBASIC\bin\win64\gcc.exe" -m64 -march=x86-64 -S -nostdlib -nostdinc -Wall -Wno-unused-label -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -Wno-main -Werror-implicit-function-declaration -O0 -mno-stack-arg-probe -fno-stack-check -fno-stack-protector -fno-strict-aliasing -frounding-math -fno-math-errno -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-ident -masm=intel helloworld.c -o helloworld.asm
"%ProgramFiles%\FreeBASIC\bin\win64\as.exe" --64 --strip-local-absolute helloworld.asm -o helloworld.o
"%ProgramFiles%\FreeBASIC\bin\win64\ld.exe" -m i386pep -e EntryPoint -o helloworld.exe -subsystem console --stack 1048576,1048576 -s -L "%ProgramFiles%\FreeBASIC\lib\win64" -L "." helloworld.o -( -lkernel32 -)

Чем заменить стандартные функции

Итак, мы выбросили всю библиотеку времени выполнения. К счастью, нам не придётся самим писать аналогичные функции, так как часть из них уже есть во внешних библиотеках.

Работа со строками

Kernel32.dll

lstrcpyW(приёмник, источник) — копирует строку «источник» в строку «приёмник».

lstrcatW(приёмник, источник) — добавляет строку «источник» к строке «приёмник».

lstrlenW(строка) — вычисляет длину строки в символах.

lstrcmpW(строка1, строка2) — сравнивает строки, если строки равны, то возвращает 0.

Shlwapi.dll

StrStr / StrStrI / StrRStrI — ищет подстроку в строке. I — если нужна нечувствительность к регистру.

StrChr / StrChrI — ищет символ в строке (в том числе нулевой).

StrToInt / StrToIntEx / StrToInt64Ex — возвращает число из строки.

StrTrim — удаляет начальные и конечные знаки из строки.

Работа с памятью

Kernel32.dll

GetProcessHeap — получет кучу процесса по умолчанию.

HeapCreate / HeapDestroy — создание / уничтожение куч.

HeapAlloc — выделяет память из кучи.

HeapFree — освобождает память из кучи.

Работа с файлами

Kernel32.dll

CreateFile — открытие файла.

CloseHandle — закрытие файла.

WriteFile — запись в файл.

ReadFile — чтение из файла.

Придется реализовывать самому разбор считанных данных на строки.

Работа с потоками

Kernel32.dll

CreateThread

ExitThread

Консольный ввод/вывод

Kernel32.dll

Print — WriteConsole

Input – ReadConsole + парсинг строк

Аргументы командной строки

Kernel32.dll + Shell32.dll

CommandLineToArgvW + GetCommandLineW

Поделись ссылочкой в социальных сетях