如何确定变量是函数的局部变量还是传递给函数的参数?

评论

需要更多信息:您正在分析什么,“变量”是什么意思,您对此有什么信息?

知道您使用哪种工具来指示您正确的输出结构\显示选项,这也将很有用。
我没有使用工具,而是课堂作业。我有一个ASM块,应该确定(如果我正确理解了问题)调用约定以及变量(在这种情况下为DWORDS)是函数的局部变量还是传递给函数的参数。

#1 楼

参数

它不仅取决于平台,而且不同的函数具有不同的调用约定。调用约定基本上告诉您如何知道参数在哪里。它没有说明本地函数堆栈的框架布局。

了解当编译器或链接器可以证明函数或方法不会被编译器在当前翻译单元或链接器中看不到的代码以外的代码访问时,也非常重要在当前的二进制文件中,他们可以自由地执行他们想要的任何事情。这包括以与ABI不对应的方式传递参数。随着链接时间代码生成的日益普及,这是一个更大的问题。

ABI基本上是平台上所有二进制文件都承诺遵守的接口,因此可以保证以二进制形式编写不同的语言可以互动。但是,如果二进制文件不导出函数,则通常不需要遵循ABI。

变量

变量实际上只能存储在三个位置


它们可以存储在当前函数的本地堆栈框架中的堆栈中。当相对于堆栈指针完成访问并且偏移量在函数堆栈帧内并且在保存的本地寄存器之前时,可以看到这一点。这通常意味着变量在没有链接寄存器的情况下早于调用约定的返回地址。通常,在偏移量位于返回地址之前的位置进行访问。带有链接寄存器的调用约定不会在堆栈上存储返回地址,除非它们必须这样做,否则变量会先于堆栈帧的已保存寄存器部分,并且基于堆栈的参数位于堆栈帧的已保存寄存器部分之前。
全局存储,通常在加载器映射的可执行二进制文件的堆中或在其片段中。
寄存器,通常用于函数中经常使用的变量,因为寄存器组是CPU可用的最快存储。如果编译器确定CPU具有足够的寄存器来将所有变量存储在寄存器组中,则变量也可以在函数有效期内保留在寄存器中。哪些寄存器存储什么以及何时存储的优化称为寄存器分配。同样重要的是要认识到超出范围或以后在函数中未使用的变量将释放寄存器。这意味着一个寄存器可以在函数的不同阶段映射到不同的变量。

调用约定

IA32调用约定

大致顺序如下:

cdecl


在cdecl中,子例程参数在堆栈上传递。整数值和存储器地址在EAX寄存器的浮点值中返回-在ST0 x87寄存器中。寄存器EAX,ECX和EDX已保存为被呼叫者保存,其余的已保存为被呼叫者。调用新函数时,x87浮点寄存器ST0至ST7必须为空(弹出或释放),并且退出函数时ST1至ST7必须为空。


此调用


在Microsoft Visual C ++编译器上,this指针在ECX中传递,并且被调用方清理堆栈,并镜像C对此编译器和Windows API函数使用的stdcall约定。当函数使用可变数量的参数时,调用方将清理堆栈。


stdcall



Callee负责清理堆栈。堆栈,但是参数按照从右到左的顺序推入堆栈,如_cdecl调用约定一样。寄存器EAX,ECX和EDX被指定在该功能内使用。返回值存储在EAX注册表中。传递适合ECX和EDX的前两个参数(从左到右评估)。其余参数从右到左被压入堆栈。


pascal


参数从左到右顺序压入堆栈(与cdecl相反),被调用方负责在返回之前平衡堆栈。


AMD64调用约定

Microsoft


Microsoft x64调用约定[9](用于x86-64上的长模式)使用寄存器RCX,RDX,R8,R9用于前四个整数或指针参数(从左到右的顺序),并且XMM0,XMM1,XMM2,XMM3用于浮点参数。其他参数将压入堆栈(从右到左)。如果不超过64位,则在RAX中返回整数返回值(类似于x86)。浮点返回值在XMM0中返回。少于64位长的参数不会扩展为零;高位包含垃圾。


几乎所有其他人


随后在Solaris,GNU / Linux,FreeBSD和其他非Microsoft操作上系统。前六个整数或指针参数在寄存器RDI,RSI,RDX,RCX,R8和R9中传递,而XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6和XMM7用于浮点参数。对于系统调用,使用R10代替RCX。[11]与Microsoft x64调用约定一样,其他参数在堆栈上传递,返回值存储在RAX中。


ARM调用约定


r14是链接寄存器,r12是过程内调用暂存寄存器,r0到r3用于保存传递给子例程的参数值,还保留从子例程返回的结果。


超过4个参数将它们压入堆栈。

PowerPC调用约定


由于PowerPC具有如此多的GPR(与ia32的8相比,有32个GPR),因此参数在以gpr3开始的寄存器中传递。寄存器gpr3至gpr12是易失性(调用者保存)寄存器,在调用子例程之前必须保存(如有必要),并在返回后将其恢复。可变参数计数函数将被调用者的参数存储在堆栈中。


MIPS调用约定

O32


寄存器$ a0- $ a3中函数的前四个参数;后续参数在堆栈上传递。如果被调用方需要保存其参数,则堆栈上的空间将为$ a0- $ a3保留,但调用方不会将寄存器存储在其中。返回值存储在寄存器$ v0中。第二个返回值可以存储在$ v1中。


N32和N64


将前八个参数传递给寄存器$中的函数a0- $ a7;后续参数在堆栈上传递。返回值(或指向它的指针)存储在寄存器$ v0中。第二个返回值可以存储在$ v1中。在N32和N64 ABI中,所有寄存器都被认为是64位宽。


评论


+1获得详细答案!备注:所描述的fastcall是ms实现它的方式。 Borland / Embarcadero实现快速调用的方式有所不同:前三个参数在EAX,EDX和ECX中传递,其余参数进入堆栈(从左至右)

–雷姆科
13年4月11日在21:14

#2 楼

尽管此答案在所有情况下都不一定正确,但您的老师正在寻找的答案可能是:


局部变量的形式为[EBP - ...]格式为[EBP + ...]



评论


最简单的反例是使用“ gcc -fomit-frame-pointer”编译的任何内容

–道格拉
13年4月12日在5:44

是的,参数可以通过寄存器传递,也可以相对于ESP引用,也可以通过其他几种方式访问​​。上面的“答案”是最常见的情况,我想这就是Jannu的老师想要的。

–詹森·格夫纳(Jason Geffner)
13年4月12日在14:08



#3 楼

我不确定变量的含义,我只是假设它是在函数体执行过程中某个时刻的寄存器或堆栈地址的值。

如果通过了通过参数,它的值是由调用者通过堆栈或寄存器定义的(在此处检查不同的调用约定)。如果不是,则由功能主体本身定义。