rubycc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +56 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +52 -0
- data/README.md +208 -0
- data/data/README.md +117 -0
- data/data/r10_corpus_scan.json +4082 -0
- data/data/r10_manual_classification.json +5342 -0
- data/data/r10_verification_m4a.json +1531 -0
- data/data/r10_verification_m4b.json +1818 -0
- data/data/r10_verification_m4c.json +1489 -0
- data/data/r10_verification_m4d.json +318 -0
- data/data/r10_verification_rbs.json +192 -0
- data/data/verified_gems.json +397 -0
- data/exe/rmake +16 -0
- data/exe/rubycc +9 -0
- data/exe/rubycc-ar +114 -0
- data/exe/rubycc-doctor +14 -0
- data/exe/rubycc-pkgconf +8 -0
- data/include/float.h +87 -0
- data/include/iso646.h +18 -0
- data/include/libc/alloca.h +18 -0
- data/include/libc/arpa/inet.h +61 -0
- data/include/libc/assert.h +43 -0
- data/include/libc/dirent.h +72 -0
- data/include/libc/dlfcn.h +58 -0
- data/include/libc/features.h +205 -0
- data/include/libc/glibc/aarch64/ctype.h +108 -0
- data/include/libc/glibc/aarch64/endian.h +54 -0
- data/include/libc/glibc/aarch64/errno.h +154 -0
- data/include/libc/glibc/aarch64/fcntl.h +144 -0
- data/include/libc/glibc/aarch64/inttypes.h +182 -0
- data/include/libc/glibc/aarch64/limits.h +63 -0
- data/include/libc/glibc/aarch64/pthread.h +131 -0
- data/include/libc/glibc/aarch64/setjmp.h +84 -0
- data/include/libc/glibc/aarch64/stdint.h +174 -0
- data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
- data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
- data/include/libc/glibc/aarch64/sys/select.h +75 -0
- data/include/libc/glibc/aarch64/sys/stat.h +136 -0
- data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
- data/include/libc/glibc/aarch64/sys/time.h +74 -0
- data/include/libc/glibc/aarch64/sys/types.h +127 -0
- data/include/libc/glibc/aarch64/time.h +113 -0
- data/include/libc/glibc/x86_64/ctype.h +108 -0
- data/include/libc/glibc/x86_64/endian.h +54 -0
- data/include/libc/glibc/x86_64/errno.h +154 -0
- data/include/libc/glibc/x86_64/fcntl.h +134 -0
- data/include/libc/glibc/x86_64/inttypes.h +182 -0
- data/include/libc/glibc/x86_64/limits.h +61 -0
- data/include/libc/glibc/x86_64/pthread.h +111 -0
- data/include/libc/glibc/x86_64/setjmp.h +82 -0
- data/include/libc/glibc/x86_64/stdint.h +163 -0
- data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
- data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
- data/include/libc/glibc/x86_64/sys/select.h +75 -0
- data/include/libc/glibc/x86_64/sys/stat.h +130 -0
- data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
- data/include/libc/glibc/x86_64/sys/time.h +74 -0
- data/include/libc/glibc/x86_64/sys/types.h +123 -0
- data/include/libc/glibc/x86_64/time.h +113 -0
- data/include/libc/grp.h +50 -0
- data/include/libc/langinfo.h +126 -0
- data/include/libc/link.h +18 -0
- data/include/libc/locale.h +83 -0
- data/include/libc/math.h +191 -0
- data/include/libc/netinet/in.h +126 -0
- data/include/libc/netinet/tcp.h +44 -0
- data/include/libc/poll.h +46 -0
- data/include/libc/pwd.h +58 -0
- data/include/libc/regex.h +53 -0
- data/include/libc/sched.h +35 -0
- data/include/libc/signal.h +204 -0
- data/include/libc/stdio.h +157 -0
- data/include/libc/stdlib.h +92 -0
- data/include/libc/string.h +74 -0
- data/include/libc/strings.h +25 -0
- data/include/libc/sys/cdefs.h +126 -0
- data/include/libc/sys/inotify.h +111 -0
- data/include/libc/sys/ioctl.h +36 -0
- data/include/libc/sys/mman.h +65 -0
- data/include/libc/sys/param.h +41 -0
- data/include/libc/sys/resource.h +109 -0
- data/include/libc/sys/socket.h +167 -0
- data/include/libc/sys/statfs.h +82 -0
- data/include/libc/sys/timerfd.h +55 -0
- data/include/libc/sys/uio.h +40 -0
- data/include/libc/sys/un.h +25 -0
- data/include/libc/sys/utsname.h +35 -0
- data/include/libc/sys/wait.h +135 -0
- data/include/libc/termios.h +179 -0
- data/include/libc/unistd.h +194 -0
- data/include/stdalign.h +16 -0
- data/include/stdarg.h +31 -0
- data/include/stdatomic.h +158 -0
- data/include/stdbool.h +15 -0
- data/include/stdckdint.h +28 -0
- data/include/stddef.h +60 -0
- data/include/stdnoreturn.h +18 -0
- data/include/x86intrin.h +16 -0
- data/lib/rubycc/backend/aarch64.rb +1724 -0
- data/lib/rubycc/backend/x86_64.rb +1369 -0
- data/lib/rubycc/compile_error.rb +36 -0
- data/lib/rubycc/compiler.rb +305 -0
- data/lib/rubycc/doctor/builder.rb +151 -0
- data/lib/rubycc/doctor/cli.rb +190 -0
- data/lib/rubycc/doctor/fetcher.rb +84 -0
- data/lib/rubycc/doctor/gemfile.rb +115 -0
- data/lib/rubycc/doctor/verified_gems.rb +114 -0
- data/lib/rubycc/doctor.rb +16 -0
- data/lib/rubycc/driver.rb +463 -0
- data/lib/rubycc/front/ast.rb +528 -0
- data/lib/rubycc/front/constant_evaluator.rb +631 -0
- data/lib/rubycc/front/initializer_resolver.rb +592 -0
- data/lib/rubycc/front/lexeme_reader.rb +460 -0
- data/lib/rubycc/front/lexer.rb +232 -0
- data/lib/rubycc/front/parser.rb +4122 -0
- data/lib/rubycc/front/token.rb +49 -0
- data/lib/rubycc/ir/call_convention.rb +486 -0
- data/lib/rubycc/ir/generator.rb +6036 -0
- data/lib/rubycc/ir/ir.rb +417 -0
- data/lib/rubycc/link/compat_runtime.rb +89 -0
- data/lib/rubycc/link/errors.rb +13 -0
- data/lib/rubycc/link/executable_linker.rb +405 -0
- data/lib/rubycc/link/library_resolver.rb +437 -0
- data/lib/rubycc/link/partial_linker.rb +546 -0
- data/lib/rubycc/link/shared_linker.rb +1732 -0
- data/lib/rubycc/mkmf_shim.rb +83 -0
- data/lib/rubycc/objfile/ar_archive.rb +437 -0
- data/lib/rubycc/objfile/elf_reader.rb +646 -0
- data/lib/rubycc/objfile/elf_writer.rb +891 -0
- data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
- data/lib/rubycc/pkgconf/cli.rb +139 -0
- data/lib/rubycc/pkgconf/errors.rb +39 -0
- data/lib/rubycc/pkgconf/model.rb +28 -0
- data/lib/rubycc/pkgconf/parser.rb +109 -0
- data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
- data/lib/rubycc/pkgconf/resolver.rb +70 -0
- data/lib/rubycc/pkgconf/search_path.rb +38 -0
- data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
- data/lib/rubycc/preprocess/constant_expression.rb +219 -0
- data/lib/rubycc/preprocess/glibc_version.rb +124 -0
- data/lib/rubycc/preprocess/pp_token.rb +62 -0
- data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
- data/lib/rubycc/preprocess/scanner.rb +290 -0
- data/lib/rubycc/preprocess/token_converter.rb +157 -0
- data/lib/rubycc/rmake/cli.rb +143 -0
- data/lib/rubycc/rmake/errors.rb +71 -0
- data/lib/rubycc/rmake/executor.rb +818 -0
- data/lib/rubycc/rmake/expander.rb +251 -0
- data/lib/rubycc/rmake/makefile.rb +352 -0
- data/lib/rubycc/rmake/model.rb +137 -0
- data/lib/rubycc/rmake/parser.rb +226 -0
- data/lib/rubycc/rmake/rmake.rb +15 -0
- data/lib/rubycc/type.rb +1236 -0
- data/lib/rubycc/version.rb +5 -0
- data/lib/rubycc.rb +36 -0
- data/lib/rubygems_plugin.rb +102 -0
- metadata +219 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Link
|
|
5
|
+
# The final-link core for a runnable program: it turns rubycc-compiled
|
|
6
|
+
# relocatable objects into a dynamically-linked, non-PIE ET_EXEC that the
|
|
7
|
+
# kernel and a runtime loader can map and run. Its purpose is narrow — passing
|
|
8
|
+
# mkmf's conftest probes (try_link / try_run) — not producing a
|
|
9
|
+
# general-quality executable, so it takes the simplest correct choices at
|
|
10
|
+
# every fork: a fixed load address, no PIE, and a minimal crt.
|
|
11
|
+
#
|
|
12
|
+
# It reuses SharedLinker's machinery wholesale — the relocation scan, the
|
|
13
|
+
# import resolution against dependency `.so`s, the three-segment page-aligned
|
|
14
|
+
# layout, the byte-patching apply engine, and the .dynsym/.hash/.dynamic
|
|
15
|
+
# tables — and only overrides the handful of decisions where an executable
|
|
16
|
+
# differs from a shared object, expressed through SharedLinker's subclass
|
|
17
|
+
# hooks. Those differences are:
|
|
18
|
+
#
|
|
19
|
+
# * ET_EXEC at a fixed non-PIE base (0x400000). Because the program is mapped
|
|
20
|
+
# at exactly this address, every internal absolute reference (R_X86_64_64 /
|
|
21
|
+
# 32 / 32S) is resolved to its final value at link time and needs no
|
|
22
|
+
# R_X86_64_RELATIVE base relocation — the single largest simplification a
|
|
23
|
+
# non-PIE image buys. External functions and data still bind through the
|
|
24
|
+
# PLT/GOT with JUMP_SLOT / GLOB_DAT, exactly as in a shared object.
|
|
25
|
+
#
|
|
26
|
+
# * A PT_INTERP segment naming the dynamic loader, and a synthesized crt
|
|
27
|
+
# whose _start hands control to libc through __libc_start_main so the C
|
|
28
|
+
# runtime (TLS, atexit, stdio, the environment) is initialized before main
|
|
29
|
+
# runs. _start is entered at e_entry and lives at the front of .text.
|
|
30
|
+
#
|
|
31
|
+
# * libc is a default dependency: _start's call to __libc_start_main makes
|
|
32
|
+
# the C library a necessary import, so it is added to `needed` unless the
|
|
33
|
+
# caller already supplied it (mirroring how a compiler driver links libc
|
|
34
|
+
# implicitly).
|
|
35
|
+
#
|
|
36
|
+
# * The executable exports nothing — _start and main are reached internally,
|
|
37
|
+
# never looked up by a loader — so .dynsym holds only the imports.
|
|
38
|
+
#
|
|
39
|
+
# Output is deterministic (N4): the crt bytes are fixed, the layout and tables
|
|
40
|
+
# inherit SharedLinker's deterministic order, and the recorded DT_NEEDED is the
|
|
41
|
+
# dependency's SONAME string, independent of where libc was found on the host.
|
|
42
|
+
class ExecutableLinker < SharedLinker
|
|
43
|
+
# ELF type of a runnable program and the load base a non-PIE executable is
|
|
44
|
+
# mapped at by convention (page-aligned, so the p_vaddr ≡ p_offset (mod page)
|
|
45
|
+
# constraint holds trivially).
|
|
46
|
+
ET_EXEC = 2
|
|
47
|
+
LOAD_BASE = 0x400000
|
|
48
|
+
|
|
49
|
+
# Program header type naming the dynamic loader to run this executable.
|
|
50
|
+
PT_INTERP = 3
|
|
51
|
+
|
|
52
|
+
# The dynamic loaders this recognizes, tried in order; the first that exists
|
|
53
|
+
# on the host is chosen unless the caller names one explicitly.
|
|
54
|
+
GLIBC_INTERP = "/lib64/ld-linux-x86-64.so.2"
|
|
55
|
+
MUSL_INTERP = "/lib/ld-musl-x86_64.so.1"
|
|
56
|
+
|
|
57
|
+
# The aarch64 dynamic loader paths. When cross-linking on an x86_64 host the
|
|
58
|
+
# target loader is not present locally, so — unlike the x86_64 case — the
|
|
59
|
+
# glibc path is used as the canonical on-target location without a host
|
|
60
|
+
# existence check (the emulator/target resolves it through its own sysroot).
|
|
61
|
+
#
|
|
62
|
+
# That reasoning stops holding the moment aarch64 is the *host*: on Alpine
|
|
63
|
+
# arm64 the loader is musl's, and naming glibc's would produce an executable
|
|
64
|
+
# nothing can start. So the musl loader is preferred when it is actually
|
|
65
|
+
# present, which can only be true on a musl aarch64 host, and the glibc name
|
|
66
|
+
# remains the answer everywhere else including every cross link.
|
|
67
|
+
AARCH64_INTERP = "/lib/ld-linux-aarch64.so.1"
|
|
68
|
+
AARCH64_MUSL_INTERP = "/lib/ld-musl-aarch64.so.1"
|
|
69
|
+
|
|
70
|
+
# The usual filesystem locations of the C library, consulted (in order) to
|
|
71
|
+
# add libc as a default dependency. Only the SONAME the chosen file carries
|
|
72
|
+
# affects the output, so which path matches does not disturb determinism.
|
|
73
|
+
#
|
|
74
|
+
# The glibc spellings come first, so a glibc host resolves exactly as it did
|
|
75
|
+
# before musl was added to the list. The musl entries are last and are a
|
|
76
|
+
# different shape on purpose: musl ships its loader and its C library as one
|
|
77
|
+
# file, so the path that answers here is the same MUSL_INTERP used as the
|
|
78
|
+
# program interpreter (Alpine's /usr/lib/libc.so is a symlink to it). There
|
|
79
|
+
# is no libc.so.6 on such a host, which is why every extconf probe that
|
|
80
|
+
# links an executable failed there until this list learned the musl name
|
|
81
|
+
# (measured on Alpine in CI, docs/development/STEPS.md Step 190).
|
|
82
|
+
DEFAULT_LIBC_PATHS = [
|
|
83
|
+
"/lib/x86_64-linux-gnu/libc.so.6",
|
|
84
|
+
"/lib64/libc.so.6",
|
|
85
|
+
"/usr/lib/x86_64-linux-gnu/libc.so.6",
|
|
86
|
+
"/usr/lib/libc.so.6",
|
|
87
|
+
"/lib/libc.so.6",
|
|
88
|
+
"/lib/ld-musl-x86_64.so.1",
|
|
89
|
+
"/usr/lib/libc.so"
|
|
90
|
+
].freeze
|
|
91
|
+
|
|
92
|
+
# The aarch64 C library locations, including the cross-toolchain sysroot the
|
|
93
|
+
# linker reads the dependency's exports (and SONAME) from when cross-linking.
|
|
94
|
+
# The musl entries mirror what Step 190 added to the x86-64 list above, for
|
|
95
|
+
# the same reason and one architecture later: musl ships its C library and
|
|
96
|
+
# its program interpreter as one file, so there is no libc.so.6 to find and
|
|
97
|
+
# every extconf probe that links an executable failed on Alpine arm64 with
|
|
98
|
+
# "cannot locate the C library" until this list learned the names. That
|
|
99
|
+
# only surfaced once an aarch64 musl suite could be run at all
|
|
100
|
+
# (m4-aarch64-acceptance-4); the x86-64 list had carried its musl entry
|
|
101
|
+
# since Step 190, and nothing copied it across.
|
|
102
|
+
#
|
|
103
|
+
# The libc.musl-* spelling comes before the loader's own name because it is
|
|
104
|
+
# the stable library name in Alpine images, matching LibraryResolver's
|
|
105
|
+
# preference; both resolve to the same ELF.
|
|
106
|
+
AARCH64_LIBC_PATHS = [
|
|
107
|
+
"/lib/aarch64-linux-gnu/libc.so.6",
|
|
108
|
+
"/usr/lib/aarch64-linux-gnu/libc.so.6",
|
|
109
|
+
"/usr/aarch64-linux-gnu/lib/libc.so.6",
|
|
110
|
+
"/lib/libc.musl-aarch64.so.1",
|
|
111
|
+
"/usr/lib/libc.musl-aarch64.so.1",
|
|
112
|
+
"/lib/ld-musl-aarch64.so.1"
|
|
113
|
+
].freeze
|
|
114
|
+
|
|
115
|
+
# The synthesized _start, assembled from the System V x86-64 process-startup
|
|
116
|
+
# ABI (not copied from any crt implementation): the loader enters here with
|
|
117
|
+
# argc/argv/envp on the stack and rdx = rtld_fini, and this marshals them
|
|
118
|
+
# into the classic __libc_start_main(main, argc, argv, init, fini, rtld_fini,
|
|
119
|
+
# stack_end) call that transfers control to libc — which initializes the C
|
|
120
|
+
# runtime and eventually calls main. The two operands the linker fills in are
|
|
121
|
+
# main's absolute address (non-PIE, R_X86_64_32) and the PC-relative call to
|
|
122
|
+
# the imported __libc_start_main (R_X86_64_PLT32). Stack alignment tracks the
|
|
123
|
+
# ABI: rsp is 16-byte aligned at entry, so after popping argc it is realigned
|
|
124
|
+
# and re-padded so that %rsp is 16-byte aligned at the call site.
|
|
125
|
+
START_CODE = [
|
|
126
|
+
0x31, 0xED, # xor ebp, ebp ; mark the outermost stack frame
|
|
127
|
+
0x49, 0x89, 0xD1, # mov r9, rdx ; r9 = rtld_fini (loader-supplied)
|
|
128
|
+
0x5E, # pop rsi ; rsi = argc (top of the entry stack)
|
|
129
|
+
0x48, 0x89, 0xE2, # mov rdx, rsp ; rdx = argv (just above argc)
|
|
130
|
+
0x48, 0x83, 0xE4, 0xF0, # and rsp, -16 ; realign the stack down to 16 bytes
|
|
131
|
+
0x50, # push rax ; padding to preserve 16-byte alignment
|
|
132
|
+
0x54, # push rsp ; stack_end (7th arg, passed on the stack)
|
|
133
|
+
0x45, 0x31, 0xC0, # xor r8d, r8d ; fini = NULL
|
|
134
|
+
0x31, 0xC9, # xor ecx, ecx ; init = NULL
|
|
135
|
+
0xBF, 0, 0, 0, 0, # mov edi, main ; edi = main's absolute address (R_X86_64_32)
|
|
136
|
+
0xE8, 0, 0, 0, 0, # call __libc_start_main ; through the PLT (R_X86_64_PLT32)
|
|
137
|
+
0xF4 # hlt ; __libc_start_main does not return
|
|
138
|
+
].pack("C*").freeze
|
|
139
|
+
|
|
140
|
+
# Byte offsets of the two operands the linker patches: the mov's imm32 and
|
|
141
|
+
# the call's rel32, each four bytes wide.
|
|
142
|
+
MAIN_IMM_OFFSET = 21
|
|
143
|
+
LIBC_START_REL_OFFSET = 26
|
|
144
|
+
|
|
145
|
+
# The synthesized aarch64 _start, assembled from the AArch64 process-startup
|
|
146
|
+
# ABI (ARM DDI 0487 encodings; not copied from any crt). The loader enters
|
|
147
|
+
# with argc/argv/envp on the stack and x0 = rtld_fini, and this marshals the
|
|
148
|
+
# classic __libc_start_main(main, argc, argv, init, fini, rtld_fini,
|
|
149
|
+
# stack_end) call in x0-x6: x5 saves rtld_fini before x0 is reloaded with
|
|
150
|
+
# main's address, x1 = argc = [sp], x2 = argv = sp + 8, x6 = stack_end = sp,
|
|
151
|
+
# x3/x4 = init/fini = 0. main's address is formed by an adrp/add pair (a
|
|
152
|
+
# non-PIE image, so the page/lo12 relocations resolve to a fixed address) and
|
|
153
|
+
# the call reaches __libc_start_main through the .plt (CALL26). sp is 16-byte
|
|
154
|
+
# aligned at entry and nothing is pushed, so no realignment is needed.
|
|
155
|
+
AARCH64_START_CODE = [
|
|
156
|
+
0xAA0003E5, # mov x5, x0 ; x5 = rtld_fini (saved before x0 is reused)
|
|
157
|
+
0xD280001D, # mov x29, #0 ; outermost frame pointer
|
|
158
|
+
0xD280001E, # mov x30, #0 ; outermost link register
|
|
159
|
+
0xF94003E1, # ldr x1, [sp] ; x1 = argc
|
|
160
|
+
0x910023E2, # add x2, sp, #8 ; x2 = argv
|
|
161
|
+
0x910003E6, # mov x6, sp ; x6 = stack_end
|
|
162
|
+
0x90000000, # adrp x0, main ; x0 = page(main) (ADR_PREL_PG_HI21)
|
|
163
|
+
0x91000000, # add x0, x0, :lo12:main; x0 = &main (ADD_ABS_LO12_NC)
|
|
164
|
+
0xD2800003, # mov x3, #0 ; init = NULL
|
|
165
|
+
0xD2800004, # mov x4, #0 ; fini = NULL
|
|
166
|
+
0x94000000, # bl __libc_start_main ; through the .plt (CALL26)
|
|
167
|
+
0xD4200000 # brk #0 ; __libc_start_main does not return
|
|
168
|
+
].pack("L<*").freeze
|
|
169
|
+
|
|
170
|
+
# Byte offsets of the three operands the linker patches in the aarch64 crt:
|
|
171
|
+
# the adrp and add forming main's address, and the bl to __libc_start_main.
|
|
172
|
+
AARCH64_MAIN_ADRP_OFFSET = 24 # 0x18
|
|
173
|
+
AARCH64_MAIN_ADD_OFFSET = 28 # 0x1c
|
|
174
|
+
AARCH64_LIBC_START_OFFSET = 40 # 0x28
|
|
175
|
+
|
|
176
|
+
class << self
|
|
177
|
+
# Links `inputs` (paths or raw ET_REL/ar bytes, as PartialLinker accepts)
|
|
178
|
+
# into an executable, returned as an ASCII-8BIT String. `needed` lists
|
|
179
|
+
# dependency shared objects to bind imports against; `interpreter` overrides
|
|
180
|
+
# the dynamic-loader path; `libc` names the C library (`:auto` discovers it,
|
|
181
|
+
# `nil` assumes the caller supplied it through `needed`, a path uses it).
|
|
182
|
+
def link(inputs, needed: [], interpreter: nil, libc: :auto)
|
|
183
|
+
new(inputs, needed: needed, interpreter: interpreter, libc: libc).link
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Convenience: link and write the executable to `path`.
|
|
187
|
+
def link_to(inputs, path, needed: [], interpreter: nil, libc: :auto)
|
|
188
|
+
File.binwrite(path, link(inputs, needed: needed, interpreter: interpreter, libc: libc))
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def initialize(inputs, needed: [], interpreter: nil, libc: :auto)
|
|
193
|
+
# SharedLinker#initialize settles the target machine off the first input
|
|
194
|
+
# object before the merge; the crt and the interpreter/libc defaults
|
|
195
|
+
# chosen here all read it.
|
|
196
|
+
super(inputs, needed: needed)
|
|
197
|
+
@interpreter = choose_interpreter(interpreter)
|
|
198
|
+
add_default_libc(libc)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
# --- SharedLinker hook overrides ---------------------------------------
|
|
204
|
+
|
|
205
|
+
# An executable prepends the synthesized crt so _start (and its references
|
|
206
|
+
# to main and __libc_start_main) take part in the merge and the shared
|
|
207
|
+
# relocation pipeline handles them like any other input's relocations. The
|
|
208
|
+
# tail keeps the shared linker's __dso_handle supplier: glibc's
|
|
209
|
+
# libc_nonshared.a members reference that symbol in an executable link too,
|
|
210
|
+
# and here it is a hard "undefined reference" rather than a leftover
|
|
211
|
+
# import, so the same lazily-extracted definition is what completes them.
|
|
212
|
+
# Its word holds its own address as in a shared object — an executable is
|
|
213
|
+
# never dlclosed, so the identity only has to be unique.
|
|
214
|
+
def link_inputs
|
|
215
|
+
[build_crt] + super
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# After the merge, require a defined main (a conftest without one cannot be
|
|
219
|
+
# started) and capture _start for the entry point.
|
|
220
|
+
def after_merge
|
|
221
|
+
main = @reader.symbol("main")
|
|
222
|
+
unless main&.defined?
|
|
223
|
+
raise LinkError, "undefined reference to `main' (an executable needs a main to start)"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
@start_symbol = @reader.symbol("_start") or
|
|
227
|
+
raise LinkError, "the synthesized _start is missing from the merged object"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# A shared object may be completed by the runtime scope, so an unresolved
|
|
231
|
+
# import is left undefined there; an executable, by contrast, must have
|
|
232
|
+
# every strong reference bound at link time — the loader will not invent a
|
|
233
|
+
# missing symbol — so a non-weak import that no dependency supplies is a
|
|
234
|
+
# hard "undefined reference", exactly as a real linker reports it (and as a
|
|
235
|
+
# conftest probe such as mkmf's have_func relies on to tell a present
|
|
236
|
+
# function from an absent one).
|
|
237
|
+
def resolve_imports
|
|
238
|
+
super
|
|
239
|
+
unresolved = @import_order.reject do |sym|
|
|
240
|
+
sym.bind == :weak || @deps.any? { |dep| dep.provides.key?(sym.name) }
|
|
241
|
+
end
|
|
242
|
+
return if unresolved.empty?
|
|
243
|
+
|
|
244
|
+
names = unresolved.map(&:name).uniq
|
|
245
|
+
raise LinkError, "undefined reference to #{names.map { |n| "`#{n}'" }.join(', ')}"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def load_base = LOAD_BASE
|
|
249
|
+
def e_type = ET_EXEC
|
|
250
|
+
def e_entry = symbol_address(@start_symbol)
|
|
251
|
+
|
|
252
|
+
# A non-PIE executable is mapped at its exact link-time address, so internal
|
|
253
|
+
# absolute references are already final — no R_X86_64_RELATIVE base
|
|
254
|
+
# relocation is emitted for them.
|
|
255
|
+
def rebase_internal? = false
|
|
256
|
+
|
|
257
|
+
# An executable exports nothing: _start and main are reached through
|
|
258
|
+
# link-time PC-relative and absolute relocations, never looked up by a
|
|
259
|
+
# loader, so .dynsym carries only the imports.
|
|
260
|
+
def plan_dynamic_symbols
|
|
261
|
+
@exports = []
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# .interp leads .text in the first (r-x) load segment so the loader path is
|
|
265
|
+
# mapped with the ELF header and program headers.
|
|
266
|
+
def leading_sections
|
|
267
|
+
[placed_generated(".interp", SHT_PROGBITS, SHF_ALLOC, 1, 0, interp_bytes.bytesize)]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# .interp is synthesized (no input backs it), so supply its bytes here; every
|
|
271
|
+
# other section falls through to the shared writer.
|
|
272
|
+
def section_bytes(sec)
|
|
273
|
+
return interp_bytes if sec.name == ".interp"
|
|
274
|
+
|
|
275
|
+
super
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# An executable adds one program header over a shared object's five: the
|
|
279
|
+
# PT_INTERP naming the dynamic loader.
|
|
280
|
+
def phnum = 6
|
|
281
|
+
|
|
282
|
+
# The program header table: the three page-aligned load segments (r-x with
|
|
283
|
+
# the header, .interp and .text; r-- with .rodata and the read-only dynamic
|
|
284
|
+
# tables; rw- with .data/.got/.got.plt/.dynamic and .bss), then PT_INTERP,
|
|
285
|
+
# PT_DYNAMIC and a non-executable PT_GNU_STACK. Load addresses come from the
|
|
286
|
+
# placed sections' assigned vaddrs; the r-x segment maps from file offset 0
|
|
287
|
+
# at the load base so its p_filesz spans the header as well as .text.
|
|
288
|
+
def build_phdrs
|
|
289
|
+
rx_last = @rx.reject { |s| s.type == SHT_NOBITS }.last
|
|
290
|
+
rx_filesz = rx_last.offset + rx_last.size
|
|
291
|
+
ro = segment_extent(@ro, base: @ro.first.offset)
|
|
292
|
+
interp = named(".interp")
|
|
293
|
+
dynamic = named(".dynamic")
|
|
294
|
+
[
|
|
295
|
+
phdr(PT_LOAD, PF_R | PF_X, 0, load_base, rx_filesz, rx_filesz, seg_align),
|
|
296
|
+
phdr(PT_LOAD, PF_R, ro[:offset], @ro.first.vaddr, ro[:filesz], ro[:filesz], seg_align),
|
|
297
|
+
phdr(PT_LOAD, PF_R | PF_W, @rw_start, @rw.first.vaddr,
|
|
298
|
+
@file_end - @rw_start, @mem_end - @rw_start, seg_align),
|
|
299
|
+
phdr(PT_INTERP, PF_R, interp.offset, interp.vaddr, interp.size, interp.size, 1),
|
|
300
|
+
phdr(PT_DYNAMIC, PF_R | PF_W, dynamic.offset, dynamic.vaddr, dynamic.size, dynamic.size, 8),
|
|
301
|
+
phdr(PT_GNU_STACK, PF_R | PF_W, 0, 0, 0, 0, 0x10)
|
|
302
|
+
].join
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# --- crt synthesis -----------------------------------------------------
|
|
306
|
+
|
|
307
|
+
# Builds the crt as a one-section ET_REL object: a .text holding _start with
|
|
308
|
+
# a defined global _start symbol, an undefined main (resolved by the user's
|
|
309
|
+
# object during the merge) and an undefined __libc_start_main (left as an
|
|
310
|
+
# import bound against libc), plus the two relocations that fill _start's
|
|
311
|
+
# operands.
|
|
312
|
+
def build_crt
|
|
313
|
+
aarch64? ? build_crt_aarch64 : build_crt_x86_64
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def build_crt_x86_64
|
|
317
|
+
writer = RelocatableWriter.new
|
|
318
|
+
text = writer.add_section(name: ".text", type: SHT_PROGBITS,
|
|
319
|
+
flags: SHF_ALLOC | SHF_EXECINSTR, addralign: 16,
|
|
320
|
+
data: START_CODE)
|
|
321
|
+
writer.add_symbol(name: "_start", bind: :global, type: :func,
|
|
322
|
+
section: text, value: 0, size: START_CODE.bytesize)
|
|
323
|
+
main = writer.add_symbol(name: "main", bind: :global, type: :notype)
|
|
324
|
+
libc_start = writer.add_symbol(name: "__libc_start_main", bind: :global, type: :notype)
|
|
325
|
+
writer.add_relocation(target: text, offset: MAIN_IMM_OFFSET, symbol: main,
|
|
326
|
+
type: R_X86_64_32, addend: 0)
|
|
327
|
+
writer.add_relocation(target: text, offset: LIBC_START_REL_OFFSET, symbol: libc_start,
|
|
328
|
+
type: R_X86_64_PLT32, addend: -4)
|
|
329
|
+
writer.to_binary
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# The aarch64 crt: the same one-section ET_REL shape as the x86_64 crt, but
|
|
333
|
+
# carrying AARCH64_START_CODE and its three aarch64 relocations — the
|
|
334
|
+
# adrp/add pair forming main's address (page + within-page) and the CALL26
|
|
335
|
+
# to the imported __libc_start_main. Emitted with EM_AARCH64 so the merged
|
|
336
|
+
# object the final link reads back reports the right machine.
|
|
337
|
+
def build_crt_aarch64
|
|
338
|
+
writer = RelocatableWriter.new(machine: EM_AARCH64)
|
|
339
|
+
text = writer.add_section(name: ".text", type: SHT_PROGBITS,
|
|
340
|
+
flags: SHF_ALLOC | SHF_EXECINSTR, addralign: 16,
|
|
341
|
+
data: AARCH64_START_CODE)
|
|
342
|
+
writer.add_symbol(name: "_start", bind: :global, type: :func,
|
|
343
|
+
section: text, value: 0, size: AARCH64_START_CODE.bytesize)
|
|
344
|
+
main = writer.add_symbol(name: "main", bind: :global, type: :notype)
|
|
345
|
+
libc_start = writer.add_symbol(name: "__libc_start_main", bind: :global, type: :notype)
|
|
346
|
+
writer.add_relocation(target: text, offset: AARCH64_MAIN_ADRP_OFFSET, symbol: main,
|
|
347
|
+
type: R_AARCH64_ADR_PREL_PG_HI21, addend: 0)
|
|
348
|
+
writer.add_relocation(target: text, offset: AARCH64_MAIN_ADD_OFFSET, symbol: main,
|
|
349
|
+
type: R_AARCH64_ADD_ABS_LO12_NC, addend: 0)
|
|
350
|
+
writer.add_relocation(target: text, offset: AARCH64_LIBC_START_OFFSET, symbol: libc_start,
|
|
351
|
+
type: R_AARCH64_CALL26, addend: 0)
|
|
352
|
+
writer.to_binary
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# --- interpreter and libc ----------------------------------------------
|
|
356
|
+
|
|
357
|
+
# The .interp contents: the NUL-terminated loader path.
|
|
358
|
+
def interp_bytes
|
|
359
|
+
@interp_bytes ||= (@interpreter.b + "\0".b)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Chooses the dynamic loader: an explicit path wins, else the first known
|
|
363
|
+
# loader that exists on the host. Neither present is a hard error rather than
|
|
364
|
+
# a silent guess, since the resulting executable would not run.
|
|
365
|
+
def choose_interpreter(explicit)
|
|
366
|
+
return explicit if explicit
|
|
367
|
+
# aarch64 is usually cross-linked from an x86_64 host, where the target
|
|
368
|
+
# loader is not present locally, so its canonical on-target path is used
|
|
369
|
+
# without a host check. A musl aarch64 *host* is the exception: there the
|
|
370
|
+
# musl loader is the one that exists, and it is the one that must be named.
|
|
371
|
+
return AARCH64_MUSL_INTERP if aarch64? && File.exist?(AARCH64_MUSL_INTERP)
|
|
372
|
+
return AARCH64_INTERP if aarch64?
|
|
373
|
+
return GLIBC_INTERP if File.exist?(GLIBC_INTERP)
|
|
374
|
+
return MUSL_INTERP if File.exist?(MUSL_INTERP)
|
|
375
|
+
|
|
376
|
+
raise LinkError,
|
|
377
|
+
"no dynamic loader found (looked for #{GLIBC_INTERP} and #{MUSL_INTERP}); pass interpreter:"
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Adds libc to the dependency set so __libc_start_main (and any other libc
|
|
381
|
+
# import) resolves. `:auto` discovers it on the host; a path uses it
|
|
382
|
+
# directly; nil trusts the caller to have supplied it through `needed`.
|
|
383
|
+
def add_default_libc(libc)
|
|
384
|
+
case libc
|
|
385
|
+
when :auto
|
|
386
|
+
found = default_libc or
|
|
387
|
+
raise LinkError, "cannot locate the C library; pass libc: with its path"
|
|
388
|
+
@needed += [found] unless @needed.include?(found)
|
|
389
|
+
when nil
|
|
390
|
+
nil
|
|
391
|
+
else
|
|
392
|
+
@needed += [libc] unless @needed.include?(libc)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def default_libc
|
|
397
|
+
libc_paths.find { |p| File.exist?(p) }
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def libc_paths
|
|
401
|
+
aarch64? ? AARCH64_LIBC_PATHS : DEFAULT_LIBC_PATHS
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|