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,546 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Rubycc
|
|
6
|
+
module Link
|
|
7
|
+
# The static-link core, an `ld -r` equivalent: it merges an ordered list of
|
|
8
|
+
# ET_REL objects (and archive members pulled in lazily) into a single ET_REL
|
|
9
|
+
# object. Relocations are *retargeted*, not applied — an `ld -r` output keeps
|
|
10
|
+
# its relocations so a later final link can resolve them; the byte-patching
|
|
11
|
+
# apply engine belongs to the executable/.so writers, not here.
|
|
12
|
+
#
|
|
13
|
+
# The pipeline is two phases over the reader structures directly (no linker
|
|
14
|
+
# IR, per the roadmap): a selection phase walks the inputs left to right to
|
|
15
|
+
# decide *which* objects take part — a `.o` joins unconditionally, an archive
|
|
16
|
+
# contributes only members that resolve a still-undefined symbol (iterated to
|
|
17
|
+
# a fixpoint within that archive; classic single-pass, so an archive already
|
|
18
|
+
# passed is never revisited) — and a merge phase concatenates same-named
|
|
19
|
+
# sections, resolves the global symbol table, and rewrites every relocation
|
|
20
|
+
# onto the merged sections and symbol table before handing the result to
|
|
21
|
+
# RelocatableWriter.
|
|
22
|
+
#
|
|
23
|
+
# The single subtle point is a relocation against a *section symbol*: its
|
|
24
|
+
# target section's bytes move to a new offset in the merged section, so the
|
|
25
|
+
# addend, which is measured from that section's start, must gain the piece's
|
|
26
|
+
# placement offset. Named-symbol references need no addend fix-up because the
|
|
27
|
+
# symbol's own value already carries the shift.
|
|
28
|
+
#
|
|
29
|
+
# Unresolved symbols are legal in an `ld -r` output and stay UND. COMMON
|
|
30
|
+
# symbols are diagnosed rather than allocated (out of scope; neither rubycc
|
|
31
|
+
# nor gcc's -fno-common default emits them). Output is deterministic (N4):
|
|
32
|
+
# sections appear in first-seen order, symbols in a fixed local-then-global
|
|
33
|
+
# order, so identical inputs yield byte-identical output.
|
|
34
|
+
class PartialLinker
|
|
35
|
+
include ObjFile
|
|
36
|
+
|
|
37
|
+
ELFMAG = "\x7FELF".b
|
|
38
|
+
AR_MAGIC = "!<arch>\n".b
|
|
39
|
+
|
|
40
|
+
# Section header types the merge treats specially. Everything else — any
|
|
41
|
+
# named PROGBITS/NOBITS/NOTE/*_ARRAY the compiler emits or gcc adds — is
|
|
42
|
+
# carried through and concatenated by name.
|
|
43
|
+
SHT_NULL = 0
|
|
44
|
+
SHT_SYMTAB = 2
|
|
45
|
+
SHT_STRTAB = 3
|
|
46
|
+
SHT_RELA = 4
|
|
47
|
+
SHT_REL = 9
|
|
48
|
+
SHT_NOBITS = 8
|
|
49
|
+
SHT_GROUP = 17
|
|
50
|
+
SHT_SYMTAB_SHNDX = 18
|
|
51
|
+
# Section kinds that describe the object's own linking metadata (symbol and
|
|
52
|
+
# string tables, relocation tables, section groups) — regenerated by the
|
|
53
|
+
# writer, so never carried from the inputs.
|
|
54
|
+
SKIPPED_TYPES = [SHT_NULL, SHT_SYMTAB, SHT_STRTAB, SHT_RELA, SHT_REL,
|
|
55
|
+
SHT_GROUP, SHT_SYMTAB_SHNDX].freeze
|
|
56
|
+
|
|
57
|
+
SHN_UNDEF = 0
|
|
58
|
+
SHN_ABS = 0xFFF1
|
|
59
|
+
|
|
60
|
+
class << self
|
|
61
|
+
# Links `inputs` (an ordered array; each element a filesystem path, or the
|
|
62
|
+
# raw bytes of an ET_REL object or an ar archive) and returns the merged
|
|
63
|
+
# ET_REL object as an ASCII-8BIT String.
|
|
64
|
+
def link(inputs)
|
|
65
|
+
new(inputs).link
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Convenience: link and write the merged object to `path`.
|
|
69
|
+
def link_to(inputs, path)
|
|
70
|
+
File.binwrite(path, link(inputs))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def initialize(inputs)
|
|
75
|
+
@inputs = inputs.each_with_index.map { |raw, i| load_input(raw, i) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def link
|
|
79
|
+
included = select_inputs
|
|
80
|
+
Merger.new(included).run
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# A classified input: :object or :archive, the reader, and a label used in
|
|
86
|
+
# diagnostics (the path, or a synthetic name for raw bytes).
|
|
87
|
+
Input = Struct.new(:kind, :label, :reader, keyword_init: true)
|
|
88
|
+
|
|
89
|
+
# Classifies one raw input. A String that already opens with an ELF or ar
|
|
90
|
+
# magic is taken as in-memory bytes; anything else is a filesystem path to
|
|
91
|
+
# read. The resulting bytes are then dispatched to the matching reader.
|
|
92
|
+
def load_input(raw, index)
|
|
93
|
+
if raw.is_a?(String) && (raw.b.start_with?(ELFMAG) || raw.b.start_with?(AR_MAGIC))
|
|
94
|
+
bytes = raw.b
|
|
95
|
+
label = "input##{index}"
|
|
96
|
+
else
|
|
97
|
+
label = raw.to_s
|
|
98
|
+
bytes = File.binread(label)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if bytes.start_with?(AR_MAGIC)
|
|
102
|
+
Input.new(kind: :archive, label: label, reader: ArReader.read(bytes))
|
|
103
|
+
else
|
|
104
|
+
Input.new(kind: :object, label: label, reader: ELFReader.read(bytes))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Selection phase: walk the inputs left to right, returning the ordered list
|
|
109
|
+
# of participating objects ([{reader:, label:}]). Object inputs join
|
|
110
|
+
# unconditionally; archives contribute members lazily.
|
|
111
|
+
def select_inputs
|
|
112
|
+
@included = []
|
|
113
|
+
@defined = Set.new # global/weak names already defined by an included object
|
|
114
|
+
@undefined = Set.new # names referenced but not yet defined
|
|
115
|
+
# When non-nil, #include_object appends every name it newly adds to
|
|
116
|
+
# @undefined here; the archive walk uses it to widen its candidate set.
|
|
117
|
+
@new_undefined = nil
|
|
118
|
+
|
|
119
|
+
@inputs.each do |input|
|
|
120
|
+
case input.kind
|
|
121
|
+
when :object then include_object(input.reader, input.label)
|
|
122
|
+
when :archive then pull_from_archive(input)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
@included
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Adds an object to the participating set and folds its global/weak symbols
|
|
129
|
+
# into the running defined/undefined name sets, so a later archive pass
|
|
130
|
+
# knows what still needs resolving.
|
|
131
|
+
def include_object(reader, label)
|
|
132
|
+
@included << { reader: reader, label: label }
|
|
133
|
+
reader.symbols.each do |sym|
|
|
134
|
+
next unless sym.bind == :global || sym.bind == :weak
|
|
135
|
+
next if sym.name.to_s.empty?
|
|
136
|
+
|
|
137
|
+
if sym.defined?
|
|
138
|
+
@defined << sym.name
|
|
139
|
+
@undefined.delete(sym.name)
|
|
140
|
+
elsif sym.undefined? && !@defined.include?(sym.name)
|
|
141
|
+
@new_undefined << sym.name if @new_undefined && !@undefined.include?(sym.name)
|
|
142
|
+
@undefined << sym.name
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Pulls members from one archive to a fixpoint: repeatedly take any not-yet
|
|
148
|
+
# included member whose exports satisfy a currently-undefined symbol, since
|
|
149
|
+
# loading a member can surface new undefined references that pull further
|
|
150
|
+
# members from this SAME archive. Members are scanned in archive order for a
|
|
151
|
+
# deterministic result. Once this call returns the archive is done — a later
|
|
152
|
+
# input cannot reach back into it (single-pass semantics).
|
|
153
|
+
#
|
|
154
|
+
# The naive form of that fixpoint rescans EVERY member after each pull, so
|
|
155
|
+
# an archive whose members are ordered against their dependency direction
|
|
156
|
+
# takes one member per pass: O(M^2) member visits, which a hostile archive
|
|
157
|
+
# controls outright by choosing the member order. Instead an exporter index
|
|
158
|
+
# (symbol name -> the members defining it) narrows each pass to the members
|
|
159
|
+
# that could possibly match: a member can only be pulled through a name
|
|
160
|
+
# that is in @undefined, and every such name either was there when the
|
|
161
|
+
# archive was reached or was added by a member pulled from it, so seeding
|
|
162
|
+
# the candidate set from the former and widening it on the latter yields a
|
|
163
|
+
# SUPERSET of the pullable members — nothing is ever missed.
|
|
164
|
+
#
|
|
165
|
+
# The pull ORDER must not change, and that is the delicate part: it fixes
|
|
166
|
+
# the order of the merged sections and of the output symbol table, so N4
|
|
167
|
+
# (identical inputs produce byte-identical output) rests on it. Two rules
|
|
168
|
+
# preserve it exactly. Candidates are visited in ascending member index, so
|
|
169
|
+
# a pass sees them in archive order; and a candidate that appears mid-pass
|
|
170
|
+
# is spliced into the remaining queue when it sits after the current
|
|
171
|
+
# position, because the all-members scan would have reached it later in the
|
|
172
|
+
# same pass. Members that are not candidates are exactly the ones the
|
|
173
|
+
# all-members scan visits and rejects, so skipping them changes nothing.
|
|
174
|
+
def pull_from_archive(input)
|
|
175
|
+
members = input.reader.members
|
|
176
|
+
exporters = exporter_index(members)
|
|
177
|
+
taken = Set.new
|
|
178
|
+
candidates = Set.new
|
|
179
|
+
@undefined.each { |name| widen(candidates, exporters[name], taken, members) }
|
|
180
|
+
|
|
181
|
+
loop do
|
|
182
|
+
pulled = pull_pass(input, members, exporters, taken, candidates)
|
|
183
|
+
break unless pulled
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# One archive-order pass over the current candidates. Returns whether any
|
|
188
|
+
# member was pulled, which is the fixpoint's termination test.
|
|
189
|
+
def pull_pass(input, members, exporters, taken, candidates)
|
|
190
|
+
pulled = false
|
|
191
|
+
queue = candidates.to_a.sort
|
|
192
|
+
qi = 0
|
|
193
|
+
while qi < queue.length
|
|
194
|
+
index = queue[qi]
|
|
195
|
+
qi += 1
|
|
196
|
+
member = members[index]
|
|
197
|
+
next if member.special? || taken.include?(member)
|
|
198
|
+
next unless member_exports(member).any? { |name| @undefined.include?(name) }
|
|
199
|
+
|
|
200
|
+
@new_undefined = []
|
|
201
|
+
include_object(ELFReader.read(member.data), "#{input.label}(#{member.name})")
|
|
202
|
+
added = @new_undefined
|
|
203
|
+
@new_undefined = nil
|
|
204
|
+
taken << member
|
|
205
|
+
candidates.delete(index)
|
|
206
|
+
pulled = true
|
|
207
|
+
added.each do |name|
|
|
208
|
+
widen(candidates, exporters[name], taken, members) do |cand|
|
|
209
|
+
# Still ahead of us: the all-members scan would reach it in this
|
|
210
|
+
# very pass, so the queue has to as well.
|
|
211
|
+
insert_pending(queue, qi, cand) if cand > index
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
pulled
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Adds the members exporting one name to the candidate set, yielding each
|
|
219
|
+
# index that was not a candidate already (so a caller mid-pass can queue
|
|
220
|
+
# it). Members already pulled are never re-added.
|
|
221
|
+
def widen(candidates, indexes, taken, members)
|
|
222
|
+
return if indexes.nil?
|
|
223
|
+
|
|
224
|
+
indexes.each do |index|
|
|
225
|
+
next if candidates.include?(index) || taken.include?(members[index])
|
|
226
|
+
|
|
227
|
+
candidates << index
|
|
228
|
+
yield index if block_given?
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Splices `value` into the not-yet-visited tail queue[from..], which is
|
|
233
|
+
# sorted ascending, keeping it sorted.
|
|
234
|
+
def insert_pending(queue, from, value)
|
|
235
|
+
lo = from
|
|
236
|
+
hi = queue.length
|
|
237
|
+
while lo < hi
|
|
238
|
+
mid = (lo + hi) / 2
|
|
239
|
+
if queue[mid] < value
|
|
240
|
+
lo = mid + 1
|
|
241
|
+
else
|
|
242
|
+
hi = mid
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
queue.insert(lo, value)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# symbol name => ascending array of the indexes of the members exporting
|
|
249
|
+
# it. Built once per archive off the same memoized #member_exports the
|
|
250
|
+
# selection uses, so it costs one parse per member — the same parse the
|
|
251
|
+
# first fixpoint pass would have done anyway.
|
|
252
|
+
def exporter_index(members)
|
|
253
|
+
index = {}
|
|
254
|
+
members.each_with_index do |member, i|
|
|
255
|
+
next if member.special?
|
|
256
|
+
|
|
257
|
+
member_exports(member).each { |name| (index[name] ||= []) << i }
|
|
258
|
+
end
|
|
259
|
+
index
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# The defined global/weak symbol names a member exports, memoized per
|
|
263
|
+
# member. A member ELFReader cannot parse contributes none.
|
|
264
|
+
def member_exports(member)
|
|
265
|
+
@member_exports ||= {}
|
|
266
|
+
@member_exports[member.object_id] ||= begin
|
|
267
|
+
reader = ELFReader.read(member.data)
|
|
268
|
+
reader.symbols.select do |sym|
|
|
269
|
+
(sym.bind == :global || sym.bind == :weak) && sym.defined? && !sym.name.to_s.empty?
|
|
270
|
+
end.map(&:name)
|
|
271
|
+
rescue ObjFile::ELFFormatError
|
|
272
|
+
[]
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Merges the selected objects into one ET_REL image. Kept as an inner class
|
|
277
|
+
# so the phase-2 state (placement maps, the output symbol table, the
|
|
278
|
+
# per-object symbol remaps) stays isolated from the selection driver.
|
|
279
|
+
class Merger
|
|
280
|
+
include ObjFile
|
|
281
|
+
|
|
282
|
+
SHT_NOBITS = PartialLinker::SHT_NOBITS
|
|
283
|
+
SHN_ABS = PartialLinker::SHN_ABS
|
|
284
|
+
|
|
285
|
+
def initialize(included)
|
|
286
|
+
@objects = included # [{reader:, label:}]
|
|
287
|
+
# The merged object keeps the inputs' architecture: the merge is
|
|
288
|
+
# machine-independent (section concatenation and numeric-type relocation
|
|
289
|
+
# retargeting), but the output header must carry the right e_machine so
|
|
290
|
+
# the final linker that reads it back can pick the target's relocation
|
|
291
|
+
# and crt logic. Empty inputs default to x86_64 through RelocatableWriter.
|
|
292
|
+
machine = included.first&.dig(:reader)&.machine
|
|
293
|
+
@writer = machine ? RelocatableWriter.new(machine: machine) : RelocatableWriter.new
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def run
|
|
297
|
+
merge_sections
|
|
298
|
+
resolve_symbols
|
|
299
|
+
retarget_relocations
|
|
300
|
+
@writer.to_binary
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
private
|
|
304
|
+
|
|
305
|
+
# --- section merging -------------------------------------------------
|
|
306
|
+
|
|
307
|
+
# Concatenates same-named content sections in object order. For each
|
|
308
|
+
# object/section it records the piece's placement offset within the merged
|
|
309
|
+
# section — the shift every symbol value and relocation offset in that
|
|
310
|
+
# piece is adjusted by. Output alignment/entsize are the max over inputs;
|
|
311
|
+
# a NOBITS section (.bss) merges by size only.
|
|
312
|
+
def merge_sections
|
|
313
|
+
@order = [] # output section names, first-seen order
|
|
314
|
+
@meta = {} # name => merge metadata
|
|
315
|
+
@data = {} # name => merged bytes (PROGBITS)
|
|
316
|
+
@size = Hash.new(0) # name => running size
|
|
317
|
+
@placement = Array.new(@objects.size) { {} } # [oi][in_section.index] => offset
|
|
318
|
+
|
|
319
|
+
@objects.each_with_index do |obj, oi|
|
|
320
|
+
obj[:reader].sections.each do |sec|
|
|
321
|
+
next unless carried?(sec)
|
|
322
|
+
|
|
323
|
+
place_piece(oi, sec)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
@out_section = {}
|
|
328
|
+
@order.each do |name|
|
|
329
|
+
m = @meta[name]
|
|
330
|
+
@out_section[name] =
|
|
331
|
+
if m[:type] == SHT_NOBITS
|
|
332
|
+
@writer.add_section(name: name, type: m[:type], flags: m[:flags],
|
|
333
|
+
addralign: m[:addralign], entsize: m[:entsize], size: @size[name])
|
|
334
|
+
else
|
|
335
|
+
@writer.add_section(name: name, type: m[:type], flags: m[:flags],
|
|
336
|
+
addralign: m[:addralign], entsize: m[:entsize], data: @data[name])
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# A section carried into the output: any named section that is not one of
|
|
342
|
+
# the regenerated linking-metadata kinds.
|
|
343
|
+
def carried?(sec)
|
|
344
|
+
!PartialLinker::SKIPPED_TYPES.include?(sec.type) && !sec.name.to_s.empty?
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Places one input section piece into its merged section, aligning the
|
|
348
|
+
# running length up to the piece's own alignment first and recording where
|
|
349
|
+
# it landed. The output metadata accumulates the max alignment/entsize and
|
|
350
|
+
# the union of flags across pieces.
|
|
351
|
+
def place_piece(oi, sec)
|
|
352
|
+
name = sec.name
|
|
353
|
+
unless @meta.key?(name)
|
|
354
|
+
@order << name
|
|
355
|
+
@meta[name] = { type: sec.type, flags: sec.flags,
|
|
356
|
+
addralign: [sec.addralign, 1].max, entsize: sec.entsize }
|
|
357
|
+
end
|
|
358
|
+
m = @meta[name]
|
|
359
|
+
m[:addralign] = [m[:addralign], sec.addralign, 1].max
|
|
360
|
+
m[:entsize] = [m[:entsize], sec.entsize].max
|
|
361
|
+
m[:flags] |= sec.flags
|
|
362
|
+
|
|
363
|
+
offset = align(@size[name], [sec.addralign, 1].max)
|
|
364
|
+
@placement[oi][sec.index] = offset
|
|
365
|
+
|
|
366
|
+
if sec.type == SHT_NOBITS
|
|
367
|
+
@size[name] = offset + sec.size
|
|
368
|
+
else
|
|
369
|
+
buf = (@data[name] ||= +"".b)
|
|
370
|
+
buf << ("\0" * (offset - buf.bytesize)).b if buf.bytesize < offset
|
|
371
|
+
buf << sec.data
|
|
372
|
+
@size[name] = buf.bytesize
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# --- symbol resolution ----------------------------------------------
|
|
377
|
+
|
|
378
|
+
# Builds the output symbol table in a fixed order — section symbols, then
|
|
379
|
+
# each object's local symbols, then the resolved globals — and records,
|
|
380
|
+
# per object, the map from an input symbol to its output symbol so
|
|
381
|
+
# relocations can be re-pointed.
|
|
382
|
+
def resolve_symbols
|
|
383
|
+
@remap = Array.new(@objects.size) { {} } # [oi][in_symbol.index] => out Symbol
|
|
384
|
+
@section_symbol = {} # output section name => out Symbol
|
|
385
|
+
@order.each do |name|
|
|
386
|
+
@section_symbol[name] =
|
|
387
|
+
@writer.add_symbol(name: nil, bind: :local, type: :section, section: @out_section[name])
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
add_local_symbols
|
|
391
|
+
resolve_globals
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Passes every input local symbol through per object, shifting a defined
|
|
395
|
+
# local's value by its section's placement offset and re-pointing it at the
|
|
396
|
+
# merged output section. Section symbols are not passed through (the output
|
|
397
|
+
# has one synthesized per merged section); undefined or dropped-section
|
|
398
|
+
# locals are skipped.
|
|
399
|
+
def add_local_symbols
|
|
400
|
+
@objects.each_with_index do |obj, oi|
|
|
401
|
+
obj[:reader].symbols.each do |sym|
|
|
402
|
+
next if sym.index.zero? # the reserved null symbol
|
|
403
|
+
next unless sym.bind == :local
|
|
404
|
+
next if sym.type == :section # synthesized, not passed through
|
|
405
|
+
|
|
406
|
+
@remap[oi][sym.index] = local_output_symbol(oi, sym)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# The output symbol for one input local, or nil when it cannot be carried
|
|
412
|
+
# (an undefined local, or one defined in a section that was not merged).
|
|
413
|
+
def local_output_symbol(oi, sym)
|
|
414
|
+
if sym.type == :file
|
|
415
|
+
@writer.add_symbol(name: sym.name, bind: :local, type: :file, shndx: SHN_ABS)
|
|
416
|
+
elsif sym.absolute?
|
|
417
|
+
@writer.add_symbol(name: sym.name, bind: :local, type: sym.type, shndx: SHN_ABS,
|
|
418
|
+
value: sym.value, size: sym.size)
|
|
419
|
+
elsif sym.defined? && sym.section && @out_section.key?(sym.section.name)
|
|
420
|
+
out = @out_section[sym.section.name]
|
|
421
|
+
@writer.add_symbol(name: sym.name, bind: :local, type: sym.type,
|
|
422
|
+
visibility: sym.visibility, section: out,
|
|
423
|
+
value: @placement[oi][sym.section.index] + sym.value, size: sym.size)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Resolves the global/weak symbols to one output entry per name, applying
|
|
428
|
+
# the definition-precedence rules, then re-points every object's reference
|
|
429
|
+
# to that single entry.
|
|
430
|
+
def resolve_globals
|
|
431
|
+
@globals = {} # name => resolution state (insertion order = first-seen)
|
|
432
|
+
@objects.each_with_index do |obj, oi|
|
|
433
|
+
obj[:reader].symbols.each do |sym|
|
|
434
|
+
next unless sym.bind == :global || sym.bind == :weak
|
|
435
|
+
next if sym.name.to_s.empty?
|
|
436
|
+
|
|
437
|
+
record_global(oi, sym, obj[:label])
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
out = emit_globals
|
|
442
|
+
# A relocation may reference a global that this object leaves undefined;
|
|
443
|
+
# it still points at the one merged entry, so map every occurrence.
|
|
444
|
+
@objects.each_with_index do |obj, oi|
|
|
445
|
+
obj[:reader].symbols.each do |sym|
|
|
446
|
+
next unless sym.bind == :global || sym.bind == :weak
|
|
447
|
+
next if sym.name.to_s.empty?
|
|
448
|
+
|
|
449
|
+
@remap[oi][sym.index] = out[sym.name]
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Folds one global/weak symbol into the resolution state, enforcing the
|
|
455
|
+
# precedence rules: a strong definition overrides a weak one, a second
|
|
456
|
+
# strong definition is a hard error, weak-vs-weak keeps the first, and a
|
|
457
|
+
# definition always beats an undefined reference. COMMON is unsupported.
|
|
458
|
+
def record_global(oi, sym, label)
|
|
459
|
+
if sym.common?
|
|
460
|
+
raise LinkError, "COMMON symbol '#{sym.name}' is not supported (compile with -fno-common)"
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
state = @globals[sym.name]
|
|
464
|
+
weak = sym.bind == :weak
|
|
465
|
+
|
|
466
|
+
if sym.defined?
|
|
467
|
+
if state.nil? || !state[:defined]
|
|
468
|
+
@globals[sym.name] = { defined: true, oi: oi, sym: sym, weak: weak, label: label }
|
|
469
|
+
elsif state[:weak] && !weak
|
|
470
|
+
@globals[sym.name] = { defined: true, oi: oi, sym: sym, weak: false, label: label }
|
|
471
|
+
elsif !state[:weak] && !weak
|
|
472
|
+
raise LinkError,
|
|
473
|
+
"multiple definition of '#{sym.name}' (defined in #{state[:label]} and #{label})"
|
|
474
|
+
end
|
|
475
|
+
# strong-over-weak keep, weak-over-strong ignore, weak-over-weak first wins
|
|
476
|
+
elsif state.nil?
|
|
477
|
+
@globals[sym.name] = { defined: false, weak: weak }
|
|
478
|
+
elsif !state[:defined] && !weak
|
|
479
|
+
state[:weak] = false # a strong reference promotes the pending UND binding
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Emits the resolved globals as output symbols and returns a name -> output
|
|
484
|
+
# Symbol map. A defined winner is shifted onto its merged section; an
|
|
485
|
+
# all-undefined name stays UND with the strongest observed binding.
|
|
486
|
+
def emit_globals
|
|
487
|
+
out = {}
|
|
488
|
+
@globals.each do |name, state|
|
|
489
|
+
out[name] =
|
|
490
|
+
if state[:defined]
|
|
491
|
+
sym = state[:sym]
|
|
492
|
+
section = @out_section[sym.section.name]
|
|
493
|
+
@writer.add_symbol(name: name, bind: state[:weak] ? :weak : :global,
|
|
494
|
+
type: sym.type, visibility: sym.visibility, section: section,
|
|
495
|
+
value: @placement[state[:oi]][sym.section.index] + sym.value,
|
|
496
|
+
size: sym.size)
|
|
497
|
+
else
|
|
498
|
+
@writer.add_symbol(name: name, bind: state[:weak] ? :weak : :global,
|
|
499
|
+
type: :notype, shndx: SHN_UNDEF)
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
out
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# --- relocation retargeting -----------------------------------------
|
|
506
|
+
|
|
507
|
+
# Carries every input relocation to the merged output: the offset gains the
|
|
508
|
+
# target piece's placement, and the symbol reference is re-pointed. A
|
|
509
|
+
# section-symbol reference moves to the OUTPUT section symbol with its
|
|
510
|
+
# addend bumped by the referenced piece's placement (the section's bytes
|
|
511
|
+
# now start there); every other reference keeps its addend. Relocation
|
|
512
|
+
# types pass through untouched.
|
|
513
|
+
def retarget_relocations
|
|
514
|
+
@objects.each_with_index do |obj, oi|
|
|
515
|
+
obj[:reader].relocation_sections.each do |rs|
|
|
516
|
+
target = rs.target
|
|
517
|
+
next unless target && @out_section.key?(target.name)
|
|
518
|
+
|
|
519
|
+
base = @placement[oi][target.index]
|
|
520
|
+
out_target = @out_section[target.name]
|
|
521
|
+
rs.relocations.each { |reloc| emit_relocation(oi, out_target, base, reloc) }
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def emit_relocation(oi, out_target, base, reloc)
|
|
527
|
+
sym = reloc.symbol
|
|
528
|
+
if sym && sym.type == :section && sym.section && @out_section.key?(sym.section.name)
|
|
529
|
+
out_sym = @section_symbol[sym.section.name]
|
|
530
|
+
addend = reloc.addend + @placement[oi][sym.section.index]
|
|
531
|
+
else
|
|
532
|
+
out_sym = (sym && @remap[oi][sym.index]) || @writer.null_symbol
|
|
533
|
+
addend = reloc.addend
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
@writer.add_relocation(target: out_target, offset: reloc.offset + base,
|
|
537
|
+
symbol: out_sym, type: reloc.type, addend: addend)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def align(value, alignment)
|
|
541
|
+
(value + alignment - 1) / alignment * alignment
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
end
|