Tha*_*red 6 assembly nasm bootloader x86-16
我正在制作引导加载程序,作为学习汇编的一种方式。我已经研究过使用部分来组织和优化我的代码,但是当我调用 printf 函数时,一件事不起作用。当我在 .data 部分中有 HELLO_WORLD 字符串时,它根本不想加载该字符串
; Set Code to run at 0x7c00
org 0x7c00
; Put into real mode
bits 16
; Variables without values
section .bss
; Our constant values
section .data
HELLO_WORLD: db 'Hello World!', 0
; Where our code runs
section .text
_start:
mov si, HELLO_WORLD ; Moves address for string into si register
call printf ; Calls printf function
jmp $ ; Jump forever
printf:
lodsb ; Load the next character
cmp al, 0 ; Compares al to 0
je _printf_done ; If they are equal...
call print_char ; Call Print Char
jmp printf ; Jump to the loop
_printf_done:
ret ; Return
print_char:
mov ah, 0x0e ; tty mode
int 0x10 ; Video interrupt
ret ; Return
; Fills the rest of the data with 0
times 510-($-$$) db 0
; BIOS boot magic number
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)
结果:
Booting into hard drive...
Run Code Online (Sandbox Code Playgroud)
但是,如果我将字符串移到外面并将其放在 printf 的底部,它似乎可以工作。
; Set Code to run at 0x7c00
org 0x7c00
; Put into real mode
bits 16
; Variables without values
section .bss
; Our constant values
section .data
; Where our code runs
section .text
_start:
mov si, HELLO_WORLD ; Moves address for string into si register
call printf ; Calls printf function
jmp $ ; Jump forever
printf:
lodsb ; Loads next character
cmp al, 0 ; Compares al to 0
je _printf_done ; If they are equal...
call print_char ; Call Print Char
jmp printf ; Jump to the loop
_printf_done:
ret ; Return
print_char:
mov ah, 0x0e ; tty mode
int 0x10 ; Video interrupt
ret ; Return
HELLO_WORLD: db 'Hello World!', 0
; Fills the rest of the data with 0
times 510-($-$$) db 0
; BIOS boot magic number
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)
结果:
Booting into hard drive...
Hello World!
Run Code Online (Sandbox Code Playgroud)
这是为什么?
$ - $$计算该.text部分内的位置,因此您将填充.text到 510 字节 + 2 字节签名。因此该.data部分在引导签名之后结束,而不是引导扇区的一部分。
我通过查看文件大小注意到了这一点:525 字节。使用十六进制转储查看内容去了哪里:
$ nasm -fbin bad.asm
$ hd bad # equivalent to hexdump -C
00000000 be 00 7e e8 02 00 eb fe ac 3c 00 74 05 e8 03 00 |..~......<.t....|
00000010 eb f6 c3 b4 0e cd 10 c3 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 00 |Hello World!.|
Run Code Online (Sandbox Code Playgroud)
我们看到Hello World!ASCII 字节从文件中的偏移量 512开始,因此它不是固件在传统 BIOS 模式下启动时加载的第一个 512 字节扇区的一部分。
平面二进制文件没有节或 ELF 或 PE 程序段,并且将加载具有读+写+执行权限的所有内容(CPU 处于实模式,因此没有分页或段权限)。最简单的想法可能是创建一个平面二进制文件,以及将内容放置在前 512 个字节中,而不是可执行文件的 .data 和 .text 部分。
您可以将 放在section .bss 后面dw 0xaa55,因为紧邻 MBR 加载位置(线性地址 0x7C00)之后的空间往往可以自由使用。将其放在源代码中的启动签名之后,使您的源代码与 NASM 布局平面二进制文件的方式相匹配。请注意,它不会像主流操作系统下的 .bss 空间那样对您进行零初始化。
如果您确实想使用section指令并在代码之后但在启动签名之前有一些.rodata指令.data,那么您需要执行$-$$.
就像可能在每个部分的开始/结束处放置标签一样,这样您就可以执行totalsize equ (text_end-text_start) + (data_end-data_start)/ times (510-totalsize) db 0/ dw 0xaa55。但是您必须在 NASM 最后放置的任何部分中执行此操作,否则您会将某些部分推出超过 512 字节边界。幸运的是,文件大小可以轻松检查这一点。
您可以控制 NASM 在平面二进制文件中排列各部分的顺序。这是 NASM 的一个特例;它充当链接器和汇编器,填充符号偏移量而不仅仅是创建重定位条目。第一次出现新部分时,请使用指令上的start=x和属性。(感谢 @ecm 指出这一点。)但默认情况下已经先排序,这正是您所需要的,因为执行从 MBR 的第一个字节开始。follows=ysection.text
起初,我假设 NASM 会按照首次出现的顺序将各节输出到平面二进制文件中,在这种情况下,问题将是执行db 'Hello World!', 0as machine code。
事实证明,这不是 NASM 所做的;它将该.text部分放在平面二进制文件的第一位,即使section .data它位于源代码的第一位。
顺便说一句,您的引导加载程序依赖于一些无法保证的东西,并且在某些 BIOS 上会失败。
在使用to load from之前,您不会初始化 DS 或 ES 以匹配您的org设置。lodsbDS:SI
您不必cld在循环之前确保lodsbSI 会递增而不是递减。与 32 位和 64 位模式下的标准调用约定/ABI 不同,您不能假设进入引导加载程序时 DF=0。事实上,假设尽可能少,只是将其加载到线性地址 7C00h,并且 DL = 可用于从同一设备加载更多扇区的驱动器号。
请参阅Michael Petch 的关于本期和上一期引导加载程序开发的一般提示。
int 10h在调用/ AH=0Eh( https://en.wikipedia.org/wiki/INT_10H / http://www.ctyme.com/intr/rb-0106.htm )之前,您没有将 BH/BL 设置为页码/颜色。看
您不会为 BPB 留出空间,BIOS 在扇区中从第 3 字节开始乱写一些字节。(请参阅上面 Michael Petch 的一般提示。)这在 QEMU 和 Bochs 上没问题,但如果从 USB 启动,则在某些实际硬件上会失败。
(通常建议使用 Bochs 对引导加载程序进行单步调试。特别是当您执行任何有关分段或切换到保护模式的操作时;连接到 Qemu 的 GDB 并不像 Bochs 那样了解分段。)
| 归档时间: |
|
| 查看次数: |
167 次 |
| 最近记录: |