lib/tldr/src/formats/elf/record.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const builtin = @import("builtin");
2 const std = @import("std");
3 const byte = @import("byte.zig");
4
5 const readU16 = byte.readU16;
6 const readU32 = byte.readU32;
7 const readU64 = byte.readU64;
8 const writeU16 = byte.writeU16;
9 const writeU32 = byte.writeU32;
10 const writeU64 = byte.writeU64;
11
12 pub const ehdr_size = 64;
13 pub const phdr_size = 56;
14 pub const shdr_size = 64;
15 pub const sym_size = 24;
16 pub const rela_size = 24;
17 pub const group_word_size = 4;
18 pub const native_endian = builtin.cpu.arch.endian();
19 pub const build_id_note_desc_offset = 16;
20 pub const build_id_fast_desc_size = 8;
21 pub const build_id_sha1_desc_size = 20;
22 pub const eh_frame_hdr_fixed_size = 12;
23 pub const eh_frame_hdr_entry_size = 8;
24 pub const dwarf_eh_pe_udata4: u8 = 0x03;
25 pub const dwarf_eh_pe_sdata4: u8 = 0x0b;
26 pub const dwarf_eh_pe_pcrel: u8 = 0x10;
27 pub const dwarf_eh_pe_datarel: u8 = 0x30;
28
29 pub const parallel_relocation_threshold = 50_000;
30 pub const relocations_per_worker = 25_000;
31
32 pub const HeaderError = error{
33 InvalidElfHeader,
34 UnsupportedFormat,
35 };
36
37 /// The ELF64 file header fields a producer picks: object type, machine, entry
38 /// address, flags, and the offsets, counts and name table index of the program
39 /// and section header tables. `write` supplies the fields the format fixes: the
40 /// magic bytes, the 64-bit class, the little-endian encoding, version 1, and
41 /// the sizes of the file header, a program header and a section header, so the
42 /// struct has no fields for them.
43 pub const Header = struct {
44 type: std.elf.ET = .REL,
45 machine: std.elf.EM = .X86_64,
46 entry: u64 = 0,
47 flags: u32 = 0,
48 phoff: u64 = 0,
49 phnum: u16 = 0,
50 shoff: u64 = 0,
51 shnum: u16 = 0,
52 shstrndx: u16 = 0,
53
54 /// Fills the first 64 bytes of `buffer` with the header, zeroing the bytes
55 /// it does not set. A program header entry size is written only when
56 /// `phnum` is nonzero, so a relocatable object records no entry size for a
57 /// table it lacks.
58 pub fn write(self: Header, buffer: []u8) void {
59 const out = buffer[0..ehdr_size];
60 @memset(out, 0);
61 std.mem.copyForwards(u8, out[0..4], std.elf.MAGIC);
62 out[std.elf.EI_CLASS] = std.elf.ELFCLASS64;
63 out[std.elf.EI_DATA] = std.elf.ELFDATA2LSB;
64 out[std.elf.EI_VERSION] = 1;
65 writeU16(out, 16, @backingInt(self.type));
66 writeU16(out, 18, @backingInt(self.machine));
67 writeU32(out, 20, 1);
68 writeU64(out, 24, self.entry);
69 writeU64(out, 32, self.phoff);
70 writeU64(out, 40, self.shoff);
71 writeU32(out, 48, self.flags);
72 writeU16(out, 52, ehdr_size);
73 writeU16(out, 54, if (self.phnum == 0) 0 else phdr_size);
74 writeU16(out, 56, self.phnum);
75 writeU16(out, 58, shdr_size);
76 writeU16(out, 60, self.shnum);
77 writeU16(out, 62, self.shstrndx);
78 }
79 };
80
81 /// Decodes the file header at the start of `bytes`. The call returns
82 /// `error.InvalidElfHeader` for fewer than 64 bytes, wrong magic bytes, or
83 /// header or section entry sizes other than ELF64's, and
84 /// `error.UnsupportedFormat` for a 32-bit or big-endian file. Object type and
85 /// machine pass through unchecked, because accepting or refusing them is the
86 /// linker's decision and the record has no rule about them.
87 pub fn readHeader(bytes: []const u8) HeaderError!Header {
88 if (bytes.len < ehdr_size) return error.InvalidElfHeader;
89 if (!std.mem.eql(u8, bytes[0..4], std.elf.MAGIC)) return error.InvalidElfHeader;
90 if (bytes[std.elf.EI_CLASS] != std.elf.ELFCLASS64) return error.UnsupportedFormat;
91 if (bytes[std.elf.EI_DATA] != std.elf.ELFDATA2LSB) return error.UnsupportedFormat;
92 if (readU16(bytes, 52) != ehdr_size) return error.InvalidElfHeader;
93 if (readU16(bytes, 58) != shdr_size) return error.InvalidElfHeader;
94 return .{
95 .type = @fromBackingInt(@intCast(readU16(bytes, 16))),
96 .machine = @fromBackingInt(@intCast(readU16(bytes, 18))),
97 .entry = readU64(bytes, 24),
98 .flags = readU32(bytes, 48),
99 .phoff = readU64(bytes, 32),
100 .phnum = readU16(bytes, 56),
101 .shoff = readU64(bytes, 40),
102 .shnum = readU16(bytes, 60),
103 .shstrndx = readU16(bytes, 62),
104 };
105 }
106
107 pub const SectionHeader = extern struct {
108 name_offset: u32 = 0,
109 section_type: u32 = std.elf.SHT_NULL,
110 flags: u64 = 0,
111 address: u64 = 0,
112 offset: u64 = 0,
113 size: u64 = 0,
114 link: u32 = 0,
115 info: u32 = 0,
116 alignment: u64 = 0,
117 entry_size: u64 = 0,
118
119 pub fn write(self: SectionHeader, buffer: []u8, at: usize) void {
120 const out = buffer[at..][0..shdr_size];
121 if (native_endian == .little) {
122 @memcpy(out, std.mem.asBytes(&self));
123 return;
124 }
125 writeU32(out, 0, self.name_offset);
126 writeU32(out, 4, self.section_type);
127 writeU64(out, 8, self.flags);
128 writeU64(out, 16, self.address);
129 writeU64(out, 24, self.offset);
130 writeU64(out, 32, self.size);
131 writeU32(out, 40, self.link);
132 writeU32(out, 44, self.info);
133 writeU64(out, 48, self.alignment);
134 writeU64(out, 56, self.entry_size);
135 }
136 };
137
138 pub fn sectionIsAllocated(section: SectionHeader) bool {
139 return (section.flags & std.elf.SHF_ALLOC) != 0;
140 }
141
142 test "ELF parsed section header fits raw SHDR size" {
143 try std.testing.expectEqual(@as(usize, shdr_size), @sizeOf(SectionHeader));
144 try std.testing.expectEqual(@as(usize, 0), @offsetOf(SectionHeader, "name_offset"));
145 try std.testing.expectEqual(@as(usize, 4), @offsetOf(SectionHeader, "section_type"));
146 try std.testing.expectEqual(@as(usize, 8), @offsetOf(SectionHeader, "flags"));
147 try std.testing.expectEqual(@as(usize, 16), @offsetOf(SectionHeader, "address"));
148 try std.testing.expectEqual(@as(usize, 24), @offsetOf(SectionHeader, "offset"));
149 try std.testing.expectEqual(@as(usize, 32), @offsetOf(SectionHeader, "size"));
150 try std.testing.expectEqual(@as(usize, 40), @offsetOf(SectionHeader, "link"));
151 try std.testing.expectEqual(@as(usize, 44), @offsetOf(SectionHeader, "info"));
152 try std.testing.expectEqual(@as(usize, 48), @offsetOf(SectionHeader, "alignment"));
153 try std.testing.expectEqual(@as(usize, 56), @offsetOf(SectionHeader, "entry_size"));
154 }
155
156 /// The 24-byte on-disk form of an ELF64 symbol table entry, with each field at
157 /// its file offset. The record stores a symbol's name as an offset into the
158 /// symbol string table, and the parsed `Symbol` adds the name itself as a
159 /// slice.
160 pub const SymbolRecord = extern struct {
161 name_offset: u32 = 0,
162 info: u8 = 0,
163 other: u8 = 0,
164 section_index: u16 = std.elf.SHN_UNDEF,
165 value: u64 = 0,
166 size: u64 = 0,
167
168 pub fn write(self: SymbolRecord, buffer: []u8, at: usize) void {
169 const out = buffer[at..][0..sym_size];
170 if (native_endian == .little) {
171 @memcpy(out, std.mem.asBytes(&self));
172 return;
173 }
174 writeU32(out, 0, self.name_offset);
175 out[4] = self.info;
176 out[5] = self.other;
177 writeU16(out, 6, self.section_index);
178 writeU64(out, 8, self.value);
179 writeU64(out, 16, self.size);
180 }
181 };
182
183 test "ELF symbol record fits raw SYM size" {
184 try std.testing.expectEqual(@as(usize, sym_size), @sizeOf(SymbolRecord));
185 try std.testing.expectEqual(@as(usize, 0), @offsetOf(SymbolRecord, "name_offset"));
186 try std.testing.expectEqual(@as(usize, 4), @offsetOf(SymbolRecord, "info"));
187 try std.testing.expectEqual(@as(usize, 5), @offsetOf(SymbolRecord, "other"));
188 try std.testing.expectEqual(@as(usize, 6), @offsetOf(SymbolRecord, "section_index"));
189 try std.testing.expectEqual(@as(usize, 8), @offsetOf(SymbolRecord, "value"));
190 try std.testing.expectEqual(@as(usize, 16), @offsetOf(SymbolRecord, "size"));
191 }
192
193 pub const Symbol = struct {
194 name_offset: u32,
195 info: u8,
196 other: u8,
197 section_index: u16,
198 value: u64,
199 size: u64,
200 name: []const u8 = "",
201
202 pub fn binding(self: Symbol) u8 {
203 return self.info >> 4;
204 }
205
206 pub fn kind(self: Symbol) u8 {
207 return self.info & 0xf;
208 }
209
210 pub fn isUndefined(self: Symbol) bool {
211 return self.section_index == std.elf.SHN_UNDEF;
212 }
213
214 pub fn isWeakUndefined(self: Symbol) bool {
215 return self.isUndefined() and self.binding() == std.elf.STB_WEAK;
216 }
217
218 pub fn isCommon(self: Symbol) bool {
219 return self.section_index == std.elf.SHN_COMMON;
220 }
221
222 pub fn isAbsolute(self: Symbol) bool {
223 return self.section_index == std.elf.SHN_ABS;
224 }
225
226 pub fn isSection(self: Symbol) bool {
227 return self.kind() == std.elf.STT_SECTION;
228 }
229
230 pub fn isGlobalDefinition(self: Symbol) bool {
231 if (self.isUndefined() or self.isSection() or self.name.len == 0) return false;
232 return symbolBindingIsExternalDefinition(self.binding());
233 }
234
235 pub fn isGnuUnique(self: Symbol) bool {
236 return self.binding() == std.elf.STB_GNU_UNIQUE;
237 }
238 };
239
240 pub fn symbolBindingIsExternalDefinition(binding: u8) bool {
241 return binding == std.elf.STB_GLOBAL or
242 binding == std.elf.STB_WEAK or
243 binding == std.elf.STB_GNU_UNIQUE;
244 }
245
246 pub const Rela = extern struct {
247 offset: u64 = 0,
248 info: u64 = 0,
249 addend: i64 = 0,
250
251 pub fn symbolIndex(self: Rela) u32 {
252 return @intCast(self.info >> 32);
253 }
254
255 pub fn relocationType(self: Rela) u32 {
256 return @truncate(self.info);
257 }
258
259 pub fn write(self: Rela, buffer: []u8, at: usize) void {
260 const out = buffer[at..][0..rela_size];
261 if (native_endian == .little) {
262 @memcpy(out, std.mem.asBytes(&self));
263 return;
264 }
265 writeU64(out, 0, self.offset);
266 writeU64(out, 8, self.info);
267 writeU64(out, 16, @bitCast(self.addend));
268 }
269 };
270
271 test "ELF parsed relocation record fits raw RELA size" {
272 try std.testing.expectEqual(@as(usize, rela_size), @sizeOf(Rela));
273 try std.testing.expectEqual(@as(usize, 0), @offsetOf(Rela, "offset"));
274 try std.testing.expectEqual(@as(usize, 8), @offsetOf(Rela, "info"));
275 try std.testing.expectEqual(@as(usize, 16), @offsetOf(Rela, "addend"));
276 }
277
278 test "ELF file header pins its fixed fields and round trips" {
279 var buffer = @as([ehdr_size]u8, @splat(0xaa));
280 const written = Header{
281 .type = .EXEC,
282 .machine = .X86_64,
283 .entry = 0x401000,
284 .flags = 0,
285 .phoff = ehdr_size,
286 .phnum = 3,
287 .shoff = 0x2000,
288 .shnum = 7,
289 .shstrndx = 6,
290 };
291 written.write(&buffer);
292
293 try std.testing.expectEqualSlices(u8, std.elf.MAGIC, buffer[0..4]);
294 try std.testing.expectEqual(@as(u8, std.elf.ELFCLASS64), buffer[std.elf.EI_CLASS]);
295 try std.testing.expectEqual(@as(u8, std.elf.ELFDATA2LSB), buffer[std.elf.EI_DATA]);
296 try std.testing.expectEqual(@as(u8, 1), buffer[std.elf.EI_VERSION]);
297 try std.testing.expectEqual(@as(u32, 1), readU32(&buffer, 20));
298 try std.testing.expectEqual(@as(u16, ehdr_size), readU16(&buffer, 52));
299 try std.testing.expectEqual(@as(u16, phdr_size), readU16(&buffer, 54));
300 try std.testing.expectEqual(@as(u16, shdr_size), readU16(&buffer, 58));
301 for (buffer[8..16]) |pad| try std.testing.expectEqual(@as(u8, 0), pad);
302
303 try std.testing.expectEqual(written, try readHeader(&buffer));
304 }
305
306 test "ELF file header omits a program header size without program headers" {
307 var buffer = @as([ehdr_size]u8, @splat(0xaa));
308 const written = Header{ .shoff = 0x40, .shnum = 5, .shstrndx = 4 };
309 written.write(&buffer);
310
311 try std.testing.expectEqual(@as(u16, 0), readU16(&buffer, 54));
312 try std.testing.expectEqual(@as(u16, 0), readU16(&buffer, 56));
313 try std.testing.expectEqual(@as(u16, @backingInt(std.elf.ET.REL)), readU16(&buffer, 16));
314 try std.testing.expectEqual(written, try readHeader(&buffer));
315 }
316
317 test "ELF file header decode rejects a foreign record" {
318 var buffer = @as([ehdr_size]u8, @splat(0));
319 (Header{ .shnum = 1 }).write(&buffer);
320 try std.testing.expectError(error.InvalidElfHeader, readHeader(buffer[0 .. ehdr_size - 1]));
321
322 buffer[std.elf.EI_CLASS] = std.elf.ELFCLASS32;
323 try std.testing.expectError(error.UnsupportedFormat, readHeader(&buffer));
324 buffer[std.elf.EI_CLASS] = std.elf.ELFCLASS64;
325 buffer[std.elf.EI_DATA] = std.elf.ELFDATA2MSB;
326 try std.testing.expectError(error.UnsupportedFormat, readHeader(&buffer));
327
328 buffer[std.elf.EI_DATA] = std.elf.ELFDATA2LSB;
329 buffer[0] = 'x';
330 try std.testing.expectError(error.InvalidElfHeader, readHeader(&buffer));
331
332 (Header{ .shnum = 1 }).write(&buffer);
333 writeU16(&buffer, 58, shdr_size + 1);
334 try std.testing.expectError(error.InvalidElfHeader, readHeader(&buffer));
335 }
336
337 test "ELF records write their wire bytes" {
338 var buffer = @as([shdr_size + sym_size + rela_size]u8, @splat(0xee));
339 const section = SectionHeader{
340 .name_offset = 0x11,
341 .section_type = std.elf.SHT_PROGBITS,
342 .flags = std.elf.SHF_ALLOC,
343 .address = 0x400000,
344 .offset = 0x1000,
345 .size = 0x20,
346 .link = 3,
347 .info = 4,
348 .alignment = 16,
349 .entry_size = 0,
350 };
351 section.write(&buffer, 0);
352 const symbol = SymbolRecord{
353 .name_offset = 0x22,
354 .info = 0x12,
355 .other = 2,
356 .section_index = 5,
357 .value = 0x30,
358 .size = 0x40,
359 };
360 symbol.write(&buffer, shdr_size);
361 const rela = Rela{ .offset = 0x50, .info = 0x0000000200000001, .addend = -8 };
362 rela.write(&buffer, shdr_size + sym_size);
363
364 try std.testing.expectEqual(@as(u32, 0x11), readU32(&buffer, 0));
365 try std.testing.expectEqual(@as(u32, std.elf.SHT_PROGBITS), readU32(&buffer, 4));
366 try std.testing.expectEqual(@as(u64, std.elf.SHF_ALLOC), readU64(&buffer, 8));
367 try std.testing.expectEqual(@as(u64, 0x400000), readU64(&buffer, 16));
368 try std.testing.expectEqual(@as(u64, 0x1000), readU64(&buffer, 24));
369 try std.testing.expectEqual(@as(u64, 0x20), readU64(&buffer, 32));
370 try std.testing.expectEqual(@as(u32, 3), readU32(&buffer, 40));
371 try std.testing.expectEqual(@as(u32, 4), readU32(&buffer, 44));
372 try std.testing.expectEqual(@as(u64, 16), readU64(&buffer, 48));
373 try std.testing.expectEqual(@as(u64, 0), readU64(&buffer, 56));
374 try std.testing.expectEqual(@as(u32, 0x22), readU32(&buffer, shdr_size + 0));
375 try std.testing.expectEqual(@as(u8, 0x12), buffer[shdr_size + 4]);
376 try std.testing.expectEqual(@as(u8, 2), buffer[shdr_size + 5]);
377 try std.testing.expectEqual(@as(u16, 5), readU16(&buffer, shdr_size + 6));
378 try std.testing.expectEqual(@as(u64, 0x30), readU64(&buffer, shdr_size + 8));
379 try std.testing.expectEqual(@as(u64, 0x40), readU64(&buffer, shdr_size + 16));
380 const rela_at = shdr_size + sym_size;
381 try std.testing.expectEqual(@as(u64, 0x50), readU64(&buffer, rela_at + 0));
382 try std.testing.expectEqual(@as(u64, 0x0000000200000001), readU64(&buffer, rela_at + 8));
383 try std.testing.expectEqual(@as(i64, -8), @as(i64, @bitCast(readU64(&buffer, rela_at + 16))));
384 try std.testing.expectEqual(@as(u32, 2), rela.symbolIndex());
385 try std.testing.expectEqual(@as(u32, 1), rela.relocationType());
386 }