Как создать файл для программирования

Обновлено: 06.07.2024

Python — это популярный язык программирования, который отличается надежностью, гибкостью и простотой освоения. Его можно бесплатно использовать на любых операционных системах. Он поддерживается широким сообществом разработчиков. Кроме того, для него доступно множество бесплатных библиотек. Этот язык поддерживает все виды разработки, в том числе веб-приложения, веб-службы, классические приложения, создание сценариев и научные вычисления. Поэтому Python используется множеством университетов, ученых, разработчиков-любителей и профессиональных разработчиков.

Visual Studio обеспечивает первоклассную поддержку языка Python. В этом учебнике рассматриваются перечисленные ниже действия.

Предварительные требования

  • Visual Studio 2017 с установленной рабочей нагрузкой Python. См. дополнительные сведения об использовании Python в Visual Studio.
  • Visual Studio 2019 с установленной рабочей нагрузкой Python. См. дополнительные сведения об использовании Python в Visual Studio.
  • Visual Studio 2022 с установленной рабочей нагрузкой Python. См. дополнительные сведения об использовании Python в Visual Studio.

Вы также можете использовать более раннюю версию Visual Studio с установленным подключаемым модулем Инструменты Python для Visual Studio. См. руководство по установке поддержки Python в Visual Studio.

Шаг 1. Создание проекта Python

С помощью проекта в Visual Studio производится управление всеми файлами, составляющими приложение. К файлам приложения можно отнести исходный код, ресурсы, конфигурации. Проект формализует и поддерживает связи между всеми файлами проекта. Проект также управляет внешними ресурсами, которые являются общими для нескольких проектов. Проект позволяет приложению легко расширяться и увеличивать свой размер. Кроме того, использовать проекты гораздо проще, чем вручную управлять связями в специальных папках, сценариях, текстовых файлах и памяти.

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

В Visual Studio выберите Файл > Создать > Проект (CTRL+SHIFT+N), после чего откроется диалоговое окно Создание проекта. В нем можно просмотреть шаблоны для разных языков, после чего выбрать один из них для вашего проекта и указать, куда среда Visual Studio должна поместить файлы.

Чтобы просмотреть шаблоны Python, выберите Установленные > Python в области слева или выполните поиск по слову "Python". Поиск — это отличный способ найти шаблон, если вы не помните, где он находится в дереве языков.

Снимок экрана: диалоговое окно "Создать проект" с шаблонами проектов Python.

Поддержка Python в Visual Studio включает в себя несколько шаблонов проектов, включая веб-приложения на платформах Bottle, Flask и Django. Однако для целей данного пошагового руководства мы начнем с пустого проекта.

Выберите шаблон Приложение Python, укажите имя проекта и нажмите кнопку ОК.

Через несколько секунд в окне обозревателя решений Visual Studio (1) будет показана структура проекта. Файл кода по умолчанию откроется в редакторе (2). Кроме того, откроется окно Свойства (3), в котором приводятся дополнительные сведения для элемента, выбранного в обозревателе решений, включая его точное расположение на диске.

Снимок экрана: открытие нового проекта в Visual Studio.

Потратьте несколько минут на знакомство с обозревателем решений, который служит для просмотра файлов и папок проекта.

Снимок экрана: развернутый Обозреватель решений с функциональными возможностями.

(1) Полужирным шрифтом выделен ваш проект, имя которого вы указали в окне Создание проекта. На диске этот проект представлен файлом .pyproj в папке проекта.

(2) На верхнем уровне находится решение, имя которого по умолчанию совпадает с именем проекта. Решение, представленное на диске файлом SLN, является контейнером для одного или нескольких связанных проектов. Например, если вы создаете расширение C++ для приложения Python, этот проект C++ может входить в то же решение. Решение также может включать в себя проект веб-службы и проекты специальных тестовых программ.

(3) В проекте можно увидеть файлы исходного кода. В нашем примере это один файл .py. При выборе файла его свойства приводятся в окне Свойства. Если дважды щелкнуть файл, он откроется в соответствующем средстве.

(4) Кроме того, в проекте есть узел Окружения Python. Если развернуть его, можно увидеть доступные интерпретаторы Python. Развернув узел интерпретатора, вы увидите библиотеки, установленные в этой среде (5).

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

В Visual Studio последовательно выберите Файл > Создать > Проект или нажмите комбинацию клавиш CTRL+SHIFT+N. Откроется экран Создать проект, где можно искать и просматривать шаблоны на разных языках.

Чтобы просмотреть шаблоны Python, выполните поиск по слову python. Поиск — это отличный способ найти шаблон, если вы не помните, где он находится в дереве языков.

Снимок экрана: диалоговое окно "Создать проект" с шаблонами проектов Python.

Поддержка Python в Visual Studio включает в себя несколько шаблонов проектов, таких как веб-приложения на платформах Bottle, Flask и Django. Для целей этого учебника мы начнем работу с пустого проекта.

Выберите шаблон PythonConsoleApp и нажмите кнопку Далее.

На экране Настроить новый проект укажите имя и расположение файла проекта, а затем нажмите кнопку Создать.

Новый проект откроется в Visual Studio.

  • В окне Обозреватель решений Visual Studio будет показана структура проекта (1) .
  • Файл кода по умолчанию откроется в редакторе (2) .
  • Откроется окно Свойства, в котором приводятся дополнительные сведения для элемента, выбранного в Обозревателе решений, включая его точное расположение на диске (3) .

Снимок экрана: открытие нового проекта в Visual Studio.

Ознакомьтесь с Обозревателем решений, который можно использовать для просмотра файлов и папок проекта.

Снимок экрана: развернутый Обозреватель решений с функциональными возможностями.

На верхнем уровне находится решение, имя которого по умолчанию совпадает с именем проекта (1).

Решение, представленное на диске файлом SLN, является контейнером для одного или нескольких связанных проектов. Например, если вы создаете расширение C++ для приложения Python, этот проект C++ может входить в то же решение. Решение также может включать в себя проект веб-службы и проекты специальных тестовых программ.

Ваш проект, имя которого вы указали в диалоговом окне Новый проект, будет выделен полужирным шрифтом (2) . На диске проект представлен файлом PYPROJ в папке проекта.

В проекте можно увидеть исходные файлы. В нашем примере это один файл PY ( (3) ). При выборе файла его свойства приводятся в окне Свойства. Если дважды щелкнуть файл, он откроется в соответствующем средстве.

Кроме того, в проекте есть узел Окружения Python ( (4) ). Разверните узел, чтобы отобразить доступные интерпретаторы Python.

Развернув узел интерпретатора, вы увидите библиотеки, установленные в этой среде (5) .

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

Соавтор(ы): Archana Ramamoorthy, MS. Арчана Рамамурти — технический директор Workday (Северная Америка). Высококлассный специалист по продуктам, поборница безопасности, сторонница большего распространения интеграции на равных для всех условиях в индустрии технологий. Получила степень бакалавра в Университете SRM и магистра в Университете Дьюка. Работает в области продакт-менеджмента более восьми лет.

По мере того как технология становится все более и более доступной широкой публике, растет и потребность в программистах. Написание компьютерных кодов и программ, оно же кодинг (от английского «сoding») — это навык, который приобретается и совершенствуется на протяжении долгого времени, но даже самый опытный программист когда-то был новичком. Существует большое разнообразие языков программирования, которые великолепно подходят для начинающих программистов, вне зависимости от того, в какой сфере деятельности вы хотите применять ваши навыки (например, JavaScript довольно сложен, так что лучше начать с HTML или CSS). Узнайте, как научиться писать компьютерные программы, прочитав эту статью.

Изображение с названием Code Step 1

  • Выбирая язык, сконцентрируйтесь на том, в каких целях вы хотите создавать программные коды, и уже потом выбирайте начальный язык. К примеру, если вы хотите заниматься разработкой веб-сайтов, то вам следует начать с изучения HTML5, а затем дополнить его языками CSS, JavaScript и PHP. Если вы хотите создавать компьютерные программы, то начните изучать C ++ или любой другой основной язык программирования.
  • Если вы станете профессиональным программистом, то вы можете обнаружить, что никогда не используете язык, который вы изначально выучили, для своей работы. Вместо этого вы будете все время продолжать учить новые языки через документацию и эксперименты.

Изображение с названием Code Step 2

Технический директор Workday

Арчана Рамамурти — технический директор Workday (Северная Америка). Высококлассный специалист по продуктам, поборница безопасности, сторонница большего распространения интеграции на равных для всех условиях в индустрии технологий. Получила степень бакалавра в Университете SRM и магистра в Университете Дьюка. Работает в области продакт-менеджмента более восьми лет. Archana Ramamoorthy, MS
Технический директор Workday

Наш специалист делится своей историей:: «Я пришла к написанию кодов, не зная ничего ни о компьютерном дизайне, ни о программировании. Когда я захотела научиться писать программы, я начала с чтения книг по языку и с использования информации из интернета. Сегодня в мире доступно так много ресурсов, что научиться новым навыкам очень легко!»

Самоизоляция это отличное время приступить к тому, что требует много времени и сил. Поэтому я решил заняться тем, чем всегда хотел — написать свой компилятор.

Сейчас он способен собрать Hello World, но в этой статье я хочу рассказать не про парсинг и внутреннее устройство компилятора, а про такую важную часть как побайтовая сборка exe файла.

Начало

Хотите спойлер? Наша программа будет занимать 2048 байт.

Обычно работа с exe файлами заключается в изучении или модификации их структуры. Сами же исполняемые файлы при этом формируют компиляторы, и этот процесс кажется немного магическим для разработчиков.

Для сборки нашей программы нам потребуется любой HEX редактор (лично я использовал HxD).

Для старта возьмем псевдокод:

Первые две строки указывают на функции импортируемые из библиотек WinAPI. Функция MessageBoxA выводит диалоговое окно с нашим текстом, а ExitProcess сообщает системе о завершении программы.
Рассматривать отдельно функцию main нет смысла, так как в ней используются функции, описанные выше.

DOS Header

Для начала нам нужно сформировать корректный DOS Header, это заголовок для DOS программ и влиять на запуск exe под Windows не должен.

Более-менее важные поля я отметил, остальные заполнены нулями.

Самое главное, что этот заголовок содержит поле e_magic означающее, что это исполняемый файл, и e_lfanew — указывающее на смещение PE-заголовка от начала файла (в нашем файле это смещение равно 0x80 = 128 байт).

Отлично, теперь, когда нам известна структура заголовка DOS Header запишем ее в наш файл.




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

Поэтому для удобства в первой скобке каждого блока указан порядок добавления в файл, а в последней смещение в файле (Offset) по которому должен располагаться данный блок.

Например, первый блок мы вставляем по смещению 0x00000000, и он займет 64 байта (0x40 в 16-ричной системе), следующий блок мы будем вставлять уже по этому смещению 0x00000040 и т.д.

Готово, первые 64 байта записали. Теперь нужно добавить еще 64, это так называемый DOS Stub (Заглушка). Во время запуска из-под DOS, она должна уведомить пользователя что программа не предназначена для работы в этом режиме.

Но в целом, это маленькая программа под DOS которая выводит строку и выходит из программы.
Запишем наш Stub в файл и рассмотрим его детальнее.




А теперь этот же код, но уже в дизассемблированном виде

Это работает так: сначала заглушка выводит строку о том, что программа не может быть запущена, а затем выходит из программы с кодом 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.

Разберемся что хранится в этой структуре:

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).

IMAGE_DATA_DIRECTORY — массив записей о каталогах. В теории его можно уменьшить, сэкономив пару байт, но вроде как все описывают все 16 полей даже если они не нужны. А теперь чуть подробнее.

У каждого каталога есть свой номер, который описывает, где хранится его содержимое. Пример:
Export(0) — Содержит ссылку на сегмент который хранит экспортируемые функции. Для нас это было бы актуально если бы мы создавали DLL. Как это примерно должно работать можно посмотреть на примере следующего каталога.

Import(1) — Этот каталог указывает на сегмент с импортируемыми функциями из других DLL. В нашем случае значения VirtualAddress = 0x3000 и Size = 0xB8. Это единственный каталог, который мы опишем.

Resource(2) — Каталог с ресурсами программы (Изображения, Текст, Файлы и т.д.)
Значения других каталогов можно посмотреть в документации.

Теперь, когда мы посмотрели из чего состоит NT-заголовок, запишем и его в файл по аналогии с остальными по адресу 0x80.

В результате получаем вот такой вид IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER64 и IMAGE_DATA_DIRECTORY заголовков:




Далее описываем все секции нашего приложения согласно структуре 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. Это полный размер нашей программы в виртуальной памяти. Вероятно, используется для выделения места под программу в памяти.

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 и запись будет начинаться с середины файла.




Следующий адрес для записи будет 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

Конкретно для этой программы первые 3 строки, ровно, как и 3 последние не обязательны.
Последние 3 даже не будут исполнены, так как выход из программы произойдет еще на второй функции call.

Но скажем так, если бы это была не функция main, а подфункция следовало бы сделать именно так.

А вот первые 3 в данном случае хоть и не обязательны, но желательны. Например, если бы мы использовали не MessageBoxA, а printf то без этих строк получили бы ошибку.

Согласно соглашению о вызовах для 64-разрядных систем MSDN, первые 4 параметра передаются в регистрах RCX, RDX, R8, R9. Если они туда помещаются и не являются, например числом с плавающей точкой. А остальные передаются через стек.

По идее если мы передаем 2 аргумента функции, то должны передать их через регистры и зарезервировать под них два места в стеке, что бы при необходимости функция могла скинуть регистры в стек. Так же мы не должны рассчитывать, что нам вернут эти регистры в исходном состоянии.

Так вот проблема функции printf заключается в том, что, если мы передаем ей всего 1 аргумент, она все равно перезапишет все 4 места в стеке, хотя вроде бы должна перезаписать только одно, по количеству аргументов.

Поэтому если не хотите, чтобы программа себя странно вела, всегда резервируйте как минимум 8 байт * 4 аргумента = 32(0x20) байт, если передаете функции хотя бы 1 аргумент.

Рассмотрим блок кода с вызовами функций


Сначала мы передаем наши аргументы:

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.

Именно эти смещения мы и видели в командах ассемблера.


Запишем в наш файл секцию .text, дополнив нулями до адреса 0x400:

(5) RAW .text section (Offset 0x00000200-0x00000400)
Хочется отметить что всего лишь 4 строки реального кода содержат весь наш код на ассемблере. А все остальное нули что бы набрать FileAlignment. Последней строкой заполненной нулями будет 0x000003F0, после идет 0x00000400, но это будет уже следующий блок. Итого в файле уже 1024 байта, наша программа весит уже целый Килобайт! Осталось совсем немного и ее можно будет запустить.



Секция .rdata

Это, пожалуй, самая простая секция. Мы просто положим сюда две строки добив нулями до 512 байт.

(6) RAW .rdata section (Offset 0x00000400-0x00000600)

Секция .idata

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

Первое что нас ждет новая структура IMAGE_IMPORT_DESCRIPTOR

OriginalFirstThunk — Адрес указывает на список имен импортируемых функций, он же Import Name Table (INT)
Name — Адрес, указывающий на название библиотеки
FirstThunk — Адрес указывает на список адресов импортируемых функций, он же Import Address Table (IAT)

Для начала нам нужно добавить 2 импортируемых библиотеки. Напомним:

(7) RAW IMAGE_IMPORT_DESCRIPTOR (Offset 0x00000600)

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


Теперь добавим имена самих библиотек:

Далее опишем библиотеку user32:

Поле 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.




Проверяем что Far смог корректно определить какие функции мы импортировали:


Отлично! Все нормально определилось, значит теперь наш файл готов к запуску.
Барабанная дробь…

Финал


Поздравляю, мы справились!

Файл занимает 2 Кб = Заголовки 512 байт + 3 секции по 512 байт.

Число 512(0x200) ни что иное, как FileAlignment, который мы указали в заголовке нашей программы.

Дополнительно:
Если хочется вникнуть чуть глубже, можно заменить надпись «Hello World!» на что-нибудь другое, только не забудьте изменить адрес строки в коде программы (секция .text). Адрес в памяти 0x00402000, но в файле будет обратный порядок байт 00 20 40 00.

Или квест чуть сложнее. Добавить в код вызов ещё одного MessageBox. Для этого придется скопировать предыдущий вызов, и пересчитать в нем относительный адрес (0x3068 — RIP).

Заключение

Статья получилась достаточно скомканной, ей бы, конечно, состоять из 3 отдельных частей: Заголовки, Программа, Таблица импорта.

Если кто-то собрал свой exe значит мой труд был не напрасен.

Думаю в скором времени создать ELF файл похожим образом, интересна ли будет такая статья?)

Visual Studio можно использовать для создания стандартных программ на языке C++. Выполнив действия, описанные в этом пошаговом руководстве, можно создать проект, добавить новый файл в проект, изменить файл для добавления кода C++, а затем скомпилировать и запустить программу с помощью Visual Studio.

Можно ввести собственную программу на языке C++ или использовать один из примеров программ. Примером программы в этом пошаговом руководстве является консольное приложение. Это приложение использует set контейнер в стандартной библиотеке C++.

Если требуется соответствие определенной версии стандарта языка C++ (т. е. C++ 14 или C++ 17), используйте /std:c++14 /std:c++17 параметр компилятора или. (Visual Studio 2017 и более поздних версий.)

Предварительные требования

Для выполнения этого пошагового руководства читатель должен владеть основами языка C++.

Создание проекта и Добавление исходного файла

Приведенные ниже инструкции немного отличаются в зависимости от используемой версии Visual Studio. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. Он находится в верхней части оглавления на этой странице.

создание проекта C++ в Visual Studio 2019

В главном меню выберите Файл > Создать > Проект, чтобы открыть диалоговое окно Создание проекта.

В верхней части диалогового окна для параметра Язык выберите значение C++ , для параметра Платформа — значение Windows, а для параметра Тип проекта — значение Консоль.

В отфильтрованном списке типов проектов щелкните Консольное приложение, а затем нажмите кнопку Далее. На следующей странице введите имя проекта и при необходимости укажите расположение проекта.

создание проекта C++ в Visual Studio 2017

Создайте проект, наведя указатель на пункт создать в меню файл и выбрав пункт Project.

на панели типы проектов Visual C++ щелкните Windows рабочий стол, а затем выберите Windows консольное приложение.

Введите имя проекта. По умолчанию решение, содержащее проект, имеет то же имя, что и проект, но можно ввести другое имя. Можно также ввести другое расположение для проекта.

Чтобы создать проект, нажмите кнопку ОК .

создание проекта C++ в Visual Studio 2015

Создайте проект, наведя указатель на пункт создать в меню файл и выбрав пункт Project.

на панели типы проектов Visual C++ щелкните Windows рабочий стол, а затем выберите Windows консольное приложение.

в диалоговом окне создание Project разверните узел установленные > шаблоны > Visual C++, а затем выберите Win32. В центральной области выберите Консольное приложение Win32.

Введите имя проекта. По умолчанию решение, содержащее проект, имеет то же имя, что и проект, но можно ввести другое имя. Можно также ввести другое расположение для проекта.

Чтобы создать проект, нажмите кнопку ОК .

Завершите работу мастера приложений Win32.

Добавить новый исходный файл

Если Обозреватель решений не отображается, в меню вид выберите пункт Обозреватель решений.

Добавьте в проект новый исходный файл, как показано ниже.

В Обозреватель решений щелкните правой кнопкой мыши папку исходные файлы , наведите указатель на пункт добавить и выберите пункт новый элемент.

В узле код щелкните файл C++ (. cpp), введите имя файла и нажмите кнопку добавить.

cpp-файл появится в папке исходные файлы в обозреватель решений, а файл откроется в редакторе Visual Studio.

В файле в редакторе введите допустимую программу на языке C++, которая использует стандартную библиотеку C++, или скопируйте один из примеров программ и вставьте его в файл.

В меню Сборка выберите Собрать решение.

В меню Отладка выберите команду Запуск без отладки.

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

Читайте также: