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,1732 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Link
|
|
5
|
+
# The final-link core that turns an ordered set of relocatable inputs into a
|
|
6
|
+
# loadable ELF64 shared object (ET_DYN, a `.so`) for Linux x86_64 or aarch64.
|
|
7
|
+
# It is the
|
|
8
|
+
# counterpart of the `ld -r` static core: where PartialLinker merges inputs
|
|
9
|
+
# into another ET_REL and only *retargets* relocations, this stage assigns
|
|
10
|
+
# load-time virtual addresses, *applies* the relocations by patching bytes,
|
|
11
|
+
# and synthesizes the dynamic-linking metadata a runtime loader (glibc's
|
|
12
|
+
# dlopen, in particular) reads to bind and run the object.
|
|
13
|
+
#
|
|
14
|
+
# It handles both a self-contained object — one that calls neither libc nor
|
|
15
|
+
# any other shared library — and one that imports functions and data from
|
|
16
|
+
# other shared libraries. Every relocation against an internal definition is
|
|
17
|
+
# resolved within this object; a relocation against an *undefined* (imported)
|
|
18
|
+
# symbol is bound through the standard dynamic mechanisms: an external call
|
|
19
|
+
# goes through a .plt stub whose .got.plt slot the loader fills from a
|
|
20
|
+
# JUMP_SLOT relocation, and an external data reference goes through a GOT slot
|
|
21
|
+
# the loader fills from a GLOB_DAT relocation. Each dependency `.so` that
|
|
22
|
+
# actually supplies at least one resolved symbol is recorded as a DT_NEEDED
|
|
23
|
+
# (an --as-needed-style trim); a still-undefined reference is left undefined,
|
|
24
|
+
# since a shared object may legitimately be completed by the runtime scope.
|
|
25
|
+
#
|
|
26
|
+
# Binding is eager (BIND_NOW): DF_BIND_NOW / DF_1_NOW ask the loader to
|
|
27
|
+
# resolve every JUMP_SLOT and GLOB_DAT at load time, so the .plt stub is just
|
|
28
|
+
# an indirect `jmp *slot(%rip)` and the lazy-resolution trampoline (the
|
|
29
|
+
# reserved .got.plt[1]/[2] and PLT[0]) is unneeded.
|
|
30
|
+
#
|
|
31
|
+
# Pipeline: the inputs are first merged into one ET_REL image by PartialLinker
|
|
32
|
+
# (reusing its section concatenation, symbol resolution and archive pull-in),
|
|
33
|
+
# then read back through ELFReader so this stage works from resolved
|
|
34
|
+
# Section/Symbol/Relocation values. From there it (1) selects the exported and
|
|
35
|
+
# imported dynamic symbols and resolves the imports against the dependency
|
|
36
|
+
# `.so`s; (2) lays the allocatable sections into three page-aligned PT_LOAD
|
|
37
|
+
# segments by permission — r-x (text + .plt), r-- (rodata + the read-only
|
|
38
|
+
# dynamic tables), rw- (data + .got/.got.plt + .dynamic) — choosing
|
|
39
|
+
# `p_vaddr == p_offset` for every placed section so the `p_vaddr ≡ p_offset
|
|
40
|
+
# (mod page)` load constraint holds trivially; (3) builds the dynamic tables
|
|
41
|
+
# (.dynsym/.dynstr, a SysV .hash, .rela.dyn, .rela.plt and .dynamic); and (4)
|
|
42
|
+
# applies each relocation against the assigned addresses, filling internal GOT
|
|
43
|
+
# slots (rebased with R_X86_64_RELATIVE), external GOT slots (GLOB_DAT) and
|
|
44
|
+
# the .got.plt (JUMP_SLOT).
|
|
45
|
+
#
|
|
46
|
+
# Output is deterministic (N4): sections keep the merged object's order, the
|
|
47
|
+
# dynamic symbol table follows the merged symbol order (exports then imports),
|
|
48
|
+
# the hash bucket count is derived from the export count, the relocation tables
|
|
49
|
+
# are ordered by slot, and no timestamp or address randomness is embedded —
|
|
50
|
+
# identical inputs yield byte-identical `.so` output.
|
|
51
|
+
class SharedLinker
|
|
52
|
+
include ObjFile
|
|
53
|
+
|
|
54
|
+
PAGE = 0x1000
|
|
55
|
+
|
|
56
|
+
# ELF header encodings.
|
|
57
|
+
ELFCLASS64 = 2
|
|
58
|
+
ELFDATA2LSB = 1
|
|
59
|
+
EV_CURRENT = 1
|
|
60
|
+
ET_DYN = 3
|
|
61
|
+
EM_X86_64 = 62
|
|
62
|
+
EM_AARCH64 = 183
|
|
63
|
+
EHDR_SIZE = 64
|
|
64
|
+
PHDR_SIZE = 56
|
|
65
|
+
SHDR_SIZE = 64
|
|
66
|
+
|
|
67
|
+
# Section header types and the section flag bits that classify a section
|
|
68
|
+
# into its load segment.
|
|
69
|
+
SHT_NULL = 0
|
|
70
|
+
SHT_PROGBITS = 1
|
|
71
|
+
SHT_SYMTAB = 2
|
|
72
|
+
SHT_STRTAB = 3
|
|
73
|
+
SHT_RELA = 4
|
|
74
|
+
SHT_HASH = 5
|
|
75
|
+
SHT_DYNAMIC = 6
|
|
76
|
+
SHT_NOBITS = 8
|
|
77
|
+
SHT_DYNSYM = 11
|
|
78
|
+
# The initializer/finalizer pointer arrays. Each is an array of function
|
|
79
|
+
# pointers (entsize 8, the target pointer width) the runtime loader calls
|
|
80
|
+
# after mapping the object (init, in order) and before unmapping it (fini,
|
|
81
|
+
# in reverse order); they are ordinary SHF_ALLOC|SHF_WRITE data reached
|
|
82
|
+
# through DT_INIT_ARRAY / DT_FINI_ARRAY rather than through a symbol.
|
|
83
|
+
SHT_INIT_ARRAY = 14
|
|
84
|
+
SHT_FINI_ARRAY = 15
|
|
85
|
+
|
|
86
|
+
SHF_WRITE = 0x1
|
|
87
|
+
SHF_ALLOC = 0x2
|
|
88
|
+
SHF_EXECINSTR = 0x4
|
|
89
|
+
|
|
90
|
+
# Program header types and permission flags.
|
|
91
|
+
PT_LOAD = 1
|
|
92
|
+
PT_DYNAMIC = 2
|
|
93
|
+
PT_GNU_STACK = 0x6474E551
|
|
94
|
+
PF_X = 0x1
|
|
95
|
+
PF_W = 0x2
|
|
96
|
+
PF_R = 0x4
|
|
97
|
+
|
|
98
|
+
# Reserved section index for an undefined symbol reference.
|
|
99
|
+
SHN_UNDEF = 0
|
|
100
|
+
SHN_LORESERVE = 0xFF00
|
|
101
|
+
SHN_ABS = 0xFFF1
|
|
102
|
+
SHN_COMMON = 0xFFF2
|
|
103
|
+
|
|
104
|
+
# Symbol binding/type/visibility encodings for the .dynsym entries.
|
|
105
|
+
STB = { local: 0, global: 1, weak: 2 }.freeze
|
|
106
|
+
STT = { notype: 0, object: 1, func: 2, section: 3, file: 4, tls: 6, ifunc: 10 }.freeze
|
|
107
|
+
STV = { default: 0, internal: 1, hidden: 2, protected: 3 }.freeze
|
|
108
|
+
|
|
109
|
+
# x86_64 relocation types this stage applies. The GOT-relative family
|
|
110
|
+
# (9/41/42) all address a symbol's GOT slot PC-relatively and are handled
|
|
111
|
+
# alike. Of the *dynamic* relocations emitted, R_X86_64_RELATIVE (8) rebases
|
|
112
|
+
# an absolute address (an internal GOT slot or an R_X86_64_64 initializer),
|
|
113
|
+
# R_X86_64_GLOB_DAT (6) fills an external data GOT slot, and
|
|
114
|
+
# R_X86_64_JUMP_SLOT (7) fills a .got.plt slot for an external function.
|
|
115
|
+
R_X86_64_64 = 1
|
|
116
|
+
R_X86_64_PC32 = 2
|
|
117
|
+
R_X86_64_PLT32 = 4
|
|
118
|
+
R_X86_64_GOTPCREL = 9
|
|
119
|
+
R_X86_64_32 = 10
|
|
120
|
+
R_X86_64_32S = 11
|
|
121
|
+
R_X86_64_GOTPCRELX = 41
|
|
122
|
+
R_X86_64_REX_GOTPCRELX = 42
|
|
123
|
+
R_X86_64_RELATIVE = 8
|
|
124
|
+
R_X86_64_GLOB_DAT = 6
|
|
125
|
+
R_X86_64_JUMP_SLOT = 7
|
|
126
|
+
GOT_RELOC_TYPES = [R_X86_64_GOTPCREL, R_X86_64_GOTPCRELX, R_X86_64_REX_GOTPCRELX].freeze
|
|
127
|
+
|
|
128
|
+
# aarch64 relocation types this stage applies. AArch64 forms an address in
|
|
129
|
+
# two instructions, so an address reference arrives as a *pair* of static
|
|
130
|
+
# relocations the reader hands over separately: ADR_PREL_PG_HI21 patches an
|
|
131
|
+
# `adrp`'s 21-bit page immediate and ADD_ABS_LO12_NC the following `add`'s
|
|
132
|
+
# 12-bit within-page offset; ADR_GOT_PAGE / LD64_GOT_LO12_NC are the same
|
|
133
|
+
# split addressing a symbol's GOT slot (the second an `ldr`'s scaled
|
|
134
|
+
# immediate). CALL26 is a `bl`'s 26-bit branch word-displacement. ABS64 is
|
|
135
|
+
# the absolute 64-bit pointer slot, aarch64's spelling of R_X86_64_64. Of
|
|
136
|
+
# the *dynamic* relocations, R_AARCH64_RELATIVE rebases an absolute address
|
|
137
|
+
# (a PIC image; a non-PIE executable writes it directly), GLOB_DAT fills an
|
|
138
|
+
# external data GOT slot, and JUMP_SLOT fills a .got.plt slot. The numbers
|
|
139
|
+
# are the ELF-for-the-Arm-64-bit-Architecture values, confirmed against real
|
|
140
|
+
# aarch64-linux-gnu tool output.
|
|
141
|
+
R_AARCH64_ABS64 = 257
|
|
142
|
+
R_AARCH64_ADR_PREL_PG_HI21 = 275
|
|
143
|
+
R_AARCH64_ADD_ABS_LO12_NC = 277
|
|
144
|
+
R_AARCH64_CALL26 = 283
|
|
145
|
+
R_AARCH64_ADR_GOT_PAGE = 311
|
|
146
|
+
R_AARCH64_LD64_GOT_LO12_NC = 312
|
|
147
|
+
R_AARCH64_GLOB_DAT = 1025
|
|
148
|
+
R_AARCH64_JUMP_SLOT = 1026
|
|
149
|
+
R_AARCH64_RELATIVE = 1027
|
|
150
|
+
AARCH64_GOT_RELOC_TYPES = [R_AARCH64_ADR_GOT_PAGE, R_AARCH64_LD64_GOT_LO12_NC].freeze
|
|
151
|
+
AARCH64_SUPPORTED_RELOC_TYPES = [
|
|
152
|
+
R_AARCH64_ABS64, R_AARCH64_ADR_PREL_PG_HI21, R_AARCH64_ADD_ABS_LO12_NC,
|
|
153
|
+
R_AARCH64_CALL26, *AARCH64_GOT_RELOC_TYPES
|
|
154
|
+
].freeze
|
|
155
|
+
|
|
156
|
+
# The maximum page size used to align load segments and stamp p_align: 4 KiB
|
|
157
|
+
# on x86_64, 64 KiB on aarch64 (matching the target toolchain default, so
|
|
158
|
+
# the image loads on a 64 KiB-page kernel as well as a 4 KiB one). ADRP page
|
|
159
|
+
# arithmetic is always 4 KiB regardless.
|
|
160
|
+
AARCH64_MAX_PAGE = 0x10000
|
|
161
|
+
|
|
162
|
+
# Dynamic array tags emitted into .dynamic.
|
|
163
|
+
DT_NULL = 0
|
|
164
|
+
DT_NEEDED = 1
|
|
165
|
+
DT_PLTRELSZ = 2
|
|
166
|
+
DT_PLTGOT = 3
|
|
167
|
+
DT_HASH = 4
|
|
168
|
+
DT_STRTAB = 5
|
|
169
|
+
DT_SYMTAB = 6
|
|
170
|
+
DT_RELA = 7
|
|
171
|
+
DT_RELASZ = 8
|
|
172
|
+
DT_RELAENT = 9
|
|
173
|
+
DT_STRSZ = 10
|
|
174
|
+
DT_SYMENT = 11
|
|
175
|
+
DT_SONAME = 14
|
|
176
|
+
DT_PLTREL = 20
|
|
177
|
+
DT_JMPREL = 23
|
|
178
|
+
# The initializer/finalizer array pointers and their sizes *in bytes*
|
|
179
|
+
# (not element counts), the loader's entry points into .init_array /
|
|
180
|
+
# .fini_array.
|
|
181
|
+
DT_INIT_ARRAY = 25
|
|
182
|
+
DT_FINI_ARRAY = 26
|
|
183
|
+
DT_INIT_ARRAYSZ = 27
|
|
184
|
+
DT_FINI_ARRAYSZ = 28
|
|
185
|
+
DT_FLAGS = 30
|
|
186
|
+
DT_RELACOUNT = 0x6FFFFFF9
|
|
187
|
+
DT_FLAGS_1 = 0x6FFFFFFB
|
|
188
|
+
|
|
189
|
+
# Dynamic flags requesting eager binding.
|
|
190
|
+
DF_BIND_NOW = 0x8
|
|
191
|
+
DF_1_NOW = 0x1
|
|
192
|
+
|
|
193
|
+
SYM_ENTSIZE = 24
|
|
194
|
+
RELA_ENTSIZE = 24
|
|
195
|
+
DYN_ENTSIZE = 16
|
|
196
|
+
|
|
197
|
+
# Each .plt stub is a 16-byte-aligned entry; a .got.plt reserves three
|
|
198
|
+
# leading slots (spec convention: [0] = &_DYNAMIC, [1]/[2] the lazy-resolver
|
|
199
|
+
# hooks left zero under BIND_NOW) before the per-function slots.
|
|
200
|
+
PLT_ENTSIZE = 16
|
|
201
|
+
GOTPLT_RESERVED = 3
|
|
202
|
+
|
|
203
|
+
# The C runtime's per-object identity word (see #build_dso_handle_object)
|
|
204
|
+
# and the archive member it is delivered in.
|
|
205
|
+
DSO_HANDLE_SYMBOL = "__dso_handle"
|
|
206
|
+
DSO_HANDLE_MEMBER = "__dso_handle.o"
|
|
207
|
+
|
|
208
|
+
# The other half of that member (see #add_dso_finalizer): the C runtime
|
|
209
|
+
# entry point that drops the handlers registered under a handle, the local
|
|
210
|
+
# symbol naming the synthesized routine that calls it, and the
|
|
211
|
+
# priority-numbered finalizer-array section the routine is reached through.
|
|
212
|
+
# Priority 0 is inside the range the ABI reserves for the implementation,
|
|
213
|
+
# which is exactly what this is, and it puts the slot at the *front* of the
|
|
214
|
+
# array — the runtime walks .fini_array backwards, so front means last.
|
|
215
|
+
# (A translation unit that spends the reserved priority itself, with
|
|
216
|
+
# `__attribute__((destructor(0)))`, lands in the same section and, being
|
|
217
|
+
# ahead of the supplier in link order, ends up running *after* this slot
|
|
218
|
+
# rather than before it. gcc warns about that priority range for exactly
|
|
219
|
+
# this kind of reason; rubycc has no warning channel, so the ordering there
|
|
220
|
+
# is simply implementation-defined.)
|
|
221
|
+
CXA_FINALIZE_SYMBOL = "__cxa_finalize"
|
|
222
|
+
DSO_FINALIZER_SYMBOL = "__rubycc_dso_finalize"
|
|
223
|
+
DSO_FINI_ARRAY_SECTION = ".fini_array.00000"
|
|
224
|
+
|
|
225
|
+
# The synthesized finalizer for x86_64, assembled from the System V AMD64
|
|
226
|
+
# instruction encodings (not copied from any crt implementation — R11) to
|
|
227
|
+
# the shape measured by disassembling a `gcc -shared -fPIC` output: test
|
|
228
|
+
# __cxa_finalize's GOT slot for NULL, and only then load the *value* of
|
|
229
|
+
# __dso_handle into the first argument register and call through the .plt.
|
|
230
|
+
#
|
|
231
|
+
# Both details are the measurement, not a preference. __cxa_finalize is
|
|
232
|
+
# referenced as a WEAK undefined symbol, so a C library that does not
|
|
233
|
+
# supply it leaves the slot zero and the call must not happen; the test
|
|
234
|
+
# reads the GOT slot itself rather than the .plt stub, because the stub
|
|
235
|
+
# would be an address whether or not the symbol resolved. And the argument
|
|
236
|
+
# is `mov` (the word's contents), not `lea` (its address): the two agree
|
|
237
|
+
# for the word this linker synthesizes, but `mov` is what the C
|
|
238
|
+
# `__cxa_finalize(__dso_handle)` — passing a `void *` variable — means, and
|
|
239
|
+
# an input that defines its own __dso_handle would tell them apart.
|
|
240
|
+
#
|
|
241
|
+
# Three operands the linker fills in: the GOT slot's PC-relative offset
|
|
242
|
+
# (R_X86_64_GOTPCREL, addend -5 because a one-byte immediate follows the
|
|
243
|
+
# displacement), the PC-relative reference to the internal __dso_handle
|
|
244
|
+
# (R_X86_64_PC32, addend -4) and the call to the import (R_X86_64_PLT32,
|
|
245
|
+
# addend -4). rsp is 8 mod 16 on entry, so one push realigns it for the
|
|
246
|
+
# call; rbp is merely the register pushed, not a frame.
|
|
247
|
+
#
|
|
248
|
+
# What gcc has and this does not is the `completed` guard that makes the
|
|
249
|
+
# routine run at most once. gcc needs it because its routine is reachable
|
|
250
|
+
# from both .fini_array and the legacy _fini/DT_FINI path; this linker
|
|
251
|
+
# emits neither _fini nor DT_FINI, so .fini_array is the only way in and
|
|
252
|
+
# the guard would never fire.
|
|
253
|
+
DSO_FINALIZE_CODE = [
|
|
254
|
+
0x55, # push %rbp ; realign rsp to 16 for the call
|
|
255
|
+
0x48, 0x83, 0x3D, 0, 0, 0, 0, 0x00, # cmpq $0, __cxa_finalize@GOTPCREL(%rip)
|
|
256
|
+
0x74, 0x0C, # je +12 ; unresolved: nothing to finalize
|
|
257
|
+
0x48, 0x8B, 0x3D, 0, 0, 0, 0, # mov __dso_handle(%rip), %rdi
|
|
258
|
+
0xE8, 0, 0, 0, 0, # call __cxa_finalize ; through the .plt
|
|
259
|
+
0x5D, # pop %rbp
|
|
260
|
+
0xC3 # ret
|
|
261
|
+
].pack("C*").freeze
|
|
262
|
+
|
|
263
|
+
# Byte offsets of the three operands patched in the x86_64 finalizer: the
|
|
264
|
+
# cmp's disp32, the mov's disp32 and the call's rel32.
|
|
265
|
+
DSO_FINALIZE_GOT_OFFSET = 4
|
|
266
|
+
DSO_FINALIZE_HANDLE_OFFSET = 14
|
|
267
|
+
DSO_FINALIZE_CALL_OFFSET = 19
|
|
268
|
+
|
|
269
|
+
# The aarch64 finalizer: the same measured shape in AArch64 encodings (ARM
|
|
270
|
+
# DDI 0487). The GOT slot is loaded into a register and tested with `cbz`
|
|
271
|
+
# (aarch64 has no memory-operand compare), then the handle's address is
|
|
272
|
+
# formed by the usual adrp/add pair and its *contents* loaded into x0
|
|
273
|
+
# before the .plt call. x29/x30 are saved because the routine calls; the
|
|
274
|
+
# stp/ldp pair also keeps sp 16-byte aligned.
|
|
275
|
+
AARCH64_DSO_FINALIZE_CODE = [
|
|
276
|
+
0xA9BF7BFD, # stp x29, x30, [sp, #-16]!
|
|
277
|
+
0x90000000, # adrp x0, :got:__cxa_finalize (ADR_GOT_PAGE)
|
|
278
|
+
0xF9400000, # ldr x0, [x0, #:got_lo12:...] (LD64_GOT_LO12_NC)
|
|
279
|
+
0xB40000A0, # cbz x0, +0x14 ; unresolved: nothing to finalize
|
|
280
|
+
0x90000001, # adrp x1, __dso_handle (ADR_PREL_PG_HI21)
|
|
281
|
+
0x91000021, # add x1, x1, #:lo12:__dso_handle (ADD_ABS_LO12_NC)
|
|
282
|
+
0xF9400020, # ldr x0, [x1] ; the handle's value, not its address
|
|
283
|
+
0x94000000, # bl __cxa_finalize (CALL26, through the .plt)
|
|
284
|
+
0xA8C17BFD, # ldp x29, x30, [sp], #16
|
|
285
|
+
0xD65F03C0 # ret
|
|
286
|
+
].pack("L<*").freeze
|
|
287
|
+
|
|
288
|
+
# Byte offsets of the five operands patched in the aarch64 finalizer: the
|
|
289
|
+
# GOT adrp/ldr pair, the __dso_handle adrp/add pair, and the bl.
|
|
290
|
+
AARCH64_DSO_FINALIZE_GOT_ADRP_OFFSET = 4
|
|
291
|
+
AARCH64_DSO_FINALIZE_GOT_LO12_OFFSET = 8
|
|
292
|
+
AARCH64_DSO_FINALIZE_HANDLE_ADRP_OFFSET = 16
|
|
293
|
+
AARCH64_DSO_FINALIZE_HANDLE_ADD_OFFSET = 20
|
|
294
|
+
AARCH64_DSO_FINALIZE_CALL_OFFSET = 28
|
|
295
|
+
|
|
296
|
+
class << self
|
|
297
|
+
# Links `inputs` (an ordered array; each element a filesystem path, or the
|
|
298
|
+
# raw bytes of an ET_REL object or an ar archive — the same shapes
|
|
299
|
+
# PartialLinker accepts) into a shared object, returned as an ASCII-8BIT
|
|
300
|
+
# String. `needed` lists the dependency shared libraries to resolve
|
|
301
|
+
# imports against (each a `.so` filesystem path or an already-parsed
|
|
302
|
+
# ELFReader); `soname` sets this object's DT_SONAME.
|
|
303
|
+
def link(inputs, needed: [], soname: nil)
|
|
304
|
+
new(inputs, needed: needed, soname: soname).link
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Convenience: link and write the shared object to `path`.
|
|
308
|
+
def link_to(inputs, path, needed: [], soname: nil)
|
|
309
|
+
File.binwrite(path, link(inputs, needed: needed, soname: soname))
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# The one-member archive supplying __dso_handle for `machine` (an ELF
|
|
313
|
+
# e_machine value), memoized per machine: the bytes are deterministic (N4)
|
|
314
|
+
# and every link appends the same archive.
|
|
315
|
+
def dso_handle_archive(machine)
|
|
316
|
+
@dso_handle_archives ||= {}
|
|
317
|
+
@dso_handle_archives[machine] ||= build_dso_handle_archive(machine)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
private
|
|
321
|
+
|
|
322
|
+
def build_dso_handle_archive(machine)
|
|
323
|
+
writer = ObjFile::ArWriter.new
|
|
324
|
+
writer.add_member(DSO_HANDLE_MEMBER, build_dso_handle_object(machine))
|
|
325
|
+
writer.to_binary
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# The __dso_handle supplier as a small ET_REL object: an 8-byte .data
|
|
329
|
+
# word, a hidden global OBJECT symbol naming it, and an absolute-64
|
|
330
|
+
# relocation of the word against that same symbol — so the word ends up
|
|
331
|
+
# holding its own address — plus the finalizer that hands the word back
|
|
332
|
+
# to the C runtime when the object is unloaded (#add_dso_finalizer).
|
|
333
|
+
#
|
|
334
|
+
# Why this shape, measured from a `gcc -shared -fPIC` output rather than
|
|
335
|
+
# copied from any crt implementation (R11): __dso_handle is the opaque
|
|
336
|
+
# per-DSO cookie passed to __cxa_atexit / __register_atfork to say which
|
|
337
|
+
# object registered a handler, so it must be an address unique to this
|
|
338
|
+
# object (zero would claim to be the main program and would make
|
|
339
|
+
# __cxa_finalize at dlclose skip — or wrongly run — the handlers). gcc's
|
|
340
|
+
# `.so` shows exactly that: an 8-byte .data word at the section start
|
|
341
|
+
# holding its own link-time address, carrying one R_X86_64_RELATIVE whose
|
|
342
|
+
# offset and addend are both that address, and reachable only internally
|
|
343
|
+
# — the symbol never enters .dynsym, so no other object can interpose it.
|
|
344
|
+
# Hidden visibility is what produces that: it is the binding the
|
|
345
|
+
# referencing member itself uses (glibc's libc_nonshared.a members name
|
|
346
|
+
# __dso_handle as a GLOBAL HIDDEN undefined), it keeps the definition out
|
|
347
|
+
# of .dynsym (#plan_dynamic_symbols exports only default/protected
|
|
348
|
+
# visibility), and unlike a local symbol it can still resolve another
|
|
349
|
+
# object's undefined reference during the merge.
|
|
350
|
+
#
|
|
351
|
+
# The self-reference is expressed as an ordinary absolute-64 relocation,
|
|
352
|
+
# so the existing apply engine produces the rest for free: an internal
|
|
353
|
+
# R_X86_64_64 / R_AARCH64_ABS64 is patched with the final address and — in
|
|
354
|
+
# a shared object, where #rebase_internal? holds — rebased at load time by
|
|
355
|
+
# one RELATIVE entry, which is precisely the measured gcc output.
|
|
356
|
+
#
|
|
357
|
+
# The word and its finalizer travel together in one member because they
|
|
358
|
+
# answer one question between them — "which object is this, and when is
|
|
359
|
+
# it gone" — and because the condition for needing them is the same: a
|
|
360
|
+
# link that never mentions __dso_handle never extracts the member and so
|
|
361
|
+
# gains neither. That is the same pairing gcc's crt file has, reached
|
|
362
|
+
# without a second lazily-extracted member or any linker-side special
|
|
363
|
+
# case.
|
|
364
|
+
def build_dso_handle_object(machine)
|
|
365
|
+
aarch64 = machine == EM_AARCH64
|
|
366
|
+
writer = ObjFile::RelocatableWriter.new(machine: machine)
|
|
367
|
+
data = writer.add_section(name: ".data", type: SHT_PROGBITS,
|
|
368
|
+
flags: SHF_ALLOC | SHF_WRITE, addralign: 8,
|
|
369
|
+
data: "\0".b * 8)
|
|
370
|
+
handle = writer.add_symbol(name: DSO_HANDLE_SYMBOL, bind: :global, type: :object,
|
|
371
|
+
visibility: :hidden, section: data, value: 0, size: 8)
|
|
372
|
+
writer.add_relocation(target: data, offset: 0, symbol: handle,
|
|
373
|
+
type: aarch64 ? R_AARCH64_ABS64 : R_X86_64_64, addend: 0)
|
|
374
|
+
add_dso_finalizer(writer, handle, aarch64)
|
|
375
|
+
writer.to_binary
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Adds the finalizer half to the supplier object: the routine itself in
|
|
379
|
+
# .text (see DSO_FINALIZE_CODE for its shape and why it is that shape),
|
|
380
|
+
# the operand relocations, and the one-slot .fini_array.00000 section
|
|
381
|
+
# pointing at it.
|
|
382
|
+
#
|
|
383
|
+
# This is what makes glibc drop the handlers an object registered under
|
|
384
|
+
# its handle — __cxa_atexit / __register_atfork record the handle as the
|
|
385
|
+
# owner, and __cxa_finalize(handle) is the call that runs and unregisters
|
|
386
|
+
# them. Without it a dlclosed object leaves its handlers pointing into
|
|
387
|
+
# the unmapped image (measured before this existed: calling
|
|
388
|
+
# __cxa_finalize by hand cleared them, so only the automatic call was
|
|
389
|
+
# missing).
|
|
390
|
+
#
|
|
391
|
+
# The routine is a *local* symbol: it is reached only from this object's
|
|
392
|
+
# own array slot, so it never needs to be visible to another object or to
|
|
393
|
+
# the loader, and a local definition cannot collide with an input's.
|
|
394
|
+
# __cxa_finalize is referenced WEAK so that a C library without it leaves
|
|
395
|
+
# the reference unresolved instead of failing the link — the NULL test in
|
|
396
|
+
# the code is the other half of that decision.
|
|
397
|
+
def add_dso_finalizer(writer, handle, aarch64)
|
|
398
|
+
code = aarch64 ? AARCH64_DSO_FINALIZE_CODE : DSO_FINALIZE_CODE
|
|
399
|
+
text = writer.add_section(name: ".text", type: SHT_PROGBITS,
|
|
400
|
+
flags: SHF_ALLOC | SHF_EXECINSTR, addralign: 16,
|
|
401
|
+
data: code)
|
|
402
|
+
routine = writer.add_symbol(name: DSO_FINALIZER_SYMBOL, bind: :local, type: :func,
|
|
403
|
+
section: text, value: 0, size: code.bytesize)
|
|
404
|
+
# The argument is the object's own definition of __dso_handle (the
|
|
405
|
+
# `handle` symbol the caller passes in), so that reference is internal
|
|
406
|
+
# and resolves without a text relocation even in a shared object; only
|
|
407
|
+
# the callee is an import.
|
|
408
|
+
cxa = writer.add_symbol(name: CXA_FINALIZE_SYMBOL, bind: :weak, type: :func)
|
|
409
|
+
if aarch64
|
|
410
|
+
add_dso_finalizer_relocations_aarch64(writer, text, handle, cxa)
|
|
411
|
+
else
|
|
412
|
+
add_dso_finalizer_relocations_x86_64(writer, text, handle, cxa)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
slot = writer.add_section(name: DSO_FINI_ARRAY_SECTION, type: SHT_FINI_ARRAY,
|
|
416
|
+
flags: SHF_ALLOC | SHF_WRITE, addralign: 8, entsize: 8,
|
|
417
|
+
data: "\0".b * 8)
|
|
418
|
+
writer.add_relocation(target: slot, offset: 0, symbol: routine,
|
|
419
|
+
type: aarch64 ? R_AARCH64_ABS64 : R_X86_64_64, addend: 0)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def add_dso_finalizer_relocations_x86_64(writer, text, handle, cxa)
|
|
423
|
+
writer.add_relocation(target: text, offset: DSO_FINALIZE_GOT_OFFSET, symbol: cxa,
|
|
424
|
+
type: R_X86_64_GOTPCREL, addend: -5)
|
|
425
|
+
writer.add_relocation(target: text, offset: DSO_FINALIZE_HANDLE_OFFSET, symbol: handle,
|
|
426
|
+
type: R_X86_64_PC32, addend: -4)
|
|
427
|
+
writer.add_relocation(target: text, offset: DSO_FINALIZE_CALL_OFFSET, symbol: cxa,
|
|
428
|
+
type: R_X86_64_PLT32, addend: -4)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def add_dso_finalizer_relocations_aarch64(writer, text, handle, cxa)
|
|
432
|
+
[[AARCH64_DSO_FINALIZE_GOT_ADRP_OFFSET, cxa, R_AARCH64_ADR_GOT_PAGE],
|
|
433
|
+
[AARCH64_DSO_FINALIZE_GOT_LO12_OFFSET, cxa, R_AARCH64_LD64_GOT_LO12_NC],
|
|
434
|
+
[AARCH64_DSO_FINALIZE_HANDLE_ADRP_OFFSET, handle, R_AARCH64_ADR_PREL_PG_HI21],
|
|
435
|
+
[AARCH64_DSO_FINALIZE_HANDLE_ADD_OFFSET, handle, R_AARCH64_ADD_ABS_LO12_NC],
|
|
436
|
+
[AARCH64_DSO_FINALIZE_CALL_OFFSET, cxa, R_AARCH64_CALL26]].each do |offset, sym, type|
|
|
437
|
+
writer.add_relocation(target: text, offset: offset, symbol: sym, type: type, addend: 0)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def initialize(inputs, needed: [], soname: nil)
|
|
443
|
+
@inputs = inputs
|
|
444
|
+
@needed = needed
|
|
445
|
+
@soname = soname
|
|
446
|
+
# The target machine is settled before the merge — the synthesized inputs
|
|
447
|
+
# (the __dso_handle supplier here, the crt in the executable subclass) and
|
|
448
|
+
# that subclass's interpreter/libc defaults all need it — so it is read
|
|
449
|
+
# off the first input object's header rather than from the (not-yet-built)
|
|
450
|
+
# merged reader. #link replaces it with the merged reader's machine, which
|
|
451
|
+
# agrees with it.
|
|
452
|
+
@em = detect_machine
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def link
|
|
456
|
+
@reader = ELFReader.read(PartialLinker.link(link_inputs))
|
|
457
|
+
@em = @reader.machine
|
|
458
|
+
check_machine!
|
|
459
|
+
after_merge
|
|
460
|
+
plan_dynamic_symbols
|
|
461
|
+
scan_relocations
|
|
462
|
+
resolve_imports
|
|
463
|
+
place_sections
|
|
464
|
+
apply_relocations
|
|
465
|
+
assemble
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
private
|
|
469
|
+
|
|
470
|
+
# --- subclass hooks ----------------------------------------------------
|
|
471
|
+
# The final-link core is shared with the executable writer (ExecutableLinker),
|
|
472
|
+
# which differs from a shared object only in a handful of decisions expressed
|
|
473
|
+
# through these hooks. A shared object links the given inputs and the
|
|
474
|
+
# __dso_handle supplier below (an executable prepends a synthesized crt on
|
|
475
|
+
# top of them), needs no post-merge validation,
|
|
476
|
+
# loads at virtual address 0 so p_vaddr == p_offset (an executable loads at a
|
|
477
|
+
# fixed non-PIE base), rebases every internal absolute address at load time
|
|
478
|
+
# through R_X86_64_RELATIVE (an executable, mapped at a fixed address, writes
|
|
479
|
+
# the final address directly and needs no base relocation), places no section
|
|
480
|
+
# ahead of .text (an executable places .interp), and is an entry-less ET_DYN
|
|
481
|
+
# (an executable is an ET_EXEC entered at _start). Each default keeps the
|
|
482
|
+
# shared-object behavior byte-for-byte.
|
|
483
|
+
|
|
484
|
+
# Every link ends with the __dso_handle supplier archive. It is an archive,
|
|
485
|
+
# not an object, so the merge's lazy member extraction is the condition:
|
|
486
|
+
# the member joins only when something ahead of it still leaves
|
|
487
|
+
# __dso_handle undefined (glibc's libc_nonshared.a members reference it,
|
|
488
|
+
# and libc supplies it from a crt file rubycc has no counterpart of), and
|
|
489
|
+
# never when the link already has a definition of its own — an input that
|
|
490
|
+
# defines __dso_handle keeps it, exactly as with the compiler-support
|
|
491
|
+
# runtime the driver appends the same way. A link that does not mention the
|
|
492
|
+
# symbol gets no .data word and no relocation, so existing output is
|
|
493
|
+
# unchanged byte for byte.
|
|
494
|
+
def link_inputs = @inputs + [SharedLinker.dso_handle_archive(@em)]
|
|
495
|
+
def after_merge; end
|
|
496
|
+
def load_base = 0
|
|
497
|
+
|
|
498
|
+
# --- target machine ----------------------------------------------------
|
|
499
|
+
# The final-link core is written for both x86_64 and aarch64: a shared
|
|
500
|
+
# object and the executable writer share it. Each target-dependent decision
|
|
501
|
+
# — the machine id, the segment page size, the relocation scan/apply, the
|
|
502
|
+
# PLT stub bytes and the dynamic relocation type numbers — branches on this
|
|
503
|
+
# flag; the whole section/symbol/table layer above it is machine-independent.
|
|
504
|
+
# An aarch64 `.so` binds through R_AARCH64_RELATIVE rebasing of its internal
|
|
505
|
+
# absolute addresses (internal GOT slots and ABS64 initializers) and through
|
|
506
|
+
# the same eager (BIND_NOW) per-function PLT the x86_64 `.so` uses, so no
|
|
507
|
+
# lazy-resolver PLT0 header is needed.
|
|
508
|
+
|
|
509
|
+
def aarch64? = @em == EM_AARCH64
|
|
510
|
+
def e_machine_id = aarch64? ? EM_AARCH64 : EM_X86_64
|
|
511
|
+
def seg_align = aarch64? ? AARCH64_MAX_PAGE : PAGE
|
|
512
|
+
|
|
513
|
+
# The set of machines this writer accepts: x86_64 and aarch64, for both the
|
|
514
|
+
# shared object and the executable subclass.
|
|
515
|
+
def supported_machines = [EM_X86_64, EM_AARCH64]
|
|
516
|
+
|
|
517
|
+
def check_machine!
|
|
518
|
+
return if supported_machines.include?(@em)
|
|
519
|
+
|
|
520
|
+
raise LinkError, "linking for machine #{@em} is not supported by this linker"
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# The ELF e_machine of the first relocatable input object, defaulting to
|
|
524
|
+
# x86_64 when no object header is readable (e.g. only archives, which a
|
|
525
|
+
# link always has a compiled object ahead of). Used to pick the synthesized
|
|
526
|
+
# inputs up front; the post-merge reader's machine agrees with it.
|
|
527
|
+
def detect_machine
|
|
528
|
+
@inputs.each do |raw|
|
|
529
|
+
header = input_elf_header(raw)
|
|
530
|
+
next unless header
|
|
531
|
+
|
|
532
|
+
return header.byteslice(18, 2).unpack1("S<")
|
|
533
|
+
end
|
|
534
|
+
EM_X86_64
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# The leading bytes of an input if it is an ELF object (not an ar archive),
|
|
538
|
+
# or nil. A String input may be raw object bytes or a filesystem path.
|
|
539
|
+
def input_elf_header(raw)
|
|
540
|
+
bytes =
|
|
541
|
+
if raw.is_a?(String) && raw.b.start_with?("\x7FELF".b)
|
|
542
|
+
raw.b
|
|
543
|
+
elsif raw.is_a?(String) && raw.b.start_with?("!<arch>\n".b)
|
|
544
|
+
nil
|
|
545
|
+
else
|
|
546
|
+
File.binread(raw.to_s, 20)
|
|
547
|
+
end
|
|
548
|
+
bytes if bytes && bytes.bytesize >= 20 && bytes.b.start_with?("\x7FELF".b)
|
|
549
|
+
rescue SystemCallError
|
|
550
|
+
nil
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# The dynamic relocation type numbers, chosen per target. x86_64 keeps its
|
|
554
|
+
# own constants so its output stays byte-for-byte identical.
|
|
555
|
+
def reloc_relative = aarch64? ? R_AARCH64_RELATIVE : R_X86_64_RELATIVE
|
|
556
|
+
def reloc_glob_dat = aarch64? ? R_AARCH64_GLOB_DAT : R_X86_64_GLOB_DAT
|
|
557
|
+
def reloc_jump_slot = aarch64? ? R_AARCH64_JUMP_SLOT : R_X86_64_JUMP_SLOT
|
|
558
|
+
def reloc_abs64 = aarch64? ? R_AARCH64_ABS64 : R_X86_64_64
|
|
559
|
+
def rebase_internal? = true
|
|
560
|
+
def leading_sections = []
|
|
561
|
+
def e_type = ET_DYN
|
|
562
|
+
def e_entry = 0
|
|
563
|
+
|
|
564
|
+
# A section placed into the image: its ELF section-header fields plus the
|
|
565
|
+
# assigned load address and file offset. `data` holds the file bytes (nil
|
|
566
|
+
# for a NOBITS section, which reserves memory only). `index` is its position
|
|
567
|
+
# in the emitted section header table, resolved during layout.
|
|
568
|
+
Placed = Struct.new(
|
|
569
|
+
:name, :type, :flags, :addralign, :entsize, :size, :data,
|
|
570
|
+
:vaddr, :offset, :link, :info, :index, keyword_init: true
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
# A dependency shared library the imports resolve against: its DT_NEEDED
|
|
574
|
+
# name (its own DT_SONAME, or the base filename when it carries none) and
|
|
575
|
+
# the set of symbol names it defines and exports.
|
|
576
|
+
Dependency = Struct.new(:name, :provides, keyword_init: true)
|
|
577
|
+
|
|
578
|
+
# --- dynamic symbols ---------------------------------------------------
|
|
579
|
+
|
|
580
|
+
# Selects the symbols to export: every defined global or weak with default
|
|
581
|
+
# or protected visibility, in the merged symbol table's order (deterministic).
|
|
582
|
+
# A hidden/internal symbol is deliberately not exported, matching the linker
|
|
583
|
+
# default that only externally visible definitions enter .dynsym.
|
|
584
|
+
def plan_dynamic_symbols
|
|
585
|
+
@exports = @reader.symbols.select do |sym|
|
|
586
|
+
(sym.bind == :global || sym.bind == :weak) &&
|
|
587
|
+
sym.defined? && !sym.name.to_s.empty? &&
|
|
588
|
+
(sym.visibility == :default || sym.visibility == :protected)
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# --- relocation scan (sizing pass) -------------------------------------
|
|
593
|
+
|
|
594
|
+
# Walks every relocation to size the tables before addresses are assigned:
|
|
595
|
+
# it collects the ordered set of imported (undefined) symbols, the symbols
|
|
596
|
+
# needing a data GOT slot (internal or external), the external functions
|
|
597
|
+
# needing a .plt stub, and the counts of absolute-64 initializers (internal
|
|
598
|
+
# rebased by RELATIVE, external bound by a symbolic R_X86_64_64).
|
|
599
|
+
def scan_relocations
|
|
600
|
+
@got_order = [] # symbols needing a data GOT slot, first-seen order
|
|
601
|
+
@got_index = {} # got key => slot index
|
|
602
|
+
@plt_order = [] # external functions needing a .plt stub, first-seen
|
|
603
|
+
@plt_index = {} # function name => stub index
|
|
604
|
+
@import_order = [] # imported (undefined) symbols, first-seen order
|
|
605
|
+
@import_index = {} # import name => position
|
|
606
|
+
@data64_count = 0 # internal R_X86_64_64 -> RELATIVE
|
|
607
|
+
@data64_dyn_count = 0 # external R_X86_64_64 -> symbolic
|
|
608
|
+
allocatable_relocation_sections.each do |rs|
|
|
609
|
+
rs.relocations.each { |reloc| scan_relocation(reloc) }
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def scan_relocation(reloc)
|
|
614
|
+
return scan_relocation_aarch64(reloc) if aarch64?
|
|
615
|
+
|
|
616
|
+
type = reloc.type
|
|
617
|
+
unless supported_relocation?(type)
|
|
618
|
+
raise LinkError, "unsupported relocation type #{reloc.type_name || type} in a shared object"
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
sym = reloc.symbol
|
|
622
|
+
external = external_import?(sym)
|
|
623
|
+
if external
|
|
624
|
+
if [R_X86_64_PC32, R_X86_64_32, R_X86_64_32S].include?(type)
|
|
625
|
+
raise LinkError, "unsupported text relocation against external symbol " \
|
|
626
|
+
"'#{sym.name}' in a shared object"
|
|
627
|
+
end
|
|
628
|
+
register_import(sym)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
if GOT_RELOC_TYPES.include?(type)
|
|
632
|
+
register_got(sym)
|
|
633
|
+
elsif type == R_X86_64_PLT32
|
|
634
|
+
register_plt(sym) if external
|
|
635
|
+
elsif type == R_X86_64_64
|
|
636
|
+
external ? (@data64_dyn_count += 1) : (@data64_count += 1)
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def supported_relocation?(type)
|
|
641
|
+
[R_X86_64_64, R_X86_64_PC32, R_X86_64_PLT32, R_X86_64_32, R_X86_64_32S,
|
|
642
|
+
*GOT_RELOC_TYPES].include?(type)
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# The aarch64 sizing pass, the counterpart of #scan_relocation: it registers
|
|
646
|
+
# the same import/GOT/PLT/absolute-64 sets from aarch64's relocation
|
|
647
|
+
# vocabulary. A CALL26 to an external symbol needs a .plt stub; the GOT pair
|
|
648
|
+
# needs a data GOT slot; an ABS64 is an absolute-64 initializer (internal
|
|
649
|
+
# rebased by RELATIVE in a PIC image, external bound symbolically). An
|
|
650
|
+
# adrp/add pair against an external symbol would be a text relocation a
|
|
651
|
+
# dynamic image cannot satisfy, diagnosed like x86_64's external PC32.
|
|
652
|
+
def scan_relocation_aarch64(reloc)
|
|
653
|
+
type = reloc.type
|
|
654
|
+
unless AARCH64_SUPPORTED_RELOC_TYPES.include?(type)
|
|
655
|
+
raise LinkError, "unsupported relocation type #{reloc.type_name || type} in an aarch64 link"
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
sym = reloc.symbol
|
|
659
|
+
external = external_import?(sym)
|
|
660
|
+
if external && [R_AARCH64_ADR_PREL_PG_HI21, R_AARCH64_ADD_ABS_LO12_NC].include?(type)
|
|
661
|
+
raise LinkError, "unsupported text relocation against external symbol " \
|
|
662
|
+
"'#{sym.name}' (build the reference through the GOT)"
|
|
663
|
+
end
|
|
664
|
+
register_import(sym) if external
|
|
665
|
+
|
|
666
|
+
if AARCH64_GOT_RELOC_TYPES.include?(type)
|
|
667
|
+
register_got(sym)
|
|
668
|
+
elsif type == R_AARCH64_CALL26
|
|
669
|
+
register_plt(sym) if external
|
|
670
|
+
elsif type == R_AARCH64_ABS64
|
|
671
|
+
external ? (@data64_dyn_count += 1) : (@data64_count += 1)
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# An imported reference: an undefined named symbol (a section symbol always
|
|
676
|
+
# names a section within this object and so is never an import).
|
|
677
|
+
def external_import?(sym)
|
|
678
|
+
sym && sym.type != :section && sym.undefined?
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def register_import(sym)
|
|
682
|
+
return if @import_index.key?(sym.name)
|
|
683
|
+
|
|
684
|
+
@import_index[sym.name] = @import_order.size
|
|
685
|
+
@import_order << sym
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
def register_got(sym)
|
|
689
|
+
key = got_key(sym)
|
|
690
|
+
return if @got_index.key?(key)
|
|
691
|
+
|
|
692
|
+
@got_index[key] = @got_order.size
|
|
693
|
+
@got_order << sym
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def register_plt(sym)
|
|
697
|
+
return if @plt_index.key?(sym.name)
|
|
698
|
+
|
|
699
|
+
@plt_index[sym.name] = @plt_order.size
|
|
700
|
+
@plt_order << sym
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# A stable key identifying the symbol a GOT slot stands for: a named symbol
|
|
704
|
+
# by its name, a section reference by its section name.
|
|
705
|
+
def got_key(sym)
|
|
706
|
+
name = sym.name.to_s
|
|
707
|
+
name.empty? ? "\0sec:#{sym.section&.name}" : "g:#{name}"
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# The relocation sections whose target lands in the loaded image; a
|
|
711
|
+
# relocation against a non-allocated section is not applied.
|
|
712
|
+
def allocatable_relocation_sections
|
|
713
|
+
@reader.relocation_sections.select { |rs| rs.target && allocatable?(rs.target) }
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def allocatable?(section)
|
|
717
|
+
(section.flags & SHF_ALLOC) != 0
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# --- import resolution -------------------------------------------------
|
|
721
|
+
|
|
722
|
+
# Resolves each imported symbol against the dependency `.so`s, in dependency
|
|
723
|
+
# order, recording (as-needed) only the dependencies that actually supply a
|
|
724
|
+
# resolved symbol as DT_NEEDED. A still-unresolved import is left undefined:
|
|
725
|
+
# a shared object may be completed by the runtime scope, so this is not an
|
|
726
|
+
# error.
|
|
727
|
+
def resolve_imports
|
|
728
|
+
@deps = build_dependencies
|
|
729
|
+
used = {}
|
|
730
|
+
@import_order.each do |sym|
|
|
731
|
+
dep = @deps.find { |d| d.provides.key?(sym.name) }
|
|
732
|
+
used[dep] = true if dep
|
|
733
|
+
end
|
|
734
|
+
@used_deps = @deps.select { |d| used[d] }
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
# Parses each dependency into a Dependency: its DT_NEEDED name and the set
|
|
738
|
+
# (a name => true hash) of the global/weak symbols it defines and exports.
|
|
739
|
+
def build_dependencies
|
|
740
|
+
@needed.map do |entry|
|
|
741
|
+
reader, name = load_dependency(entry)
|
|
742
|
+
provides = {}
|
|
743
|
+
reader.dynamic_symbols.each do |sym|
|
|
744
|
+
next unless sym.defined? && !sym.name.to_s.empty? &&
|
|
745
|
+
(sym.bind == :global || sym.bind == :weak)
|
|
746
|
+
|
|
747
|
+
provides[sym.name] = true
|
|
748
|
+
end
|
|
749
|
+
Dependency.new(name: name, provides: provides)
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
# A dependency given either as an already-parsed ELFReader or a `.so`
|
|
754
|
+
# filesystem path; its DT_NEEDED name is its DT_SONAME, falling back to the
|
|
755
|
+
# base filename for a path input (a reader without a SONAME has no name).
|
|
756
|
+
def load_dependency(entry)
|
|
757
|
+
if entry.is_a?(ELFReader)
|
|
758
|
+
soname = entry.soname or
|
|
759
|
+
raise LinkError, "dependency shared object carries no DT_SONAME; pass its path instead"
|
|
760
|
+
[entry, soname]
|
|
761
|
+
else
|
|
762
|
+
reader = ELFReader.read_file(entry)
|
|
763
|
+
[reader, reader.soname || File.basename(entry)]
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# --- address assignment ------------------------------------------------
|
|
768
|
+
|
|
769
|
+
# Lays the allocatable input sections and the synthesized dynamic sections
|
|
770
|
+
# into three page-aligned PT_LOAD segments by permission, assigning each a
|
|
771
|
+
# file offset and an equal virtual address. The r-x segment begins at file
|
|
772
|
+
# offset 0 so the ELF header and program headers it contains are mapped;
|
|
773
|
+
# the r-- and rw- segments each start on a fresh page. .bss (NOBITS) sits
|
|
774
|
+
# last in the rw- segment, extending its memory size past its file size.
|
|
775
|
+
def place_sections
|
|
776
|
+
rx = leading_sections + input_sections { |s| executable?(s) } + plt_sections
|
|
777
|
+
ro = dynamic_ro_sections + input_sections { |s| !executable?(s) && !writable?(s) }
|
|
778
|
+
rw_input = input_sections { |s| writable?(s) && s.type != SHT_NOBITS }
|
|
779
|
+
plain, arrays = split_array_sections(rw_input)
|
|
780
|
+
rw_files = plain + arrays + writable_dynamic_sections
|
|
781
|
+
bss = input_sections { |s| writable?(s) && s.type == SHT_NOBITS }
|
|
782
|
+
|
|
783
|
+
cursor = EHDR_SIZE + phnum * PHDR_SIZE
|
|
784
|
+
cursor, @rx = lay(rx, cursor)
|
|
785
|
+
cursor = align(cursor, seg_align)
|
|
786
|
+
cursor, @ro = lay(ro, cursor)
|
|
787
|
+
cursor = align(cursor, seg_align)
|
|
788
|
+
rw_start = cursor
|
|
789
|
+
cursor, rw_placed = lay(rw_files, cursor)
|
|
790
|
+
check_array_runs_contiguous
|
|
791
|
+
@file_end = cursor
|
|
792
|
+
cursor, bss_placed = lay(bss, cursor)
|
|
793
|
+
@rw = rw_placed + bss_placed
|
|
794
|
+
@rw_start = rw_start
|
|
795
|
+
@mem_end = cursor
|
|
796
|
+
|
|
797
|
+
index_sections
|
|
798
|
+
build_dynamic_contents
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def executable?(section) = (section.flags & SHF_EXECINSTR) != 0
|
|
802
|
+
def writable?(section) = (section.flags & SHF_WRITE) != 0
|
|
803
|
+
|
|
804
|
+
# The allocatable input sections matching a predicate, in merged order,
|
|
805
|
+
# each turned into a Placed carrying a mutable copy of its bytes so the
|
|
806
|
+
# relocation pass can patch them.
|
|
807
|
+
def input_sections
|
|
808
|
+
@reader.sections.select { |s| allocatable?(s) && yield(s) }.map do |s|
|
|
809
|
+
Placed.new(name: s.name, type: s.type, flags: s.flags, addralign: [s.addralign, 1].max,
|
|
810
|
+
entsize: s.entsize, size: s.size, data: s.type == SHT_NOBITS ? nil : s.data.b)
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
# --- initializer / finalizer arrays ------------------------------------
|
|
815
|
+
# .init_array / .fini_array are ordinary writable allocatable data, so the
|
|
816
|
+
# merge concatenates them by name and the apply engine patches their
|
|
817
|
+
# pointer slots like any other absolute-64 initializer (RELATIVE-rebased
|
|
818
|
+
# in a shared object, written final in a non-PIE executable). Only two
|
|
819
|
+
# things are specific to them: the loader reaches them through DT_*_ARRAY
|
|
820
|
+
# rather than a symbol, so each must be one contiguous run, and the run's
|
|
821
|
+
# *order* is the order the initializers run in.
|
|
822
|
+
#
|
|
823
|
+
# gcc spells a priority (`__attribute__((constructor(101)))`) as a separate
|
|
824
|
+
# `.init_array.00101` input section. Observed by linking such objects with
|
|
825
|
+
# gcc and reading back the resulting array (readelf -x) and the runtime
|
|
826
|
+
# order: the priority-numbered sections come first, sorted by ascending
|
|
827
|
+
# priority number across all inputs, and the unnumbered `.init_array`
|
|
828
|
+
# sections follow in input order. .fini_array is laid out by the same rule
|
|
829
|
+
# (the runtime walks it backwards, which mirrors the constructor order).
|
|
830
|
+
#
|
|
831
|
+
# Splits the writable input sections into the ordinary ones (kept in merged
|
|
832
|
+
# order) and the initializer/finalizer array run, which is appended after
|
|
833
|
+
# them so it ends up adjacent to the synthesized dynamic sections. A link
|
|
834
|
+
# with no array section leaves the first list exactly as it was, so its
|
|
835
|
+
# layout — and its output bytes — are unchanged.
|
|
836
|
+
def split_array_sections(sections)
|
|
837
|
+
reject_unwritable_array_sections!
|
|
838
|
+
plain = sections.reject { |s| array_section?(s) }
|
|
839
|
+
@init_array_run = order_array_run(sections.select { |s| s.type == SHT_INIT_ARRAY })
|
|
840
|
+
@fini_array_run = order_array_run(sections.select { |s| s.type == SHT_FINI_ARRAY })
|
|
841
|
+
[plain, @init_array_run + @fini_array_run]
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def array_section?(section)
|
|
845
|
+
section.type == SHT_INIT_ARRAY || section.type == SHT_FINI_ARRAY
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
# Orders one array run by the rule above. The sort key pairs the priority
|
|
849
|
+
# with the section's position in the merged order, so equal priorities keep
|
|
850
|
+
# input order and the result is deterministic (N4) whatever Ruby's sort
|
|
851
|
+
# does. An unnumbered section sorts after every numbered one.
|
|
852
|
+
def order_array_run(sections)
|
|
853
|
+
sections.each_with_index.sort_by { |sec, i| [array_priority(sec), i] }.map(&:first)
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
# The priority encoded in an array section's name: `.init_array.00101` has
|
|
857
|
+
# priority 101, plain `.init_array` has none (Float::INFINITY, sorting
|
|
858
|
+
# last). Any other suffix is a form this linker has not been shown, and
|
|
859
|
+
# guessing its position would silently run the initializers in the wrong
|
|
860
|
+
# order, so it is refused instead.
|
|
861
|
+
def array_priority(section)
|
|
862
|
+
base = section.type == SHT_INIT_ARRAY ? ".init_array" : ".fini_array"
|
|
863
|
+
name = section.name.to_s
|
|
864
|
+
return Float::INFINITY if name == base
|
|
865
|
+
return ::Regexp.last_match(1).to_i if name =~ /\A#{::Regexp.escape(base)}\.(\d+)\z/
|
|
866
|
+
|
|
867
|
+
raise LinkError, "unsupported initializer array section '#{name}': " \
|
|
868
|
+
"only '#{base}' and a priority-numbered '#{base}.NNNNN' are understood"
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
# DT_INIT_ARRAY / DT_FINI_ARRAY address one array, so its sections must have
|
|
872
|
+
# been laid without a gap between them. They all carry alignment 8 and a
|
|
873
|
+
# size that is a whole number of 8-byte pointers, so the placement above is
|
|
874
|
+
# contiguous; this refuses the case rather than emitting a range with a
|
|
875
|
+
# zero (null-pointer) hole the loader would call.
|
|
876
|
+
def check_array_runs_contiguous
|
|
877
|
+
[@init_array_run, @fini_array_run].each do |run|
|
|
878
|
+
run.each_cons(2) do |a, b|
|
|
879
|
+
next if a.vaddr + a.size == b.vaddr
|
|
880
|
+
|
|
881
|
+
raise LinkError, "initializer array sections '#{a.name}' and '#{b.name}' " \
|
|
882
|
+
"were not laid contiguously"
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
def init_array_addr = @init_array_run.first&.vaddr
|
|
888
|
+
def fini_array_addr = @fini_array_run.first&.vaddr
|
|
889
|
+
def init_array_size = @init_array_run.sum(&:size)
|
|
890
|
+
def fini_array_size = @fini_array_run.sum(&:size)
|
|
891
|
+
|
|
892
|
+
# Whether the link has a non-empty array. Read off the run assembled above,
|
|
893
|
+
# which #place_sections fills in before it sizes .dynamic, so the tag count
|
|
894
|
+
# and the tag values can never disagree. An absent — or present but empty —
|
|
895
|
+
# array emits no tag at all, keeping the dynamic array of a link without
|
|
896
|
+
# initializers byte-for-byte as before.
|
|
897
|
+
def init_array? = init_array_size.positive?
|
|
898
|
+
def fini_array? = fini_array_size.positive?
|
|
899
|
+
|
|
900
|
+
# An array section the ABI marks SHF_ALLOC without SHF_WRITE would be laid
|
|
901
|
+
# into the read-only segment, dropping out of the run and taking its
|
|
902
|
+
# initializers with it silently. Nothing this linker accepts emits that
|
|
903
|
+
# shape (gcc marks both arrays WA), so it is refused rather than ignored.
|
|
904
|
+
def reject_unwritable_array_sections!
|
|
905
|
+
@reader.sections.each do |sec|
|
|
906
|
+
next unless allocatable?(sec) && array_section?(sec) && !writable?(sec)
|
|
907
|
+
|
|
908
|
+
raise LinkError, "initializer array section '#{sec.name}' is not writable"
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
# Assigns offsets/addresses to a run of sections starting at `cursor`, which
|
|
913
|
+
# tracks the virtual/memory position (equal to the file offset for file-
|
|
914
|
+
# backed sections). The segment's file offset (`offset`) may precede
|
|
915
|
+
# `cursor` when the header sits in front of the first section (the r-x
|
|
916
|
+
# segment). A NOBITS section advances the memory cursor like any other — its
|
|
917
|
+
# bytes are simply skipped when the file is written — so the caller captures
|
|
918
|
+
# the file-end cursor before laying the .bss run and reads the memory-end
|
|
919
|
+
# cursor after it. Returns [end_cursor, placed].
|
|
920
|
+
def lay(sections, cursor)
|
|
921
|
+
placed = []
|
|
922
|
+
sections.each do |sec|
|
|
923
|
+
cursor = align(cursor, sec.addralign)
|
|
924
|
+
# The file offset is the raw cursor; the virtual address adds the load
|
|
925
|
+
# base (zero for a shared object, so vaddr == offset as before; a fixed
|
|
926
|
+
# base for a non-PIE executable).
|
|
927
|
+
sec.offset = cursor
|
|
928
|
+
sec.vaddr = load_base + cursor
|
|
929
|
+
cursor += sec.size
|
|
930
|
+
placed << sec
|
|
931
|
+
end
|
|
932
|
+
[cursor, placed]
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
# Assigns the final section-header index to every placed section (NULL is 0,
|
|
936
|
+
# then r-x, r--, rw- runs in placement order, then .shstrtab) and builds the
|
|
937
|
+
# section-name -> index and section-name -> vaddr maps the symbol and
|
|
938
|
+
# dynamic tables resolve through.
|
|
939
|
+
def index_sections
|
|
940
|
+
@placed = @rx + @ro + @rw
|
|
941
|
+
@section_index = { nil => 0 }
|
|
942
|
+
@vaddr = {}
|
|
943
|
+
@placed.each_with_index do |sec, i|
|
|
944
|
+
sec.index = i + 1
|
|
945
|
+
@section_index[sec.name] = sec.index
|
|
946
|
+
@vaddr[sec.name] = sec.vaddr
|
|
947
|
+
end
|
|
948
|
+
# The regular linker symbol/string tables are non-allocatable metadata
|
|
949
|
+
# appended after the loadable image. Keep their indices out of
|
|
950
|
+
# @section_index: dynamic symbols must continue to name only mapped
|
|
951
|
+
# sections.
|
|
952
|
+
@symtab_index = @placed.size + 1
|
|
953
|
+
@strtab_index = @symtab_index + 1
|
|
954
|
+
@shstrtab_index = @strtab_index + 1
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
# The executable .plt (one stub per imported function), or none when there
|
|
958
|
+
# are no external calls.
|
|
959
|
+
def plt_sections
|
|
960
|
+
return [] unless plt?
|
|
961
|
+
|
|
962
|
+
[placed_generated(".plt", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, 16, 0, @plt_order.size * PLT_ENTSIZE)]
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
# The synthesized read-only dynamic sections, in the order the loader
|
|
966
|
+
# expects to find them addressed from .dynamic: the SysV hash, the dynamic
|
|
967
|
+
# symbol table, and its string table, followed by .rela.dyn (RELATIVE /
|
|
968
|
+
# GLOB_DAT / symbolic 64) and .rela.plt (JUMP_SLOT) when present.
|
|
969
|
+
def dynamic_ro_sections
|
|
970
|
+
secs = [
|
|
971
|
+
placed_generated(".hash", SHT_HASH, SHF_ALLOC, 8, 4, hash_bytes.bytesize),
|
|
972
|
+
placed_generated(".dynsym", SHT_DYNSYM, SHF_ALLOC, 8, SYM_ENTSIZE, dynsym_size),
|
|
973
|
+
placed_generated(".dynstr", SHT_STRTAB, SHF_ALLOC, 1, 0, dynstr_bytes.bytesize)
|
|
974
|
+
]
|
|
975
|
+
secs << placed_generated(".rela.dyn", SHT_RELA, SHF_ALLOC, 8, RELA_ENTSIZE, rela_dyn_size) if rela_dyn?
|
|
976
|
+
secs << placed_generated(".rela.plt", SHT_RELA, SHF_ALLOC, 8, RELA_ENTSIZE, rela_plt_size) if plt?
|
|
977
|
+
secs
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
# The synthesized writable dynamic sections: the data GOT (only when a data
|
|
981
|
+
# GOT slot is needed), the .got.plt (only when there are external calls),
|
|
982
|
+
# and the .dynamic array.
|
|
983
|
+
def writable_dynamic_sections
|
|
984
|
+
secs = []
|
|
985
|
+
secs << placed_generated(".got", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE, 8, 8, @got_order.size * 8) unless @got_order.empty?
|
|
986
|
+
secs << placed_generated(".got.plt", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE, 8, 8, gotplt_size) if plt?
|
|
987
|
+
secs << placed_generated(".dynamic", SHT_DYNAMIC, SHF_ALLOC | SHF_WRITE, 8, DYN_ENTSIZE, dynamic_size)
|
|
988
|
+
secs
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
def placed_generated(name, type, flags, addralign, entsize, size)
|
|
992
|
+
Placed.new(name: name, type: type, flags: flags, addralign: addralign,
|
|
993
|
+
entsize: entsize, size: size, data: nil)
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
def plt? = !@plt_order.empty?
|
|
997
|
+
def bind_now? = !@import_order.empty?
|
|
998
|
+
|
|
999
|
+
def gotplt_size = (GOTPLT_RESERVED + @plt_order.size) * 8
|
|
1000
|
+
def rela_plt_count = @plt_order.size
|
|
1001
|
+
def rela_plt_size = rela_plt_count * RELA_ENTSIZE
|
|
1002
|
+
|
|
1003
|
+
# A .rela.dyn is present when any internal address must be rebased (a GOT
|
|
1004
|
+
# slot or an R_X86_64_64 initializer) or any external data reference bound
|
|
1005
|
+
# (a GLOB_DAT slot or a symbolic R_X86_64_64).
|
|
1006
|
+
def rela_dyn? = rela_dyn_count.positive?
|
|
1007
|
+
def rela_dyn_size = rela_dyn_count * RELA_ENTSIZE
|
|
1008
|
+
# The GLOB_DAT entries (external data GOT slots) and symbolic-64 entries
|
|
1009
|
+
# (external absolute-64 initializers) are always dynamic; the RELATIVE
|
|
1010
|
+
# entries only exist when internal absolute addresses are rebased at load
|
|
1011
|
+
# time (a shared object). A non-PIE executable writes those addresses
|
|
1012
|
+
# directly, so it emits no RELATIVE entry.
|
|
1013
|
+
def rela_dyn_count = external_got_count + @data64_dyn_count + relacount
|
|
1014
|
+
# The number of leading RELATIVE entries in .rela.dyn (DT_RELACOUNT): the
|
|
1015
|
+
# internal GOT slots and internal absolute-64 initializers, which precede
|
|
1016
|
+
# the GLOB_DAT and symbolic entries. None when internal addresses are not
|
|
1017
|
+
# rebased (a non-PIE executable).
|
|
1018
|
+
def relacount = rebase_internal? ? (internal_got_count + @data64_count) : 0
|
|
1019
|
+
def internal_got_count = @got_order.count { |sym| !external_import?(sym) }
|
|
1020
|
+
def external_got_count = @got_order.count { |sym| external_import?(sym) }
|
|
1021
|
+
|
|
1022
|
+
def dynsym_size = (@exports.size + @import_order.size + 1) * SYM_ENTSIZE
|
|
1023
|
+
# The dynamic array's size depends only on which tags are present, not on
|
|
1024
|
+
# the addresses filled in later, so it can be computed during sizing.
|
|
1025
|
+
def dynamic_size = dynamic_entry_count * DYN_ENTSIZE
|
|
1026
|
+
def dynamic_entry_count
|
|
1027
|
+
@used_deps.size + (@soname ? 1 : 0) + 5 +
|
|
1028
|
+
(init_array? ? 2 : 0) + (fini_array? ? 2 : 0) +
|
|
1029
|
+
(rela_dyn? ? 4 : 0) + (plt? ? 4 : 0) + (bind_now? ? 2 : 0) + 1
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# --- relocation application --------------------------------------------
|
|
1033
|
+
|
|
1034
|
+
# Patches every allocatable target section against the assigned addresses,
|
|
1035
|
+
# then builds the GOT, the .plt and their dynamic relocation entries. The
|
|
1036
|
+
# relative entries are ordered internal GOT slots (in slot order) then the
|
|
1037
|
+
# absolute-64 initializers (in relocation order), for a deterministic
|
|
1038
|
+
# .rela.dyn.
|
|
1039
|
+
def apply_relocations
|
|
1040
|
+
@section_data = {}
|
|
1041
|
+
@placed.each { |sec| @section_data[sec.name] = sec.data if sec.data }
|
|
1042
|
+
@rela_data = [] # internal R_X86_64_64 -> [offset, addend] RELATIVE
|
|
1043
|
+
@rela_data_sym = [] # external R_X86_64_64 -> [offset, dynindex, addend]
|
|
1044
|
+
|
|
1045
|
+
allocatable_relocation_sections.each do |rs|
|
|
1046
|
+
base = @vaddr[rs.target.name]
|
|
1047
|
+
buf = @section_data[rs.target.name]
|
|
1048
|
+
rs.relocations.each { |reloc| apply_relocation(reloc, base, buf) }
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
build_got
|
|
1052
|
+
build_plt if plt?
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
def apply_relocation(reloc, base, buf)
|
|
1056
|
+
return apply_relocation_aarch64(reloc, base, buf) if aarch64?
|
|
1057
|
+
|
|
1058
|
+
sym = reloc.symbol
|
|
1059
|
+
a = reloc.addend
|
|
1060
|
+
p = base + reloc.offset
|
|
1061
|
+
external = external_import?(sym)
|
|
1062
|
+
case reloc.type
|
|
1063
|
+
when R_X86_64_PLT32
|
|
1064
|
+
target = external ? plt_stub_addr(sym) : symbol_address(sym)
|
|
1065
|
+
patch32(buf, reloc.offset, target + a - p)
|
|
1066
|
+
when R_X86_64_PC32
|
|
1067
|
+
patch32(buf, reloc.offset, symbol_address(sym) + a - p)
|
|
1068
|
+
when R_X86_64_32, R_X86_64_32S
|
|
1069
|
+
patch32(buf, reloc.offset, symbol_address(sym) + a)
|
|
1070
|
+
when *GOT_RELOC_TYPES
|
|
1071
|
+
slot = got_addr + @got_index[got_key(sym)] * 8
|
|
1072
|
+
patch32(buf, reloc.offset, slot + a - p)
|
|
1073
|
+
when R_X86_64_64
|
|
1074
|
+
if external
|
|
1075
|
+
patch64(buf, reloc.offset, 0)
|
|
1076
|
+
@rela_data_sym << [p, import_dynindex(sym), a]
|
|
1077
|
+
else
|
|
1078
|
+
value = symbol_address(sym) + a
|
|
1079
|
+
patch64(buf, reloc.offset, value)
|
|
1080
|
+
# A shared object rebases this absolute initializer at load time; a
|
|
1081
|
+
# non-PIE executable's address is already final, so no RELATIVE.
|
|
1082
|
+
@rela_data << [p, value] if rebase_internal?
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
# The aarch64 apply, the counterpart of #apply_relocation. Where x86_64
|
|
1088
|
+
# patches a plain 32/64-bit displacement, aarch64 patches an immediate
|
|
1089
|
+
# bit-field packed inside a fixed 32-bit instruction word, and its address
|
|
1090
|
+
# references span two instructions the reader delivered as two relocations:
|
|
1091
|
+
# a CALL26 patches a `bl` (routed through the .plt for an external target),
|
|
1092
|
+
# ADR_PREL_PG_HI21 the page distance of an `adrp`, ADD_ABS_LO12_NC the
|
|
1093
|
+
# within-page offset of the `add`, and the GOT pair the same split against a
|
|
1094
|
+
# GOT slot. ABS64 is the absolute-64 pointer slot: written with its final
|
|
1095
|
+
# value in a non-PIE executable (no RELATIVE) or bound symbolically when it
|
|
1096
|
+
# names an import.
|
|
1097
|
+
def apply_relocation_aarch64(reloc, base, buf)
|
|
1098
|
+
sym = reloc.symbol
|
|
1099
|
+
a = reloc.addend
|
|
1100
|
+
p = base + reloc.offset
|
|
1101
|
+
external = external_import?(sym)
|
|
1102
|
+
case reloc.type
|
|
1103
|
+
when R_AARCH64_CALL26
|
|
1104
|
+
target = external ? plt_stub_addr(sym) : symbol_address(sym)
|
|
1105
|
+
patch_call26(buf, reloc.offset, target + a - p)
|
|
1106
|
+
when R_AARCH64_ADR_PREL_PG_HI21
|
|
1107
|
+
patch_adrp(buf, reloc.offset, page(symbol_address(sym) + a) - page(p))
|
|
1108
|
+
when R_AARCH64_ADD_ABS_LO12_NC
|
|
1109
|
+
patch_add_lo12(buf, reloc.offset, (symbol_address(sym) + a) & 0xFFF)
|
|
1110
|
+
when R_AARCH64_ADR_GOT_PAGE
|
|
1111
|
+
patch_adrp(buf, reloc.offset, page(got_slot_addr(sym)) - page(p))
|
|
1112
|
+
when R_AARCH64_LD64_GOT_LO12_NC
|
|
1113
|
+
patch_ld64_lo12(buf, reloc.offset, got_slot_addr(sym) & 0xFFF)
|
|
1114
|
+
when R_AARCH64_ABS64
|
|
1115
|
+
if external
|
|
1116
|
+
patch64(buf, reloc.offset, 0)
|
|
1117
|
+
@rela_data_sym << [p, import_dynindex(sym), a]
|
|
1118
|
+
else
|
|
1119
|
+
value = symbol_address(sym) + a
|
|
1120
|
+
patch64(buf, reloc.offset, value)
|
|
1121
|
+
@rela_data << [p, value] if rebase_internal?
|
|
1122
|
+
end
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def got_slot_addr(sym) = got_addr + @got_index[got_key(sym)] * 8
|
|
1127
|
+
|
|
1128
|
+
# The 4 KiB page a virtual address sits in — the unit `adrp` operates on,
|
|
1129
|
+
# independent of the (larger) load-segment alignment.
|
|
1130
|
+
def page(addr) = addr & ~0xFFF
|
|
1131
|
+
|
|
1132
|
+
def read_insn(buf, offset) = buf[offset, 4].unpack1("L<")
|
|
1133
|
+
def write_insn(buf, offset, word) = (buf[offset, 4] = [word & 0xFFFFFFFF].pack("L<"))
|
|
1134
|
+
|
|
1135
|
+
# Patches a `bl`/`b`'s 26-bit signed word offset (ARM DDI 0487, the
|
|
1136
|
+
# unconditional-branch-immediate form): the byte displacement is a multiple
|
|
1137
|
+
# of four, so imm26 = disp >> 2 occupies bits [25:0].
|
|
1138
|
+
def patch_call26(buf, offset, disp)
|
|
1139
|
+
imm = (disp >> 2) & 0x03FFFFFF
|
|
1140
|
+
write_insn(buf, offset, (read_insn(buf, offset) & ~0x03FFFFFF) | imm)
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
# Patches an `adrp`'s 21-bit signed page immediate. The page distance is a
|
|
1144
|
+
# multiple of 4 KiB, so imm21 = page_delta >> 12 splits into immlo (bits
|
|
1145
|
+
# [1:0], encoded at instruction bits [30:29]) and immhi (bits [20:2],
|
|
1146
|
+
# encoded at bits [23:5]).
|
|
1147
|
+
def patch_adrp(buf, offset, page_delta)
|
|
1148
|
+
imm = (page_delta >> 12) & 0x1FFFFF
|
|
1149
|
+
immlo = imm & 0x3
|
|
1150
|
+
immhi = (imm >> 2) & 0x7FFFF
|
|
1151
|
+
word = read_insn(buf, offset) & ~((0x3 << 29) | (0x7FFFF << 5))
|
|
1152
|
+
write_insn(buf, offset, word | (immlo << 29) | (immhi << 5))
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
# Patches an `add` (immediate) 12-bit unsigned field at bits [21:10] with the
|
|
1156
|
+
# symbol's within-page byte offset.
|
|
1157
|
+
def patch_add_lo12(buf, offset, value)
|
|
1158
|
+
imm = value & 0xFFF
|
|
1159
|
+
write_insn(buf, offset, (read_insn(buf, offset) & ~(0xFFF << 10)) | (imm << 10))
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1162
|
+
# Patches a 64-bit `ldr`'s 12-bit unsigned-offset field at bits [21:10]. The
|
|
1163
|
+
# field is the byte offset scaled by the access size (8), so the GOT slot's
|
|
1164
|
+
# 8-byte-aligned low offset encodes exactly as value >> 3.
|
|
1165
|
+
def patch_ld64_lo12(buf, offset, value)
|
|
1166
|
+
imm = (value >> 3) & 0xFFF
|
|
1167
|
+
write_insn(buf, offset, (read_insn(buf, offset) & ~(0xFFF << 10)) | (imm << 10))
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
# Fills the data GOT: an internal slot holds its symbol's link-time address
|
|
1171
|
+
# and is rebased by an R_X86_64_RELATIVE; an external slot is left zero and
|
|
1172
|
+
# bound by an R_X86_64_GLOB_DAT naming the imported symbol.
|
|
1173
|
+
def build_got
|
|
1174
|
+
@got_bytes = +"".b
|
|
1175
|
+
@rela_relative_got = []
|
|
1176
|
+
@rela_glob_dat = []
|
|
1177
|
+
@got_order.each_with_index do |sym, i|
|
|
1178
|
+
slot = got_addr + i * 8
|
|
1179
|
+
if external_import?(sym)
|
|
1180
|
+
@got_bytes << [0].pack("Q<")
|
|
1181
|
+
@rela_glob_dat << [slot, import_dynindex(sym)]
|
|
1182
|
+
else
|
|
1183
|
+
address = symbol_address(sym)
|
|
1184
|
+
@got_bytes << [address].pack("Q<")
|
|
1185
|
+
# The slot already holds its final address; a shared object still
|
|
1186
|
+
# rebases it with a RELATIVE, a non-PIE executable does not.
|
|
1187
|
+
@rela_relative_got << [slot, address] if rebase_internal?
|
|
1188
|
+
end
|
|
1189
|
+
end
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
# Builds the .plt stubs, the .got.plt and its JUMP_SLOT relocations. Each
|
|
1193
|
+
# stub is `jmp *slot(%rip)` (FF 25 + a PC-relative disp32 to its .got.plt
|
|
1194
|
+
# slot) padded with single-byte NOPs to the 16-byte entry; under BIND_NOW
|
|
1195
|
+
# the loader has already stored the resolved target in the slot, so the
|
|
1196
|
+
# first call jumps straight there. The .got.plt reserves three leading slots
|
|
1197
|
+
# ([0] = &_DYNAMIC, [1]/[2] zero) before the per-function slots.
|
|
1198
|
+
def build_plt
|
|
1199
|
+
return build_plt_aarch64 if aarch64?
|
|
1200
|
+
|
|
1201
|
+
@plt_bytes = +"".b
|
|
1202
|
+
@gotplt_bytes = +[@vaddr[".dynamic"], 0, 0].pack("Q<Q<Q<")
|
|
1203
|
+
@rela_plt = []
|
|
1204
|
+
@plt_order.each_with_index do |sym, i|
|
|
1205
|
+
stub = plt_stub_addr(sym)
|
|
1206
|
+
slot = gotplt_slot_addr(i)
|
|
1207
|
+
disp = slot - (stub + 6)
|
|
1208
|
+
@plt_bytes << [0xFF, 0x25].pack("C2") << [disp].pack("l<")
|
|
1209
|
+
@plt_bytes << ([0x90] * (PLT_ENTSIZE - 6)).pack("C*")
|
|
1210
|
+
@gotplt_bytes << [0].pack("Q<")
|
|
1211
|
+
@rela_plt << [slot, import_dynindex(sym)]
|
|
1212
|
+
end
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
# The aarch64 .plt, the counterpart of #build_plt. Each 16-byte stub loads
|
|
1216
|
+
# its function's resolved address from the .got.plt slot and branches there;
|
|
1217
|
+
# under BIND_NOW the loader has already stored the JUMP_SLOT target, so the
|
|
1218
|
+
# first call reaches the callee directly. The four-instruction sequence
|
|
1219
|
+
# (ARM DDI 0487 encodings) is: `adrp x16, page(slot)`; `ldr x17, [x16,
|
|
1220
|
+
# #lo12]`; `add x16, x16, #lo12`; `br x17`. The .got.plt reserves three
|
|
1221
|
+
# leading slots ([0] = &_DYNAMIC, [1]/[2] zero) before the per-function ones.
|
|
1222
|
+
def build_plt_aarch64
|
|
1223
|
+
@plt_bytes = +"".b
|
|
1224
|
+
@gotplt_bytes = +[@vaddr[".dynamic"], 0, 0].pack("Q<Q<Q<")
|
|
1225
|
+
@rela_plt = []
|
|
1226
|
+
@plt_order.each_with_index do |sym, i|
|
|
1227
|
+
stub = plt_stub_addr(sym)
|
|
1228
|
+
slot = gotplt_slot_addr(i)
|
|
1229
|
+
@plt_bytes << aarch64_plt_stub(stub, slot)
|
|
1230
|
+
@gotplt_bytes << [0].pack("Q<")
|
|
1231
|
+
@rela_plt << [slot, import_dynindex(sym)]
|
|
1232
|
+
end
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
# The four instruction words of one aarch64 .plt stub reaching `slot` from
|
|
1236
|
+
# its own address `stub` (see #build_plt_aarch64).
|
|
1237
|
+
def aarch64_plt_stub(stub, slot)
|
|
1238
|
+
lo12 = slot & 0xFFF
|
|
1239
|
+
adrp = aarch64_adrp(16, page(slot) - page(stub))
|
|
1240
|
+
ldr = aarch64_ldr64(17, 16, lo12)
|
|
1241
|
+
add = aarch64_add(16, 16, lo12)
|
|
1242
|
+
br = 0xD61F0000 | (17 << 5)
|
|
1243
|
+
[adrp, ldr, add, br].pack("L<4")
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
# `adrp Xd, #page_delta`: base opcode 0x90000000 with the 21-bit page
|
|
1247
|
+
# immediate split into immlo (bits [30:29]) and immhi (bits [23:5]).
|
|
1248
|
+
def aarch64_adrp(rd, page_delta)
|
|
1249
|
+
imm = (page_delta >> 12) & 0x1FFFFF
|
|
1250
|
+
0x90000000 | ((imm & 0x3) << 29) | (((imm >> 2) & 0x7FFFF) << 5) | rd
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
# `ldr Xt, [Xn, #byteoff]` (64-bit unsigned offset): base 0xF9400000 with the
|
|
1254
|
+
# scaled (byteoff / 8) 12-bit immediate at bits [21:10].
|
|
1255
|
+
def aarch64_ldr64(rt, rn, byteoff)
|
|
1256
|
+
0xF9400000 | (((byteoff >> 3) & 0xFFF) << 10) | (rn << 5) | rt
|
|
1257
|
+
end
|
|
1258
|
+
|
|
1259
|
+
# `add Xd, Xn, #imm12`: base 0x91000000 with the 12-bit immediate at bits
|
|
1260
|
+
# [21:10].
|
|
1261
|
+
def aarch64_add(rd, rn, imm12)
|
|
1262
|
+
0x91000000 | ((imm12 & 0xFFF) << 10) | (rn << 5) | rd
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
def got_addr = @vaddr[".got"]
|
|
1266
|
+
def plt_stub_addr(sym) = @vaddr[".plt"] + @plt_index[sym.name] * PLT_ENTSIZE
|
|
1267
|
+
def gotplt_slot_addr(i) = @vaddr[".got.plt"] + (GOTPLT_RESERVED + i) * 8
|
|
1268
|
+
|
|
1269
|
+
# The .dynsym index of an imported symbol: after the null entry and the
|
|
1270
|
+
# exported symbols.
|
|
1271
|
+
def import_dynindex(sym)
|
|
1272
|
+
@exports.size + 1 + @import_index[sym.name]
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
# The load-time virtual address of a relocation's symbol: a section
|
|
1276
|
+
# reference resolves to the section's base, an absolute symbol to its
|
|
1277
|
+
# value, and any other defined symbol to its section base plus its offset.
|
|
1278
|
+
def symbol_address(sym)
|
|
1279
|
+
return 0 unless sym
|
|
1280
|
+
return @vaddr[sym.section.name] if sym.type == :section && sym.section
|
|
1281
|
+
return sym.value if sym.absolute?
|
|
1282
|
+
|
|
1283
|
+
@vaddr[sym.section.name] + sym.value
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
# --- dynamic table contents --------------------------------------------
|
|
1287
|
+
|
|
1288
|
+
# Builds .dynstr and the name -> offset map, memoized so the sizing pass and
|
|
1289
|
+
# the emit share one table. It holds every exported and imported symbol name,
|
|
1290
|
+
# every recorded DT_NEEDED name and this object's DT_SONAME.
|
|
1291
|
+
def dynstr
|
|
1292
|
+
@dynstr ||= begin
|
|
1293
|
+
buf = +"\0".b
|
|
1294
|
+
offsets = {}
|
|
1295
|
+
dynstr_names.each do |name|
|
|
1296
|
+
next if name.nil? || name.empty? || offsets.key?(name)
|
|
1297
|
+
|
|
1298
|
+
offsets[name] = buf.bytesize
|
|
1299
|
+
buf << name.b << "\0".b
|
|
1300
|
+
end
|
|
1301
|
+
[buf, offsets]
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
def dynstr_names
|
|
1306
|
+
@exports.map(&:name) + @import_order.map(&:name) + @used_deps.map(&:name) + [@soname]
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
def dynstr_bytes = dynstr[0]
|
|
1310
|
+
def dynstr_offset(name) = dynstr[1].fetch(name)
|
|
1311
|
+
|
|
1312
|
+
# The .dynsym payload: the reserved null entry, then one entry per exported
|
|
1313
|
+
# symbol carrying its load address, size, binding/type and defining section,
|
|
1314
|
+
# then one UND entry per imported symbol (st_shndx = SHN_UNDEF, value 0).
|
|
1315
|
+
def dynsym_bytes
|
|
1316
|
+
_, name_offsets = dynstr
|
|
1317
|
+
buf = +("\0".b * SYM_ENTSIZE)
|
|
1318
|
+
@exports.each do |sym|
|
|
1319
|
+
info = (STB.fetch(sym.bind, 1) << 4) | STT.fetch(sym.type, 0)
|
|
1320
|
+
buf << sym_entry(name: name_offsets.fetch(sym.name), info: info,
|
|
1321
|
+
other: STV.fetch(sym.visibility, 0),
|
|
1322
|
+
shndx: @section_index.fetch(sym.section.name),
|
|
1323
|
+
value: @vaddr[sym.section.name] + sym.value, size: sym.size)
|
|
1324
|
+
end
|
|
1325
|
+
@import_order.each do |sym|
|
|
1326
|
+
info = (STB.fetch(sym.bind, 1) << 4) | STT.fetch(sym.type, 0)
|
|
1327
|
+
buf << sym_entry(name: name_offsets.fetch(sym.name), info: info,
|
|
1328
|
+
other: STV.fetch(sym.visibility, 0),
|
|
1329
|
+
shndx: SHN_UNDEF, value: 0, size: 0)
|
|
1330
|
+
end
|
|
1331
|
+
buf
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
# The SysV (.hash) table: nbucket, nchain, then the bucket heads and the
|
|
1335
|
+
# collision chain. Every dynamic symbol (exports and imports) occupies a
|
|
1336
|
+
# chain slot so nchain equals the .dynsym count, but only the exported
|
|
1337
|
+
# (defined) symbols are hashed into the buckets — an import is never looked
|
|
1338
|
+
# up here — in symbol-index order for a deterministic layout.
|
|
1339
|
+
def hash_bytes
|
|
1340
|
+
nsyms = @exports.size + @import_order.size + 1
|
|
1341
|
+
nbucket = bucket_count(@exports.size)
|
|
1342
|
+
buckets = Array.new(nbucket, SHN_UNDEF)
|
|
1343
|
+
chain = Array.new(nsyms, SHN_UNDEF)
|
|
1344
|
+
@exports.each_with_index do |sym, i|
|
|
1345
|
+
idx = i + 1
|
|
1346
|
+
b = elf_hash(sym.name) % nbucket
|
|
1347
|
+
chain[idx] = buckets[b]
|
|
1348
|
+
buckets[b] = idx
|
|
1349
|
+
end
|
|
1350
|
+
[nbucket, nsyms].pack("L<L<") + buckets.pack("L<*") + chain.pack("L<*")
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
# Derives the hash bucket count deterministically from the export count via
|
|
1354
|
+
# a fixed growth ladder, keeping average chain length near one without
|
|
1355
|
+
# depending on anything but the input's size (N4).
|
|
1356
|
+
def bucket_count(n)
|
|
1357
|
+
ladder = [1, 3, 7, 17, 37, 67, 127, 251, 509, 1021, 2039]
|
|
1358
|
+
ladder.reverse_each { |b| return b if b <= n }
|
|
1359
|
+
1
|
|
1360
|
+
end
|
|
1361
|
+
|
|
1362
|
+
# The ELF gABI symbol hash (name -> unsigned 32-bit-ish value) glibc uses to
|
|
1363
|
+
# locate a symbol in the SysV hash table.
|
|
1364
|
+
def elf_hash(name)
|
|
1365
|
+
h = 0
|
|
1366
|
+
name.each_byte do |c|
|
|
1367
|
+
h = (h << 4) + c
|
|
1368
|
+
g = h & 0xF0000000
|
|
1369
|
+
h ^= g >> 24 if g != 0
|
|
1370
|
+
h &= ~g & 0xFFFFFFFF
|
|
1371
|
+
end
|
|
1372
|
+
h
|
|
1373
|
+
end
|
|
1374
|
+
|
|
1375
|
+
# The .rela.dyn payload: the RELATIVE entries (internal GOT slots then data
|
|
1376
|
+
# initializers) first, so DT_RELACOUNT can cover them, then the GLOB_DAT
|
|
1377
|
+
# entries for external data GOT slots, then the symbolic R_X86_64_64 data
|
|
1378
|
+
# initializers pointing at imported data.
|
|
1379
|
+
def rela_dyn_bytes
|
|
1380
|
+
buf = +"".b
|
|
1381
|
+
(@rela_relative_got + @rela_data).each do |offset, addend|
|
|
1382
|
+
buf << rela_entry(offset, reloc_relative, 0, addend)
|
|
1383
|
+
end
|
|
1384
|
+
@rela_glob_dat.each do |offset, dynindex|
|
|
1385
|
+
buf << rela_entry(offset, reloc_glob_dat, dynindex, 0)
|
|
1386
|
+
end
|
|
1387
|
+
@rela_data_sym.each do |offset, dynindex, addend|
|
|
1388
|
+
buf << rela_entry(offset, reloc_abs64, dynindex, addend)
|
|
1389
|
+
end
|
|
1390
|
+
buf
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1393
|
+
# The .rela.plt payload: one JUMP_SLOT per external function, naming its
|
|
1394
|
+
# imported symbol; the loader stores the resolved address into the slot.
|
|
1395
|
+
def rela_plt_bytes
|
|
1396
|
+
buf = +"".b
|
|
1397
|
+
@rela_plt.each { |offset, dynindex| buf << rela_entry(offset, reloc_jump_slot, dynindex, 0) }
|
|
1398
|
+
buf
|
|
1399
|
+
end
|
|
1400
|
+
|
|
1401
|
+
def rela_entry(offset, type, sym_index, addend)
|
|
1402
|
+
[offset, (sym_index << 32) | type].pack("Q<Q<") + [addend].pack("q<")
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
# The .dynamic array: the DT_NEEDED dependencies and DT_SONAME first, then
|
|
1406
|
+
# the initializer/finalizer arrays (only when non-empty), then
|
|
1407
|
+
# pointers/sizes for the hash and symbol/string tables, the .rela.dyn table
|
|
1408
|
+
# (with DT_RELACOUNT), the .plt relocation table (DT_PLTGOT/PLTRELSZ/
|
|
1409
|
+
# PLTREL/JMPREL), the BIND_NOW flags, and the DT_NULL terminator.
|
|
1410
|
+
def dynamic_entries
|
|
1411
|
+
entries = []
|
|
1412
|
+
@used_deps.each { |dep| entries << [DT_NEEDED, dynstr_offset(dep.name)] }
|
|
1413
|
+
entries << [DT_SONAME, dynstr_offset(@soname)] if @soname
|
|
1414
|
+
# The initializer/finalizer arrays, sized in bytes, in gcc's position:
|
|
1415
|
+
# after the dependency names and ahead of the table pointers.
|
|
1416
|
+
entries += [[DT_INIT_ARRAY, init_array_addr], [DT_INIT_ARRAYSZ, init_array_size]] if init_array?
|
|
1417
|
+
entries += [[DT_FINI_ARRAY, fini_array_addr], [DT_FINI_ARRAYSZ, fini_array_size]] if fini_array?
|
|
1418
|
+
entries += [
|
|
1419
|
+
[DT_HASH, @vaddr[".hash"]],
|
|
1420
|
+
[DT_STRTAB, @vaddr[".dynstr"]],
|
|
1421
|
+
[DT_SYMTAB, @vaddr[".dynsym"]],
|
|
1422
|
+
[DT_STRSZ, dynstr_bytes.bytesize],
|
|
1423
|
+
[DT_SYMENT, SYM_ENTSIZE]
|
|
1424
|
+
]
|
|
1425
|
+
if rela_dyn?
|
|
1426
|
+
entries += [
|
|
1427
|
+
[DT_RELA, @vaddr[".rela.dyn"]],
|
|
1428
|
+
[DT_RELASZ, rela_dyn_size],
|
|
1429
|
+
[DT_RELAENT, RELA_ENTSIZE],
|
|
1430
|
+
[DT_RELACOUNT, relacount]
|
|
1431
|
+
]
|
|
1432
|
+
end
|
|
1433
|
+
if plt?
|
|
1434
|
+
entries += [
|
|
1435
|
+
[DT_PLTGOT, @vaddr[".got.plt"]],
|
|
1436
|
+
[DT_PLTRELSZ, rela_plt_size],
|
|
1437
|
+
[DT_PLTREL, DT_RELA],
|
|
1438
|
+
[DT_JMPREL, @vaddr[".rela.plt"]]
|
|
1439
|
+
]
|
|
1440
|
+
end
|
|
1441
|
+
entries += [[DT_FLAGS, DF_BIND_NOW], [DT_FLAGS_1, DF_1_NOW]] if bind_now?
|
|
1442
|
+
entries << [DT_NULL, 0]
|
|
1443
|
+
entries
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
def dynamic_bytes
|
|
1447
|
+
buf = +"".b
|
|
1448
|
+
dynamic_entries.each { |tag, val| buf << [tag].pack("q<") << [val].pack("Q<") }
|
|
1449
|
+
buf
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1452
|
+
# Resolves the sh_link/sh_info cross-references and materializes the byte
|
|
1453
|
+
# payload of every synthesized section once addresses are known. Input
|
|
1454
|
+
# sections carry their patched bytes; NOBITS carries none.
|
|
1455
|
+
def build_dynamic_contents
|
|
1456
|
+
dynsym = named(".dynsym")
|
|
1457
|
+
named(".hash").link = dynsym.index
|
|
1458
|
+
dynsym.link = named(".dynstr").index
|
|
1459
|
+
dynsym.info = 1 # first non-local dynamic symbol (only the null entry is local)
|
|
1460
|
+
named(".dynamic").link = named(".dynstr").index
|
|
1461
|
+
named(".rela.dyn")&.link = dynsym.index
|
|
1462
|
+
rela_plt = named(".rela.plt")
|
|
1463
|
+
return unless rela_plt
|
|
1464
|
+
|
|
1465
|
+
rela_plt.link = dynsym.index
|
|
1466
|
+
rela_plt.info = named(".got.plt").index # the section the JUMP_SLOTs modify
|
|
1467
|
+
end
|
|
1468
|
+
|
|
1469
|
+
def named(name)
|
|
1470
|
+
@placed.find { |sec| sec.name == name }
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
# The bytes to emit for a placed section: patched input bytes, or the
|
|
1474
|
+
# freshly built payload of a synthesized one.
|
|
1475
|
+
def section_bytes(sec)
|
|
1476
|
+
case sec.name
|
|
1477
|
+
when ".hash" then hash_bytes
|
|
1478
|
+
when ".dynsym" then dynsym_bytes
|
|
1479
|
+
when ".dynstr" then dynstr_bytes
|
|
1480
|
+
when ".rela.dyn" then rela_dyn_bytes
|
|
1481
|
+
when ".rela.plt" then rela_plt_bytes
|
|
1482
|
+
when ".plt" then @plt_bytes
|
|
1483
|
+
when ".got" then @got_bytes
|
|
1484
|
+
when ".got.plt" then @gotplt_bytes
|
|
1485
|
+
when ".dynamic" then dynamic_bytes
|
|
1486
|
+
else @section_data[sec.name]
|
|
1487
|
+
end
|
|
1488
|
+
end
|
|
1489
|
+
|
|
1490
|
+
# --- regular symbol table ----------------------------------------------
|
|
1491
|
+
|
|
1492
|
+
# Builds the non-allocatable symbol and string tables that describe the
|
|
1493
|
+
# final image to ordinary ELF tools. The dynamic tables above are the
|
|
1494
|
+
# loader-facing interface; `.symtab` is deliberately separate and may
|
|
1495
|
+
# contain local/hidden definitions that must not be exported through
|
|
1496
|
+
# `.dynsym`.
|
|
1497
|
+
def build_metadata_sections
|
|
1498
|
+
return @metadata_sections if @metadata_sections
|
|
1499
|
+
|
|
1500
|
+
records = regular_symbol_records
|
|
1501
|
+
strtab, offsets = regular_strtab(records)
|
|
1502
|
+
symtab = regular_symtab(records, offsets)
|
|
1503
|
+
cursor = align([@file_end, @mem_end].max, 8)
|
|
1504
|
+
symtab_section = Placed.new(
|
|
1505
|
+
name: ".symtab", type: SHT_SYMTAB, flags: 0, addralign: 8,
|
|
1506
|
+
entsize: SYM_ENTSIZE, size: symtab.bytesize, data: symtab,
|
|
1507
|
+
vaddr: 0, offset: cursor, link: @strtab_index,
|
|
1508
|
+
info: @symtab_first_global, index: @symtab_index
|
|
1509
|
+
)
|
|
1510
|
+
cursor += symtab.bytesize
|
|
1511
|
+
strtab_section = Placed.new(
|
|
1512
|
+
name: ".strtab", type: SHT_STRTAB, flags: 0, addralign: 1,
|
|
1513
|
+
entsize: 0, size: strtab.bytesize, data: strtab,
|
|
1514
|
+
vaddr: 0, offset: cursor, link: 0, info: 0, index: @strtab_index
|
|
1515
|
+
)
|
|
1516
|
+
@metadata_sections = [symtab_section, strtab_section]
|
|
1517
|
+
end
|
|
1518
|
+
|
|
1519
|
+
def regular_symbol_records
|
|
1520
|
+
candidates = @reader.symbols.drop(1).filter_map do |sym|
|
|
1521
|
+
shndx = regular_symbol_shndx(sym)
|
|
1522
|
+
next if shndx.nil?
|
|
1523
|
+
|
|
1524
|
+
value = sym.section ? @vaddr.fetch(sym.section.name) + sym.value : sym.value
|
|
1525
|
+
bind = STB.fetch(sym.bind, sym.bind)
|
|
1526
|
+
# GNU ld localizes hidden/internal definitions in the regular symbol
|
|
1527
|
+
# table at final-link time. Keep the same convention so `nm` renders
|
|
1528
|
+
# them as lowercase private symbols while default/protected definitions
|
|
1529
|
+
# remain externally visible uppercase entries.
|
|
1530
|
+
bind = STB[:local] if [STV[:hidden], STV[:internal]].include?(STV.fetch(sym.visibility, sym.visibility))
|
|
1531
|
+
{
|
|
1532
|
+
name: sym.name,
|
|
1533
|
+
bind: bind,
|
|
1534
|
+
type: STT.fetch(sym.type, sym.type),
|
|
1535
|
+
visibility: STV.fetch(sym.visibility, sym.visibility),
|
|
1536
|
+
shndx: shndx,
|
|
1537
|
+
value: value,
|
|
1538
|
+
size: sym.size
|
|
1539
|
+
}
|
|
1540
|
+
end
|
|
1541
|
+
locals, globals = candidates.partition { |sym| sym[:bind] == STB[:local] }
|
|
1542
|
+
@symtab_first_global = locals.length + 1 # the null symbol is entry zero
|
|
1543
|
+
[null_symbol_record] + locals + globals
|
|
1544
|
+
end
|
|
1545
|
+
|
|
1546
|
+
def regular_symbol_shndx(sym)
|
|
1547
|
+
return @section_index[sym.section.name] if sym.section && @section_index.key?(sym.section.name)
|
|
1548
|
+
return sym.shndx if sym.undefined? || sym.absolute? || sym.common?
|
|
1549
|
+
|
|
1550
|
+
nil
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
def null_symbol_record
|
|
1554
|
+
{ name: nil, bind: 0, type: 0, visibility: 0, shndx: SHN_UNDEF, value: 0, size: 0 }
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
def regular_strtab(records)
|
|
1558
|
+
buf = +"\0".b
|
|
1559
|
+
offsets = {}
|
|
1560
|
+
records.each do |record|
|
|
1561
|
+
name = record[:name]
|
|
1562
|
+
next if name.nil? || name.empty? || offsets.key?(name)
|
|
1563
|
+
|
|
1564
|
+
offsets[name] = buf.bytesize
|
|
1565
|
+
buf << name.b << "\0".b
|
|
1566
|
+
end
|
|
1567
|
+
[buf, offsets]
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1570
|
+
def regular_symtab(records, offsets)
|
|
1571
|
+
records.map do |record|
|
|
1572
|
+
name = record[:name]
|
|
1573
|
+
sym_entry(name: name && !name.empty? ? offsets.fetch(name) : 0,
|
|
1574
|
+
info: (record[:bind] << 4) | record[:type],
|
|
1575
|
+
other: record[:visibility], shndx: record[:shndx],
|
|
1576
|
+
value: record[:value], size: record[:size])
|
|
1577
|
+
end.join
|
|
1578
|
+
end
|
|
1579
|
+
|
|
1580
|
+
# --- assembly ----------------------------------------------------------
|
|
1581
|
+
|
|
1582
|
+
# Concatenates the ELF header, program headers, the placed sections' file
|
|
1583
|
+
# bytes, regular symbol/string tables, .shstrtab and the section header
|
|
1584
|
+
# table into the final image. The regular tables are not loaded; they are
|
|
1585
|
+
# retained so ordinary ELF tooling such as `nm` can inspect a linked `.so`.
|
|
1586
|
+
def assemble
|
|
1587
|
+
metadata = build_metadata_sections
|
|
1588
|
+
shstrtab, name_offsets = build_shstrtab
|
|
1589
|
+
shstrtab_offset = metadata.last.offset + metadata.last.size
|
|
1590
|
+
shoff = align(shstrtab_offset + shstrtab.bytesize, 8)
|
|
1591
|
+
|
|
1592
|
+
out = +"".b
|
|
1593
|
+
out << build_ehdr(shoff)
|
|
1594
|
+
out << build_phdrs
|
|
1595
|
+
@placed.each do |sec|
|
|
1596
|
+
next if sec.type == SHT_NOBITS
|
|
1597
|
+
|
|
1598
|
+
pad_to(out, sec.offset)
|
|
1599
|
+
out << section_bytes(sec)
|
|
1600
|
+
end
|
|
1601
|
+
metadata.each do |sec|
|
|
1602
|
+
pad_to(out, sec.offset)
|
|
1603
|
+
out << sec.data
|
|
1604
|
+
end
|
|
1605
|
+
pad_to(out, shstrtab_offset)
|
|
1606
|
+
out << shstrtab
|
|
1607
|
+
pad_to(out, shoff)
|
|
1608
|
+
out << build_null_shdr(name_offsets)
|
|
1609
|
+
@placed.each { |sec| out << build_shdr(sec, name_offsets) }
|
|
1610
|
+
metadata.each { |sec| out << build_shdr(sec, name_offsets) }
|
|
1611
|
+
out << build_shstrtab_shdr(shstrtab_offset, shstrtab.bytesize, name_offsets)
|
|
1612
|
+
out
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
# The program header count is fixed: three PT_LOAD segments (r-x, r--,
|
|
1616
|
+
# rw-), PT_DYNAMIC, and PT_GNU_STACK marking a non-executable stack.
|
|
1617
|
+
def phnum = 5
|
|
1618
|
+
|
|
1619
|
+
def build_phdrs
|
|
1620
|
+
# The r-x segment is the one exception to segment_extent's "span from
|
|
1621
|
+
# the first section's file offset" rule: it maps from file offset 0 /
|
|
1622
|
+
# vaddr 0, ahead of every section, so the ELF header and program header
|
|
1623
|
+
# table it carries are covered too. p_filesz must therefore be the
|
|
1624
|
+
# *absolute* length up to the last file-backed byte (last.offset +
|
|
1625
|
+
# last.size), not that length minus the leading sections' own offset —
|
|
1626
|
+
# matching ExecutableLinker's rx_filesz, which faces the same layout.
|
|
1627
|
+
rx_last = @rx.reject { |s| s.type == SHT_NOBITS }.last
|
|
1628
|
+
rx_filesz = rx_last.offset + rx_last.size
|
|
1629
|
+
ro = segment_extent(@ro, base: @ro.first.vaddr)
|
|
1630
|
+
dynamic = named(".dynamic")
|
|
1631
|
+
[
|
|
1632
|
+
phdr(PT_LOAD, PF_R | PF_X, 0, 0, rx_filesz, rx_filesz, seg_align),
|
|
1633
|
+
phdr(PT_LOAD, PF_R, ro[:offset], ro[:offset], ro[:filesz], ro[:filesz], seg_align),
|
|
1634
|
+
phdr(PT_LOAD, PF_R | PF_W, @rw_start, @rw_start,
|
|
1635
|
+
@file_end - @rw_start, @mem_end - @rw_start, seg_align),
|
|
1636
|
+
phdr(PT_DYNAMIC, PF_R | PF_W, dynamic.offset, dynamic.vaddr, dynamic.size, dynamic.size, 8),
|
|
1637
|
+
phdr(PT_GNU_STACK, PF_R | PF_W, 0, 0, 0, 0, 0x10)
|
|
1638
|
+
].join
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
# The file/memory extent of a segment given its placed sections: the file
|
|
1642
|
+
# offset of its first section and the span from that offset to the end of
|
|
1643
|
+
# its last file-backed byte.
|
|
1644
|
+
def segment_extent(sections, base:)
|
|
1645
|
+
last = sections.reject { |s| s.type == SHT_NOBITS }.last
|
|
1646
|
+
finish = last ? last.offset + last.size : base
|
|
1647
|
+
{ offset: sections.first.offset, filesz: finish - sections.first.offset }
|
|
1648
|
+
end
|
|
1649
|
+
|
|
1650
|
+
def phdr(type, flags, offset, vaddr, filesz, memsz, align)
|
|
1651
|
+
[type].pack("L<") + [flags].pack("L<") + [offset].pack("Q<") +
|
|
1652
|
+
[vaddr].pack("Q<") + [0].pack("Q<") + [filesz].pack("Q<") +
|
|
1653
|
+
[memsz].pack("Q<") + [align].pack("Q<")
|
|
1654
|
+
end
|
|
1655
|
+
|
|
1656
|
+
def build_ehdr(shoff)
|
|
1657
|
+
e_ident = [0x7F, 0x45, 0x4C, 0x46, ELFCLASS64, ELFDATA2LSB, EV_CURRENT,
|
|
1658
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0].pack("C16")
|
|
1659
|
+
e_ident +
|
|
1660
|
+
[e_type].pack("S<") + # ET_DYN for a .so, ET_EXEC for an executable
|
|
1661
|
+
[e_machine_id].pack("S<") +
|
|
1662
|
+
[EV_CURRENT].pack("L<") +
|
|
1663
|
+
[e_entry].pack("Q<") + # 0 for a shared object, _start's vaddr for an executable
|
|
1664
|
+
[EHDR_SIZE].pack("Q<") + # e_phoff (program headers follow the header)
|
|
1665
|
+
[shoff].pack("Q<") + # e_shoff
|
|
1666
|
+
[0].pack("L<") + # e_flags
|
|
1667
|
+
[EHDR_SIZE].pack("S<") + # e_ehsize
|
|
1668
|
+
[PHDR_SIZE].pack("S<") + # e_phentsize
|
|
1669
|
+
[phnum].pack("S<") + # e_phnum
|
|
1670
|
+
[SHDR_SIZE].pack("S<") + # e_shentsize
|
|
1671
|
+
[@placed.size + @metadata_sections.size + 2].pack("S<") + # NULL + sections + .shstrtab
|
|
1672
|
+
[@shstrtab_index].pack("S<") # e_shstrndx
|
|
1673
|
+
end
|
|
1674
|
+
|
|
1675
|
+
def build_shstrtab
|
|
1676
|
+
buf = +"\0".b
|
|
1677
|
+
offsets = {}
|
|
1678
|
+
(@placed.map(&:name) + @metadata_sections.map(&:name) + [".shstrtab"]).each do |name|
|
|
1679
|
+
next if offsets.key?(name)
|
|
1680
|
+
|
|
1681
|
+
offsets[name] = buf.bytesize
|
|
1682
|
+
buf << name.b << "\0".b
|
|
1683
|
+
end
|
|
1684
|
+
[buf, offsets]
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
def build_null_shdr(name_offsets)
|
|
1688
|
+
shdr(name: 0, type: SHT_NULL, flags: 0, addr: 0, offset: 0, size: 0,
|
|
1689
|
+
link: 0, info: 0, addralign: 0, entsize: 0)
|
|
1690
|
+
end
|
|
1691
|
+
|
|
1692
|
+
def build_shdr(sec, name_offsets)
|
|
1693
|
+
shdr(name: name_offsets[sec.name], type: sec.type, flags: sec.flags,
|
|
1694
|
+
addr: sec.vaddr, offset: sec.offset, size: sec.size,
|
|
1695
|
+
link: sec.link || 0, info: sec.info || 0,
|
|
1696
|
+
addralign: sec.addralign, entsize: sec.entsize)
|
|
1697
|
+
end
|
|
1698
|
+
|
|
1699
|
+
def build_shstrtab_shdr(offset, size, name_offsets)
|
|
1700
|
+
shdr(name: name_offsets[".shstrtab"], type: SHT_STRTAB, flags: 0, addr: 0,
|
|
1701
|
+
offset: offset, size: size, link: 0, info: 0, addralign: 1, entsize: 0)
|
|
1702
|
+
end
|
|
1703
|
+
|
|
1704
|
+
def shdr(name:, type:, flags:, addr:, offset:, size:, link:, info:, addralign:, entsize:)
|
|
1705
|
+
[name].pack("L<") + [type].pack("L<") + [flags].pack("Q<") + [addr].pack("Q<") +
|
|
1706
|
+
[offset].pack("Q<") + [size].pack("Q<") + [link].pack("L<") + [info].pack("L<") +
|
|
1707
|
+
[addralign].pack("Q<") + [entsize].pack("Q<")
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
def sym_entry(name:, info:, other:, shndx:, value:, size:)
|
|
1711
|
+
[name].pack("L<") + [info].pack("C") + [other].pack("C") +
|
|
1712
|
+
[shndx].pack("S<") + [value].pack("Q<") + [size].pack("Q<")
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
def patch32(buf, offset, value)
|
|
1716
|
+
buf[offset, 4] = [value & 0xFFFFFFFF].pack("L<")
|
|
1717
|
+
end
|
|
1718
|
+
|
|
1719
|
+
def patch64(buf, offset, value)
|
|
1720
|
+
buf[offset, 8] = [value & 0xFFFFFFFFFFFFFFFF].pack("Q<")
|
|
1721
|
+
end
|
|
1722
|
+
|
|
1723
|
+
def align(value, alignment)
|
|
1724
|
+
alignment <= 1 ? value : (value + alignment - 1) / alignment * alignment
|
|
1725
|
+
end
|
|
1726
|
+
|
|
1727
|
+
def pad_to(buffer, target)
|
|
1728
|
+
buffer << ("\0" * (target - buffer.bytesize)).b if buffer.bytesize < target
|
|
1729
|
+
end
|
|
1730
|
+
end
|
|
1731
|
+
end
|
|
1732
|
+
end
|