Pass1Parameter.c — adding a single argument

OST2 Arch1001 · first function parameter · 0x11 rides ecx into func() — which immediately writes it back to the stack, above its own frame
RIP = 00000001`40001020 — no instruction yet executed
☒executed instruction
♍modified value
⌘start value
RIP▶next instruction
shadow-store row (caller-allocated, callee-owned)

C source

Disassembly (execution starts at main, not at the top!)

⤴ execution continues at 00000001`40001359 — the code that called main()

Registers

rax
rcx
rsp

Stack (8-byte rows — two frames this time)

↑higher addresses
lower addresses (stack grows down)↓
rsp

main's frame = 0x28: the four gray shadow-store slots (FDE0–FDF8) + 8 alignment bytes. func's frame = 0x18 with i @ FDC0. a's home slot lives in main's frame: main sees it as [rsp], func as [rsp+8] then [rsp+20h].

←/→ step · Space play/pause · Home/End jump

Pass1Parameter.c takeaways

The experiment continues — TooManyParameters.c

One parameter told us "arg1 → ecx, homed at [rsp+8]". To find the full pattern, the course throws five parameters at the compiler and watches what falls out:

Source — five uint64 parameters, int arithmetic.
#define uint64 unsigned long long int func(uint64 a, uint64 b, uint64 c, uint64 d, uint64 e){ int i = a+b-c+d-e; return i; } int main(){ return func(0x11,0x22,0x33,0x44,0x55); }
The stack at line 1031 — the pattern from our stepper, ×4, plus a fifth argument that never saw a register:
00000000`0014FE08
return address = 00000001`40001399
00000000`0014FE00
16-byte-stack-alignment padding
00000000`0014FDF8
undef (chunk round-up)
00000000`0014FDF0
arg5 = 0x55 (stack-passed)
00000000`0014FDE8
arg4 = r9 = 0x44 (homed)
00000000`0014FDE0
arg3 = r8 = 0x33 (homed)
00000000`0014FDD8
arg2 = edx = 0x22 (homed)
00000000`0014FDD0
arg1 = ecx = 0x11 (homed)
00000000`0014FDC8
return address = 00000001`40001078
00000000`0014FDC0
16-byte-stack-alignment padding
00000000`0014FDB8
16-byte-stack-alignment padding
00000000`0014FDB0
i = undef`ffffffef
Disassembly — the four red-boxed homing stores are the "say hello to the shadow store" moment:
func: 0000000140001000 mov dword ptr [rsp+20h], r9d ; home arg4 0000000140001005 mov dword ptr [rsp+18h], r8d ; home arg3 000000014000100A mov dword ptr [rsp+10h], edx ; home arg2 000000014000100E mov dword ptr [rsp+8], ecx ; home arg1 0000000140001012 sub rsp, 18h 0000000140001016 mov eax, dword ptr [rsp+28h] ; b 000000014000101A mov ecx, dword ptr [rsp+20h] ; a 000000014000101E add ecx, eax ; a+b 0000000140001020 mov eax, ecx 0000000140001022 sub eax, dword ptr [rsp+30h] ; -c 0000000140001026 add eax, dword ptr [rsp+38h] ; +d 000000014000102A sub eax, dword ptr [rsp+40h] ; -e 000000014000102E mov dword ptr [rsp], eax ; i = 0000000140001031 mov eax, dword ptr [rsp] ; ret i 0000000140001034 add rsp, 18h 0000000140001038 ret main: 0000000140001040 sub rsp, 38h 0000000140001044 mov dword ptr [rsp+20h], 55h ; arg5 → stack! 000000014000104C mov r9d, 44h ; arg4 0000000140001052 mov r8d, 33h ; arg3 0000000140001058 mov edx, 22h ; arg2 000000014000105D mov ecx, 11h ; arg1 0000000140001062 call 0000000140001000 0000000140001067 add rsp, 38h 000000014000106B ret

Say hello to the Microsoft "shadow store"

Straight from the x64 calling convention docs:

"The x64 Application Binary Interface (ABI) uses a four-register fast-call calling convention by default. Space is allocated on the call stack as a shadow store for callees to save those registers."
"The caller must always allocate sufficient space to store four register parameters, even if the callee doesn't take that many parameters."
"Any parameters beyond the first four must be stored on the stack after the shadow store, before the call."

And from the takeaway slide: "The callee has the responsibility of dumping the register parameters into their shadow space if needed" — the compiler reserves this space even if no function parameters are passed to another function. The whole contract, as one table (offsets are from the callee's rsp at entry, i.e. [rsp] = return address):

parameterinteger / pointerfloat / doublehome / slotwho writes it
arg1rcxxmm0[rsp+8]callee (homing, if needed)
arg2rdxxmm1[rsp+10h]callee (homing, if needed)
arg3r8xmm2[rsp+18h]callee (homing, if needed)
arg4r9xmm3[rsp+20h]callee (homing, if needed)
arg5, arg6, …no register — stack only[rsp+28h], [rsp+30h], …caller, before the call
return valueraxxmm0—callee

64-bit calling conventions — MS "x64" vs. System V "x86-64"

Two specs rule the planet. Everything above was the left one; gdb on Linux speaks the right one. The full documents (they really do have everything):

Microsoft "x64" ABI
Visual Studio · Windows
System V "x86-64" psABI
GCC · Clang · Linux · BSD · macOS

Same call, two register maps

The TooManyParameters signature again — colors follow the argument:

func(…) a = 0x11 b = 0x22 c = 0x33 d = 0x44 e = 0x55
MS "x64"
4 register args
RCX RDX R8 R9 stack [rsp+20h]
System V
6 register args
RDI RSI RDX RCX R8

Read the columns and mind the trap: RCX = arg1 on Windows but arg4 on Linux; RDX = arg2 vs. arg3. Same register, different meaning — know the ABI before trusting a register. Spill-over args go on the stack, left-most at the lowest address ("pushed", though really mov, as we saw).

Who saves what — the full team sheet (hover a column)

VS caller-saved
("volatile")
RAXRCX①RDX② R8③R9④R10–11
VS callee-saved
("non-volatile")
RBX RSIRDIRBP R12–15
System V caller-saved
(GCC et al.)
RAXRCX④RDX③ RSI②RDI① R8⑤R9⑥R10–11
System V callee-saved RBXRBP R12–15
caller-saved / "volatile" — trashed across calls callee-saved / "non-volatile" — survive calls RSI & RDI — the only two that switch teams ① argument order

Caller-save vs. callee-save — who owns the registers

Caller-save — MS: "volatile"

assume they WILL be changed by the callee · the registers "belong" to the callee
caller code
save what you need
call ▶
callee clobbers freely
◀ ret
restore
VS: RAX·RCX·RDX·R8–R11
GCC: RAX·RDI·RSI·RDX·RCX·R8–R11

Callee-save — MS: "non-volatile"

assume they will NOT be changed · the registers "belong" to the caller
prologue: push rbx…
body — use them freely
epilogue: pop rbx…
ret
VS: RBX·RBP·RDI·RSI·R12–R15
GCC: RBX·RBP·R12–R15

Balance: every save is matched by a restore — the caller brackets the call site (save right before, restore right after), the callee brackets the whole function body (save in the prologue, restore in the epilogue). Anything pushed must get popped.

return ≤ 64 bits → RAX return 128 bits → RDX:RAX both ABIs agree — params & returns ride caller-save regs
Stick it: System V arg order — "Diane's Silk Dress Costs $8 9" → RDI · RSI · RDX · RCX · R8 · R9
MS arg order — alphabet, then digits: C · D · 8 · 9 → RCX · RDX · R8 · R9
And the pink pair: on Windows RSI/RDI are precious (callee-saved) — on Linux they're the first two arguments, burned on every call.

STACK FRAME TIME OUT — 001's mystery 0x28, finally solved

Back in CallASubroutine1, main() had no locals and passed no arguments, yet did sub rsp, 28h — and we had no explanation. Now we do. That frame was pure obligation: 0x20 of shadow store that main must allocate for any call ("even if the callee doesn't take that many parameters" — func took zero), plus 8 bytes of 16-byte-alignment padding.

CallASubroutine1's stack, re-labeled with what we know today:
00000000`0014FE08
return address = 00000001`40001349
00000000`0014FE00
16-byte-stack-alignment padding
00000000`0014FDF8
r9 shadow store (never written)
00000000`0014FDF0
r8 shadow store (never written)
00000000`0014FDE8
rdx shadow store (never written)
00000000`0014FDE0
rcx shadow store (never written)
00000000`0014FDD8
return address = 00000001`40001019
00000000`0014FDD0
undef
00000000`0014FDC8
undef
  • main's 0x28 = 4 × 8 shadow slots + 8 alignment. Nothing else. main never touches a byte of it.
  • func never even makes a stack frame — no locals, no parameters to home, no calls of its own → no sub rsp at all. Just mov eax, 0BEEFh / ret.
  • So the recipe for reading any MSVC x64 frame: sub rsp, N where N = locals (16-byte chunks) + out-going args area (0x20 shadow + 8·(extra args), 16-byte rounded) + 8 if needed for alignment.
"Who knows what evil lies in the hearts stacks of men compilers?"
THE SHADOW KNOWS