汇编系列文章:
https://www.jianshu.com/nb/29822876
stack的使用
- 使用SS和ESP进行寻址,每一次push和pop都是对栈顶操作,栈顶在低地址,同时是指向顶部存储的内容。如PUSH后,会先移动后再push进去东西。
- PUSH、POP都是只有一个操作数,因为只有一个栈。
- 注意由于后进先出,PUSH和POP的操作对象顺序相反。
- PUSHFD、POPFD:用于EFLAGS寄存器
- PUSHAD、POPAD:所有32位通用寄存器,顺序:
EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI
- PUSHA、POPA用于16位
过程procedure定义:
- 输入输出可以使用寄存器
- 使用PROC和ENDP定义,里面使用ret,直接用call调用
- ret用来进行EIP赋值,继续执行调用前的下一条的指令
- USES操作符:在PROC之后加上USES esi ecx可以让编译器自动在PROC内部加上两个寄存器的push和pop操作
LINK library
- 用机器码编译出来的procedures
- 自己生成lib时需要先把每一个asm编译成obj然后把他们添加在lib文件中
- 使用include来声明函数原型
64位
- 用PROTO,使用的栈指针是RSP,每次减8;
- 前四个参数必须放在RCX、RDX、R8、R9里
- 必须在stack中预留至少32bytes,子过程调用时栈指针在16-byte边界上对齐
Stack Frames栈帧
- 也叫做activation record,用于记录调用过程传递的参数、返回地址、保存的寄存器及局部变量的值
- 调用的时候参数入栈,调用过程,被调用的过程将EBP推入栈中,在函数调用时把原来的栈顶esp变成被调用函数的栈底ebp。如果需要局部变量,会在esp上减小常数以存放。
- 子过程中,
push ebp
、mov ebp, esp
,最后pop ebp
- 子过程中,
- stack parameters:比用寄存器传递参数更方便,直接把参数倒序入栈。
- 值传递时,为了保证栈对齐,保护模式中只使用32位值作为参数,返回值放在EAX里面,如果被调用过程没有把参数清除就自己清除这个栈。
- 引用传递时,传递offset
- 栈传参数&清除
- 在call之前push,如push两个参数,在子过程中可以使用[esp + 8]、[esp + 12]获取参数
- 最后可以由子过程ret 8来清除参数(相当于让esp移动),或者在caller中进行(让esp移动)
- 局部变量
- 如函数中,
int x = 10
,就在改变ebp、esp之后sub esp 4
,并且把局部变量存进去。 - 高到低:参数、return地址、ebp、局部变量
- 如函数中,
- ENTER:为被调用过程保留栈帧(
push ebp
、mov ebp esp
、sub esp, n
) - LEAVE:
mov esp ebp
、pop ebp
- LOCAL伪指令:
local var1:BYTE, var2:DWORD
这样声明 - LEA:获取栈帧中内容或者局部变量的offset,而OFFSET只能获取常offsets
INVOKE
INVOKE
后跟PROC、参数列表- 64位不可用
PROC
label PROC [attributes] [USES list], params
PROTO
label PROTO paramslist
参数类型
- 输入参数、输出参数、输入输出参数