函数说明,简单函数调用代码如下:
代码:
这里我们重复调用了这个函数,来分析编译器传递参数的过程。
我们通过PUSH将一个字符压入堆栈,这个相当于我们c函数传入的参数,然后通过call,调用一个汇编定义的函数,函数里面通过MOV将字符参数装入CL,并且输出到串口。
1. 加载编译好的系统镜像,代码调转到此处,如下图,我们可以看到反编译的系统代码。
2. 单步运行PUSH ‘B’,然后查看当前段寄存器和通用寄存器。弱弱的补充一下rax是eax的64位扩展,eax是ax的32位扩展。这里的r都可以忽略。我们可以看到当前堆栈段寄存器地址是SS=0x8。段顶指针寄存器SP=0x400fffc,段基址是0x1fff0000。我们通过x命令打印堆栈里面的20个字节用16进制表示,这个20字节从段顶开始偏向段基址。
我们可以看到从段顶开始两个字节是0x0042,这个就是我们压入堆栈的‘B,它被存在偏移地址0x400fffc-0x400fffd这个地方。之后数据都是0。
3. 继续单步调式,我们进入调用的函数,通过u我们察看当前代码后面的10条反汇编指令,可以看到我们已经进入到打印函数内部第一条语句。这条语句MOV CL,byte ptr ss:[esp+4]就是将我们刚刚压入堆栈的参数得到,然后赋值给cl寄存器。Esp+4的目的就是掉过调用call指令自动压入堆栈的CS和IP的值。接着来看这些值。
4. 接着我们继续打印当前寄存器,结果如下图。从这个图我们可以发现,段顶指针已经从0x400fffc移动到了0x400fff8,然后通过x命令,查看当前堆栈里面的数据,我们可以发现有四个字节的新数据被压入了进来分别是0x65d0 和0x0000.对了,这两个值就是我们调用CALL指令的时候自动压入的IP和CS,它们分别是CALL后面一条指令的程序偏移地址,和代码段地址。从前面的截图我们可以推算到这个0x65d0就是偏移地址IP,但是为什么这个代码段地址是0呢,当前代码段明明是CS=0x60,那是因为调用的是本段函数,所以这里的CS=0,表示不进行段间调转。所以只需要偏移地址。到目前为止我们可以看到我们一共有8个字节的数据被压入堆栈:4个字节参数,2个字节IP,2个字节CS。到这里就可以理解那个ESP+4就是要掉过4个字节的IP和CS,取得参数。
5. 设置断点直接掉到函数结尾ret 0x4。这条指令表示返回时候返回IP,并且清除4个字节压入堆栈的数据。这样说明堆栈内数据要被相当于pop出8个字节。
6. 执行ret 4.代码走到下一个CALL,这时候我们可以看到当前指令地址是0x60:0xb5d0,这个0xb5d0就是我们刚刚压入堆栈的下一条指令的IP。接下来我们再来看看堆栈。
7. 我们可以看到,这个时候Sp已经移动到了0x4010000,这样看来比调用函数之前的0x400fffc要增加了4个字节,这个说明在函数返回的时候,不仅返回了IP,CS的四个字节,系统还清除了ret指定的4个字节,也就是说,调用函数之前允许最多押送4个字节进入堆栈作为参数使用。这样也相当于重新清空了堆栈,我们可以看到新的堆栈顶部以下都没有东西了。全是0。