You have followed the PE from bytes on disk to a mapped image. The last hop is the one everybody skips: what actually runs first, and what has to happen before your main function sees a single line. The answer explains a whole class of real behavior — why a global C++ object can run code before main, why smashing a stack buffer can terminate a process instead of returning an error, why exception handlers work at all, and where those first crash addresses in a debugger come from.
It also completes the contract. The loader jumps to AddressOfEntryPoint and its job is done. Everything after that is code the file itself shipped — the CRT startup, your entry, the exit path. The PE format drew the runway; the program takes off from it.
Honest framing first: the PE specification defines the data, not the operating system algorithm, and kernel-to-thread handoff details are out of scope. What follows is a model built only from things you can verify in this file or in Microsoft documentation.
cli-64.exe that is RVA 0x1D40, VA 0x140001D40.The entry itself, first 32 bytes at file offset 0x1140 (RVA 0x1D40):
00001140: 4883 ec28 e8d7 0300 0048 83c4 28e9 72fe 00001150: ffff cccc 4053 4883 ec20 488b d933 c9ff
Disassembled, it is four moves — addresses verifiable with objdump:
140001d40: sub rsp, 0x28 ; shadow space for the call
140001d44: call 140002120 ; __security_init_cookie
140001d49: add rsp, 0x28
140001d4d: jmp 140001bc4 ; tail-jump into CRT startup
140001d52: int3 ; padding
140001d54: rex push rbx ; next function begins hereThe cookie routine at 0x140002120, verified instruction by instruction (listed in Intel-style operand order, abridged for width):
140002120: mov [rsp+0x18], rbx ; home rbx
140002125: push rbp
140002126: mov rbp, rsp
140002129: sub rsp, 0x30
14000212d: mov rax, [140005008] ; current cookie slot in .data
140002134: mov rbx, 0x2b992ddfa232 ; link-time default
14000213e: cmp rax, rbx
140002141: jne 1400021b7 ; not the sentinel, skip seeding
14000214c: call GetSystemTimeAsFileTime ; seeds [rbp+0x10]
14000215a: call GetCurrentThreadId ; xor into accumulator
140002166: call GetCurrentProcessId ; xor into accumulator
140002176: call QueryPerformanceCounter ; fills [rbp+0x18]
140002183: shl rax, 0x20
140002187: xor rax, ... ; mix counter, time, ids, pointer
140002192: mov rcx, 0xffffffffffff
14000219c: and rax, rcx ; keep 48 bits
14000219f: mov rcx, 0x2b992ddfa233 ; sentinel plus one
1400021ac: cmove rax, rcx ; never leave the default in place
1400021b0: mov [140005008], rax ; store the fresh cookie
1400021bc: not rax
1400021bf: mov [140005000], rax ; complement slot
1400021cb: retThree details worth pinning down. The indirect calls go through the IAT slots we mapped: 0x140003088, 0x140003090, 0x140003098, 0x1400030A0 — the same four imports from the table below. The cmove swaps in sentinel-plus-one if the mix would reproduce the default, so the stored value can never collide with the link-time constant. And a second slot at 0x140005000 receives the bitwise complement — the CRT global __security_cookie_complement, kept beside the cookie so runtime checks can compare against the inverted value.
The slot it seeds is the eight bytes at file offset 0x3008 in .data:
00003008: 32a2 df2d 992b 0000
That is 0x00002B992DDFA232 — the exact default the code compares against, confirming this file still carries the link-time sentinel that the first run replaces.
The tail-jump target 0x140001BC4 opens like classic CRT startup: it saves rbx, rsi, and rdi, passes 1 in the first argument register, calls 0x140001F0C, and branches on the boolean the callee returns — the shape of one-time initialization, though the exact CRT source is outside the PE contract. The documented shape is the same: initialize the CRT, run static initializers, call main, then tear down.
Put the imports next to the sequence and the story closes:
| Import | IAT slot | Used for |
|---|---|---|
| GetSystemTimeAsFileTime, GetCurrentThreadId, GetCurrentProcessId, QueryPerformanceCounter | 0x3088, 0x3090, 0x3098, 0x30A0 | Seeding the security cookie in __security_init_cookie |
| RtlCaptureContext, RtlLookupFunctionEntry, RtlVirtualUnwind | 0x30B0, 0x3040, 0x3048 | Walking .pdata frames for exceptions and debugging |
| SetUnhandledExceptionFilter, UnhandledExceptionFilter | 0x3058, 0x3050 | Available to the startup path for a last-resort crash handler |
| exit, _exit, _cexit, _c_exit | 0x3180 and neighbors | Returning from main without returning into the loader |
| TerminateProcess | 0x3068 | The hard stop when orderly shutdown is impossible |
Every row is a byte you can point at: the cookie seeds come from the entry walk, the unwinder imports match the .pdata section from the previous lesson, and the exit family sits in UCRT waiting for main to come back. The sequence is not folklore — it is the import table telling you what the startup code is allowed to do.
objdump puts the entry address on the first line of the file report, and -d starts disassembly there (raw output, operands in AT&T order):
$ objdump -d cli-64.exe --start-address=0x140001d40
140001d40: 48 83 ec 28 sub $0x28,%rsp
140001d44: e8 d7 03 00 00 call 0x140002120
140001d49: 48 83 c4 28 add $0x28,%rsp
140001d4d: e9 72 fe ff ff jmp 0x140001bc4On Windows the same view comes from dumpbin /disasm. What to look for: a call or jump in the first five instructions means you are in the CRT wrapper, not in main — search the import table or step until you see argument setup for the program itself.
Without looking back: a PE with AddressOfEntryPoint at RVA 0x1D40 loads and runs. Is 0x1D40 the address of main?
The cookie slot in .data reads 0x00002B992DDFA232 and the entry code compares against that same constant. What does it conclude on first run?
The pattern: the link-time constant is a known value precisely so the loader-side code can tell untouched files from already-running processes.
That closes the arc: bytes, headers, directories, resources, TLS, exceptions, loading, memory, and now the first instructions that ever execute. Every claim above was read out of cli-64.exe or quoted from the spec and Microsoft documentation.
Back to the course index to review the full sequence, or revisit the loader lesson — it is the hinge the whole module turns on.