为什么我的HELLO_WORLD字符串不能从 .data 部分加载?



我正在制作一个引导程序,作为学习汇编的一种方式。我已经研究过使用节来组织和优化我的代码,但有一件事不起作用,那就是当我调用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   

结果:

Booting into hard drive...

然而,如果我把字符串移到它之外,并把它放在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   

结果:

Booting into hard drive...
Hello World!

为什么?

$ - $$计算.text部分中的位置,因此将.text填充为510字节+2字节签名。因此,.data部分结束于引导签名之后,而不是引导扇区的一部分。

我通过查看文件大小注意到了这一点:525字节。使用hexdump查看发生了什么:

$ 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!.|

我们看到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的一种特殊情况;它既是一个链接器,也是一个汇编程序,填充符号偏移量,而不仅仅是重新定位条目。section指令第一次出现在新节中时,请使用该指令上的属性start=xfollows=y。(感谢@cem指出这一点。)但默认情况已经是先订购.text,这正是您所需要的,因为执行从MBR的第一个字节开始。


起初,我假设NASM会按照第一次出现的顺序将部分输出到平面二进制文件中,在这种情况下,问题将是将db 'Hello World!', 0作为机器代码执行。

事实证明,NASM不是这么做的;即使section .data在源中是第一个,它也会将.text部分放在平面二进制中的第一个。


BTW,您的引导程序依赖于一些无法保证的东西,并且在某些BIOS上会失败

  • 在使用lodsbDS:SI加载之前,您不会初始化DS或ES以匹配org设置。

  • 在循环之前不要cld,以确保lodsb将递增SI而不是递减。与32位和64位模式下的标准调用约定/ABI不同,您不能假设在进入引导加载程序时DF=0。事实上,假设尽可能少,只假设它加载在线性地址7C00h,并且DL=您可以用来从同一设备加载更多扇区的驱动器号。

    有关本期和上一期的内容,请参阅Michael Petch的Bootloader开发一般提示。

  • 在调用int 10h/AH=0Eh之前,您没有将BH/BL设置为页码/颜色(https://en.wikipedia.org/wiki/INT_10H/http://www.ctyme.com/intr/rb-0106.htm)。参见

    • 如何使用INT 10H显示寄存器值
    • 使用DOS或BIOS显示字符
    • 调用int 10h时bl的值
  • 您不会为BPB留下空间,BIOS会从字节#3开始在扇区的一些字节上乱写。(参见上面Michael Petch的一般提示。)这在QEMU和Bochs上很好,但如果从USB启动,在一些真正的硬件上会失败。

(Bochs通常被推荐用于引导加载程序的单步调试。特别是当你做任何分割或切换到保护模式时;连接到Qemu的GDB不像Bochs那样了解分割。)

相关内容

  • 没有找到相关文章

最新更新