汇编语句中的 jmp 与 call 指令

jmpcall 是两条在汇编语言中非常常用的跳转指令,它们虽然都涉及程序控制流的跳转,但在功能和应用场景上有显著的区别。

1. 基本功能和行为

  • jmp 指令:

    • 功能: jmp 用于无条件跳转,直接将程序的执行流跳转到指定的地址。
    • 行为: 执行 jmp 后,程序不会记录跳转前的位置,也不会在执行完目标代码后返回。它只是简单地更改了指令指针(IP/EIP/RIP),程序从新的地址继续执行。
    • 应用场景: jmp 常用于跳转到函数的某个位置、循环控制、跳过某些代码段等。
  • call 指令:

    • 功能: call 用于调用子程序。它不仅跳转到目标地址,还保存了返回地址,以便在子程序执行完毕后能够返回调用点继续执行。
    • 行为: 执行 call 时,当前指令的下一条指令的地址会被压入堆栈(作为返回地址),然后程序跳转到目标地址。子程序执行完后,通过 ret 指令可以从堆栈中弹出返回地址并跳回原来的调用点。
    • 应用场景: call 主要用于函数调用,确保在执行完子程序后程序能够继续从调用点之后的指令执行。

2. 栈操作

  • jmp:

    • jmp 指令不会涉及栈的操作。它只是简单地修改指令指针(IP/EIP/RIP),所以不会保存任何返回地址。
    • 因此,jmp 后执行的代码通常不会返回原位置,除非代码显式使用 jmpcall 来跳回。
  • call:

    • call 指令在跳转前会自动将当前指令的下一条指令的地址压入栈中。这一操作使得在子程序执行完后,程序可以通过 ret 指令从栈中弹出该地址,并跳回到调用点继续执行。
    • 这使得 call 非常适合用于函数调用,因为它支持在执行完子程序后返回原来的调用位置。

3. 结合内核中的 switch_to 例子

  • 在内核的上下文切换中,jmpcall 有不同的应用场景。

    • jmp 的应用: 在 switch_to 宏中,jmp __switch_to 用于直接跳转到上下文切换函数 __switch_tojmp 的作用是切换到另一个任务,并立即开始执行该任务的代码,而不需要保存当前代码的位置,因为上下文切换不需要返回到原来的位置。

    • 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 则用于函数调用,确保在执行完毕后返回继续执行。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值