MSVC
Описание
Microsoft Visual C++ (MSVC) — компилятор для приложений на языке C++, разработанный корпорацией Microsoft и поставляемый либо как часть комплекта Microsoft Visual Studio, либо Build Tools for Visual Studio.
Возможности
- Поддерживает современные стандарты языка, на текущий момент C++20
- Использование модулей, в качестве замены заголовочных файлов
- Быстрое внесение изменений стандарта в компилятор
Особенности
- Использование ABI x64
- Отсутствует inline asm при сборке x64 проекта
- Создание исполняемых файлов только формата PE и MS COFF
- Линкер не поддерживает скрипты
- Чтобы получить на выходе чистый бинарный файл нужно использовать утилиту objcopy из набора GNU
Компилятор
Флаги
Все флаги можно найти здесь
Но основные, которые могут вам понадобиться это:
- Общие
- /W - Отключите все предупреждения
- /Wn - n уровень серьезности предупреждений варьируемый от 0 до 4 включительно
- /Wall - Включите все предупреждения, включая предупреждения, которые отключены по умолчанию
- Оптимизация
- /Od - Отключает оптимизацию
- /O1 - Уменьшает размер кода
- /O2 - Создает быстрый код
- /Os - Отдает приоритет уменьшению размера кода
- /Ot - Отдает приоритет быстрому коду
- /Oi - Создает встроенные функции
- Создание кода
- /GL - Оптимизация всей программы
- /Gy - Включает компоновку на уровне функций
- /GS - Проверяет безопасность буфера
- Препроцессор
- /X - Пропускает стандартный каталог включаемых файлов
- Язык
- /std:c++14 Стандарт C++14
- /std:c++17 Стандарт C++17
- /std:c++20 Стандарт C++20
- /std:c++latest Последний черновик стандартных предварительных версий функций C++.
- /std:c11 Стандарт C11
- /std:c17 Стандарт C17
- Выходные файлы
- /FA Создает файл содержащий код ассемблера
- /FAc Включает машинный код в файл ассемблера
- /FAs Включает исходный код в файл ассемблера
- /FAcs Включает машинный код и исходный код в файл ассемблера
- /FA Создает файл содержащий код ассемблера
Линкер
Флаги
Все флаги можно найти здесь
Но основные, которые могут вам понадобиться это:
- Ввод
- /NODEFAULTLIB - Пропускает все (или только указанные) библиотеки по умолчанию при разрешении внешних ссылок
- /MANIFESTUAC - Создает параллельный файл манифеста и при необходимости включает его в двоичный файл
- /MANIFESTUAC:NO - Нет
- Отладка
- /DEBUG - Создает отладочную информацию
- /DEBUG:NO - Нет
- /MAP - Создает файл сопоставления
- /DEBUG - Создает отладочную информацию
- Система
- /SUBSYSTEM - Сообщает операционной системе, как запустить исполняемый файл
- /SUBSYSTEM:NATIVE - Машинный код
- /DRIVER - Создает драйвер режима ядра
- /SUBSYSTEM - Сообщает операционной системе, как запустить исполняемый файл
- Оптимизация
- /LTCG - Задает создание кода во время компоновки
- /ORDER - Помещает секции COMDAT в образ в предопределенном порядке
- Дополнительно
- /ENTRY - Задает начальный адрес
- /BASE - Задает базовый адрес для программы
- /FIXED - Создает программу, которая может загружаться только по предпочтительному базовому адресу
- /NXCOMPAT - Помечает исполняемый файл как файл, проверенный на совместимость с компонентом предотвращения выполнения данных Windows
- /ALIGN - Задает выравнивание каждой секции
Соединение ассемблера(FASM) и MSVC
Линкер
Линкер не поддерживает скрипты, поэтому невозможно гибко перемещать куски кода. Но можно изменять порядок секций путем добавления к названию секции знака $ после которого идет дополнительное имя по которому линкер в лексикографическом порядке сортирует одинаковые секции.
Например секции идущие в коде в таком порядке с названиями .text$mn, .text$c, .text$a, .text$b. Будут рассортированы линкером как .text$a, .text$b, .text$c, .text$mn.
Если у вас свой загрузчик
Если вы используете собственный загрузчик, то необходимо в начале файла установить формат генерации объектного файла поддерживаемый линкером MSVC
Для x64
format MS64 COFF
Для x86
format MS COFF
Так нужно объявить символ _fltused, при использовании переменных с плавающей запятой.
public _fltused
Чтобы загрузчик был всегда первым в объектном файле, нужно объявить секцию с названием .text$a, а так же указать параметры code, readable, executable.
section '.text$a' code readable executable
А так же нужно создать файл например order.txt в проекте и передать его линкеру через флаг /ORDER order.txt. В этом файле нужно написать название точки входа в ядро допустим kernel_start. Это позволит в секции кода поместить точку входа в ядро на первое место.
После этого можно писать код как обычно.
Важно загрузить ядро по базовому адресу равному тому, что прописано в настройках линкера. Проблема в том что линкер разрешает базовые адреса кратные 0x10000 байтам, поэтому скорее всего вы загрузите ядро в пустую область до 1 Мб памяти, а потом скопируете его на адрес выше 1 Мб, т.е. базовый адрес в таком случае должен быть больше или равен 0x100000 и при этом кратен 0x10000. Учитывайте, что адрес kernel_start будет отличаться от базового адреса на размер PE заголовка + размер вашего загрузчика.
Для того чтобы скопировать ядро в нужный участок памяти и прыгнуть в ядро, необходимо объявить внешний символ на функцию входа в ядро kernel_start
extrn kernel_start
В коде C++ функция kernel_start должна быть объявлена с использованием extern "C"
extern "C" void kernel_start()
{
}
Далее нужно скопировать загруженное ядро по адресу равному kernel_start. Это можно сделать например таким кодом. Где KERNEL это адрес куда вы изначально загрузили ядро, а SIZEOFKERNELINSECTORS количество секторов занимаемых вашим ядром.
mov rsi, KERNEL
mov rdi, kernel_start
mov rcx, (SIZEOFKERNELINSECTORS * 0x200 / 8)
rep movsq
После чего вы можете поместить адрес функции kernel_start в регистр, например rdi, и вызвать функцию call kernel_start
mov rdi, kernel_start
call rdi
Дополнительно нужно передать флаг /ALIGN линкеру с значением кратным степени двойки, меньше или равным 512 байтам, чтобы линкер не раздул ваш загрузчик.
Окончательный файл ASM.
format MS64 COFF
public _fltused
extrn kernel_start
section '.text$a' code readable executable
use16
org 0x7c00
...
use64
startLongMode:
mov rsi, KERNEL
mov rdi, kernel_start
mov rcx, (SIZEOFKERNELINSECTORS * 0x200 / 8)
rep movsq
mov rdi, kernel_start
call rdi