SingleLocalVariable2.c — interactive single-stepper

OST2 Arch1001 · functions with a single local variable · four frames deep: main → func → func2 → func3
RIP = 00000001`40001060 — no instruction yet executed
☒executed instruction
♍modified value
⌘start value
RIP▶next instruction

C source

Disassembly

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

Registers

rax
rsp

Stack (each row = 8 bytes · full depth, nothing collapsed)

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

←/→ step · Space play/pause · F frames · A roles · Home/End jump

The rule behind all that padding

"The stack will always be maintained 16-byte aligned, except within the prolog (for example, after the return address is pushed) …"
[1] "Stack usage" reference — docs.microsoft.com/en-us/cpp/build/stack-usage?view=msvc-160  (further exceptions left out — go read them if you'd like)

Mystery Listery 3 — Solved 🕵️

Why does func2 sub rsp, 38h — and why is j at [rsp+20h] instead of [rsp]?

func2 is the first function we've seen that is both a caller and has a local. Its 0x38 (56) bytes decompose exactly like the annotated course diagram:

0x20 shadow space at [rsp+0..1F] (bottom, for the upcoming call func3) + 0x10 local chunk at [rsp+20h..2Fh] (j in the low half) + 0x8 alignment padding on top = 0x38.

The shadow space must sit directly above the return address the call will push — that's why it claims the bottom of the frame and evicts j up to [rsp+20h]. In SingleLocalVariable.c, func called nobody, so i got to live right at [rsp].

Frame-size cheat sheet: 0x18 vs 0x28 vs 0x38

func3 — 0x18: leaf with a local → 0x10 local chunk + 0x8 padding. No shadow space (it calls nobody).

func / main — 0x28: caller with no locals → 0x20 shadow space + 0x8 padding.

func2 — 0x38: caller with a local → 0x20 shadow + 0x10 local chunk + 0x8 padding.

The +8 in every case exists because call pushed an 8-byte return address, leaving rsp at 16n−8; the sub amounts are all ≡ 8 (mod 16), restoring 16-byte alignment — exactly the ABI rule quoted above.

Who returns 0x7a11? What happened to j = 0x7a1e?

func3 puts 0x7A11 ("tall") in eax; then func2's, func's, and main's ret/ epilogues never touch eax, so the same value rides all the way out — the process exit code is 0x7A11.

j = 0x7a1e ("tale") is written to memory and then… never read. A dead store, kept only because this is an unoptimized build. Its corpse stays at 0014FD90 forever (well, until someone else's frame reuses that memory).

Eagle-eye corner: two slide bugs, fixed here

1) The lecture's disassembly slide shows the stores as 0BEEFh / 7EEFh — copy-paste leftovers from earlier examples. The C source and the stack diagrams use 0x7a11 / 0x7a1e, so this page follows those.

2) The CRT return address here is 00000001`40001389, not the …1349 from the earlier examples — more code in the binary shifts the C runtime, so the address of "the instruction after call main" moves too.

Up next — DoubleLocalVariable.c 👀

Two long long locals in one frame. Same machinery, three new tricks to spot before the next lecture:
int func() {
    long long i = 0xf01dab1ef007ba11;
    long long j = 0x0b57ac1e5;
    return i+j;
}
int main() {
    return func();
}
func:
0000000140001000  sub   rsp,18h
0000000140001004  mov   rax,0F01DAB1EF007BA11h
000000014000100E  mov   qword ptr [rsp+8],rax
0000000140001013  mov   eax,0B57AC1E5h
0000000140001018  mov   qword ptr [rsp],rax
000000014000101C  mov   rax,qword ptr [rsp]
0000000140001020  mov   rcx,qword ptr [rsp+8]
0000000140001025  add   rcx,rax
0000000140001028  mov   rax,rcx
000000014000102B  add   rsp,18h
000000014000102F  ret
main:
0000000140001060  sub   rsp,28h
0000000140001064  call  func (0140001000h)
0000000140001069  add   rsp,28h
000000014000106D  ret

…and the func() frames they build

Two locals → sub rsp,18h: 0x10 of locals fits one 16-byte chunk exactly — a single padding row.
00000000`0014FDD8
return address = 00000001`40001069
00000000`0014FDD0
16-byte-stack-alignment padding
00000000`0014FDC8
0xF01DAB1E`F007BA11
rsp→ 00000000`0014FDC0
0x00000000`B57AC1E5
Add a third local (long long k = 0x57ABBADABAD00;) → sub rsp,28h: 24 bytes of locals round up to two 16-byte chunks — now two padding rows. That's Microsoft's 16-byte allocation granularity at work.
00000000`0014FDD8
return address = 00000001`40001069
00000000`0014FDD0
16-byte-stack-alignment padding
00000000`0014FDC8
chunk round-up padding (16-byte granularity)
00000000`0014FDC0
0x0005`7ABBADABAD00
00000000`0014FDB8
0xF01DAB1E`F007BA11
rsp→ 00000000`0014FDB0
0x00000000`B57AC1E5