JMP instruction referece.

根据文档,我们可以对恒定的远段执行jmp:

jmp 0x18:00

在这里,0x18是GDT(全局描述符表)中的有效段选择器。
jmp可以与包含有效GDT条目(即代码/数据段描述符)的段寄存器一起使用:
mov es, 0x18
jmp es:0x0

在这里,0x18是一个TSS(任务状态段)描述符,当跳转到该描述符时,CPU执行任务切换,该任务自动将其状态保存到当前TSS中,然后使用新的TSS中保存的状态进行填充。

但是,TSS是系统段描述符,因此无法加载到任何段寄存器中(如Intel文档所建议)。那么,如何在运行时使用动态分配的TSS跳到任务?

我能想到的唯一方法是使用iret指令,但是我感觉很像黑客,因为我需要修改链接字段,然后将EFLAGS中的NT位设置为执行反向链接任务切换。

最佳答案

您不仅无法使用TSS选择器加载ES,而且指令jmp es:0x0也无效。没有指令将段寄存器移到另一个段寄存器(例如,ES到CS)。也没有指令可以从通用寄存器加载CS。正如玛格丽特·布鲁姆(Margaret Bloom)的回答所显示的那样,您将需要使用带有内存操作数的JMP指令加载CS,特别是需要使用远指针作为内存操作数的指令,以便获得设置CS的远跳转指令。

就实现这一点而言,将此指针放在任务结构中是有意义的,该结构是您放置任务的TSS和其他特定于任务的信息的结构。例如,要切换任务,可以使用如下代码:

struct task {
    struct {
        unsigned offset;
        unsigned short selector;
    } far_jmp_ptr;
    struct tss tss;
    // ...
};

void
switch_tasks(struct task *new_task) {
    asm("jmp FAR PTR %0" : : "m" (new_task->far_jmp_ptr));
}

该代码采用带有远指针的“任务结构”,该指针包含为任务分配的TSS选择器(偏移部分被忽略)。

从技术上讲,您还可以通过使用LTR指令后跟JMP指令跳至任务。这将在不执行任务切换的情况下更改任务,因此不会影响任何寄存器(TR,CS:EIP和您显式更改的任何其他寄存器除外)。例如:
mov  esi, [new_task]
ltr  [esi + TASK_FAR_JMP_PTR + 4]
jmp  [esi + TASK_TSS + TSS_EIP]

仅当新任务在环0上运行并且只是在不需要恢复其寄存器的已知点处开始或停止时,这才是实用的。特别是,这是您可能会启动初始内核任务(或单个TSS操作系统中唯一的任务)的方式。

请注意,大多数操作系统仅对所有任务使用一个TSS,因此不要使用CPU提供的任务切换机制。对于64位操作系统,这是必需的,因为在长模式下不支持任务切换。

08-15 23:25