切换到保护模式后的“call”

5

我正在尝试在英特尔x86中切换到保护模式。

我已经使用lgdt加载了我的GDT,将cr0的P标志设置为1,并设置了所有段选择器,但当我从函数调用返回时,我无法调用任何其他函数,否则就会出现此错误。

qemu: fatal: Trying to execute code outside RAM or ROM at 0xfeeb7c5b

这是我的switch_to_pmode函数:

gdtr:
.short      23  // limit
gdtr_base:
.long       0   // base

switch_to_pmode:
    movl $null_segment, %eax        // Address of the first byte of the GDT
    movl %eax, gdtr_base

    cli             // disable interrupts

    lgdt (gdtr)

    movl %cr0, %eax
    or $0x1, %eax
    movl %eax, %cr0         // Set the PE flag

    push $0x8
    push $reload_segments
    lret

reload_segments:
    movl $0x10, %eax
    movl %eax, %ds
    movl %eax, %ss
    movl %eax, %es
    movl %eax, %fs
    movl %eax, %gs

    ret

foo:
    ret

我的通话记录

_start:
    call switch_to_pmode
    call foo // <----- Ouch!

谢谢你。

2个回答

3
将设置或清除PE的CR0移动必须立即跟随一个far jump来重新加载PC,然后您必须重新加载%esp以及所有段寄存器。在触摸堆栈或启用中断之前,您需要完成所有这些操作。即使您在使实模式堆栈无效之前弹出返回地址,也不可能从此操作中返回,因为返回地址是实模式地址。

您似乎想使用lret来重新加载PC并同时重新启用中断,但这样做是行不通的,因为堆栈指针无效。正确的代码应该类似于以下内容:

switch_to_pmode:
    # ... what you have ...

    movl %eax, %cr0
.code32
    ljmpl reload_segments

reload_segments:
    # ... what you have ...
    movl $pm_stack, %esp
    sti # perhaps

    # and then just go on with your startup code here
    call foo

您应该阅读英特尔的系统编程指南,尤其是第9章(机器初始化),特别是第9.9节,详细描述了如何进行保护模式切换。


据我所知,只要没有执行远跳转,CPU仍然在实模式下工作。我将push $8; push reload_segments; retl解读为一种创造性的执行远跳转的方式。但是你描述的方法是规范的方式,而且以那种方式可靠地工作。 - Gunther Piez
1
这很有创意,但不能保证可行——架构手册非常清楚:“在执行MOV CR0指令之后,立即执行远跳转或远调用指令。……如果[这些指令]之间存在其他指令,则可能会发生随机故障。”(我强调)(Intel® 64和IA-32体系结构软件开发人员手册第3卷:系统编程指南,第9.9节) - zwol

3

您需要确保汇编器将受保护模式切换后的代码转换为32位代码,使用.code32(或nasm中的use32)指令。

此外,在保护模式例程之后,您的返回地址已不再有效。在这之后您不能真正地返回任何东西。相反,将esp设置为有用的值并继续进行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接