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.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +56 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +52 -0
  5. data/README.md +208 -0
  6. data/data/README.md +117 -0
  7. data/data/r10_corpus_scan.json +4082 -0
  8. data/data/r10_manual_classification.json +5342 -0
  9. data/data/r10_verification_m4a.json +1531 -0
  10. data/data/r10_verification_m4b.json +1818 -0
  11. data/data/r10_verification_m4c.json +1489 -0
  12. data/data/r10_verification_m4d.json +318 -0
  13. data/data/r10_verification_rbs.json +192 -0
  14. data/data/verified_gems.json +397 -0
  15. data/exe/rmake +16 -0
  16. data/exe/rubycc +9 -0
  17. data/exe/rubycc-ar +114 -0
  18. data/exe/rubycc-doctor +14 -0
  19. data/exe/rubycc-pkgconf +8 -0
  20. data/include/float.h +87 -0
  21. data/include/iso646.h +18 -0
  22. data/include/libc/alloca.h +18 -0
  23. data/include/libc/arpa/inet.h +61 -0
  24. data/include/libc/assert.h +43 -0
  25. data/include/libc/dirent.h +72 -0
  26. data/include/libc/dlfcn.h +58 -0
  27. data/include/libc/features.h +205 -0
  28. data/include/libc/glibc/aarch64/ctype.h +108 -0
  29. data/include/libc/glibc/aarch64/endian.h +54 -0
  30. data/include/libc/glibc/aarch64/errno.h +154 -0
  31. data/include/libc/glibc/aarch64/fcntl.h +144 -0
  32. data/include/libc/glibc/aarch64/inttypes.h +182 -0
  33. data/include/libc/glibc/aarch64/limits.h +63 -0
  34. data/include/libc/glibc/aarch64/pthread.h +131 -0
  35. data/include/libc/glibc/aarch64/setjmp.h +84 -0
  36. data/include/libc/glibc/aarch64/stdint.h +174 -0
  37. data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
  38. data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
  39. data/include/libc/glibc/aarch64/sys/select.h +75 -0
  40. data/include/libc/glibc/aarch64/sys/stat.h +136 -0
  41. data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
  42. data/include/libc/glibc/aarch64/sys/time.h +74 -0
  43. data/include/libc/glibc/aarch64/sys/types.h +127 -0
  44. data/include/libc/glibc/aarch64/time.h +113 -0
  45. data/include/libc/glibc/x86_64/ctype.h +108 -0
  46. data/include/libc/glibc/x86_64/endian.h +54 -0
  47. data/include/libc/glibc/x86_64/errno.h +154 -0
  48. data/include/libc/glibc/x86_64/fcntl.h +134 -0
  49. data/include/libc/glibc/x86_64/inttypes.h +182 -0
  50. data/include/libc/glibc/x86_64/limits.h +61 -0
  51. data/include/libc/glibc/x86_64/pthread.h +111 -0
  52. data/include/libc/glibc/x86_64/setjmp.h +82 -0
  53. data/include/libc/glibc/x86_64/stdint.h +163 -0
  54. data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
  55. data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
  56. data/include/libc/glibc/x86_64/sys/select.h +75 -0
  57. data/include/libc/glibc/x86_64/sys/stat.h +130 -0
  58. data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
  59. data/include/libc/glibc/x86_64/sys/time.h +74 -0
  60. data/include/libc/glibc/x86_64/sys/types.h +123 -0
  61. data/include/libc/glibc/x86_64/time.h +113 -0
  62. data/include/libc/grp.h +50 -0
  63. data/include/libc/langinfo.h +126 -0
  64. data/include/libc/link.h +18 -0
  65. data/include/libc/locale.h +83 -0
  66. data/include/libc/math.h +191 -0
  67. data/include/libc/netinet/in.h +126 -0
  68. data/include/libc/netinet/tcp.h +44 -0
  69. data/include/libc/poll.h +46 -0
  70. data/include/libc/pwd.h +58 -0
  71. data/include/libc/regex.h +53 -0
  72. data/include/libc/sched.h +35 -0
  73. data/include/libc/signal.h +204 -0
  74. data/include/libc/stdio.h +157 -0
  75. data/include/libc/stdlib.h +92 -0
  76. data/include/libc/string.h +74 -0
  77. data/include/libc/strings.h +25 -0
  78. data/include/libc/sys/cdefs.h +126 -0
  79. data/include/libc/sys/inotify.h +111 -0
  80. data/include/libc/sys/ioctl.h +36 -0
  81. data/include/libc/sys/mman.h +65 -0
  82. data/include/libc/sys/param.h +41 -0
  83. data/include/libc/sys/resource.h +109 -0
  84. data/include/libc/sys/socket.h +167 -0
  85. data/include/libc/sys/statfs.h +82 -0
  86. data/include/libc/sys/timerfd.h +55 -0
  87. data/include/libc/sys/uio.h +40 -0
  88. data/include/libc/sys/un.h +25 -0
  89. data/include/libc/sys/utsname.h +35 -0
  90. data/include/libc/sys/wait.h +135 -0
  91. data/include/libc/termios.h +179 -0
  92. data/include/libc/unistd.h +194 -0
  93. data/include/stdalign.h +16 -0
  94. data/include/stdarg.h +31 -0
  95. data/include/stdatomic.h +158 -0
  96. data/include/stdbool.h +15 -0
  97. data/include/stdckdint.h +28 -0
  98. data/include/stddef.h +60 -0
  99. data/include/stdnoreturn.h +18 -0
  100. data/include/x86intrin.h +16 -0
  101. data/lib/rubycc/backend/aarch64.rb +1724 -0
  102. data/lib/rubycc/backend/x86_64.rb +1369 -0
  103. data/lib/rubycc/compile_error.rb +36 -0
  104. data/lib/rubycc/compiler.rb +305 -0
  105. data/lib/rubycc/doctor/builder.rb +151 -0
  106. data/lib/rubycc/doctor/cli.rb +190 -0
  107. data/lib/rubycc/doctor/fetcher.rb +84 -0
  108. data/lib/rubycc/doctor/gemfile.rb +115 -0
  109. data/lib/rubycc/doctor/verified_gems.rb +114 -0
  110. data/lib/rubycc/doctor.rb +16 -0
  111. data/lib/rubycc/driver.rb +463 -0
  112. data/lib/rubycc/front/ast.rb +528 -0
  113. data/lib/rubycc/front/constant_evaluator.rb +631 -0
  114. data/lib/rubycc/front/initializer_resolver.rb +592 -0
  115. data/lib/rubycc/front/lexeme_reader.rb +460 -0
  116. data/lib/rubycc/front/lexer.rb +232 -0
  117. data/lib/rubycc/front/parser.rb +4122 -0
  118. data/lib/rubycc/front/token.rb +49 -0
  119. data/lib/rubycc/ir/call_convention.rb +486 -0
  120. data/lib/rubycc/ir/generator.rb +6036 -0
  121. data/lib/rubycc/ir/ir.rb +417 -0
  122. data/lib/rubycc/link/compat_runtime.rb +89 -0
  123. data/lib/rubycc/link/errors.rb +13 -0
  124. data/lib/rubycc/link/executable_linker.rb +405 -0
  125. data/lib/rubycc/link/library_resolver.rb +437 -0
  126. data/lib/rubycc/link/partial_linker.rb +546 -0
  127. data/lib/rubycc/link/shared_linker.rb +1732 -0
  128. data/lib/rubycc/mkmf_shim.rb +83 -0
  129. data/lib/rubycc/objfile/ar_archive.rb +437 -0
  130. data/lib/rubycc/objfile/elf_reader.rb +646 -0
  131. data/lib/rubycc/objfile/elf_writer.rb +891 -0
  132. data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
  133. data/lib/rubycc/pkgconf/cli.rb +139 -0
  134. data/lib/rubycc/pkgconf/errors.rb +39 -0
  135. data/lib/rubycc/pkgconf/model.rb +28 -0
  136. data/lib/rubycc/pkgconf/parser.rb +109 -0
  137. data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
  138. data/lib/rubycc/pkgconf/resolver.rb +70 -0
  139. data/lib/rubycc/pkgconf/search_path.rb +38 -0
  140. data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
  141. data/lib/rubycc/preprocess/constant_expression.rb +219 -0
  142. data/lib/rubycc/preprocess/glibc_version.rb +124 -0
  143. data/lib/rubycc/preprocess/pp_token.rb +62 -0
  144. data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
  145. data/lib/rubycc/preprocess/scanner.rb +290 -0
  146. data/lib/rubycc/preprocess/token_converter.rb +157 -0
  147. data/lib/rubycc/rmake/cli.rb +143 -0
  148. data/lib/rubycc/rmake/errors.rb +71 -0
  149. data/lib/rubycc/rmake/executor.rb +818 -0
  150. data/lib/rubycc/rmake/expander.rb +251 -0
  151. data/lib/rubycc/rmake/makefile.rb +352 -0
  152. data/lib/rubycc/rmake/model.rb +137 -0
  153. data/lib/rubycc/rmake/parser.rb +226 -0
  154. data/lib/rubycc/rmake/rmake.rb +15 -0
  155. data/lib/rubycc/type.rb +1236 -0
  156. data/lib/rubycc/version.rb +5 -0
  157. data/lib/rubycc.rb +36 -0
  158. data/lib/rubygems_plugin.rb +102 -0
  159. 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