我正在开发一个引导x86_64机器的小型演示程序。在早期的init(真实的模式)中,我通过int 10 h设置了videomode 3。然后,我在0xb 8000处写入内存Map文本。我的第二阶段已经是高级C代码了。这在32位的保护模式和分页模式下工作得很好。
我更改了引导加载程序,使其也启用PAE,然后设置LME,然后跳转到第二阶段(然后已经编译为x86_64)。这就是我的展示崩溃的地方,我不知道发生了什么。我一直在调试小样本,并有一些即使在64位模式下也能可靠工作的东西:
for (uint32_t i = 0xb8000; i < 0xb8000 + (25 * 80 * 2); i += 2) {
*((volatile uint16_t*)i) = 0x0741;
}
正如预期的那样,这将使屏幕充满所有“A”。下面是生成的程序集:
000000000000843f <main>:
843f: f3 0f 1e fa endbr64
8443: 55 push %rbp
8444: 48 89 e5 mov %rsp,%rbp
8447: c7 45 fc 00 80 0b 00 movl $0xb8000,-0x4(%rbp)
844e: eb 0c jmp 845c <main+0x1d>
8450: 8b 45 fc mov -0x4(%rbp),%eax
8453: 66 c7 00 41 07 movw $0x741,(%rax)
8458: 83 45 fc 02 addl $0x2,-0x4(%rbp)
845c: 81 7d fc 9f 8f 0b 00 cmpl $0xb8f9f,-0x4(%rbp)
8463: 76 eb jbe 8450 <main+0x11>
8465: 90 nop
8466: eb fd jmp 8465 <main+0x26>
但是,当我将代码更改为:
volatile uint16_t *screen_base = (volatile uint16_t*)0xb8000;
for (uint32_t i = 0; i < 25 * 80; i++) {
screen_base[i] = 0x0741;
}
它停止工作;它输出粉红色的控制字符(表示“0x 07”是字符,“0x 41”是颜色代码),但甚至不填充整个屏幕(右下端的最后两个字符不填充)。下面是生成的程序集:
000000000000843f <main>:
843f: f3 0f 1e fa endbr64
8443: 55 push %rbp
8444: 48 89 e5 mov %rsp,%rbp
8447: 48 c7 45 f0 00 80 0b movq $0xb8000,-0x10(%rbp)
844e: 00
844f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
8456: eb 17 jmp 846f <main+0x30>
8458: 8b 45 fc mov -0x4(%rbp),%eax
845b: 48 8d 14 00 lea (%rax,%rax,1),%rdx
845f: 48 8b 45 f0 mov -0x10(%rbp),%rax
8463: 48 01 d0 add %rdx,%rax
8466: 66 c7 00 41 07 movw $0x741,(%rax)
846b: 83 45 fc 01 addl $0x1,-0x4(%rbp)
846f: 81 7d fc cf 07 00 00 cmpl $0x7cf,-0x4(%rbp)
8476: 76 e0 jbe 8458 <main+0x19>
8478: 90 nop
8479: eb fd jmp 8478 <main+0x39>
奇怪的是,我可以通过修改指向0xb 8003的指针来掩盖这个问题,但这显然是不正确的。我不知道这是怎么回事,有人知道会发生什么吗?
1条答案
按热度按时间roejwanj1#
asm在调试版本中是fairly painful to follow,但看起来很正常。您是否100%确定您在
jmp far
或任何设置了L位的CS描述符之后处于64位模式?因为有确凿的证据表明你不是。32位模式下的
0x48
是dec eax
的操作码(而不是雷克斯.W前缀),看起来它可能解释了-3字节的偏移量。add %rdx, %rax
变为dec %eax
;add %edx, %eax
。之前,在莱亚之前有一个dec %eax
,它使dec %eax
加倍,在mov -0x10(%ebp),%eax
存储之前。您的版本通过将
uint32_t
转换为指针来避免任何雷克斯前缀。请注意,这些指令都不使用64位操作数大小、R8-R15或BPL-DIL,因此它们都不以机器码中的40
到4F
字节开头。(除了最初的mov %rsp, %rbp
,但EAX在那一点上并不活跃;对EAX的下一次访问是只写的。)所以这是相当强有力的证据,表明CPU没有处于完整的64位模式。使用Bochs单步切换到长模式,并检查您实际处于什么模式。和单步执行指令中的不工作代码;您将看到
48
字节解码为单独的指令。(你也可以在QEMU + GDB中这样做; GDB可能不确定CPU处于什么模式,但通过陷阱标志TF单步执行将反映CPU实际正在执行的操作。顺便说一句,GCC调试版本更喜欢首先使用EAX/RAX来计算任何表达式,可能是因为它是返回值寄存器。如果GCC碰巧选择了不同的寄存器,那么递减EAX也就没什么关系了。但你肯定会在某个时候遇到问题,例如。当GCC使用RAX时,因为它是返回值寄存器,或者当它试图使用带有
0x40
雷克斯前缀的DIL字节寄存器时(32位模式下为inc eax
)。