Не удается получить доступ к 32-битной версии в защищенном режиме

avatar
Dalex
9 августа 2021 в 00:01
160
2
0

Во время разработки небольшого ядра я столкнулся со странной проблемой при загрузке процессоров приложений с использованием APIC.

Как указано в OSDev и Intel-Manual, процессор сначала переходит в реальный режим, и моя цель — заставить его работать в защищенном режиме. После настройки небольшого стека и включения строки «A20» и перехода к моему 32-битному коду я попытался очистить eax, используя xor eax, eax для здравомыслия.

К моему удивлению, было очищено только младшее слово eax, но старшее слово осталось неизменным.

Running the kernel in QEMU

Забавно, но если я просто выполню xor ax, ax вместо xor eax, eax, регистр будет полностью очищен.

Ниже приведен мой код для начальной загрузки процессора приложения с помощью APIC:


; Extern reference to the GDTR
section .data
extern g_gdtr

; Serves as a temporary stack used for flushing the cpu-pipeline
SMP_BOOT_STACK_SIZE equ 64
smp_boot_stack:
    dq 0
    dq 0
    dq 0
    dq 0

section .text
global __smp_ap_rm_entry
align 0x1000
[bits 16]
; Real-Mode Entry point for other processor cores (AP's)
; after a INIT-SIPI-SIPI was issued
__smp_ap_rm_entry:
    cli
    xor ax, ax
    mov ds, ax
    mov ss, ax
    lea bp, [ds:smp_boot_stack + SMP_BOOT_STACK_SIZE - 1]
    mov sp, bp
    
    ; Enable A20 line
    sti
    in al, 0x92
    or al, 2
    out 0x92, al
    cli

    lgdt [ds:g_gdtr]

    ; Enable Protected Mode
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax

    ; Far jump
    push 0x8
    push __smp_ap_pm_entry
    retf

align 4
[bits 32]
__smp_ap_pm_entry:
    mov ax, word 0x10
    ; Doing this two times is somehow necessary (wtf?)
    mov es, ax
    mov es, ax 

    mov ss, ax
    mov fs, ax
    mov gs, ax
    mov ds, ax

    xor eax, eax
    int 3 ; To check for changed values in qemu

    jmp $

Более того, я также пытался присвоить регистру 32-битное значение, например mov eax, 0xDEADBEEF, но остается только часть BEEF.

Кто-нибудь знает, почему это не работает?

Источник
Jester
9 августа 2021 в 00:03
2

Дважды проверьте, что ваш bits 32 действителен и машинный код правильный. xor ax, ax работает вместо xor eax, eax обычно означает, что ваш ассемблер выдал код для 16 бит.

sj95126
9 августа 2021 в 00:20
0

Если вы работаете в 32-битном режиме, почему дескриптор CS отображается как CS64?

Dalex
9 августа 2021 в 00:33
0

@ sj95126 Я не думаю, что правильно понял ваш ответ. Где вы с этим столкнулись?

sj95126
9 августа 2021 в 00:35
2

В опубликованном вами выходном изображении QEMU строка, начинающаяся с CS =0008. 32-битный сегмент кода должен отображаться как CS32, а не как CS64. Вы уверены, что ваш GDT правильный?

Dalex
9 августа 2021 в 01:13
0

@ sj95126 Большое спасибо за подсказку, это действительно была проблема все это время!

Ответы (2)

avatar
Dalex
9 августа 2021 в 01:11
2

Как намекнул @sj95126, похоже, я загрузил не тот GDT. После создания и загрузки GDT с 32-битными дескрипторами сегментов проблема была решена.

Если кому-то интересно, ниже приведен код моего нового GDT, используемого для переключения с реального режима на защищенный:

struct SegmentDescriptor32_s
{
    u16 SegmentLimitLow;
    u16 BaseAddressLow;
    union
    {
        struct
        {
            u32 BaseAddressMiddle : 8;
            u32 Type : 4;
            u32 DescriptorType : 1;
            u32 DescriptorPrivilegeLevel : 2;
            u32 Present : 1;
            u32 SegmentLimitHigh : 4;
            u32 System : 1;
            u32 LongMode : 1;
            u32 DefaultBig : 1;
            u32 Granularity : 1;
            u32 BaseAddressHigh : 8;
        };
        u32 Flags;
    };
} __attribute__((packed));

__attribute__((aligned(0x1000)))
static struct SegmentDescriptor32_s smp_ap_gdt[] =
{
    { /* Null-Selector */
        .SegmentLimitLow = 0x0,
        .BaseAddressLow = 0x0,
        .Flags = 0x0
    },
    { /* Flat Code */
        .SegmentLimitLow = 0xFFFF,
        .BaseAddressLow = 0x0000,
        .Flags = 0x00CF9A00
    },
    { /* Flat Data */
        .SegmentLimitLow = 0xFFFF,
        .BaseAddressLow = 0x0000,
        .Flags = 0x008F9200,
    },
    { /* TSS */
        .SegmentLimitLow = 0x68,
        .BaseAddressLow = 0x0000,
        .Flags = 0x00CF8900
    }
};
avatar
zwol
9 августа 2021 в 01:09
1
; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax

; Far jump
push 0x8
push __smp_ap_pm_entry
retf

Когда в руководстве по архитектуре говорится, что за MOV CR0, который изменяет бит PE, должен сразу же следовать FAR JMP, это означает конкретную инструкцию FAR JMP (код операции EA или FF, в зависимости от того, как вы хотите выразить операнд), и они означают, что это должна быть самая следующая инструкция. Если этого не делать, последствия непредсказуемы. Я подозреваю, что ваш эмулятор на самом деле не переключает переключатель, пока не выполнит FAR JMP, поэтому вы все еще находитесь в 16-битном реальном режиме, а машинная инструкция 31 c0 по-прежнему означает xor ax,ax, а не xor eax, eax.

FAR JMP принимает явное абсолютное выражение сегмент:смещение; вы не можете просто написать jmp far __smp_ap_pm_entry. Я не знаю, что именно вам нужно будет написать.

(см. раздел 9.9, «Переключение режимов» в Руководстве разработчика программного обеспечения для архитектур Intel® 64 и IA-32, объединенных томах 3A, 3B, 3C и 3D: Руководство по системному программированию. Если у вас есть еще не читал это руководство, сейчас самое время.)

Michael Petch
9 августа 2021 в 10:46
1

Бит PE немедленно переключается в инструкции mov. После этого вы находитесь в 16-битном защищенном режиме. После JMP (или RETF) вы находитесь в 32-битном защищенном режиме (если GDT имеет 32-битный CS для этого селектора). Результаты инструкций после переключения бита с помощью mov определены и предсказуемы. Вы находитесь в 16-битном защищенном режиме после перемещения, но без явного изменения в CS. Не требуется сразу же изменять бит PE с помощью FAR JMP или эквивалентного, если инструкции после mov декодируются одинаково в реальном режиме и 16-битном защищенном режиме.

Michael Petch
9 августа 2021 в 10:57
0

Проблема с декодированием заключается, конечно, в том, что очередь предварительной выборки инструкций не знает, что бит PE изменился, и сам mov не очищает его. Таким образом, руководство по архитектуре предлагает следующую инструкцию (например, JMP), которая очищает очередь предварительной выборки инструкций. Это не обязательно должен быть FAR JMP. Подойдет любая инструкция, очищающая IPFQ, включая, помимо прочего, ближние и дальние JMP и CALL.

zwol
9 августа 2021 в 12:38
0

@MichaelPetch Я буквально цитирую здесь руководство по архитектуре! "Сразу после [MOV CR0] выполнить FAR JMP или FAR CALL... Возможны случайные сбои, если существуют другие инструкции между ними" (выделено мной). lcall, а также ljmp, но это все.

Michael Petch
9 августа 2021 в 12:56
1

Вы можете заметить в том же руководстве по архитектуре список кодов, в котором указано, что они используют что-то еще, кроме дальнего jmp. Поскольку у меня нет доступа к руководству по архитектуре на моем телефоне, но есть список кодов для примера загрузочного ПЗУ с защищенным режимом. выключатель. Основная проблема решается вплоть до IPFQ