Проблемы с RET FAR

Тема в разделе "WASM.BEGINNERS", создана пользователем 808Problem, 24 июл 2023.

  1. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    1.993
    DinaR.fon
     
    808Problem нравится это.
  2. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Спасибо!
     
  3. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    171
    Как оказалось, всё и тут прозрачно.
    В доке "Instruction Set Reference", к описанию каждой инструкции приводится её псевдокод (см.раздел Operation). Для SYSRET можно обнаружить следующее, где в явном виде наблюдаем запись в теневые части CS/SS "фиксированных значений" дескрипторов:

    Код (Text):
    1. IF (operand size is 64-bit)
    2.  THEN CS.Selector <-- IA32_STAR[63:48] +10h;
    3. ELSE  CS.Selector <-- IA32_STAR[63:48];
    4. FI;
    5.  
    6. CS.Selector <-- CS.Selector OR 3;          ; RPL forced to 3
    7. SS.Selector <-- IA32_STAR[63:48]+8 OR 3;   ; RPL forced to 3
    8.  
    9. //----------- Set CS to a fixed value -----------//
    10. CS.Base  <-- 0;        ; Flat segment
    11. CS.Limit <-- FFFFFh;   ; 4-GByte limit
    12. CS.Type  <-- 1011b;    ; Code, RE
    13. CS.G     <-- 1;        ; 4-KByte granularity
    14. CS.CPL   <-- 3;
    15. CS.S     <-- 1;
    16. CS.P     <-- 1;
    17.  
    18. IF (operand size is 64-bit)
    19.  THEN  CS.L   <-- 1;   ; 64-bit segment
    20.        CS.D/B <-- 0;   ; Required if CS.L = 1
    21. ELSE   CS.L   <-- 0;   ; 32-bit Compatibility mode
    22.        CS.D/B <-- 1;   ; 32-bit segment
    23. FI;
    24.  
    25. //----------- Set SS to a fixed value -----------//
    26. Copy fixed value CS to SS
    27. SS.Type  <-- 0011b;    ; Data, RW
    28. SS.L     <-- 0;        ; Clear Long-bit
    29. SS.D/B   <-- 1;        ; 32-bit stack segment

    Если коротко, то здесь говорится..
    Если возврат происходит в 64-битный режим, то происходит следующее:
    1. В регистр(CS) помещается селектор из IA32_STAR[63:48] +10h.
    2. В регистр(SS) помещается селектор из IA32_STAR[63:48] +08h.

    Если-же возврат в режим совместимости х32, то:
    1. В регистр(CS) помещается селектор из IA32_STAR[63:48].
    2. В регистр(SS) помещается селектор из IA32_STAR[63:48] +08h.

    Как ЦП вычисляет, в какой из режимов происходит возврат?
    Если размер операндов вызываемой функции равен 64-бита (т.е указан префикс REX.W), то возврат в х64, иначе в х32. Об этом свидетельствует и операция в строке(1) псевдо-кода: IF (operand size is 64-bit). Поскольку в битах IA32_STAR[63:48] лежит значение 0023h, то соответственно получаем селектор(CS) или 23h для х32, или-же: 23h+10h=33h для х64. Значение селектора(SS) в при любых обстоятельствах будет равно: 23h+08h=2Вh.

    STAR.jpg

    Здесь всплывает ещё нюанс, который актуален для тех, кто пишет свою ОС с расчётом на вызов в ней SYSCALL/SYSRET. Из псевдокода выше видно, что запись значений селекторов в CS/SS жёстко запрограммирована на уровне микро-операций, т.е. прибавляются константы 8 или 10h. От сюда следует, что дескрипторы в таблице GDT должны быть расположены в строго определённом порядке: "Kernel Code/Data", и сразу "User Code/Data". Иначе после SYSRET селекторы будут указывать в космос, а не на валидные дескрипторы в GDT. Если запросить структуру GDT в WinDbg, то оказывается так оно и есть:

    Код (Text):
    1. 0: kd> dg 0 @gdtl
    2.                                                         P Si Gr Pr Lo
    3. Sel         Base               Limit           Type     l ze an es ng  Flags
    4. ----  -----------------  -----------------  ----------  - -- -- -- --  --------
    5. 0000  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    6. 0008  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    7. 0010  00000000`00000000  00000000`00000000  Code RE Ac  0 Nb By P  Lo  0000029b
    8. 0018  00000000`00000000  00000000`ffffffff  Data RW Ac  0 Bg Pg P  Nl  00000c93
    9. 0020  00000000`00000000  00000000`ffffffff  Code RE Ac  3 Bg Pg P  Nl  00000cfb
    10. 0028  00000000`00000000  00000000`ffffffff  Data RW Ac  3 Bg Pg P  Nl  00000cf3
    11. 0030  00000000`00000000  00000000`00000000  Code RE Ac  3 Nb By P  Lo  000002fb
    12. 0038  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    13. 0040  00000000`00b92080  00000000`00000067  TSS32 Busy  0 Nb By P  Nl  0000008b
    14. 0048  00000000`0000ffff  00000000`0000f800  <Reserved>  0 Nb By Np Nl  00000000
    15. 0050  ffffffff`fffd9000  00000000`00003c00  Data RW Ac  3 Bg By P  Nl  000004f3
    16. 0058  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    17. 0060  00000000`00000000  00000000`ffffffff  Code RE     0 Bg Pg P  Nl  00000c9a
    18. 0068  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    19. 0070  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    20. 0078  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    Кстати если не указать WinDbg конкретный селектор, а запросить весь диапазон по размеру таблицы в @gdtl, то он выводит невалидный лог с шагом в 8-байт. Чтобы получить реальные значения селекторов, нужно найти в столбце PL все дескрипторы юзера с PL=3, и прибавить эту тройку к значению их селекторов. Это потому, что младшие 2-бита в селекторе задают привилегию запроса RPL:

    Selector.jpg

    Если применить к значению любого селектора операцию сдвига SHR,3, в нём останется только индекс дескриптора в GDT, т.е. его порядковый номер. Например из CS юзера "0x23 shr 3" получим номер(4) и т.д. После всех правок, причёсанный лог WinDbg будет выглядеть так (первый столбец с пп для наглядности я добавил сам):

    Код (Text):
    1. 0: kd> dg 0 @gdtl
    2.                                                             P Si Gr Pr Lo
    3. №   Sel         Base               Limit           Type     l ze an es ng  Flags
    4. --  ----  -----------------  -----------------  ----------  - -- -- -- --  --------
    5. 00  0000  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    6. 01  0008  00000000`00000000  00000000`00000000  <Reserved>  0 Nb By Np Nl  00000000
    7. 02  0010  00000000`00000000  00000000`00000000  Code RE Ac  0 Nb By P  Lo  0000029b
    8. 03  0018  00000000`00000000  00000000`ffffffff  Data RW Ac  0 Bg Pg P  Nl  00000c93
    9. 04  0023  00000000`00000000  00000000`ffffffff  Code RE Ac  3 Bg Pg P  Nl  00000cfb  ; Исправлено
    10. 05  002b  00000000`00000000  00000000`ffffffff  Data RW Ac  3 Bg Pg P  Nl  00000cf3  ; Исправлено
    11. 06  0033  00000000`00000000  00000000`00000000  Code RE Ac  3 Nb By P  Lo  000002fb  ; Исправлено
    12. 08  0040  00000000`00b92080  00000000`00000067  TSS32 Busy  0 Nb By P  Nl  0000008b
    13. 10  0053  ffffffff`fffd9000  00000000`00003c00  Data RW Ac  3 Bg By P  Nl  000004f3  ; Исправлено
    14. 12  0060  00000000`00000000  00000000`ffffffff  Code RE     0 Bg Pg P  Nl  00000c9a
    Но вернёмся к псевдокоду инструкции SYSRET..
    Если на процессорах AMD имеется баг с записью селектора в SS и "фиксированных значений" в теневую его часть, значит этот баг на уровне микроопераций самой инструкции (см.псевдокод). Походу AMD уже исправила ошибку на новых своих процессорах, но что делать обладателям старых ЦП? Как вариант, можно попробовать обновить микрокод процессора.

    А какая картина в х64 со-входом в кернел через устаревшие SYSENTER/SYSEXIT, и проприетарным int-2Eh?
    Значения Kernel CS:EIP и SS:ESP для этих инструкций прописаны в MSR IA32_SYSENTER_CS=0x174, IA32_SYSENTER_EIP=0x176, IA32_SYSENTER_ESP=0x175. При этом в кэши дескрипторов CS/SS так-же заносятся фиксированные значения на уровне микрокодов, и к SS прибавляется +8.

    Когда в MSR IA32-EFER взведёны биты 0 и 8, значения во-всех регистрах IA32_SYSENTER обнуляются. На моём узле в IA32-EFER лежит d01h. Получить из него битовую маску можно метакомандой WinDbg ".formats". Как видим в строке "Binary", все биты взведены, а значит ЦП в режиме Long + Syscall. При этом вызов Sysenter сейчас вызовет исключение доступа Access-Violation (AV):

    Код (Text):
    1. IA32_EFER  (0xC0000080) = 00000000`00000d01
    2.                           бит[0]  — RW, SYSCALL enable (SCE),
    3.                           бит[8]  — RW, Long mode enable (LME),
    4.                           бит[10] — R,  Long mode active (LMA),
    5.                           бит[11] — RW, No Execute Bit enable (NXE).
    6. 0: kd> .formats d01
    7. Evaluate expression:
    8.   Hex:     00000d01
    9.   Decimal: 3329
    10.   Octal:   0000000000000000006401
    11.   Binary:  00000000 00000000 00001101 00000001
    12.   Chars:   ........
    13.   Time:    Thu Jan 01 05:55:29 1970
    14.   Float:   low 4.66492e-042 high 0
    15.   Double:  1.64474e-320
    Что касается прерывания входа в кернел через INT-2Eh, то его поддержку легко проверить в "таблице диспетчеризации прерываний" IDT. При благоприятных обстоятельствах, в IDT должен лежать указатель на процедуру обслуживания прерывания ISR "Interrupt Service Routine" - смотрим в отладчике:

    Код (Text):
    1. 0: kd> r @idtr, @idtl
    2.    idtr=fffff800`03fff080   idtl=0fff   ;//<--- IDT base & limit
    3.  
    4. 0: kd> !idt
    5.    Dumping IDT:  fffff800`03fff080
    6.  
    7.    00:   fffff800`02c75f00  nt!KiDivideErrorFault
    8.    01:   fffff800`02c76000  nt!KiDebugTrapOrFault
    9.    02:   fffff800`02c761c0  nt!KiNmiInterrupt       Stack = FFFFF800`04011000
    10.    03:   fffff800`02c76540  nt!KiBreakpointTrap
    11.    04:   fffff800`02c76640  nt!KiOverflowTrap
    12.    05:   fffff800`02c76740  nt!KiBoundFault
    13.    06:   fffff800`02c76840  nt!KiInvalidOpcodeFault
    14.    07:   fffff800`02c76a80  nt!KiNpxNotAvailableFault
    15.    08:   fffff800`02c76b40  nt!KiDoubleFaultAbort   Stack = FFFFF800`0400F000
    16.    
    17.    09:   fffff800`02c76c00  nt!KiNpxSegmentOverrunAbort
    18.    0a:   fffff800`02c76cc0  nt!KiInvalidTssFault
    19.    0b:   fffff800`02c76d80  nt!KiSegmentNotPresentFault
    20.    0c:   fffff800`02c76ec0  nt!KiStackFault
    21.    0d:   fffff800`02c77000  nt!KiGeneralProtectionFault
    22.    0e:   fffff800`02c77140  nt!KiPageFault
    23.    10:   fffff800`02c77500  nt!KiFloatingErrorFault
    24.    11:   fffff800`02c77680  nt!KiAlignmentFault
    25.    12:   fffff800`02c77780  nt!KiMcheckAbort        Stack = FFFFF800`04013000
    26.    13:   fffff800`02c77b00  nt!KiXmmException
    27.    1f:   fffff800`02cc3f10  nt!KiApcInterrupt
    28.    2c:   fffff800`02c77cc0  nt!KiRaiseAssertion
    29.    2d:   fffff800`02c77dc0  nt!KiDebugServiceTrap
    30.    2f:   fffff800`02cc41f0  nt!KiDpcInterrupt
    31.    .....
    Всего в IDT векторов FFh=256, а в данном фрагменте видно, что INT-2Eh в таблице нет, и после 2dh сразу идёт 2fh. Если-же запросить конкретно вектор(2Eh), получим ответ "Unexpected Interrupt", прокол. Командой "u" можно зайти в отладчике по указанному адресу и посмотреть, как ОС обрабатывает подобную ситуацию.

    Код (Text):
    1. 0: kd> !idt 2e
    2.    Dumping IDT:  fffff800`03fff080
    3.  
    4.    2e:   fffff800`02db22e0   nt!KxUnexpectedInterrupt0+0x2E0
     
    808Problem и Mikl___ нравится это.