Создание простого многозадачного ядра, загруженного с помощью grub2
Я пытаюсь следовать инструкциям здесь, чтобы построить простое ядро ОС:http://mikeos.sourceforge.net/write-your-own-os.html
за исключением того, что вместо загрузки с дискеты я хочу создать ISO-образ на основе grub и загрузить компакт-диск с несколькими загрузками в эмуляторе. Я добавил следующее к источнику, указанному на этой странице, для заголовка multiboot:
MBALIGN equ 1<<0 ; align loaded modules on page boundaries
MEMINFO equ 1<<1 ; provide memory map
FLAGS equ MBALIGN | MEMINFO ; this is the Multiboot 'flag' field
MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum of above, to prove we are multiboot
section .multiboot
align 4
dd MAGIC
dd FLAGS
dd CHECKSUM
и я делаю следующее, чтобы создать образ:
nasm -felf32 -o init.bin init.s
cp init.bin target/boot/init.bin
grub2-mkrescue -o init.iso target/
затем я бегу qemu для загрузки:
qemu-system-x86_64 -cdrom ./init.iso
после выбора "myos" из меню загрузки, я получаю сообщение об ошибке
error: invalid arch-dependent ELF magic
что это значит, и как это можно исправить? Я попытался возиться с форматом elf, но только -felf32
кажется, работает...
1 ответов
GRUB поддерживает ELF32 и плоские файлы. Ваш заголовок, хотя неявно говорит, что вы предоставляете эльф бинарный.
использование плоского двоичного файла с Multiboot
если вы хотите сообщить загрузчику Multiboot (GRUB), что вы используете плоский двоичный файл, вы должны установить 16 бит до 1:
MULTIBOOT_AOUT_KLUDGE equ 1 << 16
;FLAGS[16] indicates to GRUB we are not
;an ELF executable and the fields
;header address,load address,load end address,
;bss end address, and entry address will be
;available in our Multiboot header
это не так просто, как просто указав этот флаг. Необходимо предоставить полный заголовок Multiboot, который предоставляет Multiboot загрузчик информации, чтобы загрузить наш двоичный файл в память. При использовании эльф формат эта информация находится в эльф заголовок, который предшествует нашему коду, поэтому не должен быть явно предоставлен. Заголовок Multiboot определяется в документация GRUB в деталях.
при использовании NASM С -f bin
важно отметить, что нам нужно указать исходную точку для нашего кода. Многозарядные погрузчики загружают наше ядро по физическому адресу 0x100000
. Мы должны указать в нашем файле ассемблера, что наша исходная точка 0x100000
так что правильные смещения и т. д. будет генерироваться в нашем конечном плоском двоичном изображении.
это пример, разделенный и измененный из одного из моих собственных проектов, который предоставляет простой заголовок. Вызов _Main
настроен как вызов C в Примере, но вам не нужно делать это таким образом. Обычно я вызываю функцию, которая принимает пару параметров в стеке (используя вызов C Конвенция.)
[BITS 32]
[global _start]
[ORG 0x100000] ;If using '-f bin' we need to specify the
;origin point for our code with ORG directive
;multiboot loaders load us at physical
;address 0x100000
MULTIBOOT_AOUT_KLUDGE equ 1 << 16
;FLAGS[16] indicates to GRUB we are not
;an ELF executable and the fields
;header address, load address, load end address;
;bss end address and entry address will be available
;in Multiboot header
MULTIBOOT_ALIGN equ 1<<0 ; align loaded modules on page boundaries
MULTIBOOT_MEMINFO equ 1<<1 ; provide memory map
MULTIBOOT_HEADER_MAGIC equ 0x1BADB002
;magic number GRUB searches for in the first 8k
;of the kernel file GRUB is told to load
MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_AOUT_KLUDGE|MULTIBOOT_ALIGN|MULTIBOOT_MEMINFO
CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
KERNEL_STACK equ 0x00200000 ; Stack starts at the 2mb address & grows down
_start:
xor eax, eax ;Clear eax and ebx in the event
xor ebx, ebx ;we are not loaded by GRUB.
jmp multiboot_entry ;Jump over the multiboot header
align 4 ;Multiboot header must be 32
;bits aligned to avoid error 13
multiboot_header:
dd MULTIBOOT_HEADER_MAGIC ;magic number
dd MULTIBOOT_HEADER_FLAGS ;flags
dd CHECKSUM ;checksum
dd multiboot_header ;header address
dd _start ;load address of code entry point
;in our case _start
dd 00 ;load end address : not necessary
dd 00 ;bss end address : not necessary
dd multiboot_entry ;entry address GRUB will start at
multiboot_entry:
mov esp, KERNEL_STACK ;Setup the stack
push 0 ;Reset EFLAGS
popf
push eax ;2nd argument is magic number
push ebx ;1st argument multiboot info pointer
call _Main ;Call _Main
add esp, 8 ;Cleanup 8 bytes pushed as arguments
cli
endloop:
hlt
jmp endloop
_Main:
ret ; Do nothing
Мультизагрузчик (жратва) обычно загружается в первый 8k вашего файла (будь то эльф или плоский двоичный), ищет многозадачный заголовок на 32-битной границе. Если 16 бит флага заголовка Multiboot ясно, он предполагает, что вы предоставляете эльф изображения. Затем он анализирует!--25-->эльф заголовок для получения информации, необходимой для загрузки файла ядра в память. Если 16 бит устанавливается, затем требуется полный многозадачный заголовок, чтобы загрузчик имел информацию для чтения вашего ядра в память, выполнения инициализации,а затем вызова в ваше ядро.
затем вы соберете свой init.s
на плоский двоичный файл с чем-то вроде:
nasm -f bin -o init.bin init.s
использование ELF с Multiboot
чтобы связать комментарии Шута с вашим оригинальным вопросом, вы должны были загрузиться с эльф и это сработало, но не из-за одной маленькой детали. В вашем примере вы использовали это, чтобы сделать init.бин:
nasm -f elf32 -o init.bin init.s
при использовании -f elf32
, NASM генерирует объектные файлы (они не являются исполняемыми), которые должны быть связаны (с LD например) для создания окончательного эльф(ELF32) исполняемый. Вероятно, это сработало бы, если бы вы выполнили процессы сборки и связи с чем-то вроде:
nasm -f elf32 init.s -o init.o
ld -Ttext=0x100000 -melf_i386 -o init.bin init.o
пожалуйста обратите внимание, что при использовании -f elf32
вы должны удалить ORG С init.s. The ORG С init.s и используйте эти команды, он должен генерировать плоский двоичный init.бин:
nasm -f elf32 init.s -o init.o
ld -Ttext=0x100000 -melf_i386 -o init.elf init.o
objcopy -O binary init.elf init.bin
In это NASM сказано генерировать ELF32 объекты. Мы собираемся init.s на эльф объектный файл с именем init.o. Затем мы можем использовать компоновщик (LD), чтобы создать эльф исполняемый файл из init.o под названием init.эльф. Мы используем специальную программу под названием objcopy чтобы снять все эльф заголовки выключены и генерируют плоский двоичный файл исполняемый файл называется init.бин.
это намного больше, чем просто использование NASM С -f bin
возможность генерировать плоский исполняемый файл init.бин. Тогда зачем беспокоиться? С помощью метода выше вы можете сказать NASM для создания отладочной информации, которая может быть использована gdb (отладчик GNU). Если вы попытаетесь использовать -g
(включить отладку) с помощью NASM используя -f bin
нет отладку информация генерируется. Отладочную информацию можно создать, изменив последовательность сборки следующим образом:
nasm -g3 -F dwarf -f elf32 init.s -o init.o
ld -Ttext=0x100000 -melf_i386 -o init.elf init.o
objcopy -O binary init.elf init.bin
init.o будет содержать отладочную информацию (в гном формат), который будет связан с LD на init.эльф (который сохраняет отладочную информацию). Плоские двоичные файлы не содержат отладочной информации, потому что они удаляются при использовании objcopy С -O binary
. Вы можете использовать init.эльф если включить средство удаленной отладки в QEMU и использовать GDB для отладки. Эта отладочная информация в init.эльф предоставляет отладчику информацию, которая позволяет выполнить один шаг через код, получить доступ к переменным и меткам по имени, см. исходный код ассемблера и т. д.
помимо генерации отладочной информации, есть еще одна причина использовать NASM/LD/OBJCOPY процесс генерации двоичного файла ядра. LD на Изменить. LD позволяет человеку создавать скрипты компоновщика, которые позволяют вам лучше настраивать, как вещи выкладываются в конечном двоичном файле. Это может быть полезно для более сложных ядер, которые могут содержать код из разных сред (с, ассемблер и т. д.). Для маленького игрушечного ядра это может и не понадобиться, но по мере усложнения ядра преимущества использования скрипт линкера станет более очевидным.
удаленная отладка QEMU с GDB
если вы используете метод в предыдущем разделе для создания отладочной информации внутри эльф исполняемый файл (init.эльф) можно запустить QEMU а это:
- загрузить QEMU окружающая среда и остановка процессора при запуске. Из man-страницы :
- S Не запускайте CPU при запуске (вы должны ввести " c " на мониторе).
- сделать QEMU послушать GDB удаленное соединение на localhost: 1234 . Из man-страницы :
-S сокращение для-gdb tcp:: 1234, т. е. откройте gdbserver на TCP-порту 1234.
тогда вам просто нужно запустить GDB так что:
- запуск GDB С эльф исполняемый файл (init.эльф) с отладочными символами и информация
- подключается к localhost:1234, где QEMU слушать
- настройка макета отладки по вашему выбору
- устанавливает точку останова в нашем ядре (в этом примере multiboot_entry)
вот пример запуска нашего ядра из образа CD-ROM init.iso и начало GDB для подключения к нему:
qemu-system-x86_64 -cdrom ./init.iso -S -s &
gdb init.elf \
-ex 'target remote localhost:1234' \
-ex 'layout src' \
-ex 'layout regs' \
-ex 'break multiboot_entry' \
-ex 'continue'
вы должны иметь возможность использовать GDB почти так же, как отладка обычной программы. Это предполагает, что вы не будете отлаживать 16-разрядную программу (ядро).
Важные Соображения
как указывает шут, при использовании Мультизагрузочных совместимых загрузчиков, таких как жратва, процессор находится в 32-битном защищенном режиме (не 16-битный реальный режим). В отличие от загрузившись прямо из BIOS, вы не сможете использовать 16-битный код, включая большинство прерываний PC-BIOS. Если вам нужно быть в реальном режиме, вам придется вернуться в реальный режим вручную или создать задачу VM86 (последняя не тривиальна).
это важное соображение, так как некоторые из кода, с которым вы связаны в MikeOS 16-бит.