Долго думал, писать статью эту или нет, но может информация в ней будет кому-то полезна, особенно в части формирования grubx64.efi во время grub-install - об этом собственно основная часть статьи. Как обычно, не могу сказать, что я эксперт в данном вопросе и быть может что-то из написанного окажется не совсем верным, совсем не правильным или не полным. Не обладаю глубокими знаниями по теме статьи, хоть и какой-то опыт имеется. Очень может быть, что я не раскрою всех возможностей описываемых в данной статье вещей.

Как обычно, началось всё с рабочей задачи: приехало новое железо и нужно установить на него специализированное ПО определённой версии.

Проблема в том, что данное ПО довольно старое и самый простой способ его перенести - это склонировать с установленного сервера вместе с ОС (в моём случае Debian Stretch). Сервер старый и устанавливалось всё это давно и понятно, что там Legacy в части загрузки. А новый сервер настолько новый (2024 года), что Legacy он как-то очень плохо умеет и его биос штатно называется UEFI биос и если его выключить, становилось как-то совсем грустно. К слову старое ядро ОС еще и raid контролер не видело, а соответственно и диски, но сейчас не об этом.

Думаю ни для кого не секрет, что большинство современного железа умеет загружаться в Legacy режиме (по сути это обычная загрузка биоса с помощью boot сектора и дальнейшая загрузка с MBR) и в UEFI режиме (загрузка на основе NVRAM из произвольного диска и раздела, который как правило имеет GPT разметку диска). Хотя судя по этому серверу, тенденция идёт на постепенное вытеснение поддержки Legacy, что в целом логично.

Стоит упомянуть, что раздел с UEFI загрузчиком по опыту лежит в FAT32 разделе. Он есть и в WIndows и в Linux и в Mac: как правило это небольшой раздел с папкой EFI. В данной папке находится загрузчик (вместо небольшого бут-сектора в MBR), который уже делает всё остальное.

Вот например как выглядит запись в NVRAM с загрузичком для Proxmox (Debian)

Boot0007* proxmox       HD(2,GPT,62b4a0b9-53eb-4b34-9e7f-e637286893fb,0x800,0x100000)/File(\EFI\proxmox\grubx64.efi)

Кроме айди и uuid раздела, видно путь \EFI\proxmox\grubx64.efi

Данную запись можно увидеть с помощью команды efibootmgr -v. Ей так же можно, а иногда нужно управлять записями в NVRAM.

Для начала попробую объяснить на пальцах на примере Linux, как происходит загрузка в том или ином режиме.

Legacy

Работает в MBR. BIOS производит поиск загрузочного сектора в начале диска. Данный сектор является мини загрузчиком (до 512 байт). Базово он просто находит активный раздел среди 4 возможных разделов и производит оттуда загрузку. В случае Linux и GRUB, MBR загружает промежуточный код, который умеет чуть больше, чем открыть FAT на разделе. Например может загрузить EXT2. Далее уже происходит запуск самого grub, который уже управляет дальнейшей загрузкой ОС. Ну и стоит упомянуть, что MBR поддерживает максимум 2 тб.

UEFI

Работает чуть иначе. Разница по сути только в том, что базовый загрузчик находится не в загрузочном секторе, а в своём разделе на своей ФС, вместо костылей в MBR. Это может быть даже не базовый загрузчик, который передаёт управление большому GRUB, а в целом может самостоятельно полноценно загрузить ОС без посредников и классического GRUB. Да и в целом, даже Doom чисто из UEFI можно запустить https://github.com/Cacodemon345/uefidoom, куда же без него. А информация об этом загрузчике хранится в специальной NVRAM, где есть ссылка на диск/раздел и путь к бинарному файлу, который необходимо выполнить BIOS-у для загрузки. Таблица разделов используется при этом всём GPT и нет ограничения в 2 тб.

Как я уже говорил, с EFI раздела по сути можно целиком загрузить ОС. Вероятно туда можно даже запихнуть ядро, но это не точно (как подсказывают в комментах - точно можно). В общем штука эта прикольная и со временем про MBR все забудут.

Есть конечно один минус UEFI - это необходимость записи в NVRAM. Из-за этого нельзя взять и легко переставить диски с UEFI с одного компа на другой, а так же встречающиеся глюки некоторых материнок, связанные с этой записью в NVRAM, но в целом это не такая большая проблема. Глюки - обходятся. Запись восстанавливается. К тому же всегда можно загрузить загрузочный бинарь с помощью UEFI Shell.

Вообще UEFI это не только чисто про загрузку ОС, но много про чего еще. Но в основном в реальной жизни мне лично интересна только эта его часть, да и вообще не хочется сильно часто вспоминать это слово.

Перенос ОС и установка UEFI GRUB.

Процесс переноса ОС с MBR на UEFI не совсем тривиальный, однако про него много информации в интернете и в целом он обычно не вызывает проблем - напишу кратко. Алгоритм там простой - создаём нужные разделы в GPT (EFI раздел, /boot раздел (не обязательно), и раздел под /). Можно в целом всё засунуть в LVM, включая /boot, можно LVM накатить поверх mdraid). В общем - тут всё по вкусу. Далее переносим файлы и редактируем файлы, где меняются blkid, пересоздаём initramfs, делаем grub-install. И по идее всё должно работать. Так же есть опция для обратной совместимости с MBR (раздел BIOS Boot), но это особо не интересно и не обязательно.

Но статьи бы не было, если бы всё пошло гладко. В Linux нет каких-то способов автоматизировать данное восстановление, поэтому делается всё вручную. Возможно дело еще в староватой версии ОС - Debian Stretch. В общем грузиться автоматически ОС никак после переноса не хотела. Загружался вручную из оболочки UEFI GRUB

insmod lvm
insmod all_video
set root=(hd0,gpt2)
linux /vmlinuz-4.19.0-0.bpo.19-amd64 root=/dev/mapper/a947--cf--b2g01--vg-root ro
initrd /initrd.img-4.19.0-0.bpo.19-amd64
boot

Тут возможно нужны пояснения. В данном случае производится загрузка без участия "большого" grub, сразу из UEFI GRUB. Как я говорил, такое возможно только с ним. В MBR так бы не получилось скорее всего

  1. Загружаем модуль LVM, чтоб видно было root раздел

  2. Видео-модуль - без него на консоль ничего не выводится

  3. Выставляем root на gpt2, на самом деле это не root, там просто лежит ядро и initrd

  4. Выставляем строку загрузки указываем рут раздел. Важно указывать через /, т.к на gpt2 никакого /boot нет

  5. Выставляем initrd

  6. Грузимся!

Так я хотя бы мог загрузится в ОС без необходимости грузится с rescue cd.

grubx64.efi

На EFI-системах grub-install делает две вещи - он вызывает grub-mkimage, чтобы собрать grubx64.efi, а потом прописывает его в NVRAM.

Во время сборки туда зашивается расположение grub.cfg, либо его содержимое, либо он берёт grub.cfg из той директории, откуда запущен.

То есть как ни странно grubx64.efi это не просто статический бинарь, который откуда-то копируется или собирается, но он еще и содержит в себе мини-конфиг и прежде чем продолжить чтение статьи, если это интересно - надо это осознать!

Проверить можно, вытащив секцию mods из grubx64.efi:

objcopy -j mods -O binary /boot/efi/EFI/debian/grubx64.efi /tmp/mods
hexdump -C /tmp/mods | tail -n 8

Находим там либо "03 00 00 00 SS SS SS SS", либо "02 00 00 00 SS SS SS
SS". Тип 3 - это prefix, тип 2 - это config, SS - размер в
little-endian.

То есть перед каждым блоком лежит его тип (0=elf, 1=memdisk, 2=config,
3=prefix, 4=pubkey) и размер.

Например:

00003f00  00 00 00 00 00 00 00 00  f0 08 00 00 00 00 00 00  |................|
00003f10  66 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |f...............|
00003f20  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00003f30  03 00 00 00 20 00 00 00  28 2c 67 70 74 32 29 2f  |.... ...(,gpt2)/|
00003f40  62 6f 6f 74 2f 67 72 75  62 00 00 00 00 00 00 00  |boot/grub.......|
00003f50  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

Это ошибочный параметр в моём случае. Должен быть типа того

00003f00  00 00 00 00 00 00 00 00  f0 08 00 00 00 00 00 00  |................|
00003f10  66 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |f...............|
00003f20  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00003f30  03 00 00 00 18 00 00 00  28 2c 67 70 74 32 29 2f  |........(,gpt2)/|
00003f40  67 72 75 62 00 00 00 00  00 00 00 00 00 00 00 00  |grub............|
00003f50  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

Или можно просто сделать

strings /boot/efi/EFI/debian/grubx64.efi |tail -5

Поскольку на gpt2 разделе (а это по сути папка /boot) папка с файлами grub расположена по пути /grub.

Именно в этом была проблема не загрузки. В целом проблема простая, я не первый раз на неё натыкаюсь из-за того, что в системе ядро и "большой" grub лежит в /boot, но он находится по привычке на своём разделе, поэтому относительно него, grub и ядро лежит в корне этого раздела.

Еще немного теории

У GRUB есть модуль normal, который в момент загрузки ищет grub.cfg в
следующих местах:

  1. встроенный в mods через тип 2;

  2. $prefix/grub.cfg, где $prefix берётся из mods, тип 3;

  3. ./grub.cfg, просто из текущей директории.

Конфиг генерируется утилитой grub-mkconfig, а в Debian/Ubuntu есть обёртка поверх неё - update-grub, которая записывает результат работы grub-mkconfig в grub.cfg и вызывается триггером из пакетного менеджера при установке/удалении ядер, чтобы перегенерировать меню.

grub-mkconfig конфигурируется из /etc/default/grub. Для генерации конфига используется grub-probe, чтобы определить необходимые модули для работы с /boot и /etc/grub.d, где лежат шаблоны. mkconfig обходит /boot (и, возможно, ещё ходит в os-prober для того, чтобы найти другие ОС) и создаёт по записи в меню на каждое ядро, которое может запустить.

grub-install ничего с конфигом не делает. grub-install и update-grub запускаются в postinst-скрипте пакетов grub-pc и grub-efi: https://salsa.debian.org/grub-team/grub/-/blob/debian/2.12-5/debian/postinst.in

Если в этой директории нет grub.cfg, нет этой директории, она не на втором разделе или её нет, таблица разделов не GPT, или диск другой, или в core.img нет драйвера для файловой системы, контроллера диска или GPT, то ничего не получится. fallback у этой схемы нет - то есть если блок с префиксом зашит в grubx64.efi, и префикс этот неправильный, то GRUB не будет использовать другие способы поиска grub.cfg, а просто остановится.

Тут главное что надо понять, если у grubx64.efi прописан какой-то тип и он не работает, то к другим способам поиска конфига grub efi не приступит.

Что делать? (известно что ?)

Нужно просто запустить правильную команду

grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck 

Просто grub-install почему-то не работал, надо было именно полную команду вводить.

Неправильная команда:

grub-install  --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck --root-directory=/boot

Приводила как раз к ошибкам.

Проверяем что теперь всё как надо

# strings /boot/efi/EFI/debian/grubx64.efi |tail -5
.rela.data
.module_license
.bss
.modname
(,gpt2)/grub

Перезагружаемся и радуемся.

Еще может быть не лишним привести в порядок параметры загрузки UEFI через команду efibootmgr - осторожно удалить лишнее. Из-за этого тоже что-то может не грузиться.

Так же, в случае с mdraid с raid1, EFI разделы по хорошему должны быть на обоих дисках, но при этом не быть сами в mdraid. Хотя в теории ничего не мешает и EFI в raid засунуть при использовании метаданных 0.9 в mdadm, но смысла кажется в этом особо нет.

Так вот, может случиться так, что в одном разделе grubx64.efi вы исправили, а в другом нет. А грузится как раз оттуда и можно несколько часов убить, не понимая что происходит.

В качестве интересного бонуса для тех, кому интересно. Так выглядит grubx64.efi в Debian 12:

003e1dc0  02 00 00 00 28 00 00 00  6e 6f 72 6d 61 6c 20 28  |....(...normal (|
003e1dd0  6d 65 6d 64 69 73 6b 29  2f 67 72 75 62 2e 63 66  |memdisk)/grub.cf|
003e1de0  67 0a 00 00 00 00 00 00  03 00 00 00 18 00 00 00  |g...............|
003e1df0  2f 45 46 49 2f 64 65 62  69 61 6e 00 00 00 00 00  |/EFI/debian.....|
003e1e00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

И весит этот файл 4 мб, по сравнению с Debian 9, где он весит 120 кб.

Грузится он там соответственно на основе grub.cfg. Наверное можно было бы просто подсунуть от Deb12 файл grubx64.efi в Deb9, возможно это бы даже сработало, но это было бы не так интересно.

Заключение

Нельзя сказать, что удалось полностью разобраться с механизмами формирования grubx64.efi, а особенно вот этой его странной и на мой взгляд не удобной особенностью (в комментах говорят, это для secure boot), что файл содержит всё в себе (и кажется даже модули), при этом не совсем понятно, как с ним работать. Возможно я не слишком усердно гуглил. Например до сих пор, я не понимаю почему размеры файла в deb9 и deb12 так отличаются, как и отличается наполнение секции с "конфигом".

Собственно это и есть причина, почему я решил написать данную статью - мало информации в интернете.

ps. Во время решения данных проблем обращался к @kmeawи он вывел меня на правильный путь, за что ему огромное спасибо.

upd. Дополнение от @kmeaw

Если UEFI не содержит записей BootXXXX в NVRAM, с которых он может загрузиться, то система начнёт делать это из неких стандартных мест с каждого устройства, в том порядке, что они указаны в настройках - обычно все HDD и USB флешки там есть.

Таким местом на диске является \EFI\BOOT\boot$arch.efi, где для PC/x64
будет arch=x64, для 32-битных PC arch=ia32, 32-битный ARM - arch=arm, а
для 64-битных ARM там будет arch=aa64. 

Некоторые прошивки привередливы к GPT, и хотят не просто FAT-раздел, а
чтобы на нём ещё флажок EFI System Partition стоял. Его умеет ставить
GPT fdisk (gdisk) по команде x, a, 1 (номер раздела), 0 или если в
скриптах, то sgdisk --attributes=1:set:0 /dev/nvme0n1

И GRUB это даже умеет - достаточно сделать grub-install --no-nvram, и
всё получится.

И ещё есть важный момент - для того, чтобы efibootmgr (и другие способы
потрогать NVRAM, кроме совсем хардкорных) работали, в системе должны
присутствовать UEFI Runtime Services, точка входа на которые должна быть
получена ядром - это можно проверить по наличию "файлика"
/sys/firmware/efi/runtime. Иными словами, чтобы потрогать переменные
NVRAM, надо или делать это непосредственно из Setup, либо грузить
систему из UEFI - система, загруженная из Legacy, не сможет прописать
себя в BootXXXX, и тут как раз ключик --no-nvram пригодится.

Комментарии (12)


  1. BDenis
    01.11.2024 19:04

    Немного про то, как формируется grub64.efi, можно понять из моей древней статьи. Про встроенный конфиг там есть хороший комментарий.
    Загрузчик действительно формируется grub-install динамически и включает в себя модули, которые ему нужны для того, чтобы найти ядро и собственные модули в дебрях LVM, RAID и прочих файловых систем. Причем, когда вы находитесь в целевой системе, grub-install сам выберет модули, необходимые для загрузки модулей grub и ядра, и не включит лишние модули. Это сломает загрузку если вы, например, перенесете root-раздел из раздела диска на LVM - модуля в GRUB не будет и свои модули и ядро он не найдет.
    Часто, для установки загрузчика удобно загрузится в целевую систему с другого носителя с, например, SuperGRUB, примонтировать нужные /boot и /boot/efi и выполнить grub-install - он пересоберет GRUB с нужными модулями и станет возможна загрузка с основного диска. Этот же способ помогает перейти с legacy на UEFI.


  1. czz
    01.11.2024 19:04

    Как я уже говорил, с EFI раздела по сути можно целиком загрузить ОС. Вероятно туда можно даже запихнуть ядро, но это не точно. 

    Ядро точно можно.


    1. kenomimi
      01.11.2024 19:04

      Я делал для генты, прикольно, загрузка получается в разы быстрее, особенно если в биосе включить fastboot (на некоторых ноутах есть) и выкинуть initrd. Но минус, что на каждый чих пересобирай ядро, ибо параметры ядра оказываются захардкожены при сборке.


      1. equeim
        01.11.2024 19:04

        Хз как с этим UKI, но и до этого можно было загружать ядро с UEFI и прописывать параметры через efibootmgr без пересборки


      1. khajiit
        01.11.2024 19:04

        Если использовать systemd-boot вместе с UKI — то параметры можно редактировать, не прибегая пересборке UKI. По крайней мере, если не включен Secure boot


    1. avmcoder
      01.11.2024 19:04

      И ядро и initrd, проверено на опыте


  1. dartraiden
    01.11.2024 19:04

    вот этой его странной и на мой взгляд не удобной особенностью, что файл содержит всё в себе

    Ради безопасной загрузки. Загрузчик не имеет права загружать произвольный код, поэтому все модули вкомпилены в него.

    Для пущей защиты может быть включён механизм lockdown, который ещё сильнее урезает возможности GRUB, блокируя возможные пути обхода Secure Boot, например, запрещает доступ к некоторым интерфейсам ACPI и MSR-регистрам CPU, ограничивает использование DMA для PCI-устройств, блокирует импорт кода ACPI из переменных EFI, не допускает манипуляции с портами ввода/вывода.


  1. vk6677
    01.11.2024 19:04

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

    На флешку в fat32 закидывается в каталог EFI этот единственный файл. После загрузки флешку можно вынуть. Всё очень шустро работает в ОЗУ. Жаль его объемы ограничены.


  1. AndreiVorobev
    01.11.2024 19:04

    Пользуясь случаем, хочу напомнить о существовании в grub-install флага --removable. И настоятельно рекомендовать его к использованию. Как минимум, это избавляет от записи в NVRAM и от взаимодействия с убогим efibootmgr, ну и переносимость установленной системы — бонусом.


  1. VADemon
    01.11.2024 19:04

    Именно в этом была проблема не загрузки. В целом проблема простая, я не первый раз на неё натыкаюсь из-за того, что в системе ядро и "большой" grub лежит в /boot, но он находится по привычке на своём разделе, поэтому относительно него, grub и ядро лежит в корне этого раздела.

    ЛОЛ, вот почему у меня (оказывается) файлы grub'а лежат в /boot/boot/grub*


  1. Nick0las
    01.11.2024 19:04

    Есть способ сделать grub загружаемым без записи в EFIVARS (NVRAM) Файл должен называться bootx64.efi Можно сделать такую установку передав grub-install параметр --removable. Можно существующий файл переименовать.

    При обычной установке распространенный способ это передавать в grub. --efi-directory=... это путь куда смонтирован EFI раздел и --boot-directory=... это путь куда смонтирован загрузочный раздел, на котором будет конфиг граба и модули.

    А пообще efi grub довольно просто устанавливать и легко кастомизировать, в свое время я писал скрипты для развертывания разных embedded сборок.


  1. jingvar
    01.11.2024 19:04

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