2016-11-09 26 views
2

JMP instruction referece.使用JMP指令不恒定的TSS段

根据该文件,我们可以执行jmp一个常数远段:

jmp 0x18:00 

这里,0x18是一个有效的段选择GDT,全局描述符表。

jmp可以与包含有效GDT条目的段寄存器被使用,这是一个代码/数据段描述符:

mov es, 0x18 
jmp es:0x0 

这里,0x18是TSS(任务状态段)描述符跳跃时到CPU,CPU执行任务切换,该切换将其状态自动保存到当前TSS中,然后填充保存在新TSS中的状态。但是,TSS是一个系统段描述符,因此无法加载到任何段寄存器中(正如Intel文档所建议的那样)。那么如何在运行时使用动态分配的TSS跳转到任务?

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

+0

但'ltr'不会导致任务切换,即当前状态会自动保存。但我想我会尝试切换到一个新任务(非中断任务;中断处理程序,因为任务已经工作),然后解决此限制。 – Amumu

+0

我误解了这个问题。 –

+1

我不确定为什么你不能使用JMP作为操作数的内存引用。内存操作数将指向包含选择器/偏移量的内存地址。该内存地址可以在运行时修改 –

回答

4

不仅不能加载ES与TSS选择,指令jmp es:0x0也无效。没有将段寄存器移动到另一个段寄存器的指令(例如ES到CS)。也没有指令会从通用寄存器中加载CS。正如玛格丽特布鲁姆的答案所示,您需要使用带有内存操作数的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位操作系统,这是必需的,因为长时间模式不支持任务切换。

+0

感谢您的全面回答。关于形式'jmp es:0x0',GAS不会拒绝它。这是从sytnax生成的代码:'26 ff 25 00 00 00 00 jmp *%es:0x0'(objdump output)。 – Amumu

+0

@Amumu您的文章没有使用GAS的AT&T语法,所以我的回答和Margaret Blooms使用NASM语法,这是您的文章中汇编代码最相似的语法。 GAS生成一个接近间接jmp,加载EIP,存储在ES:0的32位值不会改变CS(或任务)。 –

+0

那么我使用GAS,但将其设置为英特尔语法。 – Amumu

5
push WORD <TSS_selector> 
push DWORD 0 
jmp FAR [esp] 

假设32位代码和一个可用的堆栈。
这将使栈不平衡和未对齐的调用线程,您可能需要使用专用的存储位置:

mov WORD [tss_pointer + 4], <TSS_selector> 
jmp FAR [tss_pointer] 

tss_pointer dd 0, dw 0 
+1

根据您的上下文,您可以使用红色堆栈来减少此代码:cli; mov word [esp + 2],TSS_selector; mov dword [esp + 6],0 sti; jmp far [esp + 6]。在用户模式下,省略cli和sti指令(但如果连接了调试器,则代码将崩溃)。这段代码依赖于难以理解的事实:中断不能在sti指令之后立即切入,而仅在一条指令之后切入。 – Joshua

+0

谢谢你的建议。它很快给了我最初的想法,以查明我错在哪里。 – Amumu

-1

这里提出的答案是正确的,但是缺少一个部分:所提出的语法不会产生长时间跳跃。我的建议是Margaret Bloom ,但它没有奏效。我的代码一定有问题,因为我知道她给了我正确的答案,因为其他人也提出了同样的问题。综观GDB,当我应用上述的语法:

0x30a9 <task1_start+1> mov ebp,esp 
0x30ab <task1_start+3> pushw 0xa0 
0x30af <task1_start+7> push 0x0 
0x30b1 <task1_start+9> jmp DWORD PTR [esp+0xff06] 

显然:

asm("pushw 0xa0"); 
asm("pushd 0x0"); 
asm("jmp far [esp]"); 

(以上语法内联组件,GCC样式)

看着GDB,jmp far作为生成,[esp + 0xff06]对我来说不是很远。这是一个接近跳跃,从esp偏移。更明显地,从objdump输出:

000030a8 <task1_start>: 
    30a8:  55      push %ebp 
    30a9:  89 e5     mov %esp,%ebp 
    30ab:  66 68 a0 00    pushw $0xa0 
    30af:  6a 00     push $0x0 
    30b1:  ff a4 24 06 ff 00 00 jmp *0xff06(%esp) 
    30b8:  90      nop 
    30b9:  5d      pop %ebp 
    30ba:  c3      ret 

通知操作码在0x30ab,对应于jmp指令。纵观英特尔手册中,操作码的近跳转:

  • 0xff代表jmp指令。
  • 0xa4[--][--] + disp32有效地址为esp的ModR/M字节。这意味着,需要一个SiB字节,这是偏移量。 (参考:表2-2使用ModR/M字节的32位寻址格式)
  • 0x24SiB字节代表ESP,但没有任何缩放(值为none),实际上保持不变。 (参考:表2-3。带有SIB字节的32位寻址表单)。

上述生成jmp对应于FF /4操作码(参考:jmp instruction),这意味着一个近跳,由于所产生的MODR/M字节是0xa4。远跳的正确操作码是FF /5

显然,我必须为汇编程序产生一个跳远。所以,事实证明,它很容易使用,而不是像这样jmp far语法ljmp指令来解决:

ljmp [esp] 

在那之后,我们得到了正确生成代码:

00003088 <task1_start>: 
    3088:  55      push %ebp 
    3089:  89 e5     mov %esp,%ebp 
    308b:  66 68 a0 00    pushw $0xa0 
    308f:  6a 00     push $0x0 
    3091:  ff 2c 24    ljmp *(%esp) 
    3094:  90      nop 
    3095:  5d      pop %ebp 
    3096:  c3      ret 

在上面,ljmp是生成:

  • 0xffjmp的操作码,相同。 ljmp只是GAS(GNU汇编程序)用来生成FF /5操作码的特定语法。
  • 0x2c[--][--](无位移)的ModR/M字节,但位于表2-2中的列5。这意味着,这个操作码真的是FF /5
  • 0x24是近乎跳跃相同,这意味着没有缩放。

这是由GDB看到的实际代码:

0x308b <task1_start+3> pushw 0xa0   
0x308f <task1_start+7> push 0x0    
0x3091 <task1_start+9> jmp FWORD PTR [esp] 

现在,FWORD是新的东西,但至少现在不加随机位移。实际上,该任务正确切换到0xa0

感谢您的建议,每个人。没有它,我可能从来没有调查过。

+0

你的答案有很多问题。你不能像这样使用GCC风格的内联汇编。你需要在一个语句中做所有事情,因为编译器可以在其间插入其他指令。你使用了一个16位推送,这个推送没有对齐堆栈。你用NASM语法代替GAS的版本MASM语法,这就是为什么'jmp far [esp]'不起作用。 –

+0

我设置了这个选项:'-masm = intel',所以GCC使用intel。是的,编译器可以在两者之间插入指令。然而,这只是一种可能性,并且就实际生成的代码而言,在这些推送和跳转指令之间不会插入额外的代码。至于16位推送,这只是一个示范,接着是玛格丽特的回答。我会修复使用内存。我正在使用堆栈来验证它是否有效。 – Amumu

+0

内联汇编语句的另一个问题是旧的任务将在远程跳转后的指令处恢复,寄存器恢复到跳转前的状态。这意味着它会崩溃,因为推到栈上的远地址仍然会在那里。 –