欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 锐评 > 汇编语句中的 jmp 与 call 指令

汇编语句中的 jmp 与 call 指令

2024/10/25 10:22:29 来源:https://blog.csdn.net/zhu_superman/article/details/141507101  浏览:    关键词:汇编语句中的 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 则用于函数调用,确保在执行完毕后返回继续执行。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com