Пытаюсь реализовать вложенный вызов процедур через 64-bit call gate, cpl 3 -> cpl 2 -> cpl 0. При первом call far происходит переключение стэка, новый ss = 2. Второй call far снова переключает стэк, новый ss = 0, на этом стэке сохраняется селектор 2. Далее, retf смотрит, что предыдущий селектор стэка = 2, и вот вопрос - does RETF allow SS to be loaded with a NULL selector? Следует ли ожидать возврата управления процедуре на cpl 2 и установки ss = 2? Bochs выдает и происходит исключение #GP(0). Так и должно быть? В чем подвох? Немного кода: Код (Text): .gdt: dq 0 ; 0x00 invalid dq 0 ; 0x08 invalid dq 0x00AF9A000000FFFF ; 0x10 code dq 0x0040890000000067 + (.tss shl 16) ; 0x18 TSS dq 0 dq 0x00AF92000000FFFF ; 0x28 data dq 0x00AFDA000000FFFF ; 0x32 code dq 0x00AFD2000000FFFF ; 0x3A data dq 0x00AFFA000000FFFF ; 0x43 code dq 0x00AFF2000000FFFF ; 0x4B data dq 0x0000EC0000320000 + .ring2 ; 0x52 call gate dq 0 dq 0x0000EC0000100000 + .ring0 ; 0x60 call gate dq 0 ; <...> mov ax, 0x18 ltr ax mov ax, 0x00 ; 0x28 mov ss, ax mov ax, 0x4B mov ds, ax mov es, ax mov esp, 0x7C00 push 0x4B push 0x60000 pushf push 0x43 push .ring3 iretq .cg60: dq 0 dw 0x60 .cg52: dq 0 dw 0x52 .ring3: call tbyte [.cg60] call tbyte [.cg52] hlt ; :) .ring2: call tbyte [.cg60] retf ; #GP(0) :( .ring0: retf
Пока копаюсь, выяснилось следующее: (код в нулевом кольце) Код (Text): push 0x02 ; и 0x3A пробовалось push 0x20000 pushf push 0x32 push .ring2 iretq работает. Код (Text): push 0x3A push 0x20000 push 0x32 push .ring2 retf работает. Код (Text): push 0x02 push 0x20000 push 0x32 push .ring2 retf выдает #GP(0). Так и должно быть?
JAPH В манах посмотреть что мешает ? Не знаю как для x64, на x32 нулевой селектор генерирует экцепшин общей защиты, там черным по белому кучу раз написано.
Вообще, странно. Как я понимаю, продвижение r3 ->... ->r0 возможно только с помощью far jmp/call, а назад, r0 ->... ->r3 только с помощью retf/iret. А тут ситуация немного наоброт получается?
Mika0x65 Тут изначально предполагалось перемещение: r3 -> r2 -> r0 через call gate, r0 -> r2 -> r3 через retf. На первом retf все валится. Вопрос свелся к #2 - каково поведение retf, если сохраненный селектор стэка - 2? Это #GP(0) или нормальный возврат? В случае iretq нету никаких исключений. PS. Был бы 64-битный процессор, проверил бы) ан нету) Clerk В 32-битном режиме согласен - #GP(0) без вопросов. В 64-битном - приведена цитата из мануала, где на самом интересном месте IRET.
JAPH Я никогда x64 не интересовался, там всё криво сделано, поэтому чтото утвержать не могу, там может быть что угодно. Следует посмотреть на псевдокод обработки межкольцевого call.
Clerk В псевдокоде call все в порядке. А вот в retf начинаются недомолвки. В IA-32E-MODE-RETURN-OUTER-PRIVILEGE-LEVEL среди прочих есть проверочка В iret она тоже есть. Вопрос в том: всегда должна выполняться эта проверка или только, когда селектор ненулевой? Смотрим на исходный код bochs 2.4.1. cpu/iret.cc Код (Text): void BX_CPP_AttrRegparmN(1) BX_CPU_C::long_iret(bxInstruction_c *i) { <...> /* INTERRUPT RETURN TO OUTER PRIVILEGE LEVEL or 64 BIT MODE */ <...> if ((raw_ss_selector & 0xfffc) == 0) { if (! IS_LONG64_SEGMENT(cs_descriptor) || cs_selector.rpl == 3) { BX_ERROR(("iret64: SS selector null")); exception(BX_GP_EXCEPTION, 0, 0); } } else { <...> /* AR byte must indicate a writable data segment, * else #GP(SS selector) */ if (ss_descriptor.valid==0 || ss_descriptor.segment==0 || IS_CODE_SEGMENT(ss_descriptor.type) || !IS_DATA_SEGMENT_WRITEABLE(ss_descriptor.type)) { BX_ERROR(("iret64: SS AR byte not writable or code segment")); exception(BX_GP_EXCEPTION, raw_ss_selector & 0xfffc, 0); } <...> } <...> } Видно, что проверка на writable выполняется только когда селектор ненулевой. В cpu/ret_far.cc проверка на writable выполняется и когда селектор нулевой. Эта проверка неудачна, и Код (Text): BX_ERROR(("return_protected: SS.AR byte not writable data"));
В qemu инструкция retf работает без исключений, даже в случае сохраненного селектора стэка = 2. То есть Код (Text): push 0x02 push 0x20000 push 0x32 push .ring2 retf передает управление во второе кольцо. Однако в qemu вызов call far m16:64 Код (Text): call tbyte [.cg60] производит #GP(0), а вызов call far m16:32 Код (Text): call fword [.cg60+4] генерирует #TS(0). Bochs же проходит оба этих вызова без исключений.
Кстати, вообще странно, как вычислилось #TS(0). Потому что сначала обработчика этого исключения не было - нуль в IDT. По идее, если происходит #TS(0) и обработчика нету - в процессе вызова обработчика исключения случилось исключение #GP(0xA2), оба исключения класса Contributory => это уже #DF, а так как и обработчика #DF нету, то triple fault. Однако qemu сгенерировала #GP(0xA2). Bochs на ее месте вполне корректно перезагрузилась.