程序集如何进行参数传递:通过值,引用,指针来表示不同类型/数组?

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约定,所以我假设正在进行的事情与保留调用者保存的寄存器然后将参数推送到堆栈有关.因此,我期待看到所有内容被加载到寄存器中并"推送"到处,但不知道movs和leas发生了什么.另外,我不知道是什么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堆栈的所有参数都说明EIPcall指令后堆栈的进入.


gre*_*olf 8

在上面的例子中有太多的东西来剖析所有这些.相反,我会过去,passByValue因为这似乎是最有趣的.之后,你应该能够弄清楚其余部分.

首先要研究反汇编时要记住的一些要点,这样你就不会完全迷失在代码的海洋中:

  • 没有指令将数据从一个mem位置直接复制到另一个mem位置.例如.mov [ebp - 44], [ebp - 36]不是合法的指令.首先需要一个中间寄存器来存储数据,然后将其复制到存储器目的地.
  • 支架运算符[]mov从计算的存储器地址访问数据的方法相结合.这类似于在C/C++中解析指针.
  • 当你看到lea x, [y]通常意味着y的计算地址并保存到x.这类似于在C/C++中获取变量的地址.
  • 需要复制但又太大而无法放入寄存器的数据和对象会以零碎的方式复制到堆栈中.IOW,它将一次复制本机机器字,直到复制表示对象/数据的所有字节.通常这意味着现代处理器上有4或8个字节.
  • 编译器通常将指令交织在一起以保持处理器流水线繁忙并最小化停顿.如果您正在尝试理解反汇编,那么对代码效率有好处,但不好.

考虑到上述情况,这里对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)