Самоизоляция это отличное время приступить к тому, что требует много времени и сил. Поэтому я решил заняться тем, чем всегда хотел — написать свой компилятор.
Сейчас он способен собрать Hello World, но в этой статье я хочу рассказать не про парсинг и внутреннее устройство компилятора, а про такую важную часть как побайтовая сборка exe файла.
Начало
Хотите спойлер? Наша программа будет занимать 2048 байт.
Обычно работа с exe файлами заключается в изучении или модификации их структуры. Сами же исполняемые файлы при этом формируют компиляторы, и этот процесс кажется немного магическим для разработчиков.
Но сейчас мы с вами попробуем это исправить!
Для сборки нашей программы нам потребуется любой HEX редактор (лично я использовал HxD).
Для старта возьмем псевдокод:
Исходный код
func MessageBoxA(u32 handle, PChar text, PChar caption, u32 type) i32 ['user32.dll']
func ExitProcess(u32 code) ['kernel32.dll']
func main()
{
MessageBoxA(0, 'Hello World!', 'MyApp', 64)
ExitProcess(0)
}
Первые две строки указывают на функции импортируемые из библиотек WinAPI. Функция MessageBoxA выводит диалоговое окно с нашим текстом, а ExitProcess сообщает системе о завершении программы.
Рассматривать отдельно функцию main нет смысла, так как в ней используются функции, описанные выше.
DOS Header
Для начала нам нужно сформировать корректный DOS Header, это заголовок для DOS программ и влиять на запуск exe под Windows не должен.
Более-менее важные поля я отметил, остальные заполнены нулями.
Стуктура IMAGE_DOS_HEADER
Struct IMAGE_DOS_HEADER
{
u16 e_magic // 0x5A4D "MZ"
u16 e_cblp // 0x0080 128
u16 e_cp // 0x0001 1
u16 e_crlc
u16 e_cparhdr // 0x0004 4
u16 e_minalloc // 0x0010 16
u16 e_maxalloc // 0xFFFF 65535
u16 e_ss
u16 e_sp // 0x0140 320
u16 e_csum
u16 e_ip
u16 e_cs
u16 e_lfarlc // 0x0040 64
u16 e_ovno
u16[4] e_res
u16 e_oemid
u16 e_oeminfo
u16[10] e_res2
u32 e_lfanew // 0x0080 128
}
Самое главное, что этот заголовок содержит поле e_magic означающее, что это исполняемый файл, и e_lfanew — указывающее на смещение PE-заголовка от начала файла (в нашем файле это смещение равно 0x80 = 128 байт).
Отлично, теперь, когда нам известна структура заголовка DOS Header запишем ее в наш файл.
(1) RAW DOS Header (Offset 0x00000000)
4D 5A 80 00 01 00 00 00 04 00 10 00 FF FF 00 00
40 01 00 00 00 00 00 00 40 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00
Уточнение
Сначала я использовал левую колонку как на скриншоте для указания смещения внутри файла, но тогда неудобно копировать исходный текст, приходится обрезать каждую строку.
Поэтому для удобства в первой скобке каждого блока указан порядок добавления в файл, а в последней смещение в файле (Offset) по которому должен располагаться данный блок.
Например, первый блок мы вставляем по смещению 0x00000000, и он займет 64 байта (0x40 в 16-ричной системе), следующий блок мы будем вставлять уже по этому смещению 0x00000040 и т.д.
Готово, первые 64 байта записали. Теперь нужно добавить еще 64, это так называемый DOS Stub (Заглушка). Во время запуска из-под DOS, она должна уведомить пользователя что программа не предназначена для работы в этом режиме.
Но в целом, это маленькая программа под DOS которая выводит строку и выходит из программы.
Запишем наш Stub в файл и рассмотрим его детальнее.
(2) RAW DOS Stub (Offset 0x00000040)
0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68
69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F
74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20
6D 6F 64 65 2E 0D 0A 24 00 00 00 00 00 00 00 00
А теперь этот же код, но уже в дизассемблированном виде
Asm DOS Stub
0000 push cs ; Запоминаем Code Segment(CS) (где мы находимся в памяти)
0001 pop ds ; Указываем что Data Segment(DS) = CS
0002 mov dx, 0x0E ; Указываем адрес начала строки DS+DX, которая будет выводиться до символа $(Конец строки)
0005 mov ah, 0x09 ; Номер инструкции (Вывод строки)
0007 int 0x21 ; Вызов системного прерывания 0x21
0009 mov ax, 0x4C01 ; Номер инструкции 0x4C (Выход из программы)
; Код выхода из программы 0x01 (Неудача)
000c int 0x21 ; Вызов системного прерывания 0x21
000e "This program cannot be run in DOS mode.\x0D\x0A$" ; Выводимая строка
Это работает так: сначала заглушка выводит строку о том, что программа не может быть запущена, а затем выходит из программы с кодом 1. Что отличается от нормального завершения (Код 0).
Код заглушки может немного отличатся (от компилятора к компилятору) я сравнивал gcc и delphi, но общий смысл одинаковый.
А еще забавно, что строка заглушки заканчивается как \x0D\x0D\x0A$. Скорее всего причина такого поведения в том, что c++ по умолчанию открывает файл в текстовом режиме. В результате символ \x0A заменяется на последовательность \x0D\x0A. В результате получаем 3 байта: 2 байта возврата каретки Carriage Return (0x0D) что бессмысленно, и 1 на перевод строки Line Feed (0x0A). В бинарном режиме записи (std::ios::binary) такой подмены не происходит.
Для проверки корректности записи значений я буду использовать Far с плагином ImpEx:
NT Header
Спустя 128 (0x80) байт мы добрались до NT заголовка (IMAGE_NT_HEADERS64), который содержит в себе и PE заголовок (IMAGE_OPTIONAL_HEADER64). Несмотря на название IMAGE_OPTIONAL_HEADER64 является обязательным, но различным для архитектур x64 и x86.
Структура IMAGE_NT_HEADERS64
Struct IMAGE_NT_HEADERS64
{
u32 Signature // 0x4550 "PE"
Struct IMAGE_FILE_HEADER
{
u16 Machine // 0x8664 архитектура x86-64
u16 NumberOfSections // 0x03 Количество секций в файле
u32 TimeDateStamp // Дата создания файла
u32 PointerToSymbolTable
u32 NumberOfSymbols
u16 SizeOfOptionalHeader // Размер IMAGE_OPTIONAL_HEADER64 (Ниже)
u16 Characteristics // 0x2F
}
Struct IMAGE_OPTIONAL_HEADER64
{
u16 Magic // 0x020B Указывает что наш заголовок для PE64
u8 MajorLinkerVersion
u8 MinorLinkerVersion
u32 SizeOfCode
u32 SizeOfInitializedData
u32 SizeOfUninitializedData
u32 AddressOfEntryPoint // 0x1000
u32 BaseOfCode // 0x1000
u64 ImageBase // 0x400000
u32 SectionAlignment // 0x1000 (4096 байт)
u32 FileAlignment // 0x200
u16 MajorOperatingSystemVersion // 0x05 Windows XP
u16 MinorOperatingSystemVersion // 0x02 Windows XP
u16 MajorImageVersion
u16 MinorImageVersion
u16 MajorSubsystemVersion // 0x05 Windows XP
u16 MinorSubsystemVersion // 0x02 Windows XP
u32 Win32VersionValue
u32 SizeOfImage // 0x4000
u32 SizeOfHeaders // 0x200 (512 байт)
u32 CheckSum
u16 Subsystem // 0x02 (GUI) или 0x03 (Console)
u16 DllCharacteristics
u64 SizeOfStackReserve // 0x100000
u64 SizeOfStackCommit // 0x1000
u64 SizeOfHeapReserve // 0x100000
u64 SizeOfHeapCommit // 0x1000
u32 LoaderFlags
u32 NumberOfRvaAndSizes // 0x16
Struct IMAGE_DATA_DIRECTORY [16]
{
u32 VirtualAddress
u32 Size
}
}
}
Разберемся что хранится в этой структуре:
Описание IMAGE_NT_HEADERS64
Signature — Указывает на начало структуры PE заголовка
Далее идет заголовок IMAGE_FILE_HEADER общий для архитектур x86 и x64.
Machine — Указывает для какой архитектуры предназначен код в нашем случае для x64
NumberOfSections — Количество секции в файле (О секциях чуть ниже)
TimeDateStamp — Дата создания файла
SizeOfOptionalHeader — Указывает размер следующего заголовка IMAGE_OPTIONAL_HEADER64, ведь он может быть заголовком IMAGE_OPTIONAL_HEADER32.
Characteristics — Здесь мы указываем некоторые атрибуты нашего приложения, например, что оно является исполняемым (EXECUTABLE_IMAGE) и может работать более чем с 2 Гб RAM (LARGE_ADDRESS_AWARE), а также что некоторая информация была удалена (на самом деле даже не была добавлена) в файл (RELOCS_STRIPPED | LINE_NUMS_STRIPPED | LOCAL_SYMS_STRIPPED).
SizeOfCode — Размер исполняемого кода в байтах (секция .text)
SizeOfInitializedData — Размер инициализированных данных (секция .rodata)
SizeOfUninitializedData — Размер не инициализированных данных (секция .bss)
BaseOfCode — указывает на начало секции кода блок
SectionAlignment — Размер по которому нужно выровнять секции в памяти
FileAlignment — Размер по которому нужно выровнять секции внутри файла
SizeOfImage — Размер всех секций программы
SizeOfHeaders — Размер всех заголовков вместе (IMAGE_DOS_HEADER, DOS Stub, IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER[IMAGE_FILE_HEADER.NumberOfSections]) выровненный по FileAlignment
Subsystem — Указывает тип нашей программы GUI или Console
MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorSubsystemVersion, MinorSubsystemVersion — Говорят о том на какой системе можно запускать данный exe, и что он может поддерживать. В нашем случае мы берем значение 5.2 от Windows XP (x64).
SizeOfStackReserve — Указывает сколько приложению нужно зарезервировать памяти под стек. Этот параметр по умолчанию составляет 1 Мб, максимально можно указать 1Гб. Вроде как умные программы на Rust умеют считать необходимый размер стека, в отличии от программ на C++ где этот размер нужно править вручную.
SizeOfStackCommit — Размер по умолчанию составляет 4 Кб. Как должен работать данный параметр пока не разобрался.
SizeOfHeapReserve — Указывает сколько резервировать памяти под кучу. Равен 1 Мб по умолчанию.
SizeOfHeapCommit — Размер по умолчанию равен 4 Кб. Подозреваю что работает аналогично SizeOfStackCommit, то есть пока неизвестно как.
IMAGE_DATA_DIRECTORY — массив записей о каталогах. В теории его можно уменьшить, сэкономив пару байт, но вроде как все описывают все 16 полей даже если они не нужны. А теперь чуть подробнее.
У каждого каталога есть свой номер, который описывает, где хранится его содержимое. Пример:
Export(0) — Содержит ссылку на сегмент который хранит экспортируемые функции. Для нас это было бы актуально если бы мы создавали DLL. Как это примерно должно работать можно посмотреть на примере следующего каталога.
Import(1) — Этот каталог указывает на сегмент с импортируемыми функциями из других DLL. В нашем случае значения VirtualAddress = 0x3000 и Size = 0xB8. Это единственный каталог, который мы опишем.
Resource(2) — Каталог с ресурсами программы (Изображения, Текст, Файлы и т.д.)
Значения других каталогов можно посмотреть в документации.
Теперь, когда мы посмотрели из чего состоит NT-заголовок, запишем и его в файл по аналогии с остальными по адресу 0x80.
(3) RAW NT-Header (Offset 0x00000080)
50 45 00 00 64 86 03 00 F4 70 E8 5E 00 00 00 00
00 00 00 00 F0 00 2F 00 0B 02 00 00 3D 00 00 00
13 00 00 00 00 00 00 00 00 10 00 00 00 10 00 00
00 00 40 00 00 00 00 00 00 10 00 00 00 02 00 00
05 00 02 00 00 00 00 00 05 00 02 00 00 00 00 00
00 40 00 00 00 02 00 00 00 00 00 00 02 00 00 00
00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00
00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00
00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
00 30 00 00 B8 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
В результате получаем вот такой вид IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER64 и IMAGE_DATA_DIRECTORY заголовков:
Далее описываем все секции нашего приложения согласно структуре IMAGE_SECTION_HEADER
Структура IMAGE_SECTION_HEADER
Struct IMAGE_SECTION_HEADER
{
i8[8] Name
u32 VirtualSize
u32 VirtualAddress
u32 SizeOfRawData
u32 PointerToRawData
u32 PointerToRelocations
u32 PointerToLinenumbers
u16 NumberOfRelocations
u16 NumberOfLinenumbers
u32 Characteristics
}
Описание IMAGE_SECTION_HEADER
Name — имя секции из 8 байт, может быть любым
VirtualSize — сколько байт копировать из файла в память
VirtualAddress — адрес секции в памяти выровненный по SectionAlignment
SizeOfRawData — размер сырых данных выровненных по FileAlignment
PointerToRawData — адрес секции в файле выровненный по FileAlignment
Characteristics — Указывает какие данные хранит секция (Код, инициализированные или нет данные, для чтения, для записи, для исполнения и др.)
В нашем случае у нaс будет 3 секции.
Почему Virtual Address (VA) начинается с 1000, а не с нуля я не знаю, но так делают все компиляторы, которые я рассматривал. В результате 1000 + 3 секции * 1000 (SectionAlignment) = 4000 что мы и записали в SizeOfImage. Это полный размер нашей программы в виртуальной памяти. Вероятно, используется для выделения места под программу в памяти.
Name | RAW Addr | RAW Size | VA | VA Size | Attr
--------+---------------+---------------+-------+---------+--------
.text | 200 | 200 | 1000 | 3D | CER
.rdata | 400 | 200 | 2000 | 13 | I R
.idata | 600 | 200 | 3000 | B8 | I R
Расшифровка атрибутов:
I — Initialized data, инициализированные данные
U — Uninitialized data, не инициализированные данные
C — Code, содержит исполняемый код
E — Execute, позволяет исполнять код
R — Read, позволяет читать данные из секции
W — Write, позволяет записывать данные в секцию
.text (.code) — хранит в себе исполняемый код (саму программу), атрибуты CE
.rdata (.rodata) — хранит в себе данные только для чтения, например константы, строки и т.п., атрибуты IR
.data — хранит данные которые можно читать и записывать, такие как статические или глобальные переменные. Атрибуты IRW
.bss — хранит не инициализированные данные, такие как статические или глобальные переменные. Кроме того, данная секция обычно имеет нулевой RAW размер и ненулевой VA Size, благодаря чему не занимает места в файле. Атрибуты URW
.idata — секция содержащая в себе импортируемые из других библиотек функции. Атрибуты IR
Важный момент, секции должны следовать друг за другом. При чем как в файле, так и в памяти. По крайней мере когда я менял их порядок произвольно программа переставала запускаться.
Теперь, когда нам известно какие секции будет содержать наша программа запишем их в наш файл. Тут смещение оканчивается на 8 и запись будет начинаться с середины файла.
(4) RAW Sections (Offset 0x00000188)
2E 74 65 78 74 00 00 00
3D 00 00 00 00 10 00 00 00 02 00 00 00 02 00 00
00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60
2E 72 64 61 74 61 00 00 13 00 00 00 00 20 00 00
00 02 00 00 00 04 00 00 00 00 00 00 00 00 00 00
00 00 00 00 40 00 00 40 2E 69 64 61 74 61 00 00
B8 00 00 00 00 30 00 00 00 02 00 00 00 06 00 00
00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40
Следующий адрес для записи будет 00000200 что соответствует полю SizeOfHeaders PE-Заголовка. Если бы мы добавили еще одну секцию, а это плюс 40 байт, то наши заголовки не уложились бы в 512 (0x200) байт и пришлось бы использовать уже 512+40 = 552 байта выровненные по FileAlignment, то есть 1024 (0x400) байта. А все что останется от 0x228 (552) до адреса 0x400 нужно чем-то заполнить, лучше конечно нулями.
Взглянем как выглядит блок секций в Far:
Далее мы запишем в наш файл сами секции, но тут есть один нюанс.
Как вы могли заметить на примере SizeOfHeaders, мы не можем просто записать заголовок и перейти к записи следующего раздела. Так как что бы записать заголовок мы должны знать сколько займут все заголовки вместе. В результате нам нужно либо посчитать заранее сколько понадобиться места, либо записать пустые (нулевые) значения, а после записи всех заголовков вернуться и записать уже их реальный размер.
Поэтому программы компилируются в несколько проходов. Например секция .rdata идет после секции .text, при этом мы не можем узнать виртуальный адрес переменной в .rdata, ведь если секция .text разрастется больше чем на 0x1000 (SectionAlignment) байт, она займет адреса 0x2000 диапазона. И соответственно секция .rdata будет находиться уже не в адресе 0x2000, а в адресе 0x3000. И нам будет необходимо вернуться и пересчитать адреса всех переменных в секции .text которая идет перед .rdata.
Но в данном случае я уже все рассчитал, поэтому будем сразу записывать блоки кода.
Секция .text
Asm segment .text
0000 push rbp
0001 mov rbp, rsp
0004 sub rsp, 0x20
0008 mov rcx, 0x0
000F mov rdx, 0x402000
0016 mov r8, 0x40200D
001D mov r9, 0x40
0024 call QWORD PTR [rip + 0x203E]
002A mov rcx, 0x0
0031 call QWORD PTR [rip + 0x2061]
0037 add rsp, 0x20
003B pop rbp
003C ret
Конкретно для этой программы первые 3 строки, ровно, как и 3 последние не обязательны.
Последние 3 даже не будут исполнены, так как выход из программы произойдет еще на второй функции call.
Но скажем так, если бы это была не функция main, а подфункция следовало бы сделать именно так.
А вот первые 3 в данном случае хоть и не обязательны, но желательны. Например, если бы мы использовали не MessageBoxA, а printf то без этих строк получили бы ошибку.
Согласно соглашению о вызовах для 64-разрядных систем MSDN, первые 4 параметра передаются в регистрах RCX, RDX, R8, R9. Если они туда помещаются и не являются, например числом с плавающей точкой. А остальные передаются через стек.
По идее если мы передаем 2 аргумента функции, то должны передать их через регистры и зарезервировать под них два места в стеке, что бы при необходимости функция могла скинуть регистры в стек. Так же мы не должны рассчитывать, что нам вернут эти регистры в исходном состоянии.
Так вот проблема функции printf заключается в том, что, если мы передаем ей всего 1 аргумент, она все равно перезапишет все 4 места в стеке, хотя вроде бы должна перезаписать только одно, по количеству аргументов.
Поэтому если не хотите, чтобы программа себя странно вела, всегда резервируйте как минимум 8 байт * 4 аргумента = 32(0x20) байт, если передаете функции хотя бы 1 аргумент.
Рассмотрим блок кода с вызовами функций
MessageBoxA(0, 'Hello World!', 'MyApp', 64)
ExitProcess(0)
Сначала мы передаем наши аргументы:
rcx = 0
rdx = абсолютный адрес строки в памяти ImageBase + Sections[«.rdata»].VirtualAddress + Смещение строки от начала секции, строка читается до нулевого байта
r8 = аналогично предыдущему
r9 = 64(0x40) MB_ICONINFORMATION, значок информации
А далее идет вызов функции MessageBoxA, с которым не все так просто. Дело в том, что компиляторы стараются использовать как можно более короткие команды. Чем меньше размер команды, тем больше таких команд влезет в кэш процессора, соответственно, будет меньше промахов кэша, подзагрузок и выше скорость работы программы. Для более подробной информации по командам и внутренней работе процессора можно обратиться к документации Intel 64 and IA-32 Architectures Software Developer’s Manuals.
Мы могли бы вызвать функцию по полному адресу, но это заняло бы как минимум (1 опкод + 8 адрес = 9 байт), а с относительным адресом команда call занимает всего 6 байт.
Давайте взглянем на эту магию поближе: rip + 0x203E, это ни что иное, как вызов функции по адресу, указанному нашим смещением.
Я подсмотрел немного вперед и узнал адреса нужных нам смещений. Для MessageBoxA это 0x3068, а для ExitProcess это 0x3098.
Пора превратить магию в науку. Каждый раз, когда опкод попадает в процессор, он высчитывает его длину и прибавляет к текущему адресу инструкции (RIP). Поэтому, когда мы используем RIP внутри инструкции, этот адрес указывает на конец текущей инструкции / начало следующей.
Для первого call смещение будет указывать на конец команды call это 002A не забываем что в памяти этот адрес будет по смещению Sections[«.text»].VirtualAddress, т.е. 0x1000. Следовательно, RIP для нашего call будет равен 102A. Нужный нам адрес для MessageBoxA находится по адресу 0x3068. Считаем 0x3068 — 0x102A = 0x203E. Для второго адреса все аналогично 0x1000 + 0x0037 = 0x1037, 0x3098 — 0x1037 = 0x2061.
Именно эти смещения мы и видели в командах ассемблера.
0024 call QWORD PTR [rip + 0x203E]
002A mov rcx, 0x0
0031 call QWORD PTR [rip + 0x2061]
0037 add rsp, 0x20
Запишем в наш файл секцию .text, дополнив нулями до адреса 0x400:
(5) RAW .text section (Offset 0x00000200-0x00000400)
55 48 89 E5 48 83 EC 20 48 C7 C1 00 00 00 00 48
C7 C2 00 20 40 00 49 C7 C0 0D 20 40 00 49 C7 C1
40 00 00 00 FF 15 3E 20 00 00 48 C7 C1 00 00 00
00 FF 15 61 20 00 00 48 83 C4 20 5D C3 00 00 00
........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Хочется отметить что всего лишь 4 строки реального кода содержат весь наш код на ассемблере. А все остальное нули что бы набрать FileAlignment. Последней строкой заполненной нулями будет 0x000003F0, после идет 0x00000400, но это будет уже следующий блок. Итого в файле уже 1024 байта, наша программа весит уже целый Килобайт! Осталось совсем немного и ее можно будет запустить.
Секция .rdata
Это, пожалуй, самая простая секция. Мы просто положим сюда две строки добив нулями до 512 байт.
.rdata
0400 "Hello World!\0"
040D "MyApp\0"
(6) RAW .rdata section (Offset 0x00000400-0x00000600)
48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 4D 79 41
70 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00
........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Секция .idata
Ну вот осталась последняя секция, которая описывает импортируемые функции из библиотек.
Первое что нас ждет новая структура IMAGE_IMPORT_DESCRIPTOR
Структура IMAGE_IMPORT_DESCRIPTOR
Struct IMAGE_IMPORT_DESCRIPTOR
{
u32 OriginalFirstThunk (INT)
u32 TimeDateStamp
u32 ForwarderChain
u32 Name
u32 FirstThunk (IAT)
}
Описание IMAGE_IMPORT_DESCRIPTOR
OriginalFirstThunk — Адрес указывает на список имен импортируемых функций, он же Import Name Table (INT)
Name — Адрес, указывающий на название библиотеки
FirstThunk — Адрес указывает на список адресов импортируемых функций, он же Import Address Table (IAT)
Для начала нам нужно добавить 2 импортируемых библиотеки. Напомним:
func MessageBoxA(u32 handle, PChar text, PChar caption, u32 type) i32 ['user32.dll']
func ExitProcess(u32 code) ['kernel32.dll']
(7) RAW IMAGE_IMPORT_DESCRIPTOR (Offset 0x00000600)
58 30 00 00 00 00 00 00 00 00 00 00 3C 30 00 00
68 30 00 00 88 30 00 00 00 00 00 00 00 00 00 00
48 30 00 00 98 30 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
У нас используется 2 библиотеки, а что бы сказать что мы закончили их перечислять. Последняя структура заполняется нулями.
INT | Time | Forward | Name | IAT
--------+--------+----------+--------+--------
0x3058 | 0x0 | 0x0 | 0x303C | 0x3068
0x3088 | 0x0 | 0x0 | 0x3048 | 0x3098
0x0000 | 0x0 | 0x0 | 0x0000 | 0x0000
Теперь добавим имена самих библиотек:
Имена библиотек
063С "user32.dll\0"
0648 "kernel32.dll\0"
(8) RAW имена библиотек (Offset 0x0000063С)
75 73 65 72
33 32 2E 64 6C 6C 00 00 6B 65 72 6E 65 6C 33 32
2E 64 6C 6C 00 00 00 00
Далее опишем библиотеку user32:
(9) RAW user32.dll (Offset 0x00000658)
78 30 00 00 00 00 00 00
00 00 00 00 00 00 00 00 78 30 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 4D 65 73 73 61 67
65 42 6F 78 41 00 00 00
Поле Name первой библиотеки указывает на 0x303C если мы посмотрим чуть выше, то увидим что по адресу 0x063C находится библиотека «user32.dll\0».
Подсказка, вспомните что секция .idata соответствует смещению в файле 0x0600, а в памяти 0x3000. Для первой библиотеки INT равен 3058, значит в файле это будет смещение 0x0658. По этому адресу видим запись 0x3078 и вторую нулевую. Означающую конец списка. 3078 ссылается на 0x0678 это RAW-строка
«00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00»
Первые 2 байта нас не интересуют и равны нулю. А вот дальше идет строка с названием функции, заканчивающаяся нулем. То есть мы можем представить её как «\0\0MessageBoxA\0».
При этом IAT ссылается на аналогичную таблице IAT структуру, но только в нее при запуске программы будут загружены адреса функций. Например, для первой записи 0x3068 в памяти будет значение отличное от значения 0x0668 в файле. Там будет адрес функции MessageBoxA загруженный системой к которому мы и будем обращаться через вызов call в коде программы.
И последний кусочек пазла, библиотека kernel32. И не забываем добить нулями до SectionAlignment.
(10) RAW kernel32.dll (Offset 0x00000688-0x00000800)
A8 30 00 00 00 00 00 00
00 00 00 00 00 00 00 00 A8 30 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 45 78 69 74 50 72
6F 63 65 73 73 00 00 00 00 00 00 00 00 00 00 00
........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Проверяем что Far смог корректно определить какие функции мы импортировали:
Отлично! Все нормально определилось, значит теперь наш файл готов к запуску.
Барабанная дробь…
Финал
Поздравляю, мы справились!
Файл занимает 2 Кб = Заголовки 512 байт + 3 секции по 512 байт.
Число 512(0x200) ни что иное, как FileAlignment, который мы указали в заголовке нашей программы.
Дополнительно:
Если хочется вникнуть чуть глубже, можно заменить надпись «Hello World!» на что-нибудь другое, только не забудьте изменить адрес строки в коде программы (секция .text). Адрес в памяти 0x00402000, но в файле будет обратный порядок байт 00 20 40 00.
Или квест чуть сложнее. Добавить в код вызов ещё одного MessageBox. Для этого придется скопировать предыдущий вызов, и пересчитать в нем относительный адрес (0x3068 — RIP).
Заключение
Статья получилась достаточно скомканной, ей бы, конечно, состоять из 3 отдельных частей: Заголовки, Программа, Таблица импорта.
Если кто-то собрал свой exe значит мой труд был не напрасен.
Думаю в скором времени создать ELF файл похожим образом, интересна ли будет такая статья?)
Ссылки:
- Intel 64 and IA-32 Architectures Software Developer’s Manuals
Руководство по командам и архитектуре процессора. - PE (Portable Executable): На странных берегах
Отличная статья о структуре exe файлов. - Хранилище документации Майкрософт
Тут можно найти любую информацию по заголовкам, структурам, типам и их описание
Загрузить PDF
Загрузить PDF
Из данной статьи вы узнаете, как создать простейший EXE-файл на компьютере с Windows, а также как создать exe-контейнер, с помощью которого исполняемый файл можно запустить на другом компьютере. EXE-файлы используются для того, чтобы устанавливать программы или добавлять файлы на компьютере под управлением Windows. Чтобы создать EXE-файл, необходимо воспользоваться системной утилитой IExpress.
-
Для этого щелкните по логотипу Windows в нижнем левом углу экрана.
-
Так вы найдете программу «Блокнот».
-
Он выглядит как голубой блокнот и находится в верхней части меню «Пуск».
-
Введите код построчно или скопируйте и вставьте его в Блокнот (если у вас уже есть готовый код).
- Если вы не знаете, как писать компьютерные программы, попросите об этом другого человека.
- Также программные коды простейших EXE-файлов можно найти в интернете.
-
Это меню находится в верхнем левом углу окна Блокнота. Откроется выпадающее меню.
-
Эта опция находится в выпадающем меню «Файл».
-
Вы найдете его в нижней части окна.
- Текущей опцией меню «Тип файла» должна быть опция «Текстовые документы (*.txt)».
-
Эта опция находится в выпадающем меню.
-
В строке «Имя файла» введите имя, а затем введите расширение .exe. Так файл будет сохранен в качестве EXE-файла.
- Например, если файл будет называться «bananas» (бананы), введите bananas.exe.
-
Для этого щелкните по соответствующей папке в левой части окна.
-
Эта кнопка находится в правом нижнем углу экрана. Так EXE-файл будет сохранен в выбранной папке.
Реклама
-
Для этого щелкните по логотипу Windows в нижнем левом углу экрана.
-
Так вы найдете эту утилиту.
- Слово iexpress введите полностью.
-
Он выглядит как серый шкаф и находится в верхней части меню «Пуск».
-
Поставьте флажок у опции «Create new Self Extraction Directive file» (Создать новый самораспаковывающийся файл). Она находится посередине страницы. Эта опция должна быть отмечена по умолчанию; в противном случае выделите ее.
-
Эта кнопка находится в нижнем правом углу окна.
-
Она находится посередине страницы.
-
-
Сделайте это в текстовом поле посередине окна, а затем нажмите «Далее».
-
Если вы хотите, чтобы пользователь подтвердил желание запустить EXE-файл, поставьте флажок у опции «Prompt user with» (Запрос у пользователя) и введите текст запроса о подтверждении. В противном случае нажмите «Далее».
- Когда пользователь запустит ЕХЕ-файл, откроется окно с введенным вами текстом (если вы выбрали опцию с запросом).
-
Если вы хотите, чтобы на экране отобразился текст лицензионного соглашения, поставьте флажок у опции «Display a license» (Показать лицензию), затем нажмите «Browse» (Обзор), чтобы выбрать документ с текстом лицензионного соглашения, а затем нажмите «Открыть». В противном случае нажмите «Далее».
-
Эта кнопка находится в нижней средней части окна. Откроется новое окно Проводника, в котором можно выбрать файлы, которые будут включены в установочный EXE-файл.
- Файлы, включенные в установочный EXE-файл, будут установлены, когда пользователь запустит EXE-файл.
-
Щелкните по папке с файлами в левой части окна Проводника, а затем выделите нужные файлы; для этого зажмите левую клавишу мыши и перетащите курсор над нужными файлами.
- Также файлы можно выделить по одному; для этого зажмите Ctrl и щелкните по каждому нужному файлу.
-
Эта кнопка находится в нижнем правом углу окна. Так файлы будут добавлены в установочный EXE-файл.
- Если нужно добавить больше файлов, еще раз нажмите «Add» (Добавить) и повторите описанный процесс.
-
-
Она находится в верхней части окна.
-
Такое сообщение появляется на экране по завершении процесса установки ЕХЕ-файла. Поставьте флажок у опции «Display message» (Показать сообщение), затем введите текст сообщения, а затем нажмите «Далее».
- Если вы не хотите, чтобы на экране отображалось заключительное сообщение, просто нажмите «Далее».
-
Это программа в EXE-файле, который был создан ранее. Нажмите «Browse» (Обзор), откройте папку с файлом, щелкните по нему и нажмите «Сохранить».
- Если хотите, поставьте флажок у опции «Hide File Extracting Process Animation from User» (Скрыть отображение процесса извлечения файлов), чтобы установочный файл работал без лишних визуальных эффектов.
-
Будет создан установочный EXE-файл. Время этого процесса зависит от количества файлов, которые вы включили в установочный EXE-файл.
-
Эта кнопка находится в нижней части окна. Установочный ЕХЕ-файл будет сохранен и готов к работе.
Реклама
Советы
- Чтобы запустить EXE-файл, установочный EXE-файл не нужен, но установочный EXE-файл установит EXE-файл и любые вспомогательные элементы (например, файл «ReadMe», папки и так далее).
Реклама
Предупреждения
- Если вы не знаете, как написать программный код EXE-файла, попросите об этом человека, который умеет программировать.
Реклама
Об этой статье
Эту страницу просматривали 186 028 раз.
Была ли эта статья полезной?
Все способы:
- Способы создания
- Способ 1: Visual Studio Community
- Способ 2: Инсталляторы
- Вопросы и ответы: 0
EXE является форматом, без которого не обходится ни одно программное обеспечение. Им выполняются все процессы запуска или установки программ. Он может представлять собой как полноценное приложение, так и быть его частью.
Способы создания
Существует два варианта создания EXE файла. Первый — это использование сред для программирования, а второй — применение специальных инсталляторов, при помощи которых создаются разные «репаки» и пакеты, устанавливаемые в один клик. Далее на примерах рассмотрим оба варианта.
Рассмотрим процесс создания простой программы на основе языка программирования «Visual С++» и ее компиляции в Visual Studio Community.
Скачать бесплатно Visual Studio Community с официального сайта
- Запускаем приложение, заходим в меню «Файл», после чего жмем по пункту «Создать», а затем в открывшемся перечне на «Проект».
- Открывается окно «Создание проекта», в котором нужно кликнуть сначала по надписи «Шаблоны», а потом «Visual С++». Далее выбираем «Консольное приложение Win32», задаем наименование и местоположение проекта. По умолчанию он сохраняется в рабочей директории Вижуал Студио Коммьюнити, в системной папке «Мои документы», но по желанию возможно выбрать другой каталог. По завершении настроек щелкаем «ОК».
- Запускается «Мастер настройки приложений Win32», в котором просто жмем «Далее».
- В следующем окне определяем параметры приложения. В частности, выбираем «Консольное приложение», а в поле «Дополнительные параметры» – «Пустой проект», сняв при этом галочку с «Предварительно скомпилированный заголовок».
- Запускается проект, в котором нужно добавить область для записи кода. Для этого во вкладке «Обозреватель решений» жмем правой кнопкой мыши по надписи «Файлы ресурсов». Появляется контекстное меню, в котором последовательно кликаем на «Добавить» и «Создать элемент».
- В открывшемся окошке «Добавить новый элемент» выбираем пункт «Файл С++». Далее задаем имя файла кода будущего приложения и его расширение «.с». Для изменения папки хранения нажимаем на «Обзор».
- Открывается обозреватель, в котором уточняем местоположение и нажимаем на «Выбор папки».
- В результате появляется вкладка с заголовком «Source.с», в которой происходит набор и редактирование текста кода.
- Далее необходимо скопировать текст кода и вставить в показанную на изображении область. В качестве примера возьмем следующее:
- Для сборки проекта кликаем на «Начать отладку» на выпадающем меню «Отладка». Можно просто нажать на клавишу «F5».
- После чего выскакивает уведомление, предупреждающее о том, что текущий проект устарел. Здесь необходимо нажать на «Да».
- По завершении компиляции приложение выводит окно консоли, в котором будет написано «Hello, World!».
- Созданный файл в формате EXE можем посмотреть при помощи Проводника Windows в папке проекта.
#include
#include
int main(int argc, char* argv[]) {
printf("Hello, World!");
_getch();
return 0;
}
Примечание: Текст кода выше — это просто пример. Вместо него необходимо использовать собственный код для создания программы на языке «Visual С++».
Способ 2: Инсталляторы
Для автоматизации процесса установки ПО все более широкую популярность завоевывают так называемые инсталляторы. С их помощью создается софт, основной задачей которого является упрощение процесса развертывания ПО на компьютере. Рассмотрим процесс создания EXE файла на примере Smart Install Maker.
Скачать Smart Install Maker с официального сайта
- Запускаем программу и во вкладке «Информация» редактируем наименование будущего приложения. В поле «Сохранить как» нажимаем по значку папки для определения местоположения, куда будет сохранен выходной файл.
- Открывается Проводник, в котором выбираем желаемое расположение и кликаем «Сохранить».
- Переходим во вкладку «Файлы», где необходимо добавить файлы, из которых будет потом собран пакет. Это осуществляется путем нажатия на пиктограмму «+» в нижней части интерфейса. Возможно также добавить целую директорию, для чего надо нажать на значок, на котором изображена папка с плюсом.
- Далее открывается окно выбора файлов, где нужно щелкнуть на значок в виде папки.
- В открывшемся обозревателе обозначаем нужное приложение (в нашем случае — это «Torrent», у вас же может быть любой другой) и кликаем на «Открыть».
- В результате в окне «Добавить запись» отображается файл с указанием пути его расположения. Остальные опции оставляем по умолчанию и жмем «ОК».
- Происходит процедура добавления исходного объекта в приложение и в специальной области софта появляется соответствующая запись.
- Далее нажимаем «Требования» и открывается вкладка, где нужно отметить список поддерживаемых операционных систем. Оставляем галочки на полях «Windows XP» и все, что идут ниже нее. На всех остальных полях оставляем рекомендуемые значения.
- Затем открываем вкладку «Диалоги», кликнув по соответствующей надписи в левой части интерфейса. Здесь все оставляем по умолчанию. Для того чтобы инсталляция проходила в фоновом режиме, можно выставить галочку в поле «Скрытая установка».
- По окончании всех настроек запускаем компиляцию, нажав на пиктограмму со стрелкой вниз.
- Происходит указанный процесс и в окошке выводится его текущий статус. По завершении компиляции можно провести тестирование созданного пакета или вовсе закрыть окно, щелкнув соответствующие кнопки.
- Скомпилированное программное обеспечение можно найти при помощи Проводника Windows в той папке, который был указан при настройке.
Таким образом, в данной статье мы выяснили, что EXE файл может создаваться как при помощи специализированных сред разработки программ, например Visual Studio Community, так и специальными инсталляторами, к примеру, Smart Install Maker.
Наша группа в TelegramПолезные советы и помощь
Visual Studio is a powerful development tool that provides an environment for creating a wide range of applications. One of its notable features is the ability to create Setup.exe files, which greatly simplifies the deployment process for end-users.
In this article, we will guide you through a step-by-step tutorial on how to create a Setup.exe file for a Windows Forms application using Visual Studio. Additionally, we will explore a different approach using Advanced Installer, an alternative tool for software packaging and installation.
Add Microsoft Visual Studio Installer Projects extension
To get started with this tutorial, you will need the Microsoft Visual Studio Installer Projects extension. This extension allows us to create the Setup.exe for our application.
To install the extension, follow these steps:
1. In Visual Studio navigate to Extensions → Manage extensions.
2. In the Online section, search for the extension and download it.
3. The extension will be installed after you close the Visual Studio IDE. Once the installation is complete, you can reopen Visual Studio and continue with the tutorial.
Add the setup project to your application
Now that you have successfully installed the extension and assuming that you’ve created the Windows Forms application, we can proceed to add the Setup Project:
1. Right-click on the project solution → Add → New Project.
2. From the templates list, choose Setup Project.
3. Provide a name for the project and then create it. Then, you should see it in the Solution Explorer.
Add the project output
Once the Setup Project is created, the next step is to include the necessary files and dependencies of your application within the project. To do this, follow the steps below:
1. Right-click on the Setup Project, and navigate to View → File System.
2. Right-click on the Application Folder, and select Add → Project Output.
3. In the opened dialog, choose the Primary output option.
Edit the product details
To customize the settings and information associated with your software, edit the product details of your application package. To do this, select the Setup Project and navigate to the Properties view. Here, you will find various fields that you can modify according to your scenario.
Create shortcuts for the application
Application shortcuts offer numerous benefits, including easy access and improved usability. By providing shortcuts, users can quickly access your application, enhancing their overall experience. Moreover, shortcuts present a branding opportunity by increasing the recognition of your application.
Here is how you can create a desktop shortcut:
1. Right-click on the setup project → View → File System.
2. Navigate to the Application Folder in the left view.
3. In the right view, right-click on the Primary output from MyApp → Create Shortcut to Primary output from MyApp.
4. Set a name for the shortcut and then add it in the User’s Desktop folder from the left view. By doing this, you can guarantee that the shortcut will be placed directly on the user’s desktop once the application installation process is complete.
To create a shortcut for the Program Menu, follow the above steps, but instead of placing the shortcut in the User’s Desktop folder, make sure to place it in the Program Menu folder.
Customize a shortcut with an icon
If you want to customize a shortcut with an icon, the first step is to import the icon to the Application Folder:
1. Right-click on the Application Folder → Add → New file.
2. In the opened dialog, browse for the icon you want to be displayed for the shortcut.
3. Once the icon is added, select the shortcut for which you want to add the icon.
4. Go to the Properties view → Icon field.
5. Browse to the Application Folder and select the icon.
Add launch conditions
Launch conditions are used to verify the compatibility of an application with a target system, determining whether it can be successfully installed. By defining proper launch conditions, you ensure your application will run effectively and smoothly.
To add launch conditions to your installation package, follow these steps:
1. Right-click on the setup project → View → Launch Conditions.
2. Right-click on the Launch Conditions folder → Add Launch Condition.
3. Once the condition is created, navigate to the Properties view and specify the condition.
For example, we want to install the package exclusively on 32-bit (x86) machines, so we can use the following condition: VersionNT64 = «». This condition checks if the VersionNT64 property is undefined, which indicates that the target machine is not a 64-bit operating system. Additionally, we have the option to include a message to be displayed if the condition is not fulfilled.
Install Prerequisites
Prerequisites play a significant role in installation packages as they ensure that a target machine has all the necessary resources for software to run properly. To add Prerequisites, follow the next steps:
1. Right-click on the setup project and select Properties.
2. Click on the Prerequisites button in the opened dialog.
3. In the Prerequisite dialog, you can check the prerequisite you want to install and specify the install location for each of them.
Add Registry keys
Including registry keys to an installation package is important for proper functioning of the application. This ensures the application will operate according to its specifications.
— To add a registry key, begin by navigating to go to the Registry view: right-click on the setup project → View → Registry.
— To create a new key, right-click on a desired hive, select New Key and set a name for the key.
— To add a key value, right-click on the created key and choose the value type. Then, proceed to the properties view to configure the key accordingly.
Install the application
Once you have configured the setup project according to your requirements, you need build it to generate the Setup.exe. So, navigate to the Debug or Release folder within the Setup Project (based on your selected mode) and run the package to install the application.
How to use the Advanced Installer extension for Visual Studio to create a Setup.exe
If you are looking for a more efficient solution for creating installation packages, you can try the Advanced Installer extension. The extension is available on the Visual Studio Marketplace and to install it just follow the next steps:
1. In Visual Studio navigate to Extensions → Manage extensions.
2. In the Online section, search for the Advanced Installer for Visual Studio extension and download it.
3. The extension will be installed after you close the Visual Studio IDE. Therefore, close it, and once the installation is complete, reopen it.
When creating the installer project in Visual Studio, you need both the extension and Advanced Installer installed on your machine. If you create the installer project and Advanced Installer is not already installed, no need to worry. Simply go ahead and create the project, and once it is created, the extension can download and install Advanced Installer.
Assuming you’ve already created a Windows Forms application, let’s see how to create the installer project by using the Advanced Installer Visual Studio extension.
1. Right-click on the project solution → Add → New Project.
2. From the templates list, choose Advanced Installer Project.
3. Provide a name for the project and then create it.
The Advanced Installer Project includes a viewer that allows you to quickly access and modify essential information about your installer. To access this viewer, select the project’s .AIP file in the Solution Explorer.
To unlock additional features, you need to open the installer project in Advanced Installer by using the [Edit in Advanced Installer] button, located in the bottom left corner of the viewer.
To create the package as a single EXE file with all resources included in it, navigate to the Builds page. Here, locate the Package type sections and check the Single EXE setup (resources inside) option.
After making the edits in Advanced Installer, save the project and exit.
When returning to Visual Studio, you will be prompted to reload the Advanced Installer Project files, ensuring that all your modifications are included.
Next, build the installer project to create the Setup.exe file. Navigate to the installer project folder to find the file and run it to install the application.
Conclusion
Visual Studio provides developers with a robust platform for creating a wide range of applications, including the ability to generate Setup.exe files for simplified deployment. This article has guided you through a comprehensive tutorial, covering essential steps such as installing the necessary extensions, adding a setup project, customizing product details, creating shortcuts, incorporating registry keys, and adding launch conditions and prerequisites.
However, if you desire more advanced customization and comprehensive features, the Advanced Installer extension for Visual Studio offers an efficient solution. With its intuitive viewer and additional functionalities, you can fine-tune your installer project, including creating a single EXE file with embedded resources.
Find here more details about Advanced Installer’s Complete Integration with Microsoft Visual Studio.
Written by
Renato Ivanescu
Renato is a technical writer for Advanced Installer and an assistant professor at the University of Craiova. He is currently a PhD. student, and computers and information technology are his areas of interest. He loves innovation and takes on big challenges.
Popular Articles
C помощью Python можно автоматизировать многие задачи — достаточно написать скрипт и запустить его на терминале. Python — самый быстрый и простой язык сценариев. Если вы не хотите каждый день открывать терминал и запускать одну и ту же команду, можно создать исполняемый файл. Создать exe-файл в Python можно с помощью PyInstaller и других библиотек.
Создание exe-файла с помощью библиотеки PyInstaller
PyInstaller — популярный инструмент для преобразования скриптов Python в автономные исполняемые файлы (.exe) в Windows.
Установка PyInstaller
PyInstaller доступен в виде обычного пакета Python. Архивы с исходными текстами для выпущенных версий доступны в PyPI, но инструмент проще установить с помощью pip:
C:\> pip install pyinstaller
Это поможет обновить PyInstaller до последней версии:
C:\> pip install —upgrade pyinstaller
Установка текущей версии разработки:
C:\> pip install https://github.com/pyinstaller/pyinstaller/tarball/
Алгоритм действия
В качестве примера возьмем один файл на Python, чтобы подробно объяснить этапы упаковки. Рассмотрим Python 3.11.0 после установки aspose.cells.
1.Создайте файл с именем example.py:
import os
from jpype import *
__cells_jar_dir__ = os.path.dirname(__file__)
addClassPath(os.path.join(__cells_jar_dir__, "aspose-cells-23.1.jar"))
addClassPath(os.path.join(__cells_jar_dir__, "bcprov-jdk15on-160.jar"))
addClassPath(os.path.join(__cells_jar_dir__, "bcpkix-jdk15on-1.60.jar"))
addClassPath(os.path.join(__cells_jar_dir__, "JavaClassBridge.jar"))
import jpype
import asposecells
jpype.startJVM()
from asposecells.api import Workbook, FileFormatType, CellsHelper
print(CellsHelper.getVersion())
workbook = Workbook(FileFormatType.XLSX)
workbook.getWorksheets().get(0).getCells().get("A1").putValue("Hello World")
workbook.save("output.xlsx")
jpype.shutdownJVM()
py
2.Создайте папку c:\app и копию example.py c:\app.
3. Откройте командную строку и выполните команду example.py pyinstaller
C:\app> pyinstaller example.py
4. Скопируйте файлы jar(aspose-cells-xxx.jar, bcprov-jdk15on-160.jar, bcpkix-jdk15on-1.60.jar, JavaClassBridge.jar. Они находятся в папке C:\Python311\Lib\site-packages\asposecells\lib) в c:\app.
5. Отредактируйте файл с суффиксом spec, чтобы добавить раздел данных, подобный example.spec.
6. Запустите pyinstaller example.spec в окне командной строки.
C:\app> pyinstaller example.spec
7. Переключите каталог на C:\app\dist\example, и вы найдете файл example.exe.
Создание exe-файла с помощью библиотеки auto-py-to-exe
С помощью auto-py-to-exe, проекта Брента Воллебрегта можно создавать собственные исполняемые приложения на Python. Под графическим интерфейсом находится PyInstaller, приложение на базе терминала для создания исполняемых файлов на Python для Windows, Mac и Linux.
Установка auto-py-to-exe
- Откройте командную строку, выполнив поиск CMD.
- Используйте pip-менеджер пакетов Python для установки auto-py-to-exe:
pip install auto-py-to-exe
Алгоритм действия
- Откройте командную строку, выполнив поиск CMD.
- Запустите auto-py-to-exe из командной строки.
- Нажмите на кнопку Browse и перейдите к нашему примеру файла на Python.
- Настройте приложение на использование одного файла. Это позволит объединить приложение и поддерживающие его библиотеки Python в один исполняемый файл.
- Настройте приложение на консольное управление. Так можно увидеть все ошибки, выводимые в командную строку. Как только мы будем уверены, что приложение работает правильно, можно настроить его на оконное управление.
- Нажмите на раскрывающийся список параметров и выберите значок для вашего приложения. Это необязательный шаг, но он повышает качество приложения.
- Нажмите «Дополнительно», в поле «Имя» введите название вашего приложения. Например, «Программа запуска приложений».
- Прокрутите страницу вниз и нажмите «Преобразовать. PY в .EXE», чтобы начать процесс. Это займет пару минут.
- Нажмите на кнопку Open Output Folder, чтобы открыть папку, содержащую приложение.
- Дважды щелкните по значку, чтобы запустить приложение.
Создание exe-файла с помощью библиотеки cx_Freeze
Преобразовать скрипт Python в автономный исполняемый файл (.exe) можно и с помощью «cx_Freeze».
Установка cx_Freeze
Установить его легко с помощью pip:
Алгоритм действия
Создайте установочный скрипт (например, «setup.py») в том же каталоге, что и ваш скрипт на Python. Этот скрипт предоставит конфигурацию для «cx_Freeze». Простой пример проекта:
from cx_Freeze import setup, Executable
setup(
name="YourAppName",
version="1.0",
description="Your application description",
executables=[Executable("your_script.py")],
)
py
Замените «YourAppName» и «Your application description» на название и описание вашего приложения, а «your_script.py» — на название вашего скрипта на Python.
Откройте терминал, перейдите в каталог, содержащий ваш скрипт на Python и файл «setup.py»:
python setup.py build
Это создаст каталог «build», содержащий исполняемый файл.
После выполнения команды «build» вы можете найти исполняемый файл в каталоге `build`. Он будет находиться в подкаталоге с названием вашей операционной системы (например, «build\exe.win-amd64-3.8» для 64-разрядного исполняемого файла Windows).
Теперь у вас должен быть отдельный исполняемый файл, который вы можете распространять и запускать на компьютере без установленного Python. Помните, что если у вашего скрипта есть внешние зависимости, то может потребоваться включить их в сценарий «setup.py».
Создание exe-файла с помощью библиотеки py2exe
Преобразование интерпретируемого языкового кода в исполняемый файл еще называют замораживанием. Для этого можно использовать модуль py2exe.
Установка py2exe
Чтобы использовать модуль py2exe, нужно его установить. Сделаем это с помощью pip:
Алгоритм действия
Напишем программу, которая будет выводить текст на консоль:
import math
print("Hannibal ante Portas")
print(factorial(4))
py
Запустим следующие команды в командной строке Windows, чтобы создать каталог. Переместим код, который мы уже написали в указанный каталог и выполним его:
$ mkdir exampDir
$ move example.py exampDir
$ cd exampDir
$ py example.py
py
Всегда проверяйте скрипты, прежде чем превращать их в исполняемые файлы, чтобы убедиться, что если и есть ошибка, то она вызвана не исходным кодом.
Создайте другой файл с именем setup.py в той же папке. Здесь мы сохраним подробную информацию о конфигурации, о том, как мы хотим скомпилировать нашу программу. Пока мы просто добавим в него пару строк кода:
from distutils.core import setup # Need this to handle modules
import py2exe
import math # We have to import all modules used in our program
setup(console=['example.py']) # Calls setup function to indicate that we're dealing with a single console application
py
Теперь откройте командную строку от имени администратора и перейдите в каталог, чтобы запустить файл setup.py:
$ cd exampDir
$ python setup.py py2exe
running py2exe
*** searching for required modules ***
*** parsing results ***
…