许多消息来源写道,有必要在汇编代码中插入一个指示地址的指令.org 0x7C00,据说代码从该地址开始执行(更准确地说,BIOS从磁盘的第一个扇区进入那里)。该指令存在于 Linux 内核源代码和许多其他源代码中,例如https://github.com/fffaraz/bootloader/blob/master/bootloader.asm(顺便说一句,nasm 立即生成一个文件512字节,也不清楚为什么,只有一个猜测:它被自动“trunked”。这个“顺便说一下”指的是链接后的文本)。
理论上说:需要设置16位模式,BIOS读取第一个扇区,分别检查511和512字节是否存在0x55和0xAA,如果检查成功,则将代码放置在地址处0x0000:0x7C00 并开始执行。
我只是想在启动时在屏幕上显示“X”,这是代码和后续步骤:
.org 0x7C00
.code16
start:
mov $'X', %al
mov $0xE, %ah
int $0x10
cli
hlt
. = start + 510
.byte = 0x55
.byte = 0xAA
我编译:
as -o boot.o boot.S
我得到 objdump -D
boot.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start-0x7c00>:
...
0000000000007c00 <_start>:
7c00: b0 58 mov $0x58,%al
7c02: b4 0e mov $0xe,%ah
7c04: cd 10 int $0x10
7c06: fa cli
7c07: f4 hlt
...
7dfc: 00 00 add %al,(%rax)
7dfe: 55 push %rbp
7dff: aa stos %al,%es:(%rdi)
Disassembly of section .note.gnu.property:
0000000000000000 <.note.gnu.property>:
0: 04 00 add $0x0,%al
2: 00 00 add %al,(%rax)
4: 20 00 and %al,(%rax)
6: 00 00 add %al,(%rax)
8: 05 00 00 00 47 add $0x47000000,%eax
d: 4e 55 rex.WRX push %rbp
f: 00 02 add %al,(%rdx)
11: 00 01 add %al,(%rcx)
13: c0 04 00 00 rolb $0x0,(%rax,%rax,1)
...
1f: 00 01 add %al,(%rcx)
21: 00 01 add %al,(%rcx)
23: c0 04 00 00 rolb $0x0,(%rax,%rax,1)
27: 00 01 add %al,(%rcx)
29: 00 00 add %al,(%rax)
2b: 00 00 add %al,(%rax)
2d: 00 00 add %al,(%rax)
我删除 .note.gnu.property
strip --remove-section=.note.gnu.property boot.o
关联:
ld --oformat binary -o boot boot.o
输出是一个大小为 32256 字节的文件。呵呵,当然,代码是从0x7C00开始的!查看该文件的十六进制转储,您可以看到该“地址”之前有零。那么问题来了,人们如何将这个文件写入第一个 512 字节扇区呢?当然没办法,有人不告诉什么,qemu-system-i386 -drive format=raw,file=boot他们甚至不会运行这个文件,前512字节中不会有代码
如果我们删除.org 0x7C00,生成的文件大小将恰好为 512 字节。这个文件已经可以启动qemu了。
如何在计算机启动时将控制权从 USB 转移到代码。我这样做:
为此,我使用 dd 实用程序。团队:
dd if=boot of=/dev/sdb bs=512 count=1 conv=notrunc
一切都被记录下来,hexdump也适用于/dev/sdb,前512的内容是相同的。qemu可以运行/dev/sdb
在BIOS中,我禁用安全模式,启用CSM支持(毕竟,uefi已经无处不在),准备好,转到启动选项卡,BIOS看到这个闪存驱动器(顺便说一下,KingstonDatatraveler 3.0),选择它,启动它,打印出令人垂涎的“X”符号。该代码无需 .org 0x7C00 即可运行。
问题:为什么需要这个指令?
更新:
现在我将0xAA更改为0xAF,代码仍然可以读取并运行。我是这样理解的:BIOS(没有uefi)仅将第一个扇区的前440字节复制到RAM中(https://wiki.archlinux.org/title/Arch_boot_process),这意味着它不会读取后续扇区和0x55和 0xAA 并不重要,它们是 uefi 所需要的。
问:我理解的对吗,或者是怎么回事?
汇编指令
ORG将跳转指令代码中的基址偏移设置为“绝对地址”,例如call和jmp。如果代码中没有,则不必为二进制文件指定它。要了解细节,您需要记住编译器实际上如何编码转换?x16/32 模式下的处理器使用 2 种形式的跳转 - 通过绝对地址和相对地址。对于 x64,不再有绝对转换,所有转换都是 RIP 相对的,具有 32 位目标操作数。Intel对该指令的描述
jmp如下:1.近(near)——转换到当前CS段内的指令(段内)。
2. Short(短)——与Near相同,但跳转范围限制为距离当前(E)IP值+/-127字节。
3.远(far)——转移到位于与当前CS不同的段中的指令,有时称为段间。
4. 任务切换(任务切换)——转换到位于另一个任务中的指令。
这里Near(操作码E9h)和Short(操作码EBh)是“相对转换”。它们之间的区别在于,Near 具有 2 字节操作数,而 Short 具有 1 字节有符号值的形式。如果该字节为负值,则从当前字节向后跳转
(E)IP,如果为正值,则向前跳转。至于FAR(操作码 EAh),这始终是到“绝对地址”的转换。该操作数在 x16 模式下具有 2 字节值,在 x32 模式下具有 4 字节值。我们来看下面的加载器代码,并立即将其送入反汇编器(这里主要是无条件跳转
jmp和条件跳转loop):这是他的拆解外观:
通过相对寻址,操作数值存储相对于当前值的 +/- 127 范围内的偏移量
EIP,该偏移量始终指向代码中的下一条指令,如上面反汇编中的 Short 操作码所示。但是在长距离 FAR 过渡的情况下我们看到了什么?编译器神奇地计算出正确的转换值0x7C1C。他从哪里得到的?这要归功于ORG指令- 多亏了它,编译器才能收到物理内存的有效“绝对地址” 。如果我们从源代码中删除 ORG,我们会得到下面的二进制文件,其中转换指向 space
CS:001C,而 RAM 中的引导加载程序基址 =CS:7С00。