Я перечитал два раза, и думаю, что понял наконец, о чем речь. Вам, видимо, не нравится тот факт, что EFI пытается абстрагировать сложность оборудования от пользователя, и делает это громадным количеством кода, который вам бы не хотелось запускать, а он все равно запускается и повлиять на это крайне не просто.
Альтернатив тут несколько, но выстрелить может, на мой взгляд, только одна — LinuxBoot, который сейчас развивают Google и Facebook. Там ребята выкинули из EFI весь верхний уровень (тот самый Extensible Firmware Interface) и заменили его ядром Linux, которое основную систему затем загружает kexec'ом без всяких интерфейсов, и умирает после этого практически без следа.
Идея отличная и работает замечательно, но для загрузки произвольной ОС (которой нужен ACPI, к примеру) не подходит, а если начать подводить — снова получится EFI, и далеко не факт, что во второй раз получится лучше.
А вот чтобы взаимодействовать с EFI — вам нужна масса весьма сложного кода, который, от того, что вы его скачали с сайта и запустили «не глядя» проще не становится.
Не нужна там никакая масса кода, все взаимодействие идет через EFI_SYSTEM_TABLE, которая по факту обыкновенная таблица указателей на функции. Ничего хитрого в работе с EFI нет, и взаимодействовать с ним можно и в машинном коде, и на ассемблере, и на чем угодно. Придется узнать немного о формате PE, и соглашении о вызовах Microsoft x64, и о том, что зачем нужен ExitBootServices(), но это все значительно проще, чем BIOS Interrupt Call с его магическими номерами, магическими адресами, нереальным режимом, управлением адресной линией А20, ресетом через порт клавиатуры и ручным пробросом VGA через мосты.
Вы, мне кажется, смотрите на GNU EFI SDK или EDK2 и думаете, что они необходимы и без них с EFI нельзя договориться. Если так — вы ошибаетесь.
Мне очень нравится идея концептуальной простоты, но реализации таковой не выдерживают, к сожалению, столкновения с реальностью, начальством и отделом маркетинга, и потому либо не существуют, либо не используются. Приходится ехать на том, что есть, и на вот этом ехать с EFI значительно веселее, чем без него, на мой взгляд.
Если коротко — Intel ACM, который процессор еще до ресет-вектора найдет через FIT, проверит его подпись при помощи МЕ, и затем исполнит, а он уже передаст управление на legacy reset vector, или сразу на SEC core.
Нужно это для организации root-of-trust для verified boot и measured boot, загрузки микрокода из FIT, и других задач, с которыми нужно закончить до начала исполнения пользовательского кода, но которые при этом нельзя выполнить на МЕ. Вот тут @matrosov хорошо написал про ACM и BootGuard, почитайте если интересно.
Мы, мне кажется, друг друга не понимаем.
"Положите загрузчик на диск по вот этому пути, сделайте в нем функцию EFIAPI efi_main(EFI_HANDLE Handle, EFI_SYSTEM_TABLE *SystemTable), которую вам прошивка и вызовет" — это вот, на мой взгляд, в разы проще спецификации multiboot, прерываний этих номерных, карты памяти e820 и прочего EBDA.
EFI — это интерфейс, а сложным он стал от того, что оборудование стало сложным, плюс вагон особенностей реализации наконец-то решили стандартизировать.
От способа загрузки чудовищность зависит напрямую, с EFI-загрузкой нам доступны все сервисы и возможности прошивки с человеко-понятном виде, с нормальными именами, со спецификацией открытой. А с легаси — кривая эмуляция на IoTrap-ах, трамплины в 16-битный режим и обратно, и прочие костыли для совместимости с седой древностью, которые теперь приходится заводить через жертвоприношения.
Если ваша ОС нормально работает с EFI-загрузкой — ей и пользуйтесь, а CSM отключайте — от него на современном железе одни проблемы.
Так что если ваша цель — не потратить несколько лет на разборки с этими механизмами, а загрузить какую-нибудь OS… то лучше сделать вид, что мы вернулись в 1980е и у нас простой и бесхитростный 80386й «под капотом».
Лучше не надо, потому что те механизмы, которые делают современный ПК похожим на 80386 — это такое хтоническое чудовище, что с ними лучше не сталкиваться никогда. UEFI имеет в разы более простой интерфейс с прошивкой, и лучше изучить один раз код Linux EFI stub, или любого минимального EFI-загрузчика, и забыть про этот легаси-ад навсегда.
Чуть подробнее.
Прямо от ресет-вектора выполняется так называемая прошивка или firmware (firm она потому, что это ровно между hard и soft). Выполняется она на том же центральном процессоре, что и все остальное (начинается исполнение с одного конкретного ядра, обычно нулевого, которое называют BootStrap Processor, а все остальные ядра ничего не делают, пока не получат специальное SIPI-прерывание). Код прошивки исполняется прямо из ПЗУ, подключенного к одной из простых шин — LPC/SPI/eSPI, а затем копируется в ОЗУ после того, как это самое ОЗУ удасться найти и проинициализировать (процесс этот называется тренировкой, memory training).
По сути своей, прошивка состоит из двух относительно независимых слоев, нижний из которых занимается инициализацией конкретного оборудования (процессора, памяти, шин), абсолютно необходимого для верхнего слоя и ОС, и верхнего, который реализует интерфейс к этой самой ОС, и которым ОС пользуется для своих собственных целей. Само слово BIOS (т.е. базовая система ввода-вывода) — оно именно про интерфейс (который полностью называется BIOS Interrupt Call, т.к. использует программные прерывания практически для всего), а UEFI уже и называется именно интерфейсом.
Intel Management Engine — это отдельный процессор со своей собственной прошивкой (хранящейся обычно на том же ПЗУ, что и основная), стартующий раньше центрального и исполняющий различные сервисные задачи, которые на центральном процессоре исполнять по разным причинам не удобно.
Тов. khim прав в том, что тут надо книги писать и не одну, и одной Beyond BIOS тут не обойдешься, потому что она исключительно про одну (хоть и популярную) реализацию верхнего уровня прошивок для современных архитектур.
Кроме UEFI/TianoCore можно посмотреть на coreboot/Intel FSP/AMD AGESA/U-boot (это реализации именно нижнего уровня), а для верхнего — LinuxBoot и SeaBIOS.
Думаю, любой разработчик, пишущий критичный с точки зрения безопасности код, в идеале должен хорошо владеть семантикой языка, на котором он пишет, а также знать о его подводных камнях. Применительно к C это означает, что необходимо знать семантику переполнения и тонкости неопределённого поведения.
Спорить очень трудно, но я попробую. Вместо того, чтобы требовать знания тонкостей реализации переполнения, нужно требовать использования функций, аналогичных os_*_overflow из macOS, которые для GCC и CLang отображаются на встроенные, а для остальных выполены макросами.
В итоге получается, что при осторожном программировании на С любой арифметический оператор — потенциальный источник проблем, и должен быть либо заменен на вышеупомянутую функцию даже если автор кода мамой клянется, что он все до этого 3 раза проверил.
В общем, не надо надеяться только на профессионализм людей (потому что не очень хороший день бывает даже у очень матерых волков), лучше надеяться на процессы и автоматику, именно поэтому стоит посмотреть на более безопасный Rust в качестве замены крайне опасного С, и научиться пользоваться valgrind, asan, ubsan и статическими анализаторами.
Смотря откуда начинать, если у вас уже все заведено и есть BIOS и его сервисы (т.е. либо старый интерфейс interrupt call, либо новый UEFI) — никакой разницы нет, память уже работает, карту бери у прошивки и действуй.
Если же ничего нет, даже стека, а вы сами из флеша исполняетесь — заводить современный контролер памяти DDR3/DDR4 (т.е. опросить сам контролер, добраться до SPD через SMBus, считать оттуда конфигурацию, отправить контролеру нужные команды в нужном порядке, выполнить тренировку, отключить неработающие модули, и все остальное) намного сложнее, чем раньше.
Еррату не забудь еще, которую никто не читает.
«Мы обнаружили, что при определенных условиях у нас процессор намертво зависает. Чинить мы это, конечно, не будем, поэтому вы просто софт с вот такими инструкциями не запускайте».
Или еще лучше, «мы тут после нескольких лет производства обнаружили, что наше замечательное железо с DMA иногда пишет в физическую память по вот этим адресам, и портит там все. Не кладите туда ничего важного больше.»
Тут с отладчиком то не отладишь, а без отладчика можно вообще с ума сдуреть.
Самая большая проблема в том, что этот подход не масштабируется. Это у вас пока еще ОС маленькая, и ума хватает охватить ее целиком, думать как процессор, и вот это все. Реальные системы давно уже не укладываются целиком в голову одного человека, и потому нужно всеми силами разгружать эту самую голову, автоматизировать всю рутину, изучать инструменты, подходы, заниматься упрощением, декомпозицией, короче — снижением когнитивной нагрузки. Чем проще в вашем коде разобраться — тем этот код лучше, и даже если сейчас вы сами в нем сейчас разбираетесь досконально и «сразу видите ошибку», уже завтра это обязательно изменится.
Ошибки, которые сразу видно, можно даже за ошибки не считать, потому что они тривиально находятся и справляются, и вот после того, как их исправили, начинается самое интересное — ошибки в железе, ошибки, которые не повторяются, ошибки, связанные с взаимодействием нескольких частей системы, и т.п., и чем больше инструментов вы освоите (санитайзеры, статические анализаторы, визуализаторы карты памяти, обработчики точек останова, и все остальное, придуманное для отладки именно «тяжелых случаев») — тем лучше будет и для вас, и для пользователей вашей ОС, если таковые появятся.
Умение пользоваться инструментами, облегчающими жизнь и ускоряющими разработку — намного важнее умения обходиться без них, потому что время — ресурс невосполнимый. Опыт работы без отладчика придет сам, когда придется попасть в ситуацию вроде вашей, и можно даже специально тренировать этот навык иногда, когда время позволяет, но разработка ОС общего назначения на уже работающем оборудовании с доступными (и весьма развитыми) средствами отладки — это мазохизм в чистом виде.
А главное — никаких отладчиков, выводи в текстовый режим, либо в окошечки — повисло — ставь return'ы.
Для чего так себя мучать?
Сейчас есть DCI, который на современных десктопных платах включается тривиально, и в продаже открытой до сих пор есть Intel Galileo, у которой JTAG доступен через OpenOCD.
Понятно когда приходится прошивку на ранних стадиях через порт 80 отлаживать, потому что вокруг вообще ничего не инициализированно, но писать полноценную ОС без отладчика — это мазохизм и потеря времени.
Автоматизируйте все, что видите — сборку, развертывание, EFI stub напишите, чтобы легаси загрузку не использовать (заодно не придется в защищенный режим переходить, вы и так уже там), тратьте время непосредственно на ОС, а развертывание и тестирование пусть скрипты делают — им не скучно.
У i-Tool два недостатка (при вагоне достоинств) — плохая изоляция ручки, от чего она ощутимо греется при долгой работе, и не очень удачный механизм крепления жал (нужно сразу купить вторую гайку, потому что снимать ее с горячего жала довольно неприятно, пару раз чуть не получил этим жалом в глаз). В остальном — отличный инструмент.
У меня есть таковая от 2013 года, и только про самые первые шаги. Вообще посоветую вот эту статью, в которой авторы занимаются оптимизацией скорости загрузки, хотя она и не про «что происходит», а про «какого хрена так медленно», но и нужный вам вопрос тоже освещен неплохо.
Про шпионское ПО я даже комментировать не стал, потому что там ничего другого, кроме как послать по вектору еще раз, в голову не приходит. Фанаты — они фанаты и есть, им сколько не рассказывай, почему так, а не иначе — все равно останешься махровым проприетарщиком, продавшимся капиталистам за корзину печенья.
Практически все, что здесь написано — не выдерживает никакой критики, извините. Просто по порядку первые несколько вещей:
В системах x86_64 есть программный пакет Intel Management Engine (IME) для удаленного управления системами.
Это не программный пакет, а аппаратно-программный комплекс.
Согласно собственному инструменту Intel, в IME есть непропатченные уязвимости.
Которые были пропатчены сразу же.
Траммелл Хадсон (Trammell Hudson) создал проект me_cleaner
Треммелл не имеет отношения к этому проект, а создал его итальянский студент Никола Корна.
Прошивка IME и программа System Management Mode (SMM), которая следует за ней на загрузке, основаны на операционной системе Minix и запускаются на отдельном процессоре Platform Controller Hub, а не основном ЦП системы.
PCH — это северный мост, и на отдельном процессоре запускается только прошивка ME, а SMM — это режим основного процессора x86, и код, работающий в нем, не имеет никакого отношения к Minix.
Затем SMM запускает на основном процессоре программу Universal Extensible Firmware Interface (UEFI)
Буллшит, UEFI — это не программа, а интерфейс, а SMM к UEFI вообще отношения не имеет, и тем более не реализует его. Запускается при старте не UEFI, а PI/AGESA, и первый код SMM стартует значительно позже, в фазе DXE.
Задача загрузчика — предоставить только что включенному процессору необходимые ресурсы для запуска операционной системы общего назначения вроде Linux. Во время включения нет не только виртуальной памяти, но и DRAM до момента поднятия ее контроллера. Затем загрузчик включает источники питания и сканирует шины и интерфейсы, чтобы найти образ ядра и корневую файловую систему.
Вот это все — вовсе не задача загрузчика. Задача загрузчика — считать с диска ядро ОС, распаковать его, отобразить в память и передать ему управление. Все описанное в цитате делается прошивкой, в задачи которой, кроме инициализации железа, входит еще и предоставление интерфейса для загрузчиков, абстрагирующего их от оборудования. Одним из таких интерфейсов как раз и является UEFI, пришедший на смену более старому интерфейсу BIOS Interrupt Call.
Дальше читать не стал, прошу пардону. К переводчику претензий особых не имею, а автору посоветую идти в жопу почитать нормальных книг и самому сначала понять, как система работает, а не звон слушать очередной, тем более ретранслировать его в виде подобных статей.
Не знаю точно, выполняет ли PMU свой кусок AGESA Boot Loader на том же ядре, или на соседнем (скорее все же на соседнем), но со стороны прошивки это выглядит как «подготовил таблицы, отправил их в PSP mailbox, дождался ответа, вуаля — память готова», со слотовой DIMM такая магия даже удобнее, но как только начинает хотеться странного, памяти там себе на обе стороны платы напаять, вот это все, как магия дает сбой и начинаются танцы с бубном.
Альтернатив тут несколько, но выстрелить может, на мой взгляд, только одна — LinuxBoot, который сейчас развивают Google и Facebook. Там ребята выкинули из EFI весь верхний уровень (тот самый Extensible Firmware Interface) и заменили его ядром Linux, которое основную систему затем загружает kexec'ом без всяких интерфейсов, и умирает после этого практически без следа.
Идея отличная и работает замечательно, но для загрузки произвольной ОС (которой нужен ACPI, к примеру) не подходит, а если начать подводить — снова получится EFI, и далеко не факт, что во второй раз получится лучше.
Не нужна там никакая масса кода, все взаимодействие идет через EFI_SYSTEM_TABLE, которая по факту обыкновенная таблица указателей на функции. Ничего хитрого в работе с EFI нет, и взаимодействовать с ним можно и в машинном коде, и на ассемблере, и на чем угодно. Придется узнать немного о формате PE, и соглашении о вызовах Microsoft x64, и о том, что зачем нужен ExitBootServices(), но это все значительно проще, чем BIOS Interrupt Call с его магическими номерами, магическими адресами, нереальным режимом, управлением адресной линией А20, ресетом через порт клавиатуры и ручным пробросом VGA через мосты.
Вы, мне кажется, смотрите на GNU EFI SDK или EDK2 и думаете, что они необходимы и без них с EFI нельзя договориться. Если так — вы ошибаетесь.
Мне очень нравится идея концептуальной простоты, но реализации таковой не выдерживают, к сожалению, столкновения с реальностью, начальством и отделом маркетинга, и потому либо не существуют, либо не используются. Приходится ехать на том, что есть, и на вот этом ехать с EFI значительно веселее, чем без него, на мой взгляд.
Нужно это для организации root-of-trust для verified boot и measured boot, загрузки микрокода из FIT, и других задач, с которыми нужно закончить до начала исполнения пользовательского кода, но которые при этом нельзя выполнить на МЕ.
Вот тут @matrosov хорошо написал про ACM и BootGuard, почитайте если интересно.
"Положите загрузчик на диск по вот этому пути, сделайте в нем функцию EFIAPI efi_main(EFI_HANDLE Handle, EFI_SYSTEM_TABLE *SystemTable), которую вам прошивка и вызовет" — это вот, на мой взгляд, в разы проще спецификации multiboot, прерываний этих номерных, карты памяти e820 и прочего EBDA.
EFI — это интерфейс, а сложным он стал от того, что оборудование стало сложным, плюс вагон особенностей реализации наконец-то решили стандартизировать.
От способа загрузки чудовищность зависит напрямую, с EFI-загрузкой нам доступны все сервисы и возможности прошивки с человеко-понятном виде, с нормальными именами, со спецификацией открытой. А с легаси — кривая эмуляция на IoTrap-ах, трамплины в 16-битный режим и обратно, и прочие костыли для совместимости с седой древностью, которые теперь приходится заводить через жертвоприношения.
Если ваша ОС нормально работает с EFI-загрузкой — ей и пользуйтесь, а CSM отключайте — от него на современном железе одни проблемы.
Лучше не надо, потому что те механизмы, которые делают современный ПК похожим на 80386 — это такое хтоническое чудовище, что с ними лучше не сталкиваться никогда. UEFI имеет в разы более простой интерфейс с прошивкой, и лучше изучить один раз код Linux EFI stub, или любого минимального EFI-загрузчика, и забыть про этот легаси-ад навсегда.
Прямо от ресет-вектора выполняется так называемая прошивка или firmware (firm она потому, что это ровно между hard и soft). Выполняется она на том же центральном процессоре, что и все остальное (начинается исполнение с одного конкретного ядра, обычно нулевого, которое называют BootStrap Processor, а все остальные ядра ничего не делают, пока не получат специальное SIPI-прерывание). Код прошивки исполняется прямо из ПЗУ, подключенного к одной из простых шин — LPC/SPI/eSPI, а затем копируется в ОЗУ после того, как это самое ОЗУ удасться найти и проинициализировать (процесс этот называется тренировкой, memory training).
По сути своей, прошивка состоит из двух относительно независимых слоев, нижний из которых занимается инициализацией конкретного оборудования (процессора, памяти, шин), абсолютно необходимого для верхнего слоя и ОС, и верхнего, который реализует интерфейс к этой самой ОС, и которым ОС пользуется для своих собственных целей. Само слово BIOS (т.е. базовая система ввода-вывода) — оно именно про интерфейс (который полностью называется BIOS Interrupt Call, т.к. использует программные прерывания практически для всего), а UEFI уже и называется именно интерфейсом.
Intel Management Engine — это отдельный процессор со своей собственной прошивкой (хранящейся обычно на том же ПЗУ, что и основная), стартующий раньше центрального и исполняющий различные сервисные задачи, которые на центральном процессоре исполнять по разным причинам не удобно.
Тов. khim прав в том, что тут надо книги писать и не одну, и одной Beyond BIOS тут не обойдешься, потому что она исключительно про одну (хоть и популярную) реализацию верхнего уровня прошивок для современных архитектур.
Кроме UEFI/TianoCore можно посмотреть на coreboot/Intel FSP/AMD AGESA/U-boot (это реализации именно нижнего уровня), а для верхнего — LinuxBoot и SeaBIOS.
Спорить очень трудно, но я попробую. Вместо того, чтобы требовать знания тонкостей реализации переполнения, нужно требовать использования функций, аналогичных os_*_overflow из macOS, которые для GCC и CLang отображаются на встроенные, а для остальных выполены макросами.
В итоге получается, что при осторожном программировании на С любой арифметический оператор — потенциальный источник проблем, и должен быть либо заменен на вышеупомянутую функцию даже если автор кода мамой клянется, что он все до этого 3 раза проверил.
В общем, не надо надеяться только на профессионализм людей (потому что не очень хороший день бывает даже у очень матерых волков), лучше надеяться на процессы и автоматику, именно поэтому стоит посмотреть на более безопасный Rust в качестве замены крайне опасного С, и научиться пользоваться valgrind, asan, ubsan и статическими анализаторами.
Если же ничего нет, даже стека, а вы сами из флеша исполняетесь — заводить современный контролер памяти DDR3/DDR4 (т.е. опросить сам контролер, добраться до SPD через SMBus, считать оттуда конфигурацию, отправить контролеру нужные команды в нужном порядке, выполнить тренировку, отключить неработающие модули, и все остальное) намного сложнее, чем раньше.
«Мы обнаружили, что при определенных условиях у нас процессор намертво зависает. Чинить мы это, конечно, не будем, поэтому вы просто софт с вот такими инструкциями не запускайте».
Или еще лучше, «мы тут после нескольких лет производства обнаружили, что наше замечательное железо с DMA иногда пишет в физическую память по вот этим адресам, и портит там все. Не кладите туда ничего важного больше.»
Тут с отладчиком то не отладишь, а без отладчика можно вообще с ума сдуреть.
Ошибки, которые сразу видно, можно даже за ошибки не считать, потому что они тривиально находятся и справляются, и вот после того, как их исправили, начинается самое интересное — ошибки в железе, ошибки, которые не повторяются, ошибки, связанные с взаимодействием нескольких частей системы, и т.п., и чем больше инструментов вы освоите (санитайзеры, статические анализаторы, визуализаторы карты памяти, обработчики точек останова, и все остальное, придуманное для отладки именно «тяжелых случаев») — тем лучше будет и для вас, и для пользователей вашей ОС, если таковые появятся.
Сейчас есть DCI, который на современных десктопных платах включается тривиально, и в продаже открытой до сих пор есть Intel Galileo, у которой JTAG доступен через OpenOCD.
Понятно когда приходится прошивку на ранних стадиях через порт 80 отлаживать, потому что вокруг вообще ничего не инициализированно, но писать полноценную ОС без отладчика — это мазохизм и потеря времени.
Автоматизируйте все, что видите — сборку, развертывание, EFI stub напишите, чтобы легаси загрузку не использовать (заодно не придется в защищенный режим переходить, вы и так уже там), тратьте время непосредственно на ОС, а развертывание и тестирование пусть скрипты делают — им не скучно.
Весь комплект стоит примерно 1200 евро.
Про шпионское ПО я даже комментировать не стал, потому что там ничего другого, кроме как послать по вектору еще раз, в голову не приходит. Фанаты — они фанаты и есть, им сколько не рассказывай, почему так, а не иначе — все равно останешься махровым проприетарщиком, продавшимся капиталистам за корзину печенья.
Это не программный пакет, а аппаратно-программный комплекс.
Которые были пропатчены сразу же.
Треммелл не имеет отношения к этому проект, а создал его итальянский студент Никола Корна.
PCH — это северный мост, и на отдельном процессоре запускается только прошивка ME, а SMM — это режим основного процессора x86, и код, работающий в нем, не имеет никакого отношения к Minix.
Буллшит, UEFI — это не программа, а интерфейс, а SMM к UEFI вообще отношения не имеет, и тем более не реализует его. Запускается при старте не UEFI, а PI/AGESA, и первый код SMM стартует значительно позже, в фазе DXE.
Вот это все — вовсе не задача загрузчика. Задача загрузчика — считать с диска ядро ОС, распаковать его, отобразить в память и передать ему управление. Все описанное в цитате делается прошивкой, в задачи которой, кроме инициализации железа, входит еще и предоставление интерфейса для загрузчиков, абстрагирующего их от оборудования. Одним из таких интерфейсов как раз и является UEFI, пришедший на смену более старому интерфейсу BIOS Interrupt Call.
Дальше читать не стал, прошу пардону. К переводчику претензий особых не имею, а автору посоветую
идти в жопупочитать нормальных книг и самому сначала понять, как система работает, а не звон слушать очередной, тем более ретранслировать его в виде подобных статей.