使用 AT&T 语法,您可以在引导加载程序的开头放置一个标签,然后使用如下所示的内容:
.global _start
.text
.code16
_start:
jmp .
.space 510-(.-_start)
.word 0xaa55
Period .
是相对于当前节开头的当前位置计数器。时期之间的差异.
and _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 可能生成的构建 ID。 0x7c00 是代码预计加载的偏移量。因为我们不能使用标准库或C运行时我们排除它们-nostartfiles -nostdlib
如果您打算将多个文件链接在一起,您将无法使用此方法。在这种情况下,您需要将启动签名保留在代码之外,并让链接器使用特制的链接器脚本来处理它。如果您将引导加载程序包含在单个程序集文件中,则上述方法将起作用。
我有一些通用的引导加载程序提示用于编写引导加载程序代码。人们通常遇到的一大问题是没有设置段寄存器。如果您使用原点 0x7c00,那么您至少需要确保DSregister us 设置为 0。如果您编写的代码使用引用代码中标签的内存操作数,那么这一点很重要。
使用 GNU 汇编器进行汇编时,请确保设置了所需的正确指令编码。.code16
将使汇编器假定目标处理器正在 16 位模式下运行。.code32
对于 32 位编码,.code64
假设采用 64 位编码。默认为as
一般不会.code16
.
具有多个目标文件的引导加载程序
正如我上面提到的,使用多个目标文件来创建引导加载程序会带来汇编指令无法克服的挑战。为此,您可以创建一个特殊的链接器脚本,将 Origin 点设置为 0x7c00,并让链接器将引导签名放置在输出文件中。使用此方法,您不需要执行任何填充,链接器将为您完成此操作。处理传统部分的基本链接器脚本,例如.text
, .data
, .rodata
如下所示。您可能永远不会使用该部分的某些部分,但我将它们添加为示例:
File 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*);
}
}
File 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"
File 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(原点)调整为 0x7dfe 并写入启动签名 (0xaa55)。 0x7dfe 比前 512 字节末尾低 2 个字节。我们不再在汇编代码中进行任何填充,也不再在那里发出启动签名。
运行此示例引导加载程序时,应在显示屏的左上角打印一个绿色背景上黑色的字符串。