Вопрос или проблема
Почему регистры защищенного режима x86 32-бит не дают правильное значение?
Я настроил 32-битный защищенный режим и пытаюсь загрузить глобальную таблицу дескрипторов (GDT) с помощью C и ассемблера и вывести значения различных регистров с помощью функции check_gdt(), где
void check_gdt() {
uint16_t cs, ds, es, fs, gs, ss;
// Встроенный ассемблер для получения значений сегментных регистров
asm volatile("mov %%cs, %0" : "=r"(cs));
asm volatile("mov %%ds, %0" : "=r"(ds));
asm volatile("mov %%es, %0" : "=r"(es));
asm volatile("mov %%fs, %0" : "=r"(fs));
asm volatile("mov %%gs, %0" : "=r"(gs));
asm volatile("mov %%ss, %0" : "=r"(ss));
// Вывод значений сегментных регистров
printf("CS: 0x%x\n", cs);
printf("DS: 0x%x\n", ds);
printf("ES: 0x%x\n", es);
printf("FS: 0x%x\n", fs);
printf("GS: 0x%x\n", gs);
printf("SS: 0x%x\n", ss);
}
Инициализируйте GDT следующим образом
void initGdt(){
gdt_ptr.limit = (sizeof(struct gdt_entry_struct) * 5) - 1;
gdt_ptr.base = (uint32_t)&gdt_entries;
setGdtGate(0,0,0,0,0); // Нулевой сегмент _________________ _______________
// Объяснение: |P|DPL|S|E|DC|RW|A| |G|DB|L|Res| |
setGdtGate(1,0,0xFFFFFFFF, 0x9A, 0xCF); // Сегмент кода ядра : доступ = 0x9A =>|1|00 |1|1|0 |1 |0|, гран = 0xCF => |1|1 |0|0 |1111|
setGdtGate(2,0,0xFFFFFFFF, 0x92, 0xCF); // Сегмент данных ядра : доступ = 0x92 =>|1|00 |1|0|0 |1 |0|, гран = 0xCF => |1|1 |0|0 |1111|
// _________________ _______________
setGdtGate(3,0,0xFFFFFFFF, 0xFA, 0xCF); // Сегмент кода пользователя : доступ = 0xFA =>|1|11 |1|1|0 |1 |0|, гран = 0xCF => |1|1 |0|0 |1111|
setGdtGate(4,0,0xFFFFFFFF, 0xF2, 0xCF); // Сегмент данных пользователя : доступ = 0x9A =>|1|11 |1|0|0 |1 |0|, гран = 0xCF => |1|1 |0|0 |1111|
// ----------------- ---------------
gdt_flush((uint32_t) &gdt_ptr);
}
void setGdtGate(uint32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran){
gdt_entries[num].base_low = (base & 0xFFFF); // Присвоить младший базовый 16 бит
gdt_entries[num].base_middle = (base >> 16) & 0xFF; // Присвоить средний базовый 8 бит
gdt_entries[num].base_high = (base >> 24) & 0xFF; // Присвоить старший базовый 8 бит
gdt_entries[num].limit_low = (limit & 0xFFFF); // Присвоить младший лимит 16 бит
gdt_entries[num].flags = (limit >> 16) & 0x0F; // Присвоить флаги младшие 4 бита
gdt_entries[num].flags |= (gran & 0xF0); // Присвоить флаги старшие 4 бита
gdt_entries[num].access = access; // Присвоить доступ 8 бит
}
и загрузить GDT в ЦП с помощью функции gdt_flush
gdt_flush:
; [esp] Адрес возврата (из CALL)
; Загрузить адрес дескриптора GDT
MOV eax, [esp + 4] ; Чтобы загрузить содержимое адреса [esp+4], который является первым аргументом gdt_flush((uint32_t) &gdt_ptr)
LGDT [eax] ; Загрузить GDT в ЦП
MOV ax, 0x10 ; Загрузить селектор сегмента данных ядра (0x10)
MOV ds, ax ; Установить регистр сегмента данных в сегмент данных ядра
MOV es, ax ; Установить регистр дополнительного сегмента в сегмент данных ядра
MOV fs, ax ; Установить общий сегмент в сегмент данных ядра
MOV gs, ax ; Установить общий сегмент в сегмент данных ядра
MOV ss, ax ; Установить сегмент стека в сегмент данных ядра
JMP 0x08:.flush ;Дальняя передача к селектору 0x08 (сегмент кода) и адресу flush
; Запись 1: Сегмент кода ядра (селектор = 0x08)
.flush:
RET
Я ожидаю от загрузки gdt выше
CS : 0x08
DS : 0x10
ES : 0X10
FS : 0X10
GS : 0X10
SS : 0X10
Но почему я получаю неправильный результат, как показано ниже?
Ответ или решение
Проблема, с которой вы столкнулись, связана с неправильными значениями регистров сегмента в вашем коде для работы с таблицей глобальных дескрипторов (GDT) в защищенном режиме x86. Давайте рассмотрим возможные причины этого поведения и предложим решения.
Потенциальные причины проблемы:
-
Неправильная инициализация GDT:
Убедитесь, что ваша функцияinitGdt()
правильно инициализирует сегменты GDT. У вас есть 5 записей в GDT, где первая запись является нулевой (недействительной), а последующие четыре — для кода и данных в режиме ядра и пользовательских сегментах. Убедитесь, что итоговая структураgdt_entries
правильно определена и содержит нужные значения. -
Ошибки в загрузке и установке сегментных регистров:
В основной функции загрузки GDT (gdt_flush
) регистры сегментов устанавливаются по значениям, которые вы определили. Убедитесь, что правильные сегментные селекторы (0x08 для кода и 0x10 для данных) используются и что они соответствуют записям в GDT. -
Ошибки в
gdt_flush
:
В вашем коде используетсяLGDT
, чтобы загрузить адрес GDT. Убедитесь, что вы правильно передаете адрес GDT. Ваш код выглядит правильно, но проверьте, что структураgdt_ptr
и ассоциированная с ней память была правильно инициализирована и не содержит ошибок. - Неправильный порядок операций:
Вы используетеMOV ax, 0x10
для присвоения значения регистрам, не забывайте, что эти операции могут быть не видны сразу после выполнения, если вы не используете соответствующие механизмы синхронизации.
Возможные решения:
-
Отладка GDT:
Проверьте, правильно ли инициализирована каждая запись в GDT. Вы можете использовать отладчик или вывод отладочной информации, чтобы убедиться, что каждая часть GDT настроена правильно. -
Проверка регистров после
gdt_flush
:
Программа должна корректно перейти в защищенный режим и после этого проверить значения регистров. Убедитесь, что вы проверяете значения сразу после выполнения инструкций загрузки GDT и установки регистров. -
Использование версий инструментов отладки:
Используйте отладочные инструменты, такие какgdb
или встроенные средства вашей среды разработки, чтобы проследить за изменениями в регистрах сегментов во время выполнения программы. - Корректный выбор селекторов:
Убедитесь, что выбранные вами селекторы действительно соответствуют тем сегментам, которые вы инициализируете в GDT. Селектор сегмента включает битовые поля, и ваши значения должны правильно учитывать уровень привилегий и тип сегмента.
Пример кода для проверки:
Возможно, будет полезно добавить дополнительный вывод для отладки в вашем коде, чтобы видеть, что происходит на каждом этапе. Добавьте вывод значений переменных перед их использованием и после их присвоения, чтобы легче было найти место, где могло произойти расхождение.
Заключение:
Убедитесь, что ваша система правильно инициализирует и использует GDT и что после всех операций по загрузке и установке регистров вы проверяете их значения. Следите за точностью при написании кода, и после выполнения этих шагов у вас не должно возникать проблем с получением нужных значений регистров сегментов.