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 }