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,891 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Rubycc
6
+ module ObjFile
7
+ # Writes a minimal ELF64 relocatable object (ET_REL) for Linux x86_64.
8
+ #
9
+ # Section layout (in this order): NULL, .text, .rodata (only with string
10
+ # literals), .data (only with initialized globals), .bss (only with
11
+ # zero-initialized globals), .rela.text (only when there are text
12
+ # relocations), .rela.data (only when a .data pointer slot needs one),
13
+ # .note.GNU-stack, .symtab, .strtab, .shstrtab. Section indices are not
14
+ # hard-coded: the ordered name list is assembled in #to_binary and a
15
+ # name -> index lookup resolves the cross-references (symtab's sh_link,
16
+ # rela's sh_link/sh_info, and each symbol's st_shndx). Symbol table order is
17
+ # NULL, STT_FILE, the .text section symbol, the .rodata section symbol (when
18
+ # present), then the file-local (`static`) functions and objects — every
19
+ # STB_LOCAL must precede the first STB_GLOBAL — followed by the external
20
+ # symbols (defined functions, then defined file-scope objects, then
21
+ # undefined externals); r_info in .rela.text indexes into that final order.
22
+ #
23
+ # .rela.text carries five machine-independent relocation kinds — :call,
24
+ # :func, :string, :global and :got — each translated through the injected
25
+ # machine description (see MachineDescription) into one or more concrete ELF
26
+ # relocation entries. On the default x86_64 machine each kind costs exactly
27
+ # one entry: a `call` site as an R_X86_64_PLT32 against its (defined or
28
+ # undefined) symbol, a taken function address the same way, a string
29
+ # reference as an R_X86_64_PC32 against the .rodata section symbol with the
30
+ # string's byte offset as its addend, a global reference as an
31
+ # R_X86_64_PC32 against that global's own object symbol, and a PIC GOT
32
+ # reference (a "-fPIC" access to a symbol this unit does not define) as an
33
+ # R_X86_64_REX_GOTPCRELX against that symbol — all four PC-relative kinds
34
+ # biased by -4 for the rel32 field's placement. On aarch64 an address-forming
35
+ # kind instead costs a *pair* of entries, one per instruction of the adrp/add
36
+ # (or adrp/ldr) sequence that machine needs; the writer emits whatever the
37
+ # description lists.
38
+ #
39
+ # .rela.data carries the absolute R_X86_64_64 relocations that patch a .data
40
+ # pointer slot: one against another object's symbol (addend 0) for a "&other"
41
+ # or a decayed global array, and one against the .rodata section symbol (the
42
+ # string's byte offset as its addend) for a string-literal pointer.
43
+ #
44
+ # .data holds the initialized globals' little-endian bytes; .bss is a NOBITS
45
+ # section that reserves space for the zero-initialized ones without occupying
46
+ # any file bytes. Both are writable (SHF_WRITE | SHF_ALLOC).
47
+ #
48
+ # The empty .note.GNU-stack marks the stack as non-executable so the linker
49
+ # does not warn about a missing GNU_STACK note. Output is fully
50
+ # deterministic (N4): no timestamps or other varying data are embedded.
51
+ class ELFWriter
52
+ # ELF constants
53
+ ELFCLASS64 = 2
54
+ ELFDATA2LSB = 1
55
+ EV_CURRENT = 1
56
+ ET_REL = 1
57
+ EM_X86_64 = 62
58
+ EM_AARCH64 = 183
59
+
60
+ SHN_UNDEF = 0
61
+ SHN_ABS = 0xFFF1
62
+
63
+ # Section header types
64
+ SHT_NULL = 0
65
+ SHT_PROGBITS = 1
66
+ SHT_SYMTAB = 2
67
+ SHT_STRTAB = 3
68
+ SHT_RELA = 4
69
+ SHT_NOBITS = 8
70
+ # The two array section types the runtime walks: SHT_INIT_ARRAY holds
71
+ # pointers to the constructors it calls at startup/dlopen, SHT_FINI_ARRAY
72
+ # the destructors it calls at exit/dlclose. Distinct types (rather than
73
+ # PROGBITS with a magic name) because the linker groups them by type and
74
+ # advertises each run through DT_INIT_ARRAY / DT_FINI_ARRAY.
75
+ SHT_INIT_ARRAY = 14
76
+ SHT_FINI_ARRAY = 15
77
+
78
+ # Section header flags
79
+ SHF_WRITE = 0x1
80
+ SHF_ALLOC = 0x2
81
+ SHF_EXECINSTR = 0x4
82
+ SHF_INFO_LINK = 0x40
83
+
84
+ # Symbol binding/type (st_info = (bind << 4) | type)
85
+ STB_LOCAL = 0
86
+ STB_GLOBAL = 1
87
+ STT_NOTYPE = 0
88
+ STT_OBJECT = 1
89
+ STT_FUNC = 2
90
+ STT_SECTION = 3
91
+ STT_FILE = 4
92
+ STV = { default: 0, internal: 1, hidden: 2, protected: 3 }.freeze
93
+
94
+ # x86_64 relocation types: 64 for an absolute 64-bit address (a pointer
95
+ # slot in .data initialized to another object's address), PC32 for a plain
96
+ # PC-relative reference (e.g. a "lea rip" into .rodata), PLT32 for a near
97
+ # call (PC-relative, PLT-aware) and REX_GOTPCRELX for a PIC data access
98
+ # (a "mov rax, sym@GOTPCREL(rip)" reading the symbol's GOT slot; the "REX"
99
+ # form marks the REX.W-prefixed mov a linker may relax back to a lea).
100
+ R_X86_64_64 = 1
101
+ R_X86_64_PC32 = 2
102
+ R_X86_64_PLT32 = 4
103
+ R_X86_64_REX_GOTPCRELX = 42
104
+
105
+ # aarch64 relocation types. CALL26 patches the 26-bit immediate of a `bl`
106
+ # (or `b`) with the PC-relative word distance to its target. The remaining
107
+ # four come in pairs, because aarch64 forms a symbol's address in two
108
+ # instructions rather than one: ADR_PREL_PG_HI21 fills the 21-bit
109
+ # page-offset immediate of an `adrp` (the distance from the referring
110
+ # instruction's own 4 KiB page to the symbol's), and ADD_ABS_LO12_NC fills
111
+ # the 12-bit immediate of the following `add` with the symbol's offset
112
+ # within that page. The GOT pair is the same split applied to the symbol's
113
+ # Global Offset Table slot: ADR_GOT_PAGE names the slot's page and
114
+ # LD64_GOT_LO12_NC the scaled 12-bit immediate of the `ldr` reading it.
115
+ # "NC" is "no check": the low half cannot overflow, so the linker does not
116
+ # range-check it. The numbers were read off real `aarch64-linux-gnu-gcc`
117
+ # output rather than transcribed.
118
+ # ABS64 is aarch64's absolute 64-bit pointer slot, the counterpart of
119
+ # R_X86_64_64, used in .data rather than .text.
120
+ R_AARCH64_ABS64 = 257
121
+ R_AARCH64_ADR_PREL_PG_HI21 = 275
122
+ R_AARCH64_ADD_ABS_LO12_NC = 277
123
+ R_AARCH64_CALL26 = 283
124
+ R_AARCH64_ADR_GOT_PAGE = 311
125
+ R_AARCH64_LD64_GOT_LO12_NC = 312
126
+
127
+ # Machine description: the injected, target-specific half of the writer.
128
+ # It pairs the ELF e_machine value with a table translating each
129
+ # machine-independent relocation `kind` (the vocabulary the backend
130
+ # records — :call/:func/:string/:global/:got in .text, :symbol/:rodata in
131
+ # .data) into that target's ELF relocation entries.
132
+ #
133
+ # A kind maps to an *array* of RelocDesc, not a single one, because how
134
+ # many ELF entries one source-level reference costs is a property of the
135
+ # machine. x86_64 forms an address in a single instruction with a single
136
+ # patched field, so every kind is a one-element array; aarch64 needs two
137
+ # instructions (adrp + add, or adrp + ldr) with a relocation apiece, so its
138
+ # address-forming kinds are two-element arrays. The writer simply emits
139
+ # each descriptor in order.
140
+ #
141
+ # Within a descriptor: `type` is the concrete ELF relocation type (an
142
+ # R_<arch>_* number). `addend` is either a fixed value or :recorded,
143
+ # meaning the relocation record carries its own addend. `addend_bias` is
144
+ # added on top of it and is where a target's field-placement convention
145
+ # lives — x86_64's PC-relative fields are measured from the *end* of the
146
+ # instruction, so a rel32 whose four bytes precede that end needs -4, while
147
+ # aarch64's adrp/add pair is biased by nothing. Keeping the bias here, and
148
+ # not in the caller, lets the compiler hand over a plain unbiased byte
149
+ # offset in the machine-independent vocabulary. `offset_delta` is the byte
150
+ # distance from the offset the backend recorded (always the first
151
+ # instruction of the sequence) to the field this descriptor patches: 0 and
152
+ # 4 for an aarch64 pair, 0 everywhere on x86_64. `symbol` is :named to
153
+ # resolve against the relocation's own symbol or :rodata_section to resolve
154
+ # against the .rodata section symbol.
155
+ #
156
+ # `text_padding` is the filler the compiler repeats in the alignment gap
157
+ # between two functions — a no-op encoding of the target, so the gap
158
+ # disassembles cleanly and a stray fall-through lands on nothing harmful.
159
+ # Retargeting the writer is a matter of injecting a different
160
+ # MachineDescription — the section layout and symbol-table logic below is
161
+ # machine-independent.
162
+ RelocDesc = Data.define(:type, :addend, :symbol, :addend_bias, :offset_delta) do
163
+ def initialize(addend_bias: 0, offset_delta: 0, **rest)
164
+ super
165
+ end
166
+ end
167
+ MachineDescription = Data.define(:e_machine, :relocations, :text_padding)
168
+
169
+ # Inter-function padding: x86_64's one-byte `nop` (0x90), and aarch64's
170
+ # four-byte `nop` word 0xD503201F stored little-endian (every aarch64
171
+ # instruction is four bytes, so the unit of padding is a whole word and
172
+ # the 16-byte function alignment is always a whole number of them).
173
+ X86_64_NOP = "\x90".b
174
+ AARCH64_NOP = [0xD503201F].pack("L<")
175
+
176
+ # The default machine: x86_64. Its relocation table fixes the exact ELF
177
+ # types and addend conventions the System V AMD64 psABI defines for each
178
+ # kind. Every .text kind patches a rel32 field measured from the end of the
179
+ # instruction it belongs to, hence the uniform -4 bias; a :call and a taken
180
+ # :func address are both near PC-relative references and share PLT32. A
181
+ # string reference carries the interned string's .rodata byte offset as its
182
+ # recorded addend, which the same -4 then biases.
183
+ X86_64 = MachineDescription.new(
184
+ e_machine: EM_X86_64,
185
+ relocations: {
186
+ call: [RelocDesc.new(type: R_X86_64_PLT32, addend: 0, symbol: :named, addend_bias: -4)],
187
+ func: [RelocDesc.new(type: R_X86_64_PLT32, addend: 0, symbol: :named, addend_bias: -4)],
188
+ string: [RelocDesc.new(type: R_X86_64_PC32, addend: :recorded, symbol: :rodata_section,
189
+ addend_bias: -4)],
190
+ global: [RelocDesc.new(type: R_X86_64_PC32, addend: 0, symbol: :named, addend_bias: -4)],
191
+ got: [RelocDesc.new(type: R_X86_64_REX_GOTPCRELX, addend: 0, symbol: :named,
192
+ addend_bias: -4)],
193
+ symbol: [RelocDesc.new(type: R_X86_64_64, addend: :recorded, symbol: :named)],
194
+ rodata: [RelocDesc.new(type: R_X86_64_64, addend: :recorded, symbol: :rodata_section)]
195
+ }.freeze,
196
+ text_padding: X86_64_NOP
197
+ )
198
+
199
+ # The aarch64 machine. A direct :call is a single `bl` and so a single
200
+ # CALL26; every other .text kind forms an address across two instructions
201
+ # and therefore takes two entries, the second four bytes past the first.
202
+ #
203
+ # :string, :global and :func all use the adrp/add pair, differing only in
204
+ # what they resolve against — the .rodata section symbol for a string
205
+ # (biased by the string's own byte offset, which the linker folds into the
206
+ # page computation for both halves alike) and the named symbol otherwise.
207
+ # :got uses the adrp/ldr pair addressing the symbol's GOT slot. None of
208
+ # them takes a bias: aarch64's relocations name the symbol directly rather
209
+ # than a displacement from the end of a field.
210
+ #
211
+ # The .data kinds (:symbol, :rodata) are absolute 64-bit pointer slots and
212
+ # so are not machine-shaped at all beyond the type number; ABS64 is
213
+ # aarch64's spelling of the same thing R_X86_64_64 does.
214
+ AARCH64 = MachineDescription.new(
215
+ e_machine: EM_AARCH64,
216
+ relocations: {
217
+ call: [RelocDesc.new(type: R_AARCH64_CALL26, addend: 0, symbol: :named)],
218
+ string: [RelocDesc.new(type: R_AARCH64_ADR_PREL_PG_HI21, addend: :recorded,
219
+ symbol: :rodata_section),
220
+ RelocDesc.new(type: R_AARCH64_ADD_ABS_LO12_NC, addend: :recorded,
221
+ symbol: :rodata_section, offset_delta: 4)],
222
+ global: [RelocDesc.new(type: R_AARCH64_ADR_PREL_PG_HI21, addend: 0, symbol: :named),
223
+ RelocDesc.new(type: R_AARCH64_ADD_ABS_LO12_NC, addend: 0, symbol: :named,
224
+ offset_delta: 4)],
225
+ func: [RelocDesc.new(type: R_AARCH64_ADR_PREL_PG_HI21, addend: 0, symbol: :named),
226
+ RelocDesc.new(type: R_AARCH64_ADD_ABS_LO12_NC, addend: 0, symbol: :named,
227
+ offset_delta: 4)],
228
+ got: [RelocDesc.new(type: R_AARCH64_ADR_GOT_PAGE, addend: 0, symbol: :named),
229
+ RelocDesc.new(type: R_AARCH64_LD64_GOT_LO12_NC, addend: 0, symbol: :named,
230
+ offset_delta: 4)],
231
+ symbol: [RelocDesc.new(type: R_AARCH64_ABS64, addend: :recorded, symbol: :named)],
232
+ rodata: [RelocDesc.new(type: R_AARCH64_ABS64, addend: :recorded, symbol: :rodata_section)]
233
+ }.freeze,
234
+ text_padding: AARCH64_NOP
235
+ )
236
+
237
+ # --- initializer / finalizer arrays ------------------------------------
238
+ # An array section is a flat vector of 8-byte function pointers, each slot
239
+ # filled in by an absolute 64-bit relocation against the function it names.
240
+ # Its shape is fixed by the ABI and by what the linker's array pass demands
241
+ # (SharedLinker#split_array_sections): writable and allocatable, entsize 8,
242
+ # 8-byte aligned.
243
+ ARRAY_ENTSIZE = 8
244
+ ARRAY_ALIGN = 8
245
+
246
+ ARRAY_SECTION_BASE = { init: ".init_array", fini: ".fini_array" }.freeze
247
+ ARRAY_SECTION_TYPE = { init: SHT_INIT_ARRAY, fini: SHT_FINI_ARRAY }.freeze
248
+ # Constructors before destructors, so grouping the entries is deterministic
249
+ # whatever order the caller registered them in.
250
+ ARRAY_KIND_ORDER = %i[init fini].freeze
251
+
252
+ # How a priority is spelled. A run-order number below the default goes into
253
+ # its own section named "<base>.NNNNN", which is how the *linker* learns the
254
+ # order (a priority is nowhere in the section's contents). Both numbers were
255
+ # measured off gcc's own objects rather than assumed: priority 101 emits
256
+ # `.init_array.00101`, so the field is zero-padded to five digits, and
257
+ # priority 65535 emits the plain, unnumbered `.init_array`, so 65535 is the
258
+ # default. The five digits are exactly what the linker's array_priority
259
+ # regexp (`\.\d+`) reads back.
260
+ DEFAULT_ARRAY_PRIORITY = 65535
261
+ ARRAY_PRIORITY_DIGITS = 5
262
+
263
+ SYM_ENTSIZE = 24
264
+ RELA_ENTSIZE = 24
265
+ SHDR_ENTSIZE = 64
266
+ EHDR_SIZE = 64
267
+
268
+ # `machine` is the injected MachineDescription selecting the target's
269
+ # e_machine value and relocation-type table; it defaults to x86_64 so an
270
+ # existing caller needs no change.
271
+ def initialize(machine: X86_64)
272
+ @machine = machine
273
+ @text_bytes = "".b
274
+ @rodata_bytes = nil
275
+ @data_bytes = nil
276
+ @data_align = 1
277
+ @bss_size = 0
278
+ @bss_align = 1
279
+ @file_symbol = nil
280
+ @func_symbols = []
281
+ @object_symbols = []
282
+ # Kept as an Array (in first-added order) because that order feeds the
283
+ # symbol table's layout, and the layout must be deterministic (DESIGN
284
+ # N4: identical input -> identical binary). @undefined_symbol_set
285
+ # mirrors its contents purely for O(1) membership checks, so a large
286
+ # translation unit's undefined-symbol lookups stay linear overall
287
+ # instead of quadratic.
288
+ @undefined_symbols = []
289
+ @undefined_symbol_set = Set.new
290
+ @relocations = []
291
+ @data_relocations = []
292
+ # Constructor/destructor registrations, in registration order (which is
293
+ # the order their slots are laid out within a section, and so the order
294
+ # the runtime calls them in).
295
+ @array_entries = []
296
+ end
297
+
298
+ def add_text_section(bytes)
299
+ @text_bytes = bytes.b
300
+ self
301
+ end
302
+
303
+ # Sets the .rodata payload (the NUL-terminated, concatenated string pool).
304
+ # The section, and its section symbol, are emitted only when this is set
305
+ # to a non-empty value.
306
+ def set_rodata(bytes)
307
+ @rodata_bytes = bytes.b
308
+ self
309
+ end
310
+
311
+ # Sets the .data payload (the initialized file-scope variables laid out
312
+ # in order) and the section's alignment. The section, a writable
313
+ # PROGBITS, is emitted only when this is set to a non-empty value.
314
+ def set_data(bytes, align: 1)
315
+ @data_bytes = bytes.b
316
+ @data_align = align
317
+ self
318
+ end
319
+
320
+ # Sets the .bss size in bytes (the zero-initialized file-scope variables)
321
+ # and the section's alignment. The section, a writable NOBITS occupying
322
+ # no file space, is emitted only when the size is positive.
323
+ def set_bss(size, align: 1)
324
+ @bss_size = size
325
+ @bss_align = align
326
+ self
327
+ end
328
+
329
+ def add_global_func(name, offset, size, visibility: :default)
330
+ @func_symbols << { name: name, offset: offset, size: size, bind: STB_GLOBAL,
331
+ visibility: STV.fetch(visibility, visibility) }
332
+ self
333
+ end
334
+
335
+ # Registers a defined `static` function as a file-local STT_FUNC symbol
336
+ # (STB_LOCAL): private to this object, so a same-named function elsewhere
337
+ # does not collide with it. Laid out in .text like any other function.
338
+ def add_local_func(name, offset, size)
339
+ @func_symbols << { name: name, offset: offset, size: size, bind: STB_LOCAL }
340
+ self
341
+ end
342
+
343
+ # Registers a defined file-scope variable as a global STT_OBJECT symbol.
344
+ # `section` is :data or :bss, `offset` its byte offset within that section
345
+ # (st_value) and `size` its storage width (st_size).
346
+ def add_global_object(name, section, offset, size, visibility: :default)
347
+ @object_symbols << { name: name, section: section, offset: offset, size: size,
348
+ bind: STB_GLOBAL, visibility: STV.fetch(visibility, visibility) }
349
+ self
350
+ end
351
+
352
+ # Registers a `static` file-scope variable (or a block-scope `static`
353
+ # lowered to a uniquely named one) as a file-local STT_OBJECT symbol
354
+ # (STB_LOCAL). Placed in .data/.bss exactly like a global object.
355
+ def add_local_object(name, section, offset, size)
356
+ @object_symbols << { name: name, section: section, offset: offset, size: size, bind: STB_LOCAL }
357
+ self
358
+ end
359
+
360
+ # Registers an external symbol (a call target defined elsewhere). Repeated
361
+ # names collapse to a single symbol so several call sites share one entry.
362
+ def add_undefined_symbol(name)
363
+ unless @undefined_symbol_set.include?(name)
364
+ @undefined_symbols << name
365
+ @undefined_symbol_set << name
366
+ end
367
+ self
368
+ end
369
+
370
+ def add_text_relocation(offset:, symbol:)
371
+ @relocations << { kind: :call, offset: offset, symbol: symbol }
372
+ self
373
+ end
374
+
375
+ # Records a taken function address in .text (a function pointer value, as
376
+ # opposed to a call site). x86_64 resolves it exactly as a call does, but
377
+ # aarch64 does not — a `bl`'s CALL26 and an adrp/add address pair are
378
+ # different sequences — so the two kinds stay distinct here and the machine
379
+ # description decides whether they coincide.
380
+ def add_func_relocation(offset:, symbol:)
381
+ @relocations << { kind: :func, offset: offset, symbol: symbol }
382
+ self
383
+ end
384
+
385
+ # Records a reference from .text into .rodata: `offset` is the start of the
386
+ # referring instruction sequence within .text and `addend` the string's
387
+ # plain byte offset within .rodata, with no target-specific bias applied —
388
+ # the machine description supplies that (see RelocDesc#addend_bias).
389
+ # Resolved against the .rodata section symbol.
390
+ def add_rodata_relocation(offset:, addend:)
391
+ @relocations << { kind: :string, offset: offset, addend: addend }
392
+ self
393
+ end
394
+
395
+ # Records a PC-relative reference from .text to the named file-scope
396
+ # variable `symbol` (a "lea rip" displacement addressing a global).
397
+ # Resolved against that symbol as R_X86_64_PC32 with an addend of -4.
398
+ def add_global_relocation(offset:, symbol:)
399
+ @relocations << { kind: :global, offset: offset, symbol: symbol }
400
+ self
401
+ end
402
+
403
+ # Records a PIC reference from .text to the named symbol's Global Offset
404
+ # Table slot (a "mov rax, sym@GOTPCREL(rip)" that loads the symbol's
405
+ # run-time address). `offset` is the rel32 field within .text. Resolved
406
+ # against that symbol as R_X86_64_REX_GOTPCRELX with an addend of -4, the
407
+ # same PC-relative bias a "lea rip" uses.
408
+ def add_got_relocation(offset:, symbol:)
409
+ @relocations << { kind: :got, offset: offset, symbol: symbol }
410
+ self
411
+ end
412
+
413
+ # Records an absolute 64-bit reference inside .data to the named file-scope
414
+ # object `symbol` (a pointer global initialized with "&other", a decayed
415
+ # global array, or a computed address constant like "&arr[i]"). `offset` is
416
+ # the pointer slot's byte offset within .data and `addend` the constant byte
417
+ # displacement past the symbol (0 for a bare "&other"). Resolved against that
418
+ # symbol as R_X86_64_64 with that addend.
419
+ def add_data_relocation(offset:, symbol:, addend: 0)
420
+ @data_relocations << { kind: :symbol, offset: offset, symbol: symbol, addend: addend }
421
+ self
422
+ end
423
+
424
+ # Records an absolute 64-bit reference inside .data into .rodata (a pointer
425
+ # global initialized with a string literal). `offset` is the pointer slot's
426
+ # byte offset within .data and `addend` the string's byte offset within
427
+ # .rodata. Resolved against the .rodata section symbol as R_X86_64_64.
428
+ def add_data_rodata_relocation(offset:, addend:)
429
+ @data_relocations << { kind: :rodata, offset: offset, addend: addend }
430
+ self
431
+ end
432
+
433
+ def add_file_symbol(filename)
434
+ @file_symbol = filename
435
+ self
436
+ end
437
+
438
+ # Registers `symbol` — a function this object defines — as a constructor
439
+ # (`kind` :init) or a destructor (`kind` :fini). It becomes one 8-byte slot
440
+ # in the matching array section, filled by an absolute 64-bit relocation
441
+ # against that symbol. `priority` selects the section: the default goes to
442
+ # the plain ".init_array"/".fini_array", a lower number to its own
443
+ # ".init_array.NNNNN". The symbol may be file-local (a `static`
444
+ # constructor, which is the common case), since an absolute relocation
445
+ # against a local symbol resolves within the object just as well.
446
+ def add_array_entry(kind:, symbol:, priority: DEFAULT_ARRAY_PRIORITY)
447
+ raise ArgumentError, "unknown array kind: #{kind.inspect}" unless ARRAY_SECTION_BASE.key?(kind)
448
+
449
+ @array_entries << { kind: kind, priority: priority, symbol: symbol }
450
+ self
451
+ end
452
+
453
+ # Assembles and returns the ELF object as an ASCII-8BIT String.
454
+ def to_binary
455
+ # Fixed first: the section name list, the layout and the .rela payloads
456
+ # all read it, and they must agree on one grouping.
457
+ @array_sections = build_array_sections
458
+ @symbols = build_symbol_list
459
+ @section_names = build_section_names
460
+ symbol_indices = index_symbols_by_name(@symbols)
461
+ rodata_sym_index = @symbols.index { |sym| sym[:type] == STT_SECTION && sym[:shndx] == :rodata }
462
+
463
+ strtab, sym_name_offsets = build_strtab
464
+ symtab = build_symtab(@symbols, sym_name_offsets)
465
+ rela = relocations? ? build_rela(symbol_indices, rodata_sym_index) : nil
466
+ rela_data = data_relocations? ? build_rela_data(symbol_indices, rodata_sym_index) : nil
467
+ rela_arrays = @array_sections.to_h do |group|
468
+ [group[:name], build_rela_array(group[:entries], symbol_indices, rodata_sym_index)]
469
+ end
470
+
471
+ sections = section_layout(symtab: symtab, strtab: strtab, rela: rela, rela_data: rela_data,
472
+ rela_arrays: rela_arrays)
473
+ assemble(sections)
474
+ end
475
+
476
+ private
477
+
478
+ # The array sections to emit, one per (kind, priority) the registrations
479
+ # used, each as { name:, kind:, entries: }. Ordered constructors first, then
480
+ # destructors, ascending priority within each kind — the same order the
481
+ # linker lays the run in, so reading the object's section list already shows
482
+ # the run order. Entries keep registration (source) order within a section.
483
+ # Empty when nothing was registered, which is what keeps a translation unit
484
+ # without constructors byte-identical to before.
485
+ def build_array_sections
486
+ @array_entries
487
+ .group_by { |entry| [entry[:kind], entry[:priority]] }
488
+ .sort_by { |(kind, priority), _| [ARRAY_KIND_ORDER.index(kind), priority] }
489
+ .map do |(kind, priority), entries|
490
+ { name: array_section_name(kind, priority), kind: kind, entries: entries }
491
+ end
492
+ end
493
+
494
+ # The section name a (kind, priority) pair is spelled with. See
495
+ # DEFAULT_ARRAY_PRIORITY / ARRAY_PRIORITY_DIGITS for where the two numbers
496
+ # in this format come from.
497
+ def array_section_name(kind, priority)
498
+ base = ARRAY_SECTION_BASE.fetch(kind)
499
+ return base if priority == DEFAULT_ARRAY_PRIORITY
500
+
501
+ "#{base}.#{format("%0#{ARRAY_PRIORITY_DIGITS}d", priority)}"
502
+ end
503
+
504
+ def relocations?
505
+ !@relocations.empty?
506
+ end
507
+
508
+ def data_relocations?
509
+ !@data_relocations.empty?
510
+ end
511
+
512
+ def rodata?
513
+ !@rodata_bytes.nil? && !@rodata_bytes.empty?
514
+ end
515
+
516
+ def data?
517
+ !@data_bytes.nil? && !@data_bytes.empty?
518
+ end
519
+
520
+ def bss?
521
+ @bss_size.positive?
522
+ end
523
+
524
+ # The ordered list of section names (nil for the anonymous NULL section);
525
+ # .rodata/.data appear only with their data, .bss only with a positive
526
+ # size, and .rela.text only when there is something to relocate.
527
+ def build_section_names
528
+ names = [nil, ".text"]
529
+ names << ".rodata" if rodata?
530
+ names << ".data" if data?
531
+ names << ".bss" if bss?
532
+ @array_sections.each { |group| names << group[:name] }
533
+ names << ".rela.text" if relocations?
534
+ names << ".rela.data" if data_relocations?
535
+ @array_sections.each { |group| names << ".rela#{group[:name]}" }
536
+ names.concat([".note.GNU-stack", ".symtab", ".strtab", ".shstrtab"])
537
+ end
538
+
539
+ # Resolves a section reference to its index: a Symbol like :text maps to
540
+ # ".text", otherwise the argument is treated as a literal name.
541
+ def section_index(ref)
542
+ name = ref.is_a?(Symbol) ? ".#{ref}" : ref
543
+ @section_names.index(name) or raise "unknown section: #{ref.inspect}"
544
+ end
545
+
546
+ # Ordered descriptors for every symbol table entry. Each is a Hash whose
547
+ # :shndx may be the symbol :text (resolved to the .text index at emit
548
+ # time) or a literal section index / SHN_* value.
549
+ def build_symbol_list
550
+ syms = []
551
+ syms << { name: nil, bind: STB_LOCAL, type: STT_NOTYPE, shndx: 0, value: 0, size: 0 }
552
+ if @file_symbol
553
+ syms << { name: @file_symbol, bind: STB_LOCAL, type: STT_FILE,
554
+ shndx: SHN_ABS, value: 0, size: 0 }
555
+ end
556
+ syms << { name: nil, bind: STB_LOCAL, type: STT_SECTION,
557
+ shndx: :text, value: 0, size: 0 }
558
+ if rodata?
559
+ syms << { name: nil, bind: STB_LOCAL, type: STT_SECTION,
560
+ shndx: :rodata, value: 0, size: 0 }
561
+ end
562
+ # ELF requires every STB_LOCAL symbol to precede the first STB_GLOBAL,
563
+ # so the defined symbols are emitted in two passes: the `static`
564
+ # (internal-linkage) functions and objects first, then the external
565
+ # ones. Within each pass functions come before objects, keeping the
566
+ # global-only case's original function-then-object order. sh_info
567
+ # (#first_global_index) then lands on the first external symbol.
568
+ [STB_LOCAL, STB_GLOBAL].each do |bind|
569
+ @func_symbols.each do |sym|
570
+ next unless sym[:bind] == bind
571
+
572
+ syms << { name: sym[:name], bind: bind, type: STT_FUNC,
573
+ shndx: :text, value: sym[:offset], size: sym[:size],
574
+ visibility: sym[:visibility] || 0 }
575
+ end
576
+ @object_symbols.each do |obj|
577
+ next unless obj[:bind] == bind
578
+
579
+ syms << { name: obj[:name], bind: bind, type: STT_OBJECT,
580
+ shndx: obj[:section], value: obj[:offset], size: obj[:size],
581
+ visibility: obj[:visibility] || 0 }
582
+ end
583
+ end
584
+ @undefined_symbols.each do |name|
585
+ syms << { name: name, bind: STB_GLOBAL, type: STT_NOTYPE,
586
+ shndx: SHN_UNDEF, value: 0, size: 0 }
587
+ end
588
+ syms
589
+ end
590
+
591
+ # name -> symtab index for every named symbol, so a relocation can point
592
+ # at either a defined function or an undefined external.
593
+ def index_symbols_by_name(symbols)
594
+ indices = {}
595
+ symbols.each_with_index { |sym, i| indices[sym[:name]] = i if sym[:name] }
596
+ indices
597
+ end
598
+
599
+ # Index of the first global symbol (= number of leading local symbols),
600
+ # reported as .symtab's sh_info.
601
+ def first_global_index(symbols)
602
+ symbols.index { |sym| sym[:bind] == STB_GLOBAL } || symbols.size
603
+ end
604
+
605
+ # Builds the .strtab (symbol names). Returns [bytes, name->offset map].
606
+ def build_strtab
607
+ buf = +"\0".b
608
+ offsets = {}
609
+ names = []
610
+ names << @file_symbol if @file_symbol
611
+ @func_symbols.each { |sym| names << sym[:name] }
612
+ @object_symbols.each { |obj| names << obj[:name] }
613
+ names.concat(@undefined_symbols)
614
+ names.uniq.each do |name|
615
+ offsets[name] = buf.bytesize
616
+ buf << name.b << "\0".b
617
+ end
618
+ [buf, offsets]
619
+ end
620
+
621
+ def build_symtab(symbols, sym_name_offsets)
622
+ buf = +"".b
623
+ symbols.each do |sym|
624
+ buf << sym_entry(
625
+ name: sym[:name] ? sym_name_offsets[sym[:name]] : 0,
626
+ info: (sym[:bind] << 4) | sym[:type],
627
+ other: sym[:visibility] || 0,
628
+ shndx: sym[:shndx].is_a?(Symbol) ? section_index(sym[:shndx]) : sym[:shndx],
629
+ value: sym[:value],
630
+ size: sym[:size]
631
+ )
632
+ end
633
+ buf
634
+ end
635
+
636
+ # Builds the .rela.text payload, one entry per recorded relocation. Each
637
+ # kind (:call/:string/:global/:got) is translated through the injected
638
+ # machine description into its concrete ELF relocation type, addend and
639
+ # target symbol (see #append_machine_reloc). On x86_64 that yields a
640
+ # :call as R_X86_64_PLT32 (addend -4), a :string as R_X86_64_PC32 against
641
+ # the .rodata section symbol (its own addend), a :global as R_X86_64_PC32
642
+ # against that global's symbol (addend -4), and a :got as
643
+ # R_X86_64_REX_GOTPCRELX against the symbol's GOT slot (addend -4).
644
+ def build_rela(symbol_indices, rodata_sym_index)
645
+ buf = +"".b
646
+ @relocations.each { |reloc| append_machine_reloc(buf, reloc, symbol_indices, rodata_sym_index) }
647
+ buf
648
+ end
649
+
650
+ # Builds the .rela.data payload, one entry per recorded data relocation.
651
+ # Both kinds resolve through the machine description; on x86_64 they are
652
+ # absolute R_X86_64_64 relocations. A :symbol reloc points at another
653
+ # object's symbol with its recorded addend (the pointer slot holds that
654
+ # object's address, plus any "&arr[i]" displacement); a :rodata reloc
655
+ # points at the .rodata section symbol with the string's byte offset (plus
656
+ # a cast/computed displacement) as its addend.
657
+ def build_rela_data(symbol_indices, rodata_sym_index)
658
+ buf = +"".b
659
+ @data_relocations.each { |reloc| append_machine_reloc(buf, reloc, symbol_indices, rodata_sym_index) }
660
+ buf
661
+ end
662
+
663
+ # Builds one array section's .rela payload: every slot is an absolute
664
+ # 64-bit pointer to its function, so each reuses the machine description's
665
+ # :symbol kind — R_X86_64_64 on x86_64, R_AARCH64_ABS64 on aarch64 — with a
666
+ # zero addend, at the slot's byte offset within the section.
667
+ def build_rela_array(entries, symbol_indices, rodata_sym_index)
668
+ buf = +"".b
669
+ entries.each_with_index do |entry, slot|
670
+ reloc = { kind: :symbol, offset: slot * ARRAY_ENTSIZE, symbol: entry[:symbol], addend: 0 }
671
+ append_machine_reloc(buf, reloc, symbol_indices, rodata_sym_index)
672
+ end
673
+ buf
674
+ end
675
+
676
+ # Emits the Elf64_Rela entries for one machine-independent relocation
677
+ # record, looking its kind up in the injected machine description. A kind
678
+ # maps to one descriptor per ELF entry the target needs — one on x86_64,
679
+ # two for an aarch64 address-forming pair — and each yields its own
680
+ # relocation type, offset (the recorded offset plus the descriptor's byte
681
+ # delta into the instruction sequence), addend (a fixed value or the
682
+ # record's own recorded one, plus the target's field-placement bias) and
683
+ # target symbol (the record's named symbol, or the .rodata section symbol
684
+ # for a :rodata_section descriptor).
685
+ def append_machine_reloc(buf, reloc, symbol_indices, rodata_sym_index)
686
+ @machine.relocations.fetch(reloc[:kind]).each do |desc|
687
+ sym_index = desc.symbol == :rodata_section ? rodata_sym_index : symbol_indices.fetch(reloc[:symbol])
688
+ addend = (desc.addend == :recorded ? reloc[:addend] : desc.addend) + desc.addend_bias
689
+ append_rela(buf, reloc[:offset] + desc.offset_delta, sym_index, desc.type, addend)
690
+ end
691
+ end
692
+
693
+ # Appends a single 24-byte Elf64_Rela entry (r_offset, r_info, r_addend).
694
+ def append_rela(buf, offset, sym_index, type, addend)
695
+ r_info = (sym_index << 32) | type
696
+ buf << [offset].pack("Q<")
697
+ buf << [r_info].pack("Q<")
698
+ buf << [addend].pack("q<")
699
+ end
700
+
701
+ # Ordered section descriptors, matching @section_names. sh_link/sh_info
702
+ # are held as section references (:symtab, :strtab, :text) and resolved
703
+ # once every section index is fixed.
704
+ def section_layout(symtab:, strtab:, rela:, rela_data:, rela_arrays:)
705
+ sections = {}
706
+ sections[nil] = { type: SHT_NULL, flags: 0, data: nil,
707
+ link: 0, info: 0, addralign: 0, entsize: 0 }
708
+ sections[".text"] = { type: SHT_PROGBITS, flags: SHF_ALLOC | SHF_EXECINSTR,
709
+ data: @text_bytes, link: 0, info: 0,
710
+ addralign: 16, entsize: 0 }
711
+ if rodata?
712
+ sections[".rodata"] = { type: SHT_PROGBITS, flags: SHF_ALLOC, data: @rodata_bytes,
713
+ link: 0, info: 0, addralign: 8, entsize: 0 }
714
+ end
715
+ if data?
716
+ sections[".data"] = { type: SHT_PROGBITS, flags: SHF_ALLOC | SHF_WRITE, data: @data_bytes,
717
+ link: 0, info: 0, addralign: @data_align, entsize: 0 }
718
+ end
719
+ if bss?
720
+ sections[".bss"] = { type: SHT_NOBITS, flags: SHF_ALLOC | SHF_WRITE, data: nil,
721
+ nobits_size: @bss_size, link: 0, info: 0,
722
+ addralign: @bss_align, entsize: 0 }
723
+ end
724
+ if rela
725
+ sections[".rela.text"] = { type: SHT_RELA, flags: SHF_INFO_LINK,
726
+ data: rela, link: :symtab, info: :text,
727
+ addralign: 8, entsize: RELA_ENTSIZE }
728
+ end
729
+ if rela_data
730
+ sections[".rela.data"] = { type: SHT_RELA, flags: SHF_INFO_LINK,
731
+ data: rela_data, link: :symtab, info: :data,
732
+ addralign: 8, entsize: RELA_ENTSIZE }
733
+ end
734
+ # Each array section is a run of empty pointer slots (the relocations
735
+ # supply every byte), paired with its own .rela table. Both are named by
736
+ # literal string, since a priority-numbered name has no symbolic form.
737
+ @array_sections.each do |group|
738
+ name = group[:name]
739
+ sections[name] = { type: ARRAY_SECTION_TYPE.fetch(group[:kind]),
740
+ flags: SHF_ALLOC | SHF_WRITE,
741
+ data: "\0".b * (group[:entries].size * ARRAY_ENTSIZE),
742
+ link: 0, info: 0, addralign: ARRAY_ALIGN, entsize: ARRAY_ENTSIZE }
743
+ sections[".rela#{name}"] = { type: SHT_RELA, flags: SHF_INFO_LINK,
744
+ data: rela_arrays.fetch(name), link: :symtab, info: name,
745
+ addralign: 8, entsize: RELA_ENTSIZE }
746
+ end
747
+ sections[".note.GNU-stack"] = { type: SHT_PROGBITS, flags: 0, data: "".b,
748
+ link: 0, info: 0, addralign: 1, entsize: 0 }
749
+ sections[".symtab"] = { type: SHT_SYMTAB, flags: 0, data: symtab,
750
+ link: :strtab, info: first_global_index(@symbols),
751
+ addralign: 8, entsize: SYM_ENTSIZE }
752
+ sections[".strtab"] = { type: SHT_STRTAB, flags: 0, data: strtab,
753
+ link: 0, info: 0, addralign: 1, entsize: 0 }
754
+ sections[".shstrtab"] = { type: SHT_STRTAB, flags: 0, data: nil,
755
+ link: 0, info: 0, addralign: 1, entsize: 0 }
756
+ @section_names.map { |name| sections.fetch(name).merge(name: name) }
757
+ end
758
+
759
+ # Computes file offsets, fills in the .shstrtab, and concatenates
760
+ # everything (header, section payloads, section header table).
761
+ def assemble(sections)
762
+ shstrtab, sec_name_offsets = build_shstrtab
763
+ sections.each { |sec| sec[:data] = shstrtab if sec[:name] == ".shstrtab" }
764
+
765
+ offset = EHDR_SIZE
766
+ sections.each do |sec|
767
+ # A NOBITS section (.bss) occupies no file space: it still gets an
768
+ # aligned sh_offset for tooling, but the running offset does not
769
+ # advance past it.
770
+ if sec[:type] == SHT_NOBITS
771
+ offset = align(offset, [sec[:addralign], 1].max)
772
+ sec[:offset] = offset
773
+ next
774
+ end
775
+ next if sec[:data].nil?
776
+
777
+ offset = align(offset, [sec[:addralign], 1].max)
778
+ sec[:offset] = offset
779
+ offset += sec[:data].bytesize
780
+ end
781
+ shoff = align(offset, 8)
782
+
783
+ out = +"".b
784
+ out << build_ehdr(shoff)
785
+ sections.each do |sec|
786
+ next if sec[:data].nil?
787
+
788
+ pad_to(out, sec[:offset])
789
+ out << sec[:data]
790
+ end
791
+ pad_to(out, shoff)
792
+ sections.each { |sec| out << build_shdr(sec, sec_name_offsets) }
793
+ out
794
+ end
795
+
796
+ def build_ehdr(shoff)
797
+ e_ident = [0x7F, 0x45, 0x4C, 0x46, ELFCLASS64, ELFDATA2LSB, EV_CURRENT,
798
+ 0, 0, 0, 0, 0, 0, 0, 0, 0].pack("C16")
799
+ e_ident +
800
+ [ET_REL].pack("S<") + # e_type
801
+ [@machine.e_machine].pack("S<") + # e_machine
802
+ [EV_CURRENT].pack("L<") + # e_version
803
+ [0].pack("Q<") + # e_entry
804
+ [0].pack("Q<") + # e_phoff
805
+ [shoff].pack("Q<") + # e_shoff
806
+ [0].pack("L<") + # e_flags
807
+ [EHDR_SIZE].pack("S<") + # e_ehsize
808
+ [0].pack("S<") + # e_phentsize
809
+ [0].pack("S<") + # e_phnum
810
+ [SHDR_ENTSIZE].pack("S<") + # e_shentsize
811
+ [@section_names.size].pack("S<") + # e_shnum
812
+ [section_index(".shstrtab")].pack("S<") # e_shstrndx
813
+ end
814
+
815
+ # Builds the .shstrtab (section names). Returns [bytes, name->offset map].
816
+ def build_shstrtab
817
+ buf = +"\0".b
818
+ offsets = {}
819
+ @section_names.each do |name|
820
+ next if name.nil? || offsets.key?(name)
821
+
822
+ offsets[name] = buf.bytesize
823
+ buf << name.b << "\0".b
824
+ end
825
+ [buf, offsets]
826
+ end
827
+
828
+ def sym_entry(name:, info:, other:, shndx:, value:, size:)
829
+ [name].pack("L<") +
830
+ [info].pack("C") +
831
+ [other].pack("C") +
832
+ [shndx].pack("S<") +
833
+ [value].pack("Q<") +
834
+ [size].pack("Q<")
835
+ end
836
+
837
+ def build_shdr(section, sec_name_offsets)
838
+ shdr(
839
+ name: section[:name] ? sec_name_offsets[section[:name]] : 0,
840
+ type: section[:type],
841
+ flags: section[:flags],
842
+ addr: 0,
843
+ offset: section[:offset] || 0,
844
+ size: section_size(section),
845
+ link: resolve_ref(section[:link]),
846
+ info: resolve_ref(section[:info]),
847
+ addralign: section[:addralign],
848
+ entsize: section[:entsize]
849
+ )
850
+ end
851
+
852
+ # A section's sh_size: a NOBITS section (.bss) reports its in-memory size
853
+ # although it stores no file bytes; every other section reports the byte
854
+ # length of its payload.
855
+ def section_size(section)
856
+ return section[:nobits_size] || 0 if section[:type] == SHT_NOBITS
857
+
858
+ section[:data] ? section[:data].bytesize : 0
859
+ end
860
+
861
+ # A section's sh_link/sh_info is either a literal integer or a section
862
+ # reference resolved to an index — a symbolic one (:symtab, :strtab, :text)
863
+ # or, for a priority-numbered array section, its literal name.
864
+ def resolve_ref(value)
865
+ value.is_a?(Integer) ? value : section_index(value)
866
+ end
867
+
868
+ def shdr(name:, type:, flags:, addr:, offset:, size:, link:, info:,
869
+ addralign:, entsize:)
870
+ [name].pack("L<") +
871
+ [type].pack("L<") +
872
+ [flags].pack("Q<") +
873
+ [addr].pack("Q<") +
874
+ [offset].pack("Q<") +
875
+ [size].pack("Q<") +
876
+ [link].pack("L<") +
877
+ [info].pack("L<") +
878
+ [addralign].pack("Q<") +
879
+ [entsize].pack("Q<")
880
+ end
881
+
882
+ def align(value, alignment)
883
+ (value + alignment - 1) / alignment * alignment
884
+ end
885
+
886
+ def pad_to(buffer, target_offset)
887
+ buffer << ("\0" * (target_offset - buffer.bytesize)).b if buffer.bytesize < target_offset
888
+ end
889
+ end
890
+ end
891
+ end