想要循环调用一个函数,其实有很多种方法,除了以下常见实现方式(for ,while),还可以通过修改rip寄存器来实现循环,文章末尾会介绍具体的案例。
常见循环实现
1 | // 1. for |
rip实现循环
先来看一段代码
1 | // loop.c |
1 | 执行 |
为什么会出现这种效果?让我们先了解一下程序的执行过程。
运行时栈
main函数中调用loop,调用堆栈如下图所示:
在main函数的栈帧中可以看到,会将loop函数(例子中没有参数)参数入栈,随后保存PC(即main中printf的地址)。通过call指令实现将PC值push到栈中,并将PC设置为loop函数的地址。loop执行后调用ret指令,从栈中保存的返回地址恢复到PC中,从而main函数继续执行。
PC (program counter), 程序计数器,在x86-64中,用%rip寄存器表示。
The instruction pointer register points to the memory address which the processor will next attempt to execute.
反汇编
现在我们大致了解了程序执行时栈的变化过程,要想实现loop函数循环,只要将saved PC在栈中的值改为loop()这条指令的地址。接下来,让我们看看a.out的关键部分的反汇编代码。
1 | objdump反汇编a.out |
27行汇编 callq调用loop的时候,会将下一条指令(leaq)的地址写入栈中,同时将PC设置为loop的地址,实现调用loop的效果,callq和leaq的地址相差5,在loop函数中,我们可以通过变量i来寻找saved PC的在栈中的地址,从来达到loop返回后,继续执行loop的效果。
1 | # &i+2是saved PC的地址 |
案例-go defer实现
在函数ret之前,调用deferreturn,deferreturn会循环调用,检查defer函数是否全部调用完成。
1 | runtime.jmpdefer: |