Вычисление длины прокладки с помощью директив GAS AT&T для загрузочного сектора?
Поэтому я хочу добавить прокладку в загрузочный сектор. Допустим, в настоящее время существует только бесконечный цикл: jmp .
. Длина сектора должна составлять 512 байт. Кроме того, требуется магическое число 0xaa55
, которое добавляется в конце.
jmp .
.skip 508, 0
.word 0xaa55
Но что делать, если я хочу что-то напечатать, но не хочу считать все байты, чтобы поместить его в нужный размер?
В синтаксисе Intel / NASM это будет:
; print something
times 510-($-$$) db 0
dw 0xaa55
Но в синтаксисе AT&T? Ну, цикл (.rept
) здесь не работает, потому что .
не дает абсолютная величина, которая здесь нужна. У нас та же проблема с .skip
/.space
, им тоже нужна абсолютная величина.
Есть ли способ добавить заполнение, используя какой-то цикл/.align
/.skip
/и так далее?
Править:
Я использую as
для построения и связывания ld -Ttext 0x7c00 --oformat binary
до тех пор, пока yasm
не станет достаточно стабильным для синтаксиса AT&T.
2 ответа:
С помощью синтаксиса AT&T вы можете поместить метку в начале вашего загрузчика, а затем использовать что-то вроде этого:
.global _start .text .code16 _start: jmp . .space 510-(.-_start) .word 0xaa55
Период
.
- счетчик текущего местоположения относительно начала текущего раздела. Разница между периодами.
и_start
является абсолютной величиной, поэтому она должна работать в этом выражении.Вы можете использовать GCC (который вызовет LD), чтобы собрать это в загрузчик с помощью команды типа:
gcc -Wl,--oformat=binary -Wl,-Ttext=0x7c00 -Wl,--build-id=none \ -nostartfiles -nostdlib -m32 -o boot.bin boot.s
Вариант
-Wl,--oformat=binary
передает эту опцию компоновщику, который принудительно выводит ее в плоский двоичный файл.-Wl,-Ttext=0x7c00
передаст эту опцию компоновщику, который фактически установит исходную точку в 0x07c00.-Wl,--build-id=none
попросите компоновщика не использовать идентификатор сборки, который может генерировать GCC. 0x7c00-это смещение, при котором ожидается загрузка кода. Поскольку мы не можем использовать стандартную библиотеку илиC runtime, мы исключаем их с помощью-nostartfiles -nostdlib
Вы не сможете использовать этот метод, если собираетесь связать несколько файлов вместе. В этом случае вам нужно будет оставить подпись загрузки вне кода и предоставить компоновщику позаботиться о ней с помощью специально созданного сценария компоновщика. Метод, описанный выше, будет работать, если вы содержите свой загрузчик в одном файле сборки.
У меня есть некоторые общие советы загрузчика для написания кода загрузчика. Одна большая проблема, с которой обычно сталкиваются люди, - это не настройка сегментных регистров. Если вы используете исходную точку 0x7c00, то вам нужно как минимум убедиться, что DS зарегистрируйте нас равными 0. Это будет важно, если вы пишете код, который использует операнды памяти, ссылающиеся на метку в вашем коде.
При сборке с помощью GNU assembler убедитесь, что вы установили правильную кодировку инструкций, которую хотите.
.code16
заставит ассемблер предположить, что целевой процессор работает в 16-битном режиме..code32
для 32-битного кодирования,.code64
предполагает 64-битное кодирование. По умолчанию дляas
обычно никогда не используется.code16
.
Загрузчик с несколькими Объектные Файлы
Как я уже упоминал выше, использование нескольких объектных файлов для создания загрузчика создает проблемы,которые не могут быть преодолены директивами сборки. Для этого можно создать специальный сценарий компоновщика, который устанавливает исходную точку в 0x7c00 и позволяет компоновщику поместить подпись загрузки в выходной файл. Используя этот метод, вам не нужно делать никаких прокладок, компоновщик сделает это за вас. Базовый сценарий компоновщика, который имеет дело с традиционными разделами, такими как
.text
,.data
,.rodata
показано ниже. Вы можете никогда не использовать некоторые из разделов, но я добавил их в качестве примера:Файл
bootloader.ld
OUTPUT_FORMAT("elf32-i386"); ENTRY(_start); SECTIONS { . = 0x7C00; /* Code section, .text.bootentry code before other code */ .text : SUBALIGN(0) { *(.text.bootentry); *(.text) } /* Read only data section with no alignment */ .rodata : SUBALIGN(0) { *(.rodata) } /* Data section with no alignment */ .data : SUBALIGN(0) { *(.data) } /* Boot signature at 510th byte from 0x7c00 */ .sig : AT(0x7DFE) { SHORT(0xaa55); } /DISCARD/ : { *(.eh_frame); *(.comment); *(.note*); } }
Файл
boot.s
, содержащий главную точку входа загрузчика:# Section .text.bootentry is always placed before all other code and data # in the linker script. If using multiple object files only specify # one .text.bootentry as that will be the code that will start executing # at 0x7c00 .section .text.bootentry .code16 .global _start _start: # Initialize the segments especially DS and set the stack to grow down from # start of bootloader at _start. SS:SP=0x0000:0x7c00 xor %ax, %ax mov %ax, %ds mov %ax, %ss mov $_start, %sp cld # Set direction flag forward for string instructions mov $0x20, %al # 1st param: Attribute black on green xor %cx, %cx # 2nd param: Screen cell index to write to. (0, 0) = upper left mov $boot_msg, %dx # 3rd param: String pointer call print_str # Infinite loop to end bootloader cli .endloop: hlt jmp .endloop .section .rodata boot_msg: .asciz "My bootloader is running"
Файл
aux.s
с простой функцией отображения строки непосредственно на экране:.global print_str # Make this available to other modules .section .text .code16 # print_str (uint8_t attribute, char *str, uint16_t cellindex) # # Print a NUL terminated string directly to video memory at specified screen cell # using a specified attribute (foreground/background) # # Calling convention: # Watcom # Inputs: # AL = Attribute of characters to print # CX = Pointer to NUL terminated string to print # DX = Screen cell index to start printing at (cells are 2 bytes wide) # Clobbers: # AX, ES # Returns: # Nothing print_str: push %di push %si mov $0xb800, %di # Segment b800 = text video memory mov %di, %es mov %cx, %di # DI = screen cell index (0 = upper left corner) mov %dx, %si # SI = pointer to string (2nd parameter) mov %al, %ah # AH = attribute (3rd parameter) jmp .testchar # Print each character until NUL terminator found .nextchar: stosw # Store current attrib(AH) and char(AL) to screen # Advances DI by 2. Each text mode cell is 2 bytes .testchar: lodsb # Load current char from string into AL(advances SI by 1) test %al, %al jne .nextchar # If we haven't reach NUL terminator display character # and advance to the next one pop %si pop %di ret
Чтобы построить этот загрузчик в файл с именем
boot.bin
, мы могли бы сделать что-то вроде:as --32 aux.s -o aux.o as --32 boot.s -o boot.o ld -melf_i386 --oformat=binary -Tlink.ld -nostartfiles -nostdlib \ aux.o boot.o -o boot.bin
Специальный
.text.bootentry
помещается в качестве первого кода скриптом компоновщика. Этот раздел должен только быть определенным в одном объектном файле, так как это будет код, который появляется прямо в начале загрузчика в 0x7c00. Сценарий компоновщика настраивает VMA (origin) на 0x7dfe и записывает сигнатуру загрузки(0xaa55). 0x7dfe находится на 2 байта ниже конца первых 512 байт. Мы больше не делаем никаких дополнений в ассемблерном коде и не выдаем там сигнатуру загрузки.При запуске этого примера загрузчик должен вывести строку в верхнем левом углу экрана с черным на зеленом фон.
Вы можете сделать это очень просто с помощью
.org
директива :.code16 .text jmp . .org 510 .word 0xaa55
Директива
.org
перемещает счетчик местоположения (.
) к заданному значению, заполняя все пропущенные местоположения нулями (по умолчанию).Обратите внимание, что счетчик местоположения находится относительно начала текущего раздела в создаваемом объектном файле. Это делает Директиву
.org
выше такой же, как и директиву.space 510-(.-.text)
, где.text
является началом раздела.text
в текущем объекте, а не конечным связанный выход. Это означает, что он действительно работает только при создании загрузчика из одного файла сборки.