1(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[24200],{26286:(e,s,n)=>{"use strict";n.d(s,{e:()=>i});var a=n(37876);function i({children:e,title:s="Key Takeaways",id:n="key-takeaways",headingLevel:i=3,className:r=""}){return(0,a.jsxs)("aside",{className:["not-prose my-6 rounded-xl bg-teal-50 px-5 py-4 dark:bg-teal-900/20","[&_ol]:my-0 [&_ol]:space-y-1.5 [&_ol]:pl-5 [&_ul]:my-0 [&_ul]:space-y-1.5 [&_ul]:pl-5","[&_li]:my-0 [&_li]:text-sm [&_li]:leading-snug [&_li]:text-zinc-700 dark:[&_li]:text-zinc-300","[&_code]:text-[0.9em] [&_code]:text-teal-700 dark:[&_code]:text-teal-300","[&_p]:my-0 [&_p]:text-sm","[&_strong]:font-semibold [&_strong]:text-zinc-900 dark:[&_strong]:text-zinc-100",r].filter(Boolean).join(" "),children:[(0,a.jsx)(2===i?"h2":"h3",{id:n,className:"!mb-2.5 !mt-0 text-lg font-semibold tracking-tight text-zinc-900 dark:text-zinc-100",children:(0,a.jsx)("a",{className:"heading-link",href:`#${n}`,children:s})}),e]})}},40028:(e,s,n)=>{"use strict";n.d(s,{A:()=>i,b:()=>r});var a=n(37876);function i({className:e="h-64"}){return(0,a.jsx)("div",{className:`${e} animate-pulse rounded-xl bg-zinc-100 dark:bg-zinc-800`})}function r(){return(0,a.jsx)("div",{className:"h-48 animate-pulse rounded-xl bg-zinc-100 dark:bg-zinc-800"})}},50585:(e,s,n)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/concepts/language-internals/loading",function(){return n(62372)}])},62372:(e,s,n)=>{"use strict";n.r(s),n.d(s,{DynamicLoader:()=>m,ElfSegmentViewer:()=>p,MemoryLayout:()=>x,ProgramLoader:()=>h,StartupSequenceWalker:()=>j,__N_SSG:()=>o,default:()=>f,meta:()=>g});var a=n(37876),i=n(91668),r=n(35674),l=n(26286),c=n(18847),t=n.n(c),d=n(40028),o=!0;let h=t()(()=>Promise.all([n.e(10985),n.e(28534)]).then(n.bind(n,28534)),{loadableGenerated:{webpack:()=>[28534]},ssr:!1,loading:d.A}),x=t()(()=>Promise.all([n.e(10985),n.e(80342)]).then(n.bind(n,80342)),{loadableGenerated:{webpack:()=>[80342]},ssr:!1,loading:d.A}),m=t()(()=>Promise.all([n.e(10985),n.e(59809)]).then(n.bind(n,59809)),{loadableGenerated:{webpack:()=>[59809]},ssr:!1,loading:d.A}),p=t()(()=>Promise.all([n.e(10985),n.e(95085)]).then(n.bind(n,95085)),{loadableGenerated:{webpack:()=>[95085]},ssr:!1,loading:d.A}),j=t()(()=>Promise.all([n.e(10985),n.e(40513)]).then(n.bind(n,40513)),{loadableGenerated:{webpack:()=>[40513]},ssr:!1,loading:d.A}),g={title:"C++ Program Loading: From ELF to Running Process",description:"How C++ programs are loaded â ELF segments, the _start to main() chain, dynamic linking with PLT/GOT, ASLR, real readelf/strace/proc maps output, and startup debugging.",category:"language-internals",date:"2025-01-15",order:4,slug:"language-internals/loading",tags:["programming","cpp","runtime","linking"],difficulty:"intermediate",keywords:["program loading","ELF format","readelf segments","memory layout","process creation","_start vs main","__libc_start_main","dynamic linker ld.so","PLT GOT lazy binding","ASLR address randomization","global constructors","strace execve","/proc/pid/maps","LD_DEBUG","static vs dynamic linking"],readingTime:"20 min"},u=e=>(0,a.jsx)(r.b,Object.assign({meta:g},e));function b(e){let s=Object.assign({h2:"h2",a:"a",p:"p",code:"code",strong:"strong",ul:"ul",li:"li",pre:"pre",span:"span",h3:"h3",ol:"ol",table:"table",thead:"thead",tr:"tr",th:"th",tbody:"tbody",td:"td"},(0,i.RP)(),e.components),{RelatedConceptsGrid:n}=s;return n||function(e,s){throw Error("Expected "+(s?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("RelatedConceptsGrid",!0),(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(s.h2,{id:"from-file-to-process",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#from-file-to-process",children:"From File to Process"})}),"\n",(0,a.jsxs)(s.p,{children:["Running ",(0,a.jsx)(s.code,{children:"./program"})," looks simple, but behind that one command three separate systems cooperate to turn a file on disk into a live process. The ",(0,a.jsx)(s.strong,{children:"kernel"})," reads the binary and maps its segments into virtual memory. The ",(0,a.jsx)(s.strong,{children:"dynamic linker"})," (",(0,a.jsx)(s.code,{children:"ld-linux-x86-64.so.2"}),") resolves shared library dependencies and patches addresses. The ",(0,a.jsx)(s.strong,{children:"C runtime"})," (",(0,a.jsx)(s.code,{children:"crt0"})," / ",(0,a.jsx)(s.code,{children:"__libc_start_main"}),") initializes the standard library, runs global constructors, and finally calls ",(0,a.jsx)(s.code,{children:"main()"}),". If any one of these stages fails, your program never reaches its first line of code."]}),"\n",(0,a.jsx)(h,{}),"\n",(0,a.jsx)(s.h2,{id:"the-elf-binary",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#the-elf-binary",children:"The ELF Binary"})}),"\n",(0,a.jsxs)(s.p,{children:["Every Linux executable (and shared library) uses ",(0,a.jsx)(s.strong,{children:"ELF"})," â the Executable and Linkable Format. ELF has two parallel views of the same file:"]}),"\n",(0,a.jsxs)(s.ul,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"Linking view (sections)"}),": used by the linker at build time â ",(0,a.jsx)(s.code,{children:".text"}),", ",(0,a.jsx)(s.code,{children:".data"}),", ",(0,a.jsx)(s.code,{children:".bss"}),", ",(0,a.jsx)(s.code,{children:".symtab"}),", ",(0,a.jsx)(s.code,{children:".rela.dyn"}),", etc."]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"Execution view (segments)"}),": used by the kernel at load time â ",(0,a.jsx)(s.code,{children:"LOAD"}),", ",(0,a.jsx)(s.code,{children:"INTERP"}),", ",(0,a.jsx)(s.code,{children:"DYNAMIC"}),", ",(0,a.jsx)(s.code,{children:"GNU_STACK"}),", etc."]}),"\n"]}),"\n",(0,a.jsxs)(s.p,{children:["Sections are fine-grained (one per purpose). Segments group multiple sections that share the same memory permissions so the kernel can ",(0,a.jsx)(s.code,{children:"mmap"})," them in a single call."]}),"\n",(0,a.jsxs)(s.p,{children:["Hereâs the ELF header from a real C++ binary (",(0,a.jsx)(s.code,{children:"readelf -h"}),"):"]}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsxs)(s.code,{className:"code-highlight",children:[(0,a.jsx)(s.span,{className:"code-line",children:"ELF Header:\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Magic: 7f 45 4c 46 02 01 01 00 ...\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Class: ELF64\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Type: DYN (Position-Independent Executable)\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Machine: Advanced Micro Devices X86-64\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Entry point address: 0x1060\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Start of program headers: 64 (bytes into file)\n"}),(0,a.jsx)(s.span,{className:"code-line",children:" Number of program headers: 13\n"})]})}),"\n",(0,a.jsxs)(s.p,{children:["The ",(0,a.jsx)(s.code,{children:"Type: DYN"})," means this is a position-independent executable (PIE) â it can be loaded at any address, which is essential for ASLR. The ",(0,a.jsx)(s.code,{children:"Entry point address: 0x1060"})," is ",(0,a.jsx)(s.code,{children:"_start"}),", not ",(0,a.jsx)(s.code,{children:"main"}),"."]}),"\n",(0,a.jsx)(p,{}),"\n",(0,a.jsx)(s.h3,{id:"key-segments",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#key-segments",children:"Key Segments"})}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"LOAD segments"})," are the segments the kernel actually maps into memory. A typical binary has two or three:"]}),"\n",(0,a.jsxs)(s.ul,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"LOAD (r--p)"}),": ELF headers + ",(0,a.jsx)(s.code,{children:".rodata"})," (read-only data, string literals)"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"LOAD (r-xp)"}),": ",(0,a.jsx)(s.code,{children:".text"})," (executable code)"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"LOAD (rw-p
1)"}),": ",(0,a.jsx)(s.code,{children:".data"})," + ",(0,a.jsx)(s.code,{children:".bss"})," (writable globals)"]}),"\n"]}),"\n",(0,a.jsxs)(s.p,{children:["Why does ",(0,a.jsx)(s.code,{children:"memsz"})," sometimes exceed ",(0,a.jsx)(s.code,{children:"filesz"}),"? Because the ",(0,a.jsx)(s.code,{children:".bss"})," segment (zero-initialized globals) doesnât need to occupy space in the file â the kernel just allocates zeroed pages. So ",(0,a.jsx)(s.code,{children:"memsz - filesz = .bss size"}),"."]}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"INTERP segment"})," contains a single string: ",(0,a.jsx)(s.code,{children:"/lib64/ld-linux-x86-64.so.2"}),". This tells the kernel which dynamic linker to invoke before transferring control to the program."]}),"\n",(0,a.jsx)(s.h2,{id:"memory-layout",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#memory-layout",children:"Memory Layout"})}),"\n",(0,a.jsx)(s.p,{children:"Once the kernel and dynamic linker finish their work, the process has a well-defined virtual address space:"}),"\n",(0,a.jsx)(x,{}),"\n",(0,a.jsxs)(s.p,{children:["You can see the real layout by reading ",(0,a.jsx)(s.code,{children:"/proc/PID/maps"}),":"]}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsxs)(s.code,{className:"code-highlight",children:[(0,a.jsx)(s.span,{className:"code-line",children:"55a3f2400000-55a3f2401000 r--p program (ELF headers)\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"55a3f2401000-55a3f2402000 r-xp program (.text)\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"55a3f2402000-55a3f2403000 r--p program (.rodata)\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"55a3f2403000-55a3f2405000 rw-p program (.data, .bss)\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"7f8c12000000-7f8c12200000 r-xp /lib/x86_64-linux-gnu/libc.so.6\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"7ffca1200000-7ffca1221000 rw-p [stack]\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"7ffca1304000-7ffca1306000 r-xp [vdso]\n"})]})}),"\n",(0,a.jsxs)(s.p,{children:["Each line shows the virtual address range, permissions (",(0,a.jsx)(s.code,{children:"r"})," = read, ",(0,a.jsx)(s.code,{children:"w"})," = write, ",(0,a.jsx)(s.code,{children:"x"})," = execute, ",(0,a.jsx)(s.code,{children:"p"})," = private), and what occupies that region. Notice the program occupies four small mappings with different permissions, libc is mapped separately, and the stack is at the top of the address space. The ",(0,a.jsx)(s.code,{children:"[vdso]"})," is a kernel-provided shared object that accelerates system calls like ",(0,a.jsx)(s.code,{children:"gettimeofday"})," without a full context switch."]}),"\n",(0,a.jsx)(s.h2,{id:"aslr-address-space-layout-randomization",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#aslr-address-space-layout-randomization",children:"ASLR: Address Space Layout Randomization"})}),"\n",(0,a.jsxs)(s.p,{children:["Run ",(0,a.jsx)(s.code,{children:"cat /proc/self/maps"})," twice and youâll get different addresses each time. Thatâs ",(0,a.jsx)(s.strong,{children:"ASLR"})," â the kernel randomizes the base addresses of the executable, shared libraries, stack, and heap on every execution."]}),"\n",(0,a.jsxs)(s.p,{children:["ASLR defeats ",(0,a.jsx)(s.strong,{children:"return-oriented programming (ROP)"})," and ",(0,a.jsx)(s.strong,{children:"ret2libc"})," attacks. If an attacker overflows a buffer, they canât hardcode a jump target because the addresses are different every run. Combined with PIE binaries (which randomize the code segment too, not just the stack and libraries), ASLR makes exploitation significantly harder."]}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"PIE vs non-PIE"}),": a PIE binary (",(0,a.jsx)(s.code,{children:"gcc -pie"}),", the default since GCC 6+) gets its code segment randomized. A non-PIE binary always loads at ",(0,a.jsx)(s.code,{children:"0x400000"}),", making code addresses predictable."]}),"\n",(0,a.jsx)(s.p,{children:"For debugging, you can disable ASLR temporarily:"}),"\n",(0,a.jsx)(s.pre,{className:"language-bash",children:(0,a.jsx)(s.code,{className:"language-bash code-highlight",children:(0,a.jsxs)(s.span,{className:"code-line",children:["setarch ",(0,a.jsxs)(s.span,{className:"token variable",children:[(0,a.jsx)(s.span,{className:"token variable",children:"$("}),(0,a.jsx)(s.span,{className:"token function",children:"uname"})," ",(0,a.jsx)(s.span,{className:"token parameter variable",children:"-m"}),(0,a.jsx)(s.span,{className:"token variable",children:")"})]})," ",(0,a.jsx)(s.span,{className:"token parameter variable",children:"-R"})," ./program\n"]})})}),"\n",(0,a.jsx)(s.p,{children:"Or system-wide (not recommended for production):"}),"\n",(0,a.jsx)(s.pre,{className:"language-bash",children:(0,a.jsx)(s.code,{className:"language-bash code-highlight",children:(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token builtin class-name",children:"echo"})," ",(0,a.jsx)(s.span,{className:"token number",children:"0"})," ",(0,a.jsx)(s.span,{className:"token operator",children:"|"})," ",(0,a.jsx)(s.span,{className:"token function",children:"sudo"})," ",(0,a.jsx)(s.span,{className:"token function",children:"tee"})," /proc/sys/kernel/randomize_va_space\n"]})})}),"\n",(0,a.jsx)(s.h2,{id:"the-startup-sequence",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#the-startup-sequence",children:"The Startup Sequence"})}),"\n",(0,a.jsxs)(s.p,{children:["The path from ",(0,a.jsx)(s.code,{children:"execve"})," to your ",(0,a.jsx)(s.code,{children:"main()"})," function involves several handoffs. Understanding this chain explains why programs can crash âbefore mainâ and how the C runtime sets up the environment your co
1de depends on."]}),"\n",(0,a.jsx)(j,{}),"\n",(0,a.jsx)(s.h3,{id:"why-_start-exists",children:(0,a.jsxs)(s.a,{className:"heading-link",href:"#why-_start-exists",children:["Why ",(0,a.jsx)(s.code,{children:"_start"})," Exists"]})}),"\n",(0,a.jsxs)(s.p,{children:["The kernel doesnât call ",(0,a.jsx)(s.code,{children:"main()"})," â it jumps to ",(0,a.jsx)(s.code,{children:"_start"}),", a tiny assembly stub provided by ",(0,a.jsx)(s.code,{children:"crt1.o"})," (linked into every executable). ",(0,a.jsx)(s.code,{children:"_start"})," extracts ",(0,a.jsx)(s.code,{children:"argc"}),", ",(0,a.jsx)(s.code,{children:"argv"}),", and ",(0,a.jsx)(s.code,{children:"envp"})," from the stack (which the kernel set up during ",(0,a.jsx)(s.code,{children:"execve"}),") and passes them to ",(0,a.jsx)(s.code,{children:"__libc_start_main"}),"."]}),"\n",(0,a.jsx)(s.h3,{id:"what-__libc_start_main-does",children:(0,a.jsxs)(s.a,{className:"heading-link",href:"#what-__libc_start_main-does",children:["What ",(0,a.jsx)(s.code,{children:"__libc_start_main"})," Does"]})}),"\n",(0,a.jsx)(s.p,{children:"This function is the C runtimeâs bootstrap. It performs a surprising amount of work:"}),"\n",(0,a.jsxs)(s.ol,{children:["\n",(0,a.jsx)(s.li,{children:"Sets up the thread-local storage (TLS) area"}),"\n",(0,a.jsxs)(s.li,{children:["Registers ",(0,a.jsx)(s.code,{children:"__libc_csu_fini"})," with ",(0,a.jsx)(s.code,{children:"atexit"})," so destructors run on exit"]}),"\n",(0,a.jsxs)(s.li,{children:["Calls ",(0,a.jsx)(s.code,{children:"__libc_csu_init"}),", which iterates over the ",(0,a.jsx)(s.code,{children:".init_array"})," section â this is where global constructors run"]}),"\n",(0,a.jsxs)(s.li,{children:["Calls ",(0,a.jsx)(s.code,{children:"main(argc, argv, envp)"})]}),"\n",(0,a.jsxs)(s.li,{children:["Passes ",(0,a.jsx)(s.code,{children:"main"}),"âs return value to ",(0,a.jsx)(s.code,{children:"exit()"})]}),"\n"]}),"\n",(0,a.jsx)(s.h3,{id:"the-static-initialization-order-fiasco",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#the-static-initialization-order-fiasco",children:"The Static Initialization Order Fiasco"})}),"\n",(0,a.jsxs)(s.p,{children:["Global constructors run in translation-unit order within a single file, but across files the order is ",(0,a.jsx)(s.strong,{children:"unspecified"}),". If global ",(0,a.jsx)(s.code,{children:"A"})," depends on global ",(0,a.jsx)(s.code,{children:"B"})," in a different file, you have a 50/50 chance of a crash:"]}),"\n",(0,a.jsx)(s.pre,{className:"language-cpp",children:(0,a.jsxs)(s.code,{className:"language-cpp code-highlight",children:[(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token keyword",children:"class"})," ",(0,a.jsx)(s.span,{className:"token class-name",children:"Logger"})," ",(0,a.jsx)(s.span,{className:"token punctuation",children:"{"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[" ",(0,a.jsx)(s.span,{className:"token function",children:"Logger"}),(0,a.jsx)(s.span,{className:"token punctuation",children:"("}),(0,a.jsx)(s.span,{className:"token punctuation",children:")"})," ",(0,a.jsx)(s.span,{className:"token punctuation",children:"{"})," std",(0,a.jsx)(s.span,{className:"token double-colon punctuation",children:"::"}),"cout ",(0,a.jsx)(s.span,{className:"token operator",children:"<<"})," ",(0,a.jsx)(s.span,{className:"token string",children:'"Logger init\\n"'}),(0,a.jsx)(s.span,{className:"token punctuation",children:";"})," ",(0,a.jsx)(s.span,{className:"token punctuation",children:"}"})," ",(0,a.jsx)(s.span,{className:"token comment",children:"// runs BEFORE main"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token punctuation",children:"}"})," g_logger",(0,a.jsx)(s.span,{className:"token punctuation",children:";"}),"\n"]}),(0,a.jsx)(s.span,{className:"code-line",children:"\n"}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token keyword",children:"int"})," ",(0,a.jsx)(s.span,{className:"token function",children:"main"}),(0,a.jsx)(s.span,{className:"token punctuation",children:"("}),(0,a.jsx)(s.span,{className:"token punctuation",children:")"})," ",(0,a.jsx)(s.span,{className:"token punctuation",children:"{"})," std",(0,a.jsx)(s.span,{className:"token double-colon punctuation",children:"::"}),"cout ",(0,a.jsx)(s.span,{className:"token operator",children:"<<"})," ",(0,a.jsx)(s.span,{className:"token string",children:'"main\\n"'}),(0,a.jsx)(s.span,{className:"token punctuation",children:";"})," ",(0,a.jsx)(s.span,{className:"token punctuation",children:"}"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"// Output: Logger init, then main"}),"\n"]})]})}),"\n",(0,a.jsxs)(s.p,{children:["This works fine in isolation, but if ",(0,a.jsx)(s.code,{children:"Logger"}),"âs constructor tries to use another global from a different translation unit that hasnât been constructed yet, you get undefined behavior. The fix is the ",(0,a.jsx)(s.strong,{children:"Construct on First Use"})," idiom: wrap the global in a function that returns a reference to a local static."]}),"\n",(0,a.jsx)(s.h2,{id:"dynamic-linking-plt-and-got",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#dynamic-linking-plt-and-got",children:"Dynamic Linking: PLT and GOT"})}),"\n",(0,a.jsxs)(s.p,{children:["When your program calls a shared library function like ",(0,a.jsx)(s.code,{children:"printf"}),", the compiler doesnât know the final address at compile time. Instead, it generates a call through two indirection tables: the ",(0,a.jsx)(s.strong,{children:"PLT"})," (Procedure Linkage Table) and the ",(0,a.jsx)(s.strong,{children:"GOT"})," (Global Offset Table)."]}),"\n",(0,a.jsx)(s.h3,{id:"how-lazy-binding-works",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#how-lazy-binding-works",children:"How Lazy Binding Works"})}),"\n",(0,a.jsxs)(s.ol,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"First call"}),": the PLT stub for ",(0,a.jsx)(s.code,{children:"printf"})," jumps to the GOT entry, which initially points back to a PLT resolver stub"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsxs)(s.strong,{children:["Resolver invokes ",(0,a.jsx)(s.code,{children:"_dl_runtime_resolve"})]}),": the dynamic linker searches loaded libraries for ",(0,a.jsx)(s.code,{children:"printf"}
1),", finds its address in ",(0,a.jsx)(s.code,{children:"libc.so"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"GOT is patched"}),": the resolver writes the real address into the GOT entry"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"Subsequent calls"}),": the PLT jumps to the GOT, which now contains the real address â no resolver overhead"]}),"\n"]}),"\n",(0,a.jsx)(s.p,{children:"This means the first call to each library function is slow (symbol lookup), but every call after that is a single indirect jump."}),"\n",(0,a.jsx)(s.h3,{id:"relro-hardening-the-got",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#relro-hardening-the-got",children:"RELRO: Hardening the GOT"})}),"\n",(0,a.jsxs)(s.p,{children:["The GOT is writable (so the resolver can patch it), which makes it an attractive attack target. ",(0,a.jsx)(s.strong,{children:"RELRO"})," (Relocation Read-Only) protections exist in two forms:"]}),"\n",(0,a.jsxs)(s.ul,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"Partial RELRO"})," (default): resolves ",(0,a.jsx)(s.code,{children:".got"})," at load time and marks it read-only, but ",(0,a.jsx)(s.code,{children:".got.plt"})," stays writable for lazy binding"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"Full RELRO"})," (",(0,a.jsx)(s.code,{children:"-Wl,-z,relro,-z,now"}),"): resolves all symbols at load time and marks the entire GOT read-only"]}),"\n"]}),"\n",(0,a.jsx)(s.p,{children:"Full RELRO increases startup time but eliminates GOT overwrite attacks."}),"\n",(0,a.jsx)(s.h3,{id:"forcing-eager-resolution",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#forcing-eager-resolution",children:"Forcing Eager Resolution"})}),"\n",(0,a.jsx)(s.pre,{className:"language-bash",children:(0,a.jsx)(s.code,{className:"language-bash code-highlight",children:(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token assign-left variable",children:"LD_BIND_NOW"}),(0,a.jsx)(s.span,{className:"token operator",children:"="}),(0,a.jsx)(s.span,{className:"token number",children:"1"})," ./program\n"]})})}),"\n",(0,a.jsxs)(s.p,{children:["This forces the dynamic linker to resolve all PLT entries at load time instead of lazily. Equivalent to compiling with ",(0,a.jsx)(s.code,{children:"-Wl,-z,now"}),". Useful for catching missing symbols early and for security hardening."]}),"\n",(0,a.jsx)(m,{}),"\n",(0,a.jsx)(s.h2,{id:"static-vs-dynamic-linking",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#static-vs-dynamic-linking",children:"Static vs Dynamic Linking"})}),"\n",(0,a.jsxs)(s.table,{children:[(0,a.jsx)(s.thead,{children:(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.th,{children:"Aspect"}),(0,a.jsxs)(s.th,{children:["Static (",(0,a.jsx)(s.code,{children:"-static"}),")"]}),(0,a.jsx)(s.th,{children:"Dynamic (default)"})]})}),(0,a.jsxs)(s.tbody,{children:[(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Binary size"}),(0,a.jsx)(s.td,{children:"Large (libc included, ~1 MB+)"}),(0,a.jsx)(s.td,{children:"Small (~16 KB)"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Startup time"}),(0,a.jsx)(s.td,{children:"Faster (no ld.so resolution)"}),(0,a.jsx)(s.td,{children:"Slower (symbol resolution)"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Memory sharing"}),(0,a.jsx)(s.td,{children:"None (each process has copy)"}),(0,a.jsx)(s.td,{children:"Shared (one libc.so in RAM)"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Deployment"}),(0,a.jsx)(s.td,{children:"Single file, portable"}),(0,a.jsx)(s.td,{children:"Needs matching .so files"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Security patches"}),(0,a.jsx)(s.td,{children:"Must recompile everything"}),(0,a.jsx)(s.td,{children:"Update .so, all programs benefit"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Use case"}),(0,a.jsx)(s.td,{children:"Containers, Go binaries, embedded"}),(0,a.jsx)(s.td,{children:"Desktop, servers, system packages"})]})]})]}),"\n",(0,a.jsxs)(s.p,{children:["Static linking produces a self-contained binary that works on any compatible kernel. Dynamic linking saves memory when many processes share the same library and lets you patch vulnerabilities by updating a single ",(0,a.jsx)(s.code,{children:".so"})," file. Most production systems use dynamic linking; containers and cross-compiled binaries often use static."]}),"\n",(0,a.jsx)(s.h2,{id:"debugging-loading",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#debugging-loading",children:"Debugging Loading"})}),"\n",(0,a.jsx)(s.h3,{id:"trace-system-calls",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#trace-system-calls",children:"Trace System Calls"})}),"\n",(0,a.jsx)(s.pre,{className:"language-bash",children:(0,a.jsxs)(s.code,{className:"language-bash code-highlight",children:[(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"# Trace syscalls during loading"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token function",children:"strace"})," ",(0,a.jsx)(s.span,{className:"token parameter variable",children:"-f"})," ",(0,a.jsx)(s.span,{className:"token parameter variable",children:"-e"})," ",(0,a.jsx)(s.span,{className:"token assign-left variable",children:"trace"}),(0,a.jsx)(s.span,{className:"token operator",children:"="}),"openat,mmap,mprotect ./program ",(0,a.jsxs)(s.span,{className:"token operator",children:[(0,a.jsx)(s.span,{className:"token file-descriptor important",children:"2"}),">"]}),(0,a.jsx)(s.span,{className:"token file-descriptor important",children:"&1"})," ",(0,a.jsx)(s.span,{className:"token operator",children:"|"})," ",(0,a.jsx)(s.span,{className:"token function",children:"head"})," ",(0,a.jsx)(s.span,{className:"token parameter variable",children:"-20"}),"\n"]})]})}),"\n",(0,a.jsxs)(s.p,{children:["Real output from ",(0,a.jsx)(s.code,{children:"strace"}),":"]}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsxs)(s.code,{className:"code-highlight",children:[(0,a.jsx)(s.span,{className:"code-line",children:'execve("./program", ["./program"], 0x7ffd...) = 0\n'}),(0,a.jsx)(s.span,{className:"code-line",children:"brk(NULL) = 0x55a3f4a00000\n"}),(0,a.jsx)(s.span,{className:"code-line",children:'openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 3\n'}),(0,a.jsx)(s.span,{className:"code-line",children:"mmap(NULL, 2136936, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8c12000000\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"mprotect(0x7f8c12028000, 1990656, PROT_NONE) = 0\n"})]})}),"\n",(0,a.jsxs)(s.p,{children:["This shows the kernel executing the binary (",(0,a.jsx)(s.code,{children:"execve"}),"), then the dynamic linker opening ",(0,a.jsx)(s.code,{children:"libc.so.6"}),", mapping it into memory with ",(0,a.jsx)(s.code,{children:"mmap"}),", and setting up page protections with ",(0,a.jsx)(s.code,{children:"mprotect"}),"."]}),"\n",(0,a.jsx)(s.h3,{id:"debug-the-dynamic-linker",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#debug-the-dynamic-linker",children:"Debug the Dynamic Linker"})}),"\n",(0,a.jsx)(s.pre,{className:"language-bash",children:(0,a.jsxs)(s.code,{className:"language-bash code-highlight",children:[(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"# Library search paths"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token assign-left variable",children:"LD_DEBUG"}),(0,a.jsx)(s.span,{className:"token operator",children:"="}),"libs ./program\n"]}),(0,a.jsx)(s.span,{className:"code-line",children:"\n"}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"# Symbol resolution"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token assign-left variable",children:"LD_DEBUG"}),(0,a.jsx)(s.span,{className:"token operator",children:"="}),"bindings ./program\n"]}),(0,a.jsx)(s.span,{className:"code-line",children:"\n"}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"# Everything"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token assign-left variable",children:"LD_DEBUG"}),(0,a.jsx)(s.span,{className:"token operator",children:"="}),"all ./program\n"]})]})}),"\n",(0,a.jsx)(s.h3,{id:"check-dependencies-and-segments",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#check-dependencies-and-segments",children:"Check Dependencies and Segments"})}),"\n",(0,a.jsx)(s.pre,{className:"language-bash",children:(0,a.jsxs)(s.code,{className:"language-bash code-highlight",children:[(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"# Check library dependencies"}),"\n"]}),(0,a.jsx)(s.span,{className:"code-line",children:"ldd ./program\n"}),(0,a.jsx)(s.span,{className:"code-line",children:"\n"}),(0,a.jsxs)(s.span,{className:"code-line",children:[(0,a.jsx)(s.span,{className:"token comment",children:"# View segments"}),"\n"]}),(0,a.jsxs)(s.span,{className:"code-line",children:["readelf ",(0,a.jsx)(s.span,{className:"token parameter variable",children:"-l"})," program\n"]})]})}),"\n",(0,a.jsx)(s.h2,{id:"common-loading-errors",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#common-loading-errors",children:"Common Loading Errors"})}),"\n",(0,a.jsxs)(s.table,{children:[(0,a.jsx)(s.thead,{children:(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.th,{children:"Error"}),(0,a.jsx)(s.th,{children:"Cause"}),(0,a.jsx)(s.th,{children:"Fix"})]})}),(0,a.jsxs)(s.tbody,{children:[(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"cannot open shared object file"})}),(0,a.jsx)(s.td,{children:"Library not in search path"}),(0,a.jsxs)(s.td,{children:[(0,a.jsx)(s.code,{children:"ldconfig"}),", or set ",(0,a.jsx)(s.code,{children:"LD_LIBRARY_PATH"}),", or install the package"]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"GLIBC_2.34 not found"})}),(0,a.jsx)(s.td,{children:"Binary compiled with newer glibc than target"}),(0,a.jsx)(s.td,{children:"Compile on older system, use static linking, or update target"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Segfault before main()"}),(0,a.jsx)(s.td,{children:"Global constructor crash"}),(0,a.jsxs)(s.td,{children:["Run with ",(0,a.jsx)(s.code,{children:"gdb"}),", break on ",(0,a.jsx)(s.code,{children:"__libc_start_main"}),", step through constructors"]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"version GLIBCXX_3.4.30 not found"})}),(0,a.jsx)(s.td,{children:"C++ stdlib mismatch"}),(0,a.jsxs)(s.td,{children:["Update libstdc++ or use ",(0,a.jsx)(s.code,{children:"-static-libstdc++"})]})]})]})]}),"\n",(0,a.jsx)(l.e,{children:(0,a.jsxs)(s.ol,{children:["\n",(0,a.jsxs)(s.li,{children:["\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"Loading is a 3-party collaboration"})," â kernel maps segments, dynamic linker resolves symbols, C runtime initializes and calls main()."]}),"\n"]}),"\n",(0,a.jsxs)(s.li,{children:["\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"ELF segments define memory layout"})," â LOAD segments map file regions to memory with specific R/W/E permissions."]}),"\n"]}),"\n",(0,a.jsxs)(s.li,{children:["\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"main() is not the entry point"})," â _start â __libc_start_main â global constructors â main(). Constructor bugs crash before main()."]}),"\n"]}),"\n",(0,a.jsxs)(s.li,{children:["\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"ASLR randomizes addresses"})," â PIE binaries get a new base every run, defeating return-oriented programming exploits."]}),"\n"]}),"\n",(0,a.jsxs)(s.li,{children:["\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"Static vs dynamic is a deployment tradeoff"})," â static = portable single binary; dynamic = shared libraries, smaller, patchable."]}),"\n"]}),"\n"]})}),"\n",(0,a.jsx)(s.h2,{id:"further-reading",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#further-reading",children:"Further Reading"})}),"\n",(0,a.jsxs)(s.ul,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.a,{href:"https://lwn.net/Articles/631631/",children:"How Programs Get Run: ELF Binaries"})," â LWN deep dive into ELF loading"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.a,{href:"http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html",children:"A Whirlwind Tutorial on Creating Really Teensy ELF Executables"})," â Classic article on minimal ELF"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.a,{href:"https://www.akkadia.org/drep
1per/dsohowto.pdf",children:"Drepper: How to Write Shared Libraries"})," â Ulrich Drepperâs definitive guide to ELF and dynamic linking"]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.a,{href:"https://man7.org/tlpi/",children:"The Linux Programming Interface, Ch. 41-42"})," â Michael Kerriskâs comprehensive coverage of shared libraries and loading"]}),"\n"]}),"\n",(0,a.jsx)(s.h2,{id:"related-concepts",children:(0,a.jsx)(s.a,{className:"heading-link",href:"#related-concepts",children:"Related concepts"})}),"\n",(0,a.jsx)(n,{})]})}void 0!==g&&g&&((void 0===g.wordCount||null===g.wordCount)&&(g.wordCount=1633),g.readingTime||(g.readingTime="8 min"));let f=function(e={}){return(0,a.jsx)(u,Object.assign({},e,{children:(0,a.jsx)(b,e)}))}}},e=>{e.O(0,[15863,82667,35674,90636,46593,38792],()=>e(e.s=50585)),_N_E=e.O()}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.