使用引导扇区的 GAS AT&T 指令计算填充长度?

Calculating padding length with GAS AT&T directives for a boot sector?

所以我想在引导扇区中添加填充。比方说,目前那里只有一个无限循环: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 语法足够稳定。

使用 AT&T 语法,您可以在引导加载程序的开头放置一个标签,然后使用如下内容:

.global _start
.text
.code16
_start:
    jmp .

.space 510-(.-_start)
.word 0xaa55

句点. 是相对于当前部分开头的当前位置计数器。句点 ._start 之间的差异是绝对值,因此应该适用于此表达式。

您可以使用 GCC(将调用 LD)来 assemble 引导加载程序,命令如下:

gcc -Wl,--oformat=binary -Wl,-Ttext=0x7c00 -Wl,--build-id=none \
    -nostartfiles -nostdlib -m32 -o boot.bin boot.s

选项 -Wl,--oformat=binary 将此选项传递给 linker,这将强制它输出到平面二进制文件。 -Wl,-Ttext=0x7c00 会将此选项传递给 linker,这将有效地将原点设置为 0x07c00。 -Wl,--build-id=none 告诉 linker 不要使用 GCC 可能生成的构建 ID。 0x7c00 是代码预期加载的偏移量。由于我们不能使用标准库或 C 运行 我们用 -nostartfiles -nostdlib

排除它们

如果您打算 link 将多个文件放在一起,则无法使用此方法。在这种情况下,您需要将引导签名排除在代码之外,让 linker 使用特制的 linker 脚本来处理它。如果您将引导加载程序包含在单个程序集文件中,上述方法将起作用。


我有一些通用的 用于编写引导加载程序代码。人们通常遇到的一个大问题是没有设置段寄存器。如果您使用 0x7c00 的原点,那么您至少需要确保 DS 将我们注册为 0。如果您编写的代码使用引用的内存操作数,那将很重要代码中的标签。

使用 GNU assembler 进行汇编时,请确保您设置了所需的正确指令编码。 .code16 将使 assembler 假设目标处理器是 运行ning 在 16 位模式下。 .code32 用于 32 位编码,.code64 假定为 64 位编码。 as 的默认值通常从不 .code16.


具有多个目标文件的引导加载程序

正如我上面提到的,使用多个目标文件来创建引导加载程序会带来汇编指令无法克服的挑战。为此,您可以创建一个特殊的 linker 脚本,将原点设置为 0x7c00 并让 linker 将引导签名放入输出文件中。使用这种方法你不需要做任何填充,linker 会为你做。下面显示了一个基本的 linker 脚本,用于处理 .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  [=13=]x20, %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  [=14=]xb800, %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 被 linker 脚本放置为第一个代码。此部分应仅在一个目标文件中定义,因为它将是出现在引导加载程序开头 0x7c00 处的代码。 linker 脚本将 VMA(原点)调整为 0x7dfe 并写入引导签名(0xaa55)。 0x7dfe 是前 512 个字节末尾下方 2 个字节。我们不再在汇编代码中进行任何填充,也不再在那里发出引导签名。

当 运行 时,此示例引导加载程序应在显示屏的左上角打印一个字符串,绿色背景为黑色。

您可以使用 .org directive:

非常简单地做到这一点
    .code16
    .text
    jmp     .
    .org    510
    .word   0xaa55

.org 指令将位置计数器 (.) 增加到给定的值,用零填充任何跳过的位置(默认情况下)。

请注意,位置计数器是相对于正在生成的目标文件中当前节的开头的。这使得上面的 .org 指令与执行 .space 510-(.-.text) 相同,其中 .text 是当前对象中 .text 部分的开始,而不是最终链接的输出。这意味着它仅在从单个汇编文件创建引导加载程序时才真正起作用。