Resolved: How passing parameters by ref works in assembly


I’m very very new to assembly so please bear with me.
I have this code that passes a value by reference to a function in order to modify it :
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Main()
    int a = 5 ;
    Modify(ref a); 
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Modify (ref int a )
    a = 77 ; 
the generated assembly fot that code is
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
my question is : in the instruction L0006 : mov [ebp-4], eax this will copy the value of the register eax into the memory location pointed to by Ebp-4 but Ebp-4 does not point to anything ? what I’m missing ?


Posting the unoptimized assembly makes things more confusing, but basically, the lines,
push ebp
mov ebp, esp
makes ebp point to near the top of this function’s stack frame. (i.e. set up EBP as a frame pointer).
At function entry [esp] has the return address. Any push will first decrement esp by 4 (assuming a 32-bit environment) and store the value at the new [esp]. With push ebp, [esp] (decremented by 4) stores the old value of ebp, and mov ebp, esp makes ebp = esp.
   push eax
This instruction just just a compact way to do sub esp, 4 to reserve stack space for one dword (int) sized local variable. Before this, ESP = EBP, so the new space is at address ebp-4, whatever that happens to be at 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
You can ignore the first two lines. It’s there because you decided to turn off optimization, which often makes things more confusing. The final line mov dword ptr [ebp-4], 5 overwrites the value written by mov [ebp-4], eax.
You cannot write mov dword ptr [ebp], 5 because [ebp] is storing the old value of ebp as I mentioned earlier.
The function call takes its arg in ECX, as per the fastcall calling convention. This register is loaded with a pointer to [ebp-4] using an LEA instruction:
lea ecx, [ebp-4]          ; ECX = ebp-4.   (Not [ebp-4], it's not a load)
In C terms, it’s exactly like &a, the address of the local variable. LEA calculates the address, but then puts that in the destination register instead of using it to load from memory.
Notice that after that first function call returns, that memory is reloaded:
mov ecx, [ebp-4]         ; load a  as the arg for the next function call
call System.Console.WriteLine(Int32)

If you have better answer, please add a comment about this, thank you!