jmp
和 call
是两条在汇编语言中非常常用的跳转指令,它们虽然都涉及程序控制流的跳转,但在功能和应用场景上有显著的区别。
1. 基本功能和行为
-
jmp
指令:- 功能:
jmp
用于无条件跳转,直接将程序的执行流跳转到指定的地址。 - 行为: 执行
jmp
后,程序不会记录跳转前的位置,也不会在执行完目标代码后返回。它只是简单地更改了指令指针(IP/EIP/RIP),程序从新的地址继续执行。 - 应用场景:
jmp
常用于跳转到函数的某个位置、循环控制、跳过某些代码段等。
- 功能:
-
call
指令:- 功能:
call
用于调用子程序。它不仅跳转到目标地址,还保存了返回地址,以便在子程序执行完毕后能够返回调用点继续执行。 - 行为: 执行
call
时,当前指令的下一条指令的地址会被压入堆栈(作为返回地址),然后程序跳转到目标地址。子程序执行完后,通过ret
指令可以从堆栈中弹出返回地址并跳回原来的调用点。 - 应用场景:
call
主要用于函数调用,确保在执行完子程序后程序能够继续从调用点之后的指令执行。
- 功能:
2. 栈操作
-
jmp
:jmp
指令不会涉及栈的操作。它只是简单地修改指令指针(IP/EIP/RIP),所以不会保存任何返回地址。- 因此,
jmp
后执行的代码通常不会返回原位置,除非代码显式使用jmp
或call
来跳回。
-
call
:call
指令在跳转前会自动将当前指令的下一条指令的地址压入栈中。这一操作使得在子程序执行完后,程序可以通过ret
指令从栈中弹出该地址,并跳回到调用点继续执行。- 这使得
call
非常适合用于函数调用,因为它支持在执行完子程序后返回原来的调用位置。
3. 结合内核中的 switch_to
例子
-
在内核的上下文切换中,
jmp
和call
有不同的应用场景。-
jmp
的应用: 在switch_to
宏中,jmp __switch_to
用于直接跳转到上下文切换函数__switch_to
。jmp
的作用是切换到另一个任务,并立即开始执行该任务的代码,而不需要保存当前代码的位置,因为上下文切换不需要返回到原来的位置。 -
call
的应用: 如果内核希望在执行完一个子任务后继续执行当前的任务,比如在系统调用或者中断处理程序中,通常会使用call
。它确保任务执行完后能够通过ret
返回调用点,从而继续执行原来的代码。
-
-
内核上下文切换的行为:
jmp __switch_to
会导致跳转到__switch_to
函数,而不需要返回。而call
会将返回地址(即1:
标签的地址)保存到栈中,确保在__switch_to
执行完后能够通过ret
返回到1:
处继续执行。
-
#define switch_to(prev,next,last) do { \ unsigned long esi,edi; \ asm volatile("pushfl\n\t" \ "pushl %%ebp\n\t" \ /** * 把esp的内容保存到prev->thread.esp中 * 这样该字段指向prev内核栈的栈顶。 */ "movl %%esp,%0\n\t" /* save ESP */ \ /** * 将next->thread.esp装入到esp. * 此时,内核开始在next的栈上进行操作。这条指令实际上完成了从prev到next的切换。 * 由于进程描述符的地址和内核栈的地址紧挨着,所以改变内核栈意味着改变当前进程。 */ "movl %5,%%esp\n\t" /* restore ESP */ \ /** * 将标记为1f的地址存入prev->thread.eip. * 当被替换的进程重新恢复执行时,进程执行被标记为1f的那条指令。 */ "movl $1f,%1\n\t" /* save EIP */ \ /** * 将next->thread.eip的值保存到next的内核栈中。 * 这样,_switch_to调用ret返回时,就会跳转到next->thread.eip执行。 * 这个地址一般情况下就会是1f. */ "pushl %6\n\t" /* restore EIP */ \ /** * 注意,这里不是用call,是jmp,这样,上一条语句中压入的eip地址就可以执行了。 */ "jmp __switch_to\n" \ /** * 到这里,进程A再次获得CPU。它从栈中弹出ebp和eflags。 */ "1:\t" \ "popl %%ebp\n\t" \ "popfl" \ :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \ /* last被作为输出参数,它的值会由eax赋给它。 */ "=a" (last),"=S" (esi),"=D" (edi) \ :"m" (next->thread.esp),"m" (next->thread.eip), \ "2" (prev), "d" (next)); \ } while (0)
4. 总结
-
jmp
:- 用于无条件跳转,不保存返回地址。
- 改变程序执行流,但不会返回到原跳转点。
-
call
:- 用于调用子程序,保存返回地址。
- 在子程序执行完毕后,可以通过
ret
返回调用点继续执行。
-
在操作系统内核中,
jmp
常用于上下文切换等不需要返回的场景,而call
则用于函数调用,确保在执行完毕后返回继续执行。