x86/x64:修改TSS字段

Giu*_*ini 3 x86 assembly x86-64

活动时会发生什么(即TR寄存器指向的那个)TSS的字段会发生变化?特别是,对ESP0/RSP0字段的更改是否立即生效?或者处理器是否与段选择器一样保留TSS的高速缓存,因此需要LTR指令来强制处理器重新加载TSS字段?

Mar*_*oom 7

处理器使用TSS存储当前上下文并在任务切换期间加载下一个要调度的上下文.
在CPU切换到此类TSS之前,更改TSS结构不会影响任何上下文.

CPU执行任务切换时

软件或处理器可以通过以下方式之一分派任务执行:

•使用CALL指令显式调用任务.
•使用JMP指令显式跳转到任务.
•(通过处理器)对中断处理程序任务的隐式调用.
•对异常处理程序任务的隐式调用.
•当EFLAGS寄存器中的NT标志置位时,返回(使用IRET指令启动).

您可以在英特尔手册3的第7章上阅读有关TSS的信息.


ltr不执行开关,从英特尔手册2:

在任务寄存器中加载段选择器之后,处理器使用段选择器在全局描述符表(GDT)中定位TSS的段描述符.
然后,它将TSS的段限制和基址从段描述符加载到任务寄存器中.
任务寄存器指向的任务标记为忙,但不会切换到任务.


编辑:我实际测试过CPU是否缓存了TSS的静态值.
测试包括一个引导程序(附件)

  • 创建一个GDT,其中包含两个带DPL 0和3的代码段,两个带DPL 0和3的数据段,一个TSS和一个带DPL 3的调用门,代码段为DPL 0.
  • 切换到保护模式,将TSS中的ESP0值设置为v1并加载tr.
  • 使用DPL 3返回代码段,将ESP0的值更改v2并调用Call gate.
  • 检查ESP是否为v1 -10h或v2 -10h,分别打印1或2(如果由于某种原因不匹配,则为0).

在我的Haswell和Bochs上,结果是2,这意味着CPU在需要时从内存(层次结构)读取TSS.

虽然对模型的测试不能推广到ISA,但事实并非如此.


BITS 16

xor ax, ax          ;Most EFI CPS need the first instruction to be this

;But I like to have my offset to be close to 0, not 7c00h

jmp 7c0h : WORD __START__

__START__:

  cli

  ;Set up the segments to 7c0h

  mov ax, cs
  mov ss, ax
  xor sp, sp
  mov ds, ax


  ;Switch to PM

  lgdt [GDT]

  mov eax, cr0
  or ax, 1
  mov cr0, eax

  ;Set CS

  jmp CS_DPL0 : WORD __PM__ + 7c00h

__PM__:

  BITS 32

  ;Set segments

  mov ax, DS_DPL0
  mov ss, ax
  mov ds, ax
  mov es, ax

  mov esp, ESP_VALUE0

  ;Make a minimal TSS BEFORE loading TR

  mov eax, DS_DPL0
  mov DWORD [TSS_BASE + TSS_SS0], eax
  mov DWORD [TSS_BASE + TSS_ESP0], ESP_VALUE1


  ;Load TSS in TR

  mov ax, TSS_SEL
  ltr ax

  ;Go to CPL = 3

  push DWORD DS_DPL3 | RPL_3
  push DWORD ESP_VALUE0
  push DWORD CS_DPL3 | RPL_3
  push DWORD __PMCPL3__ + 7c00h
  retf

__PMCPL3__:

  ;UPDATE ESP IN TSS

  mov ax, DS_DPL3 | RPL_3
  mov ds, ax

  mov DWORD [TSS_BASE + TSS_ESP0], ESP_VALUE2


  ;SWITCH STACK

  call CALL_GATE : 0

  jmp $


__PMCG__:

  mov eax, esp


  mov bx, 0900h | '1'
  cmp eax, ESP_VALUE1 - 10h
  je __write

  mov bl, '2'
  cmp eax, ESP_VALUE2 - 10h
  je __write

  mov bl, '0'

__write:

  mov WORD [0b8000h + 80*5*2], bx

  cli
  hlt


GDT dw 37h
    dd GDT + 7c00h      ;GDT symbol is relative to 0 for the assembler
                ;We translate it to linear

    dw 0


    ;Index 1 (Selector 08h)
    ;TSS starting at 8000h and with length = 64KiB

    dw 0ffffh
    dw TSS_BASE
    dd 0000e900h


    ;Index 2 (Selector 10h)
    ;Code segment with DPL=3

    dd 0000ffffh, 00cffa00h

    ;Index 3 (Selector 18h)
    ;Data segment with DPL=0

    dd 0000ffffh, 00cff200h


    ;Index 4 (Selector 20h)
    ;Code segment with DPL=0

    dd 0000ffffh, 00cf9a00h

    ;Index 5 (Selector 28h)
    ;Data segment with DPL=0

    dd 0000ffffh, 00cf9200h

    ;Index 6 (Selector 30h)
    ;Call gate with DPL = 3 for SEL=20

    dw __PMCG__ + 7c00h
    dw CS_DPL0
    dd 0000ec00h


  ;Fake partition table entry

  TIMES 446-($-$$) db 0

  db 80h, 0,0,0, 07h


  TIMES 510-($-$$) db 0
  dw 0aa55h

  TSS_BASE  EQU     8000h
  TSS_ESP0  EQU     4
  TSS_SS0   EQU     8

  ESP_VALUE0    EQU 7c00h
  ESP_VALUE1    EQU 6000h
  ESP_VALUE2    EQU 7000h

  CS_DPL0   EQU 20h
  CS_DPL3   EQU 10h
  DS_DPL0   EQU 28h
  DS_DPL3   EQU 18h
  TSS_SEL   EQU 08h
  CALL_GATE EQU 30h


  RPL_3     EQU 03h
Run Code Online (Sandbox Code Playgroud)

  • @GiuseppeGuerrini我实际上已经测试了CPU的行为,在我的模型中,值在需要时从内存中读取.我相信这可以推广到任何模型. (2认同)

Ros*_*dge 5

仅在必要时读取TSS,并且没有特殊的TSS缓存。(GDT中的TSS描述符与段描述符一样被缓存,但是TSS本身的内容不被缓存。TSS可以像任何其他内存区域一样被缓存在普通的L1 / L2 / L3内存缓存中。)

在不同的情况下可以读取TSS的三个不同区域。在适当的情况下,更改TSS中的任何值均无效。他们是:

  1. I / O指令(IN,INS,OUT,OUTS)在虚拟8086模式下或在CPL> IOPL时执行。这将导致I / O映射基地址字段和它指向的I / O映射被读取。
  2. 当CR4.VME为1时,将在虚拟8086模式下执行软件中断(INT)。这将导致读取I / O映射基字段和它指向的中断重定向位图。
  3. 从较低的特权级别更改为较高的特权级别(从较高编号的环到较低编号的环)会导致堆栈切换。这将导致根据新的特权级别读取SS0 / ESP0,SS1 / ESP1,SS2 / ESP2,RSP0,RSP1或RSP2字段。
  4. 发生任务切换,导致读取I / O映射基地址,SS0 / ESP0,SS1 / ESP1,SS2 / ESP2和上一个任务链接字段之外的所有新TSS中定义的字段。当IRET指令导致任务切换回嵌套任务时,将读取旧TSS的“先前任务链接”字段。
  5. 在64位模式下会发生任何类型的中断或异常,并且相应IDT条目的IST字段不为0。这将导致读取TSS的相应ISTn字段。

请注意,在64位模式下,只能发生情况1、3和5,因为64位模式不支持虚拟8086模式,也不支持任务切换。

除了GDT中与给定选择器相对应的条目之外,LTR指令不会导致读取任何内存区域,也没有刷新它的内部TSS缓存。

  • @GiuseppeGuerrini我相信Ross和我确实在说同一件事,需要时从内存层次结构中读取TSS。 (2认同)