atk*_*yla 14 c c++ arrays x86 assembly
为了看看这个,我写了这个简单的代码,我刚刚创建了不同类型的变量,并通过值,引用和指针将它们传递给函数:
int i = 1;
char c = 'a';
int* p = &i;
float f = 1.1;
TestClass tc; // has 2 private data members: int i = 1 and int j = 2
Run Code Online (Sandbox Code Playgroud)
函数体留空,因为我只是看看如何传入参数.
passByValue(i, c, p, f, tc);
passByReference(i, c, p, f, tc);
passByPointer(&i, &c, &p, &f, &tc);
Run Code Online (Sandbox Code Playgroud)
想知道数组的不同之处以及如何访问参数.
int numbers[] = {1, 2, 3};
passArray(numbers);
Run Code Online (Sandbox Code Playgroud)
部件:
passByValue(i, c, p, f, tc)
mov EAX, DWORD PTR [EBP - 16]
mov DL, BYTE PTR [EBP - 17]
mov ECX, DWORD PTR [EBP - 24]
movss XMM0, DWORD PTR [EBP - 28]
mov ESI, DWORD PTR [EBP - 40]
mov DWORD PTR [EBP - 48], ESI
mov ESI, DWORD PTR [EBP - 36]
mov DWORD PTR [EBP - 44], ESI
lea ESI, DWORD PTR [EBP - 48]
mov DWORD PTR [ESP], EAX
movsx EAX, DL
mov DWORD PTR [ESP + 4], EAX
mov DWORD PTR [ESP + 8], ECX
movss DWORD PTR [ESP + 12], XMM0
mov EAX, DWORD PTR [ESI]
mov DWORD PTR [ESP + 16], EAX
mov EAX, DWORD PTR [ESI + 4]
mov DWORD PTR [ESP + 20], EAX
call _Z11passByValueicPif9TestClass
passByReference(i, c, p, f, tc)
lea EAX, DWORD PTR [EBP - 16]
lea ECX, DWORD PTR [EBP - 17]
lea ESI, DWORD PTR [EBP - 24]
lea EDI, DWORD PTR [EBP - 28]
lea EBX, DWORD PTR [EBP - 40]
mov DWORD PTR [ESP], EAX
mov DWORD PTR [ESP + 4], ECX
mov DWORD PTR [ESP + 8], ESI
mov DWORD PTR [ESP + 12], EDI
mov DWORD PTR [ESP + 16], EBX
call _Z15passByReferenceRiRcRPiRfR9TestClass
passByPointer(&i, &c, &p, &f, &tc)
lea EAX, DWORD PTR [EBP - 16]
lea ECX, DWORD PTR [EBP - 17]
lea ESI, DWORD PTR [EBP - 24]
lea EDI, DWORD PTR [EBP - 28]
lea EBX, DWORD PTR [EBP - 40]
mov DWORD PTR [ESP], EAX
mov DWORD PTR [ESP + 4], ECX
mov DWORD PTR [ESP + 8], ESI
mov DWORD PTR [ESP + 12], EDI
mov DWORD PTR [ESP + 16], EBX
call _Z13passByPointerPiPcPS_PfP9TestClass
passArray(numbers)
mov EAX, .L_ZZ4mainE7numbers
mov DWORD PTR [EBP - 60], EAX
mov EAX, .L_ZZ4mainE7numbers+4
mov DWORD PTR [EBP - 56], EAX
mov EAX, .L_ZZ4mainE7numbers+8
mov DWORD PTR [EBP - 52], EAX
lea EAX, DWORD PTR [EBP - 60]
mov DWORD PTR [ESP], EAX
call _Z9passArrayPi
// parameter access
push EAX
mov EAX, DWORD PTR [ESP + 8]
mov DWORD PTR [ESP], EAX
pop EAX
Run Code Online (Sandbox Code Playgroud)
我假设我正在查看与参数传递相关的正确程序集,因为每个结尾都有调用!
但由于我对装配的知识非常有限,我不知道这里发生了什么.我学习了ccall约定,所以我假设正在进行的事情与保留调用者保存的寄存器然后将参数推送到堆栈有关.因此,我期待看到所有内容被加载到寄存器中并"推送"到处,但不知道mov
s和lea
s发生了什么.另外,我不知道是什么DWORD PTR
.
我只学习了寄存器:eax, ebx, ecx, edx, esi, edi, esp
而且ebp
,所以看到类似的东西XMM0
或者DL
只是让我感到困惑.我想这看起来很有意义lea
通过引用/指针传递因为它们使用内存地址,但我实际上无法告诉发生了什么.当涉及到传递值时,似乎有很多指令,所以这可能与将值复制到寄存器有关.不知道何时将数组作为参数传递和访问.
如果有人能够向我解释每个装配块的一般概念,我将非常感激.
Igo*_*pov 18
使用CPU寄存器传递参数比使用内存更快,即堆栈.但是,CPU中的寄存器数量有限(特别是在x86兼容的CPU中),因此当函数有许多参数时,则使用堆栈而不是CPU寄存器.在您的情况下,有5个函数参数,因此编译器使用堆栈作为参数而不是寄存器.
原则上,编译器可以使用push
指令在实际操作之前将参数推送到堆栈call
,但是许多编译器(包括gnu c ++)用于mov
将参数推送到堆栈.这种方式很方便,因为它不会在调用函数的代码部分中更改ESP寄存器(堆栈顶部).
如果passByValue(i, c, p, f, tc)
参数值放在堆栈上.您可以看到mov
从存储器位置到寄存器以及从寄存器到堆栈的适当位置的许多指令.原因是x86程序集禁止直接从一个内存位置移动到另一个内存位置(异常是movs
将值从一个数组(或字符串)移动到另一个数组).
如果passByReference(i, c, p, f, tc)
您可以看到许多5个lea指令,这些指令将参数的地址复制到CPU寄存器,并且寄存器的这些值将被移入堆栈.
这种情况passByPointer(&i, &c, &p, &f, &tc)
类似于passByValue(i, c, p, f, tc)
.在内部,在程序集级别,按引用传递使用指针,而在较高的C++级别,程序员不需要明确地使用引用上的&
和*
运算符.
在将参数移动到堆栈之后call
,将指令指针EIP
发送到堆栈,然后将程序执行转移到子程序.moves
堆栈的所有参数都说明EIP
了call
指令后堆栈的进入.
在上面的例子中有太多的东西来剖析所有这些.相反,我会过去,passByValue
因为这似乎是最有趣的.之后,你应该能够弄清楚其余部分.
首先要研究反汇编时要记住的一些要点,这样你就不会完全迷失在代码的海洋中:
mov [ebp - 44], [ebp - 36]
是不是合法的指令.首先需要一个中间寄存器来存储数据,然后将其复制到存储器目的地.[]
与mov
从计算的存储器地址访问数据的方法相结合.这类似于在C/C++中解析指针.lea x, [y]
它通常意味着y的计算地址并保存到x.这类似于在C/C++中获取变量的地址.考虑到上述情况,这里对passByValue
函数的调用重新排列了一些,使其更容易理解:
.define arg1 esp
.define arg2 esp + 4
.define arg3 esp + 8
.define arg4 esp + 12
.define arg5.1 esp + 16
.define arg5.2 esp + 20
; copy first parameter
mov EAX, [EBP - 16]
mov [arg1], EAX
; copy second parameter
mov DL, [EBP - 17]
movsx EAX, DL
mov [arg2], EAX
; copy third
mov ECX, [EBP - 24]
mov [arg3], ECX
; copy fourth
movss XMM0, DWORD PTR [EBP - 28]
movss DWORD PTR [arg4], XMM0
; intermediate copy of TestClass?
mov ESI, [EBP - 40]
mov [EBP - 48], ESI
mov ESI, [EBP - 36]
mov [EBP - 44], ESI
;copy fifth
lea ESI, [EBP - 48]
mov EAX, [ESI]
mov [arg5.1], EAX
mov EAX, [ESI + 4]
mov [arg5.2], EAX
call passByValue(int, char, int*, float, TestClass)
Run Code Online (Sandbox Code Playgroud)
上面的代码是无法解释的,并且指令混合,以清楚地说明实际发生了什么,但有些仍然需要解释.首先,char是signed
,它的大小是单个字节.这里的说明:
; copy second parameter
mov DL, [EBP - 17]
movsx EAX, DL
mov [arg2], EAX
Run Code Online (Sandbox Code Playgroud)
从[ebp - 17]
(堆栈中的某个地方)读取一个字节并将其存储到较低的第一个字节中edx
.然后将该字节复制到eax
使用符号扩展移动.最终将完整的32位值eax
复制到passByValue
可以访问的堆栈中.如果需要更多细节,请参阅寄存器布局.
第四个论点:
movss XMM0, DWORD PTR [EBP - 28]
movss DWORD PTR [arg4], XMM0
Run Code Online (Sandbox Code Playgroud)
使用SSE movss
指令将堆栈中的浮点值复制到xmm0
寄存器中.简而言之,SSE指令允许您同时对多个数据执行相同的操作,但此处编译器将其用作复制堆栈上浮点值的中间存储.
最后一个论点:
; copy intermediate copy of TestClass?
mov ESI, [EBP - 40]
mov [EBP - 48], ESI
mov ESI, [EBP - 36]
mov [EBP - 44], ESI
Run Code Online (Sandbox Code Playgroud)
对应于TestClass
.显然,这个类的大小为8字节,位于堆栈[ebp - 40]
中[ebp - 33]
.这里的类一次被复制4个字节,因为该对象不能适合单个寄存器.
这是以前堆栈的大致情况call passByValue
:
lower addr esp => int:arg1 <--.
esp + 4 char:arg2 |
esp + 8 int*:arg3 | copies passed
esp + 12 float:arg4 | to 'passByValue'
esp + 16 TestClass:arg5.1 |
esp + 20 TestClass:arg5.2 <--.
...
...
ebp - 48 TestClass:arg5.1 <-- intermediate copy of
ebp - 44 TestClass:arg5.2 <-- TestClass?
ebp - 40 original TestClass:arg5.1
ebp - 36 original TestClass:arg5.2
...
ebp - 28 original arg4 <--.
ebp - 24 original arg3 | original (local?) variables
ebp - 20 original arg2 | from calling function
ebp - 16 original arg1 <--.
...
higher addr ebp prev frame
Run Code Online (Sandbox Code Playgroud)