发布时间:2025-12-09 18:54:03 浏览次数:4
汇编眼中的函数,函数就是一系列指令的集合,为了完成某个会重复使用的特定功能。
可以使用JMP指令或者CALL指令来进行调用函数,先看JMP指令。
假设定义一个函数功能为将eax,ecx的值赋值为0,假设使用JMP来进行调用
此时就会出现一个问题,当通过JMP调用了指令后,无法再次回到使用JMP指令的地方,解决的话可以在函数中再次使用JMP指令跳转回来。
但是这样做同样也会出现问题,回想函数的定义,重复使用的特定功能,那么下次再进行函数时,仍然会回到首次定义的JMP地方,无法回到下次使用函数的地方,所以使用JMP指令来调用函数就不太方便。
这里再使用CALL指令来调用函数,由于CALL指令会将当前指令的下一行存储在堆栈中,所以直接在函数的最下面进行ret就可以回到之前执行函数的地方了。
运行后观察结果
以写一个加法的函数为例子
add eax,ecxret这里的参数指的是就是eax和ecx,返回值就是eax,如下
运行结果后,eax应该为7,同时指针回到0040ef44,运行后观察结果。
如果在参数很多的情况下,计数器可能不够用情况,此时就可以用堆栈进行传递参数。
这里以计算5个参数值为例,先将值压入栈中
push 1push 2push 3push 4push 5定义函数,此时应该要将最上层的栈的值给到eax中,然后连续让eax加上下面的几层栈存储的值
mov eax,dword ptr ds:[esp+4]add eax,dword ptr ds:[esp+8]add eax,dword ptr ds:[esp+C]add eax,dword ptr ds:[esp+10]add eax,dword ptr ds:[esp+14]ret运行测试
效果正常实现
虽然上述实验成功实现效果,但是存在一个小问题,最后堆栈并没有还原,也就是所谓的没有堆栈平衡。
上述程序在运行前,栈的最上面是12ffc4,但是函数运行结束后,则变成了12ffb0
针对上面的问题,第一个解决方案就是采用外平栈,在call指令后使用add esp,8就可以恢复栈的原有值了。
当然还可以直接将ret改为ret 8(等同于ret后再add esp,8),实现函数内的栈平衡,称为内平栈。
从上面的例子可以看到最终拿出之前压入栈中的值时,是以esp为基址进行查找的,这种行为称为esp寻址。
mov eax,dowrd ptr ss:[esp+8]add eax,dowrd ptr ss:[esp+4]ret这种寻址方式有非常明显的好处,因为esp寻找起来非常简单和直白。同样的,也是有存在缺点的。
假设某函数在使用时需要用寄存器,但是又无法将寄存器的值进行直接清空,需要保留,所以在执行函数前需要先保留寄存器中的值
push eaxpush ecxmov eax,dowrd ptr ss:[esp+8]add eax,dowrd ptr ss:[esp+4]ret但是此时就会存在一个问题,由于push指令改变了栈,所以此时esp的值不能再直接去加了,而是要根据使用的指令情况来增加,这里由于使用了两个push,所以整体函数变成了
push ecxpush edxmov eax,dowrd ptr ss:[esp+C]add eax,dowrd ptr ss:[esp+10]ret同时在使用完ecx和edx后也需要还原,所以还得继续使用pop做堆栈平衡。
push ecxpush edxmov eax,dowrd ptr ss:[esp+C]add eax,dowrd ptr ss:[esp+10]pop edxpop ecxret从这个例子中也能看到缺点,如果之前push的指令比较多,影响了堆栈,那么在使用esp寻址时就需要手动计算esp的变更后的值,相对麻烦一些。
从刚刚的情况中找到了不足,这里可以使用ebp来进行寻址,ebp是栈底指针。可以看下面的例子
push ebpmov ebp,espsub esp,10先将ebp的值存储栈中以便后续还原,将着将ebp设置到原有的esp的位置,接着减少esp的值,这样就可以重新扩展出一块堆栈了,使用时不会影响原有的栈。此时以ebp来寻址的话,就不会再重新计算参数的位置了,因为在使用堆栈的时候ebp的值是不会改变的。所以此时可以直接取值
mov eax,dword ptr ss:[ebp+4]add eax,dowrd ptr ss:[bgp+8]同时在完成函数后,还需要做平栈,还原ebp和esp。
mov esp,ebppop ebpret虽然感觉多花了一些步骤,但是实际上如果函数步骤复杂,使用的堆栈较多的情况下,使用ebp寻址还是很有优势的。
有条件修改eip寄存器的指令,比如JMP和CALL都是无条件修改。
JCC指令是通过查看标记寄存器来进行判断的
常见指令如下
JE,JZ,是结果为0则跳转,ZF=1JNE,JNZ,是结果不为0则跳转,ZF=0JS,结果为负则跳转,SF=1JNS,结果为非负则跳转,SF=0JP,JPE,结果中1的个数要是偶数则跳转,PF=1JNP,JPO,结果中1的个数要是奇数则跳转,PF=0JO:结果溢出则跳转,OF=1JNO,结果未溢出则跳转,OF=0JB,JNAE,是无符号数小于则跳转,CF=1JNB,JAE,是无符号数大于等于则跳转,CF=0JBE,JNA,是无符号数小于等于则跳转,CF=1 or ZF=1JNBE,JA,是无符号数大于则跳转,CF=0 and ZF=0JL,JNGE,是有符号数小于则跳转,SF!=OFJNL,JGE,是有符号数大于等于则跳转SF=OFJLE,JNG,是有符号数小于等于则跳转,ZF=1 or SF!=OFJNLE,JG ,是有符号数大于则跳转 ZF=0 and SF=OF