Вычисление длины прокладки с помощью директив 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 4

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 в текущем объекте, а не конечным связанный выход. Это означает, что он действительно работает только при создании загрузчика из одного файла сборки.