ref 传递参数在汇编中的工作原理
How passing parameters by ref works in assembly
我对组装非常陌生,所以请多多包涵。
我有这段代码通过引用函数传递一个值以便修改它:
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Main()
{
int a = 5 ;
Modify(ref a);
Console.WriteLine(a);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Modify (ref int a )
{
a = 77 ;
}
该代码生成的程序集是
Program.Main()
L0000: push ebp
L0001: mov ebp, esp
L0003: push eax
L0004: xor eax, eax
L0006: mov [ebp-4], eax ******* (the confusing part 1 )
L0009: mov dword ptr [ebp-4], 5
L0010: lea ecx, [ebp-4]
L0013: call dword ptr [0xa9dc6ac]
L0019: mov ecx, [ebp-4]
L001c: call System.Console.WriteLine(Int32)
L0021: mov esp, ebp
L0023: pop ebp
L0024: ret
我的问题是:在指令 L0006 中:mov [ebp-4], eax
这会将寄存器 eax 的值复制到由
Ebp-4 但 Ebp-4 没有指向任何东西?我缺少什么?
发布未优化的程序集会使事情变得更加混乱,但基本上,行,
push ebp
mov ebp, esp
使 ebp
指向此函数堆栈帧的顶部附近。
(即将 EBP 设置为 帧指针 )。
在函数入口 [esp]
有 return 地址。任何 push
将首先将 esp
减 4(假设是 32 位环境)并将值存储在新的 [esp]
中。使用 push ebp
,[esp]
(减 4)存储 ebp
的旧值,而 mov ebp, esp
使 ebp = esp
.
push eax
此指令只是一种紧凑的方式来执行 sub esp, 4
为一个双字 (int
) 大小的局部变量保留堆栈 space。在此之前,ESP = EBP,所以新的 space 位于地址 ebp-4
,无论它恰好位于 run-time.
xor eax, eax ; eax = 0
mov [ebp-4], eax ; store EAX, possibly to zero-init int a ?
mov dword ptr [ebp-4], 5
您可以忽略前两行。它在那里是因为您决定关闭优化,这通常会使事情变得更加混乱。最后一行 mov dword ptr [ebp-4], 5
覆盖了 mov [ebp-4], eax
.
写入的值
您不能写 mov dword ptr [ebp], 5
,因为 [ebp]
正在存储 ebp
的旧值,正如我之前提到的。
根据 fastcall 调用约定,函数调用在 ECX 中获取其 arg。该寄存器使用 LEA 指令加载 指向 [ebp-4]
的 指针:
lea ecx, [ebp-4] ; ECX = ebp-4. (Not [ebp-4], it's not a load)
在C术语中,它就像&a
,局部变量的地址。 LEA 计算地址,但随后将其放入目标寄存器而不是使用它从内存加载。
请注意 在第一个函数调用 return 之后 ,内存被重新加载:
mov ecx, [ebp-4] ; load a as the arg for the next function call
call System.Console.WriteLine(Int32)
我对组装非常陌生,所以请多多包涵。
我有这段代码通过引用函数传递一个值以便修改它:
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Main()
{
int a = 5 ;
Modify(ref a);
Console.WriteLine(a);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Modify (ref int a )
{
a = 77 ;
}
该代码生成的程序集是
Program.Main()
L0000: push ebp
L0001: mov ebp, esp
L0003: push eax
L0004: xor eax, eax
L0006: mov [ebp-4], eax ******* (the confusing part 1 )
L0009: mov dword ptr [ebp-4], 5
L0010: lea ecx, [ebp-4]
L0013: call dword ptr [0xa9dc6ac]
L0019: mov ecx, [ebp-4]
L001c: call System.Console.WriteLine(Int32)
L0021: mov esp, ebp
L0023: pop ebp
L0024: ret
我的问题是:在指令 L0006 中:mov [ebp-4], eax 这会将寄存器 eax 的值复制到由 Ebp-4 但 Ebp-4 没有指向任何东西?我缺少什么?
发布未优化的程序集会使事情变得更加混乱,但基本上,行,
push ebp
mov ebp, esp
使 ebp
指向此函数堆栈帧的顶部附近。
(即将 EBP 设置为 帧指针 )。
在函数入口 [esp]
有 return 地址。任何 push
将首先将 esp
减 4(假设是 32 位环境)并将值存储在新的 [esp]
中。使用 push ebp
,[esp]
(减 4)存储 ebp
的旧值,而 mov ebp, esp
使 ebp = esp
.
push eax
此指令只是一种紧凑的方式来执行 sub esp, 4
为一个双字 (int
) 大小的局部变量保留堆栈 space。在此之前,ESP = EBP,所以新的 space 位于地址 ebp-4
,无论它恰好位于 run-time.
xor eax, eax ; eax = 0
mov [ebp-4], eax ; store EAX, possibly to zero-init int a ?
mov dword ptr [ebp-4], 5
您可以忽略前两行。它在那里是因为您决定关闭优化,这通常会使事情变得更加混乱。最后一行 mov dword ptr [ebp-4], 5
覆盖了 mov [ebp-4], eax
.
您不能写 mov dword ptr [ebp], 5
,因为 [ebp]
正在存储 ebp
的旧值,正如我之前提到的。
根据 fastcall 调用约定,函数调用在 ECX 中获取其 arg。该寄存器使用 LEA 指令加载 指向 [ebp-4]
的 指针:
lea ecx, [ebp-4] ; ECX = ebp-4. (Not [ebp-4], it's not a load)
在C术语中,它就像&a
,局部变量的地址。 LEA 计算地址,但随后将其放入目标寄存器而不是使用它从内存加载。
请注意 在第一个函数调用 return 之后 ,内存被重新加载:
mov ecx, [ebp-4] ; load a as the arg for the next function call
call System.Console.WriteLine(Int32)