Master the fundamental concepts of linkers & loaders through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksDynamic linking loads .so / .dylib / .dll at runtime. PLT and GOT on x86-64 redirect external calls through stubs resolved on first call. ld.so maps libraries and performs relocations. Most Linux programs dynamically link libc.
First call printf jumps through PLT stub to GOT entry pointing at resolver. Resolver patches GOT with real printf address; later calls go direct.
cLoading…
-fPIC generates position-independent code-shared builds shared objectsdlopen loads libraries at runtime manuallyProduction compilers embed this step inside a longer pipeline. GCC flows through cpp, cc1, assembly, and ld; Clang uses the driver, Sema, LLVM IR passes, and a target backend. LLVM bitcode, JVM bytecode, and WASM are other familiar IRs at the same layer. The exercise isolates one pass so you can test it alone before chaining it to the next stage.
You will understand and implement dynamic linking concepts. This exercise asks you to model GOT/PLT indirection and runtime symbol resolution for external function calls.
Simulate how a dynamically linked program calls shared-library functions through the PLT and GOT, including lazy binding (resolve on first call, then patch the GOT) and LD_BIND_NOW-style eager binding.
cLoading…
k (0-based) owns PLT entry k+1 at 0x401020 + 16·(k+1) and GOT slot k+3 at 0x404000 + 8·(k+3) (slots 0-2 are reserved for the dynamic linker).base + offset.+ 6 (the push instruction after the indirect jmp), so the first call falls into the resolver, which looks the symbol up and patches the slot. Later calls jump straight to the function.bind SYMBOL -> LIB 0xADDR for each.now mode, at its first call in lazy mode: print fatal: symbol lookup error: undefined symbol: SYMBOL and stop.fatal: SYMBOL has no PLT entry.For every call:
cLoading…
(lazy when the slot still points back into the PLT, bound when it holds the function's address.) Finish with resolver-calls=R bound-calls=B: also printed after a fatal error.
Input:
cLoading…
Output:
cLoading…
Hidden tests cover interposition (the same symbol in two libraries, where load order decides), an import that is never called, and an undefined import that is only discovered at its first call in lazy mode.