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

caf*_*fox 4 assembly gnu-assembler bootloader att x86-16

所以我想在bootsector中添加填充.比方说,目前只有一个无限循环:jmp ..扇区需要512字节长.此外,0xaa55需要在最后添加的幻数.

jmp .
.skip 508, 0
.word 0xaa55
Run Code Online (Sandbox Code Playgroud)

但是,如果我想打印一些但不想将所有字节计算到适当大小的内容,该怎么办?
在Intel/NASM中,语法是:

; print something
times 510-($-$$) db 0
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)

但在AT&T语法中呢?好了,循环(.rept)在这里不起作用,因为这里.不需要给出绝对值.我们和.skip/ 有同样的问题.space,他们也需要一个绝对值.

是否有使用某种循环/ .align/ .skip/ etc 添加填充的方法?

编辑:我as用来构建和链接,ld -Ttext 0x7c00 --oformat binary直到yasmAT&T语法足够稳定.

Mic*_*tch 9

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

.global _start
.text
.code16
_start:
    jmp .

.space 510-(.-_start)
.word 0xaa55
Run Code Online (Sandbox Code Playgroud)

Period .是当前位置计数器相对于当前节的开头.周期之间的差._start是一个绝对值所以应在此表达工作.

您可以使用GCC(将调用LD)将其组装到引导加载程序,其命令如下:

gcc -Wl,--oformat=binary -Wl,-Ttext=0x7c00 -Wl,--build-id=none \
    -nostartfiles -nostdlib -m32 -o boot.bin boot.s
Run Code Online (Sandbox Code Playgroud)

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

如果要将多个文件链接在一起,则无法使用此方法.在这种情况下,您需要将引导签名保留在代码之外,并让链接器使用特制的链接描述文件来处理它.如果将引导加载程序包含在单个程序集文件中,则上述方法将起作用.


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

与GNU汇编程序一起组装时,请确保设置所需的正确指令编码..code16将使汇编程序假定目标处理器以16位模式运行..code32对于32位编码,.code64假定为64位编码.默认值as通常为never .code16.


具有多个目标文件的Bootloader

正如我上面提到的,使用多个目标文件来创建引导加载程序会带来装配指令无法克服的挑战.为此,您可以创建一个特殊的链接描述文件,将Origin点设置为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*);
    }
}
Run Code Online (Sandbox Code Playgroud)

boot.s包含bootloader主入口点的文件:

# 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"
Run Code Online (Sandbox Code Playgroud)

文件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
Run Code Online (Sandbox Code Playgroud)

要将此引导加载程序构建到一个名为的文件,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
Run Code Online (Sandbox Code Playgroud)

特殊信息.text.bootentry由链接描述文件作为第一个代码放置.此部分只应在一个目标文件中定义,因为它将出现在引导加载程序开头0x7c00处的代码.链接器脚本将VMA(原点)调整为0x7dfe并写入引导签名(0xaa55).0x7dfe比前512字节末尾低2个字节.我们不再在汇编代码中执行任何填充,也不在那里发出引导签名.

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

  • @fuz你是对的,但只要有一个文件,它仍然有效.如果他将对象链接在一起作为引导程序,他还有其他问题.我有一个关于将gnu汇编程序用于启动加载程序的另一个答案,它显示了这样一个链接器脚本(用于LD).如果OP正在做这样的事情.我可能对此做出了错误的假设,但是OP没有说明他是如何组装这个东西的. (3认同)
  • 干净的方法当然是使用链接器脚本将位和部分放入适当的位置,但这也应该这样做. (2认同)

Ros*_*dge 7

您可以使用该.org指令非常简单地执行此操作:

    .code16
    .text
    jmp     .
    .org    510
    .word   0xaa55
Run Code Online (Sandbox Code Playgroud)

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

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