Всем привет. У меня тоже проблема с сабжем, и тоже какая-то непонятная. В качестве сегмента стека используется обычный (не expand down) сегмент данных. Если размер предела сегмента в дескрипторе равен 0ffffh, то всё работает. Иначе - либо зависание, либо исключение с перезагрузкой (я сначала хотел установить размер соответствующий количеству выделенных под стек байт, потом пробовал всякие разные круглые числа - 128, 256, 0fffeh). Правда, это всё проверялось не на живой машине, а в VMWare 5.0 В Григорьеве и мануалах Intel ничего на эту тему не нашёл. Тестовая программа: Код (Text): ; stack.asm ; Программа, переключающаяся в PM и меняющая сегмент стека .model tiny .code .386p org 100h ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Структуры ; ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Сегментный дескриптор segment_descriptor struct limit_low dw 0 ; Младшие два байта поля Segment limit base_low dw 0 ; Младшие два байта поля Base Address base_high0 db 0 ; Второй байт поля Base Address type_and_permit db 0 ; Флаги flags db 0 ; Ещё одни флаги base_high1 db 0 ; Старший байт поля Base Address segment_descriptor ends ; Регистр, описывающий таблицу дескриптров table_register struct limit dw 0 ; Table Limit base dd 0 ; Linear Base Address table_register ends ;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Код ; ;;;;;;;;;;;;;;;;;;;;;;;;;; start: ; Подготавливаем DS push cs pop ds ; Вычисляем и записываем базу стека в дескриптор call cs_to_eax add eax, offset stack0_seg mov word ptr ss0_dsc.base_low, ax shr eax, 16 mov byte ptr ss0_dsc.base_high0, al ; Инициалиируем GDT call initialize_gdt call disable_interrupts call set_PE ; Сохраняем старый стек mov old_ss, ss mov old_esp, esp ; И устанавливаем новый mov ax, 001000b mov ss, ax mov esp, stack0_size call clear_PE ; Восстанавливаем стек mov dx, old_ss mov ss, dx mov esp, old_esp call enable_interrupts ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Данные ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Глобальная таблица дескрипторов GDT label byte ; Нулевой дескриптор segment_descriptor <> ; Дескриптор сегмента стека ; Если изменить предел, машина падает или виснет ss0_dsc segment_descriptor <0ffffh, 0, 0, 10010010b, 0, 0> ; Данные для загрузки в GDTR gdtr table_register <$ - GDT - 1, 0> ; Собственно сегмент стека stack0_seg db 128 dup (0) stack0_size equ $ - stack0_seg ; Место для сохранения указателя стека RM old_ss dw 0 old_esp dd 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Служебные функции ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Инициализирует GDT initialize_gdt: ; Вычисляем линейный адрес начала массива дескрипторов call cs_to_eax add eax, offset GDT ; Записываем его в структуру mov dword ptr gdtr.base, eax ; Загружаем GDTR lgdt fword ptr gdtr ret ; Запрещает маскируемые и немаскируемые прерывания disable_interrupts: cli ; запретить прерывания in al, 70h ; индексный порт CMOS or al, 80h ; установка бита 7 в нем запрещает NMI out 70h, al ret ; Разрешает маскируемые и немаскируемые прерывания enable_interrupts: in al, 70h ; индексный порт CMOS and al, 7Fh ; сброс бита 7 отменяет блокирование NMI out 70h, al sti ; разрешить прерывания ret ; Устанавливает флаг PE set_PE: mov eax, cr0 ; прочитать регистр CR0 or al, 1 ; установить бит PE, mov cr0, eax ; с этого момента мы в защищенном режиме ret ; Сбрасывает флаг PE clear_PE: mov eax, cr0 ; прочитать CR0 and al, 0FEh ; сбросить бит PE mov cr0, eax ; с этого момента мы в реальном режиме ret ; Вычисляет линейный адрес начала сгмента кода cs_to_eax: mov eax, 0 mov ax, cs shl eax, 4 ret end start
Код (Text): mov ss, ax mov esp, stack0_size Не уверен, что дело в этом, но, по-моему, stack_size надо тоже пересчитать в линейный адрес (база + stack_size), перед тем как класть в esp.
Не, это вряд ли. Там же стандартная адресация <сегмент>:<смещение>, соответсвенно в esp - смещение. Тем более, что с пределом сегмента 0ffffh всё работает. Да и вообще, линейные адреса напрямую крайне редко используются (LGDT, LIDT, SGDT, SIDT, больше ничего не припоминаю)... И никогда они не используются пользователем, это слишком интимная подробность организации системы.
Я сейчас подумал, и мне кажется, что все же дело в этом . Что лежит в cs я не знаю, но скорее всего нечто большее чем 0x80. Это же, большее чем 0х80 значение станет базой для сегмента стека. А в esp ляжет как раз 0x80. Получается, что esp указывает аккурат мимо сегмента стека.
P.s. Как решишь проблему -- отпиши, pls, в чем дело было. Общественности (по крайней мере в моем лице ) будет интересно.
Решил. Это оказался уникальный случай, когда тестовый пример слишком маленький На самом деле в защищённом режиме всё нормально работает. Проблемы начинаются когда мы переключаемся в реальный режим. Я как-то акцентировался на защищённом и совершенно забыл про реальный. Известный глюк x86 - при переключении между режимами сохраняются размеры сегментов. Естественно так происходит и со стеком. И естественно, DOS отказывается работать с "укороченным" стеком. Если наоборот, увеличить сегмент стека - всё работает.
Где ты всё физические адреса находишь? Нету никаких физических адресов, вообще почти нигде. Обычная сегментная адресация. Тем более, что в конце можно найти такие строчки: ; Восстанавливаем стек mov dx, old_ss mov ss, dx mov esp, old_esp То есть и ss и esp восстанавливаются как были. Проблема в том, что размер сегмента, который адресуется сегментным регистром (в данном случае SS) при этом не восстанавливается, а остаётся таким же, каким был в PM - особенность такая у x86. На этом основан широко известный Unreal Mode и этот мой баг.
Физический -- он же линейный, который в RM (неявно base) + (seg_reg << 4) + effective. Я просто думал, что после перезагрузки сегментного регистра, база соответствующего ему теневого скидывается на 0, а лимит на 0xFFFFF.