вызов после перехода в защищенный режим

Я пытаюсь перейти в защищенный режим в Intel x86.

Я загрузил свой gdt с помощью lgdt, установил флаг P cr0 в 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!

Спасибо


person marmottus    schedule 06.02.2012    source источник


Ответы (2)


Вы должны убедиться, что ассемблер переводит код, следующий за переключателем защищенного режима, как 32-битный код с директивой .code32 (или use32 в nasm).

Кроме того, ваш обратный адрес после процедуры защищенного режима больше недействителен. Вы не можете вернуться ни к чему после этого. Вместо этого установите esp на что-нибудь полезное и продолжайте.

person Gunther Piez    schedule 06.02.2012

Переход к CR0, который устанавливает или очищает PE, должен немедленно сопровождаться дальним переходом для перезагрузки ПК, а затем вы должны перезагрузить %esp, а также все сегментные регистры. Вам нужно сделать все это до того, как вы прикоснетесь к стеку или разрешите прерывания. И (как говорит drhirsch) невозможно вернуться из этой операции, даже если вы удалите адрес возврата до того, как сделаете стек реального режима недействительным, потому что адрес возврата является адресом реального режима.

Похоже, вы пытаетесь использовать lret для перезагрузки ПК и одновременного повторного включения прерываний, но это не сработает, потому что указатель стека недействителен. Правильный код будет выглядеть примерно так:

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, в котором подробно описано, как выполнить переключение защищенного режима.

person zwol    schedule 06.02.2012
comment
Насколько я знаю, пока не выполняется дальний переход, ЦП все еще работает в реальном режиме. Я прочитал push $8; push reload_segments; retl как творческий способ выполнения дальнего прыжка. Но то, как вы описываете, является каноническим способом, и таким образом он работает надежно. - person Gunther Piez; 07.02.2012
comment
Это креативно, но не гарантируется, что это сработает - руководство по архитектуре довольно ясное: Сразу после инструкции MOV CR0 выполните инструкцию far JMP или far CALL. ... Возможны случайные сбои, если между [этими инструкциями] существуют другие инструкции. (выделено мной) (Руководство разработчика программного обеспечения для архитектур Intel® 64 и IA-32, том 3: Руководство по системному программированию, раздел 9.9) - person zwol; 07.02.2012