Class: Rubycc::Link::ExecutableLinker
- Inherits:
-
SharedLinker
- Object
- SharedLinker
- Rubycc::Link::ExecutableLinker
- Defined in:
- lib/rubycc/link/executable_linker.rb
Overview
The final-link core for a runnable program: it turns rubycc-compiled relocatable objects into a dynamically-linked, non-PIE ET_EXEC that the kernel and a runtime loader can map and run. Its purpose is narrow — passing mkmf's conftest probes (try_link / try_run) — not producing a general-quality executable, so it takes the simplest correct choices at every fork: a fixed load address, no PIE, and a minimal crt.
It reuses SharedLinker's machinery wholesale — the relocation scan, the
import resolution against dependency .sos, the three-segment page-aligned
layout, the byte-patching apply engine, and the .dynsym/.hash/.dynamic
tables — and only overrides the handful of decisions where an executable
differs from a shared object, expressed through SharedLinker's subclass
hooks. Those differences are:
* ET_EXEC at a fixed non-PIE base (0x400000). Because the program is mapped
at exactly this address, every internal absolute reference (R_X86_64_64 /
32 / 32S) is resolved to its final value at link time and needs no
R_X86_64_RELATIVE base relocation — the single largest simplification a
non-PIE image buys. External functions and data still bind through the
PLT/GOT with JUMP_SLOT / GLOB_DAT, exactly as in a shared object.
* A PT_INTERP segment naming the dynamic loader, and a synthesized crt
whose _start hands control to libc through __libc_start_main so the C
runtime (TLS, atexit, stdio, the environment) is initialized before main
runs. _start is entered at e_entry and lives at the front of .text.
* libc is a default dependency: _start's call to __libc_start_main makes
the C library a necessary import, so it is added to `needed` unless the
caller already supplied it (mirroring how a compiler driver links libc
implicitly).
* The executable exports nothing — _start and main are reached internally,
never looked up by a loader — so .dynsym holds only the imports.
Output is deterministic (N4): the crt bytes are fixed, the layout and tables inherit SharedLinker's deterministic order, and the recorded DT_NEEDED is the dependency's SONAME string, independent of where libc was found on the host.
Constant Summary collapse
- ET_EXEC =
ELF type of a runnable program and the load base a non-PIE executable is mapped at by convention (page-aligned, so the p_vaddr ≡ p_offset (mod page) constraint holds trivially).
2- LOAD_BASE =
0x400000- PT_INTERP =
Program header type naming the dynamic loader to run this executable.
3- GLIBC_INTERP =
The dynamic loaders this recognizes, tried in order; the first that exists on the host is chosen unless the caller names one explicitly.
"/lib64/ld-linux-x86-64.so.2"- MUSL_INTERP =
"/lib/ld-musl-x86_64.so.1"- AARCH64_INTERP =
The aarch64 dynamic loader paths. When cross-linking on an x86_64 host the target loader is not present locally, so — unlike the x86_64 case — the glibc path is used as the canonical on-target location without a host existence check (the emulator/target resolves it through its own sysroot).
That reasoning stops holding the moment aarch64 is the host: on Alpine arm64 the loader is musl's, and naming glibc's would produce an executable nothing can start. So the musl loader is preferred when it is actually present, which can only be true on a musl aarch64 host, and the glibc name remains the answer everywhere else including every cross link.
"/lib/ld-linux-aarch64.so.1"- AARCH64_MUSL_INTERP =
"/lib/ld-musl-aarch64.so.1"- DEFAULT_LIBC_PATHS =
The usual filesystem locations of the C library, consulted (in order) to add libc as a default dependency. Only the SONAME the chosen file carries affects the output, so which path matches does not disturb determinism.
The glibc spellings come first, so a glibc host resolves exactly as it did before musl was added to the list. The musl entries are last and are a different shape on purpose: musl ships its loader and its C library as one file, so the path that answers here is the same MUSL_INTERP used as the program interpreter (Alpine's /usr/lib/libc.so is a symlink to it). There is no libc.so.6 on such a host, which is why every extconf probe that links an executable failed there until this list learned the musl name (measured on Alpine in CI, docs/development/STEPS.md Step 190).
[ "/lib/x86_64-linux-gnu/libc.so.6", "/lib64/libc.so.6", "/usr/lib/x86_64-linux-gnu/libc.so.6", "/usr/lib/libc.so.6", "/lib/libc.so.6", "/lib/ld-musl-x86_64.so.1", "/usr/lib/libc.so" ].freeze
- AARCH64_LIBC_PATHS =
The aarch64 C library locations, including the cross-toolchain sysroot the linker reads the dependency's exports (and SONAME) from when cross-linking. The musl entries mirror what Step 190 added to the x86-64 list above, for the same reason and one architecture later: musl ships its C library and its program interpreter as one file, so there is no libc.so.6 to find and every extconf probe that links an executable failed on Alpine arm64 with "cannot locate the C library" until this list learned the names. That only surfaced once an aarch64 musl suite could be run at all (m4-aarch64-acceptance-4); the x86-64 list had carried its musl entry since Step 190, and nothing copied it across.
The libc.musl-* spelling comes before the loader's own name because it is the stable library name in Alpine images, matching LibraryResolver's preference; both resolve to the same ELF.
[ "/lib/aarch64-linux-gnu/libc.so.6", "/usr/lib/aarch64-linux-gnu/libc.so.6", "/usr/aarch64-linux-gnu/lib/libc.so.6", "/lib/libc.musl-aarch64.so.1", "/usr/lib/libc.musl-aarch64.so.1", "/lib/ld-musl-aarch64.so.1" ].freeze
- START_CODE =
The synthesized _start, assembled from the System V x86-64 process-startup ABI (not copied from any crt implementation): the loader enters here with argc/argv/envp on the stack and rdx = rtld_fini, and this marshals them into the classic __libc_start_main(main, argc, argv, init, fini, rtld_fini, stack_end) call that transfers control to libc — which initializes the C runtime and eventually calls main. The two operands the linker fills in are main's absolute address (non-PIE, R_X86_64_32) and the PC-relative call to the imported __libc_start_main (R_X86_64_PLT32). Stack alignment tracks the ABI: rsp is 16-byte aligned at entry, so after popping argc it is realigned and re-padded so that %rsp is 16-byte aligned at the call site.
[ 0x31, 0xED, # xor ebp, ebp ; mark the outermost stack frame 0x49, 0x89, 0xD1, # mov r9, rdx ; r9 = rtld_fini (loader-supplied) 0x5E, # pop rsi ; rsi = argc (top of the entry stack) 0x48, 0x89, 0xE2, # mov rdx, rsp ; rdx = argv (just above argc) 0x48, 0x83, 0xE4, 0xF0, # and rsp, -16 ; realign the stack down to 16 bytes 0x50, # push rax ; padding to preserve 16-byte alignment 0x54, # push rsp ; stack_end (7th arg, passed on the stack) 0x45, 0x31, 0xC0, # xor r8d, r8d ; fini = NULL 0x31, 0xC9, # xor ecx, ecx ; init = NULL 0xBF, 0, 0, 0, 0, # mov edi, main ; edi = main's absolute address (R_X86_64_32) 0xE8, 0, 0, 0, 0, # call __libc_start_main ; through the PLT (R_X86_64_PLT32) 0xF4 # hlt ; __libc_start_main does not return ].pack("C*").freeze
- MAIN_IMM_OFFSET =
Byte offsets of the two operands the linker patches: the mov's imm32 and the call's rel32, each four bytes wide.
21- LIBC_START_REL_OFFSET =
26- AARCH64_START_CODE =
The synthesized aarch64 _start, assembled from the AArch64 process-startup ABI (ARM DDI 0487 encodings; not copied from any crt). The loader enters with argc/argv/envp on the stack and x0 = rtld_fini, and this marshals the classic __libc_start_main(main, argc, argv, init, fini, rtld_fini, stack_end) call in x0-x6: x5 saves rtld_fini before x0 is reloaded with main's address, x1 = argc = [sp], x2 = argv = sp + 8, x6 = stack_end = sp, x3/x4 = init/fini = 0. main's address is formed by an adrp/add pair (a non-PIE image, so the page/lo12 relocations resolve to a fixed address) and the call reaches __libc_start_main through the .plt (CALL26). sp is 16-byte aligned at entry and nothing is pushed, so no realignment is needed.
[ 0xAA0003E5, # mov x5, x0 ; x5 = rtld_fini (saved before x0 is reused) 0xD280001D, # mov x29, #0 ; outermost frame pointer 0xD280001E, # mov x30, #0 ; outermost link register 0xF94003E1, # ldr x1, [sp] ; x1 = argc 0x910023E2, # add x2, sp, #8 ; x2 = argv 0x910003E6, # mov x6, sp ; x6 = stack_end 0x90000000, # adrp x0, main ; x0 = page(main) (ADR_PREL_PG_HI21) 0x91000000, # add x0, x0, :lo12:main; x0 = &main (ADD_ABS_LO12_NC) 0xD2800003, # mov x3, #0 ; init = NULL 0xD2800004, # mov x4, #0 ; fini = NULL 0x94000000, # bl __libc_start_main ; through the .plt (CALL26) 0xD4200000 # brk #0 ; __libc_start_main does not return ].pack("L<*").freeze
- AARCH64_MAIN_ADRP_OFFSET =
Byte offsets of the three operands the linker patches in the aarch64 crt: the adrp and add forming main's address, and the bl to __libc_start_main.
24- AARCH64_MAIN_ADD_OFFSET =
0x18
28- AARCH64_LIBC_START_OFFSET =
0x1c
40
Constants inherited from SharedLinker
SharedLinker::AARCH64_DSO_FINALIZE_CALL_OFFSET, SharedLinker::AARCH64_DSO_FINALIZE_CODE, SharedLinker::AARCH64_DSO_FINALIZE_GOT_ADRP_OFFSET, SharedLinker::AARCH64_DSO_FINALIZE_GOT_LO12_OFFSET, SharedLinker::AARCH64_DSO_FINALIZE_HANDLE_ADD_OFFSET, SharedLinker::AARCH64_DSO_FINALIZE_HANDLE_ADRP_OFFSET, SharedLinker::AARCH64_GOT_RELOC_TYPES, SharedLinker::AARCH64_MAX_PAGE, SharedLinker::AARCH64_SUPPORTED_RELOC_TYPES, SharedLinker::CXA_FINALIZE_SYMBOL, SharedLinker::DF_1_NOW, SharedLinker::DF_BIND_NOW, SharedLinker::DSO_FINALIZER_SYMBOL, SharedLinker::DSO_FINALIZE_CALL_OFFSET, SharedLinker::DSO_FINALIZE_CODE, SharedLinker::DSO_FINALIZE_GOT_OFFSET, SharedLinker::DSO_FINALIZE_HANDLE_OFFSET, SharedLinker::DSO_FINI_ARRAY_SECTION, SharedLinker::DSO_HANDLE_MEMBER, SharedLinker::DSO_HANDLE_SYMBOL, SharedLinker::DT_FINI_ARRAY, SharedLinker::DT_FINI_ARRAYSZ, SharedLinker::DT_FLAGS, SharedLinker::DT_FLAGS_1, SharedLinker::DT_HASH, SharedLinker::DT_INIT_ARRAY, SharedLinker::DT_INIT_ARRAYSZ, SharedLinker::DT_JMPREL, SharedLinker::DT_NEEDED, SharedLinker::DT_NULL, SharedLinker::DT_PLTGOT, SharedLinker::DT_PLTREL, SharedLinker::DT_PLTRELSZ, SharedLinker::DT_RELA, SharedLinker::DT_RELACOUNT, SharedLinker::DT_RELAENT, SharedLinker::DT_RELASZ, SharedLinker::DT_SONAME, SharedLinker::DT_STRSZ, SharedLinker::DT_STRTAB, SharedLinker::DT_SYMENT, SharedLinker::DT_SYMTAB, SharedLinker::DYN_ENTSIZE, SharedLinker::EHDR_SIZE, SharedLinker::ELFCLASS64, SharedLinker::ELFDATA2LSB, SharedLinker::EM_AARCH64, SharedLinker::EM_X86_64, SharedLinker::ET_DYN, SharedLinker::EV_CURRENT, SharedLinker::GOTPLT_RESERVED, SharedLinker::GOT_RELOC_TYPES, SharedLinker::PAGE, SharedLinker::PF_R, SharedLinker::PF_W, SharedLinker::PF_X, SharedLinker::PHDR_SIZE, SharedLinker::PLT_ENTSIZE, SharedLinker::PT_DYNAMIC, SharedLinker::PT_GNU_STACK, SharedLinker::PT_LOAD, SharedLinker::RELA_ENTSIZE, SharedLinker::R_AARCH64_ABS64, SharedLinker::R_AARCH64_ADD_ABS_LO12_NC, SharedLinker::R_AARCH64_ADR_GOT_PAGE, SharedLinker::R_AARCH64_ADR_PREL_PG_HI21, SharedLinker::R_AARCH64_CALL26, SharedLinker::R_AARCH64_GLOB_DAT, SharedLinker::R_AARCH64_JUMP_SLOT, SharedLinker::R_AARCH64_LD64_GOT_LO12_NC, SharedLinker::R_AARCH64_RELATIVE, SharedLinker::R_X86_64_32, SharedLinker::R_X86_64_32S, SharedLinker::R_X86_64_64, SharedLinker::R_X86_64_GLOB_DAT, SharedLinker::R_X86_64_GOTPCREL, SharedLinker::R_X86_64_GOTPCRELX, SharedLinker::R_X86_64_JUMP_SLOT, SharedLinker::R_X86_64_PC32, SharedLinker::R_X86_64_PLT32, SharedLinker::R_X86_64_RELATIVE, SharedLinker::R_X86_64_REX_GOTPCRELX, SharedLinker::SHDR_SIZE, SharedLinker::SHF_ALLOC, SharedLinker::SHF_EXECINSTR, SharedLinker::SHF_WRITE, SharedLinker::SHN_ABS, SharedLinker::SHN_COMMON, SharedLinker::SHN_LORESERVE, SharedLinker::SHN_UNDEF, SharedLinker::SHT_DYNAMIC, SharedLinker::SHT_DYNSYM, SharedLinker::SHT_FINI_ARRAY, SharedLinker::SHT_HASH, SharedLinker::SHT_INIT_ARRAY, SharedLinker::SHT_NOBITS, SharedLinker::SHT_NULL, SharedLinker::SHT_PROGBITS, SharedLinker::SHT_RELA, SharedLinker::SHT_STRTAB, SharedLinker::SHT_SYMTAB, SharedLinker::STB, SharedLinker::STT, SharedLinker::STV, SharedLinker::SYM_ENTSIZE
Constants included from ObjFile
Class Method Summary collapse
-
.link(inputs, needed: [], interpreter: nil, libc: :auto) ⇒ Object
Links
inputs(paths or raw ET_REL/ar bytes, as PartialLinker accepts) into an executable, returned as an ASCII-8BIT String. -
.link_to(inputs, path, needed: [], interpreter: nil, libc: :auto) ⇒ Object
Convenience: link and write the executable to
path.
Instance Method Summary collapse
-
#initialize(inputs, needed: [], interpreter: nil, libc: :auto) ⇒ ExecutableLinker
constructor
A new instance of ExecutableLinker.
Methods inherited from SharedLinker
Constructor Details
#initialize(inputs, needed: [], interpreter: nil, libc: :auto) ⇒ ExecutableLinker
Returns a new instance of ExecutableLinker.
192 193 194 195 196 197 198 199 |
# File 'lib/rubycc/link/executable_linker.rb', line 192 def initialize(inputs, needed: [], interpreter: nil, libc: :auto) # SharedLinker#initialize settles the target machine off the first input # object before the merge; the crt and the interpreter/libc defaults # chosen here all read it. super(inputs, needed: needed) @interpreter = choose_interpreter(interpreter) add_default_libc(libc) end |
Class Method Details
.link(inputs, needed: [], interpreter: nil, libc: :auto) ⇒ Object
Links inputs (paths or raw ET_REL/ar bytes, as PartialLinker accepts)
into an executable, returned as an ASCII-8BIT String. needed lists
dependency shared objects to bind imports against; interpreter overrides
the dynamic-loader path; libc names the C library (:auto discovers it,
nil assumes the caller supplied it through needed, a path uses it).
182 183 184 |
# File 'lib/rubycc/link/executable_linker.rb', line 182 def link(inputs, needed: [], interpreter: nil, libc: :auto) new(inputs, needed: needed, interpreter: interpreter, libc: libc).link end |
.link_to(inputs, path, needed: [], interpreter: nil, libc: :auto) ⇒ Object
Convenience: link and write the executable to path.
187 188 189 |
# File 'lib/rubycc/link/executable_linker.rb', line 187 def link_to(inputs, path, needed: [], interpreter: nil, libc: :auto) File.binwrite(path, link(inputs, needed: needed, interpreter: interpreter, libc: libc)) end |