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