lib/tldr/src/formats/elf/object/model.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const elf = @import("../root.zig");
  3 
  4 const format = elf.format;
  5 const group_word_size = format.group_word_size;
  6 
  7 /// Names each defect that stops a description (the values describing a
  8 /// relocatable object) from becoming an object file, so callers of `plan` and
  9 /// `build` switch on these to tell a malformed description apart from running
 10 /// out of memory. Allocation failure is kept out of this set, and `Error` joins
 11 /// the two for `plan` and `build`.
 12 pub const DescriptionError = error{
 13     InvalidAlignment,
 14     InvalidSize,
 15     InvalidSectionIndex,
 16     SymbolOrder,
 17     TooManySections,
 18     TooManySymbols,
 19     SizeOverflow,
 20 };
 21 
 22 pub const Error = std.mem.Allocator.Error || DescriptionError;
 23 
 24 /// The struct holds the file header fields a producer picks: object type
 25 /// (relocatable by default), machine (x86-64 by default), entry address and
 26 /// flags. A description (the values describing a relocatable object) carries
 27 /// this struct to set the object's file header. The section table's offset,
 28 /// count and name table index come from the layout (the file offsets `plan`
 29 /// computes for each record), so a description has no fields for them.
 30 pub const Header = struct {
 31     type: std.elf.ET = .REL,
 32     machine: std.elf.EM = .X86_64,
 33     entry: u64 = 0,
 34     flags: u32 = 0,
 35 };
 36 
 37 /// Describes one section of a description (the values describing a relocatable
 38 /// object), so a caller lists one per section the object carries, in file
 39 /// order. Each entry `sections[i]` gets file index `i + 1`, because the writer
 40 /// places the empty section header at index 0 itself. A section of type
 41 /// `SHT_NOBITS` has no bytes and reserves `size` bytes of memory. Any other
 42 /// section is as long as its `bytes`, and `plan` refuses a nonzero `size` that
 43 /// differs from that length.
 44 pub const Section = struct {
 45     name: []const u8,
 46     section_type: u32 = std.elf.SHT_PROGBITS,
 47     flags: u64 = 0,
 48     alignment: u64 = 1,
 49     bytes: []const u8 = &.{},
 50     size: u64 = 0,
 51     address: u64 = 0,
 52     link: u32 = 0,
 53     info: u32 = 0,
 54     entry_size: u64 = 0,
 55 
 56     pub fn progbits(name: []const u8, bytes: []const u8, flags: u64, alignment: u64) Section {
 57         return .{
 58             .name = name,
 59             .section_type = std.elf.SHT_PROGBITS,
 60             .flags = flags | std.elf.SHF_ALLOC,
 61             .alignment = alignment,
 62             .bytes = bytes,
 63         };
 64     }
 65 
 66     pub fn nobits(name: []const u8, size: u64, flags: u64, alignment: u64) Section {
 67         return .{
 68             .name = name,
 69             .section_type = std.elf.SHT_NOBITS,
 70             .flags = flags | std.elf.SHF_ALLOC,
 71             .alignment = alignment,
 72             .size = size,
 73         };
 74     }
 75 
 76     pub fn mergeable(
 77         name: []const u8,
 78         bytes: []const u8,
 79         flags: u64,
 80         alignment: u64,
 81         entry_size: u64,
 82     ) Section {
 83         return .{
 84             .name = name,
 85             .section_type = std.elf.SHT_PROGBITS,
 86             .flags = flags | std.elf.SHF_ALLOC | std.elf.SHF_MERGE,
 87             .alignment = alignment,
 88             .bytes = bytes,
 89             .entry_size = entry_size,
 90         };
 91     }
 92 
 93     pub fn strings(name: []const u8, bytes: []const u8, alignment: u64) Section {
 94         return .{
 95             .name = name,
 96             .section_type = std.elf.SHT_PROGBITS,
 97             .flags = std.elf.SHF_ALLOC | std.elf.SHF_MERGE | std.elf.SHF_STRINGS,
 98             .alignment = alignment,
 99             .bytes = bytes,
100             .entry_size = 1,
101         };
102     }
103 
104     pub fn nonAlloc(
105         name: []const u8,
106         bytes: []const u8,
107         section_type: u32,
108         alignment: u64,
109     ) Section {
110         return .{
111             .name = name,
112             .section_type = section_type,
113             .alignment = alignment,
114             .bytes = bytes,
115         };
116     }
117 
118     /// Builds a COMDAT group section whose payload is `bytes`, which the caller
119     /// fills with `groupBytes` first, for a caller that needs sections kept or
120     /// dropped together. The caller states only the signature symbol's index,
121     /// and the writer points the section's link at the symbol table.
122     pub fn group(name: []const u8, bytes: []const u8, signature_symbol: u32) Section {
123         return .{
124             .name = name,
125             .section_type = std.elf.SHT_GROUP,
126             .alignment = group_word_size,
127             .bytes = bytes,
128             .info = signature_symbol,
129             .entry_size = group_word_size,
130         };
131     }
132 
133     pub fn isNoBits(self: Section) bool {
134         return self.section_type == std.elf.SHT_NOBITS;
135     }
136 
137     /// Returns the number of bytes the section takes in the file: zero for a
138     /// reservation, else the length of `bytes`. The layout (the file offsets
139     /// `plan` computes) uses it to advance the file offset past a section.
140     pub fn fileSize(self: Section) u64 {
141         if (self.isNoBits()) return 0;
142         return self.bytes.len;
143     }
144 
145     /// Returns the value written into the section header's `sh_size` field: the
146     /// reserved size for a NOBITS section, else the length of `bytes`.
147     pub fn wireSize(self: Section) u64 {
148         if (self.isNoBits()) return self.size;
149         return self.bytes.len;
150     }
151 };
152 
153 /// Writes a COMDAT group payload into `out`: the COMDAT flag word, then one
154 /// 32-bit word per member section index. The destination slice `out` holds at
155 /// least `(members.len + 1) * 4` bytes, which a debug assertion checks. The
156 /// function returns the filled prefix of `out`, ready to pass to
157 /// `Section.group`.
158 pub fn groupBytes(out: []u8, members: []const u16) []u8 {
159     const needed = (members.len + 1) * group_word_size;
160     std.debug.assert(out.len >= needed);
161     const bytes = out[0..needed];
162     format.writeU32(bytes, 0, std.elf.GRP_COMDAT);
163     for (members, 0..) |member, index| {
164         format.writeU32(bytes, (index + 1) * group_word_size, member);
165     }
166     return bytes;
167 }
168 
169 /// Describes one symbol of a description (the values describing a relocatable
170 /// object), so a description lists one per symbol table entry, locals first.
171 /// Each entry `symbols[i]` gets table index `i + 1`, because the writer places
172 /// the empty entry at index 0 itself. The writer adds no symbols of its own, so
173 /// a description that needs section symbols lists them. The design this
174 /// namespace follows names the section field `section`, but because Zig rejects
175 /// a struct with a field and a declaration of the same name and the `section`
176 /// constructor kept that name, the field is `section_index`, the name
177 /// `record.SymbolRecord` also uses.
178 pub const Symbol = struct {
179     name: []const u8 = "",
180     binding: u8 = std.elf.STB_LOCAL,
181     kind: u8 = std.elf.STT_NOTYPE,
182     other: u8 = 0,
183     section_index: u16 = std.elf.SHN_UNDEF,
184     value: u64 = 0,
185     size: u64 = 0,
186 
187     pub fn section(index: u16) Symbol {
188         return .{ .kind = std.elf.STT_SECTION, .section_index = index };
189     }
190 
191     pub fn function(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
192         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_FUNC, section_index, value, size);
193     }
194 
195     pub fn weakFunction(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
196         return defined(name, std.elf.STB_WEAK, std.elf.STT_FUNC, section_index, value, size);
197     }
198 
199     pub fn ifunc(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
200         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_GNU_IFUNC, section_index, value, size);
201     }
202 
203     pub fn object(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
204         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_OBJECT, section_index, value, size);
205     }
206 
207     pub fn localObject(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
208         return defined(name, std.elf.STB_LOCAL, std.elf.STT_OBJECT, section_index, value, size);
209     }
210 
211     pub fn gnuUniqueObject(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
212         const unique = std.elf.STB_GNU_UNIQUE;
213         return defined(name, unique, std.elf.STT_OBJECT, section_index, value, size);
214     }
215 
216     pub fn tlsObject(name: []const u8, section_index: u16, value: u64, size: u64) Symbol {
217         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_TLS, section_index, value, size);
218     }
219 
220     pub fn absoluteObject(name: []const u8, value: u64, size: u64) Symbol {
221         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_OBJECT, std.elf.SHN_ABS, value, size);
222     }
223 
224     /// Builds a global common symbol with section index `SHN_COMMON`, the
225     /// required alignment in `value`, and the block's size in `size`, for a
226     /// caller describing a C tentative definition or any common block. For a
227     /// symbol in `SHN_COMMON`, ELF defines the value field as the alignment.
228     pub fn commonObject(name: []const u8, alignment: u64, size: u64) Symbol {
229         const block = std.elf.SHN_COMMON;
230         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_OBJECT, block, alignment, size);
231     }
232 
233     pub fn undefinedObject(name: []const u8) Symbol {
234         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_OBJECT, std.elf.SHN_UNDEF, 0, 0);
235     }
236 
237     pub fn undefinedFunction(name: []const u8) Symbol {
238         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_FUNC, std.elf.SHN_UNDEF, 0, 0);
239     }
240 
241     pub fn weakUndefinedFunction(name: []const u8) Symbol {
242         return defined(name, std.elf.STB_WEAK, std.elf.STT_FUNC, std.elf.SHN_UNDEF, 0, 0);
243     }
244 
245     pub fn undefinedSymbol(name: []const u8) Symbol {
246         return defined(name, std.elf.STB_GLOBAL, std.elf.STT_NOTYPE, std.elf.SHN_UNDEF, 0, 0);
247     }
248 
249     pub fn isLocal(self: Symbol) bool {
250         return self.binding == std.elf.STB_LOCAL;
251     }
252 
253     pub fn info(self: Symbol) u8 {
254         return format.elfSymbolInfo(self.binding, self.kind);
255     }
256 
257     fn defined(
258         name: []const u8,
259         binding: u8,
260         kind: u8,
261         section_index: u16,
262         value: u64,
263         size: u64,
264     ) Symbol {
265         return .{
266             .name = name,
267             .binding = binding,
268             .kind = kind,
269             .section_index = section_index,
270             .value = value,
271             .size = size,
272         };
273     }
274 };
275 
276 /// Describes one relocation against a described section: the target section's
277 /// index, the offset inside it, the symbol index, the relocation type and the
278 /// addend. A caller lists one per relocation in the object. The one field
279 /// `plan` checks is the target index, since placing the record depends on it.
280 /// The symbol index, offset, type and addend reach the file unchecked, so a
281 /// test can build the malformed objects that the linker's own checks must
282 /// reject.
283 pub const Relocation = struct {
284     section: u16,
285     offset: u64,
286     symbol: u32,
287     type: u32,
288     addend: i64 = 0,
289 
290     pub fn x86_64(
291         section: u16,
292         offset: u64,
293         symbol: u32,
294         kind: std.elf.R_X86_64,
295         addend: i64,
296     ) Relocation {
297         return .{
298             .section = section,
299             .offset = offset,
300             .symbol = symbol,
301             .type = @backingInt(kind),
302             .addend = addend,
303         };
304     }
305 
306     pub fn info(self: Relocation) u64 {
307         return (@as(u64, self.symbol) << 32) | self.type;
308     }
309 };
310 
311 pub const Description = struct {
312     header: Header = .{},
313     sections: []const Section,
314     symbols: []const Symbol = &.{},
315     relocations: []const Relocation = &.{},
316 };
317 
318 test "ELF object sections separate their file size from their wire size" {
319     const progbits = Section.progbits(".text", "\x90\x90\x90\x90", std.elf.SHF_EXECINSTR, 16);
320     try std.testing.expectEqual(@as(u64, std.elf.SHT_PROGBITS), progbits.section_type);
321     const code_flags = std.elf.SHF_ALLOC | std.elf.SHF_EXECINSTR;
322     try std.testing.expectEqual(@as(u64, code_flags), progbits.flags);
323     try std.testing.expectEqual(@as(u64, 4), progbits.fileSize());
324     try std.testing.expectEqual(@as(u64, 4), progbits.wireSize());
325 
326     const reserved = Section.nobits(".bss", 4096, std.elf.SHF_WRITE, 8);
327     try std.testing.expect(reserved.isNoBits());
328     try std.testing.expectEqual(@as(u64, 0), reserved.fileSize());
329     try std.testing.expectEqual(@as(u64, 4096), reserved.wireSize());
330     try std.testing.expectEqual(@as(u64, std.elf.SHF_ALLOC | std.elf.SHF_WRITE), reserved.flags);
331 }
332 
333 test "ELF object sections carry their merge and group shape" {
334     const merged = Section.mergeable(".rodata.cst8", "01234567", 0, 8, 8);
335     try std.testing.expectEqual(@as(u64, std.elf.SHF_ALLOC | std.elf.SHF_MERGE), merged.flags);
336     try std.testing.expectEqual(@as(u64, 8), merged.entry_size);
337 
338     const text = Section.strings(".rodata.str1.1", "hi\x00", 1);
339     try std.testing.expectEqual(
340         @as(u64, std.elf.SHF_ALLOC | std.elf.SHF_MERGE | std.elf.SHF_STRINGS),
341         text.flags,
342     );
343     try std.testing.expectEqual(@as(u64, 1), text.entry_size);
344 
345     const notes = Section.nonAlloc(".comment", "tldr\x00", std.elf.SHT_PROGBITS, 1);
346     try std.testing.expectEqual(@as(u64, 0), notes.flags);
347 
348     var storage: [3 * 4]u8 = undefined;
349     const payload = groupBytes(&storage, &.{ 1, 2 });
350     try std.testing.expectEqual(@as(usize, 12), payload.len);
351     try std.testing.expectEqual(@as(u32, std.elf.GRP_COMDAT), format.readU32(payload, 0));
352     try std.testing.expectEqual(@as(u32, 1), format.readU32(payload, 4));
353     try std.testing.expectEqual(@as(u32, 2), format.readU32(payload, 8));
354 
355     const comdat = Section.group(".group", payload, 7);
356     try std.testing.expectEqual(@as(u32, std.elf.SHT_GROUP), comdat.section_type);
357     try std.testing.expectEqual(@as(u32, 7), comdat.info);
358     try std.testing.expectEqual(@as(u64, 4), comdat.alignment);
359     try std.testing.expectEqual(@as(u64, 4), comdat.entry_size);
360 }
361 
362 test "ELF object symbols fold their binding and kind into one info byte" {
363     try std.testing.expectEqual(@as(u8, 0x03), Symbol.section(2).info());
364     try std.testing.expectEqual(@as(u16, 2), Symbol.section(2).section_index);
365     try std.testing.expect(Symbol.section(2).isLocal());
366 
367     try std.testing.expectEqual(@as(u8, 0x12), Symbol.function("main", 1, 0, 4).info());
368     try std.testing.expectEqual(@as(u8, 0x22), Symbol.weakFunction("weak", 1, 0, 4).info());
369     try std.testing.expectEqual(@as(u8, 0x1a), Symbol.ifunc("resolve", 1, 0, 4).info());
370     try std.testing.expectEqual(@as(u8, 0x11), Symbol.object("data", 2, 0, 8).info());
371     try std.testing.expectEqual(@as(u8, 0x01), Symbol.localObject("private", 2, 0, 8).info());
372     try std.testing.expectEqual(@as(u8, 0xa1), Symbol.gnuUniqueObject("unique", 2, 0, 8).info());
373     try std.testing.expectEqual(@as(u8, 0x16), Symbol.tlsObject("thread", 3, 0, 8).info());
374     try std.testing.expectEqual(@as(u8, 0x10), Symbol.undefinedSymbol("plain").info() & 0xf0);
375 
376     const fixed = Symbol.absoluteObject("fixed", 9, 0);
377     try std.testing.expectEqual(@as(u16, std.elf.SHN_ABS), fixed.section_index);
378     const common = Symbol.commonObject("shared", 16, 32);
379     try std.testing.expectEqual(@as(u16, std.elf.SHN_COMMON), common.section_index);
380     try std.testing.expectEqual(@as(u64, 16), common.value);
381     try std.testing.expectEqual(@as(u64, 32), common.size);
382     const undefined_object = Symbol.undefinedObject("missing");
383     const undefined_function = Symbol.undefinedFunction("missing");
384     try std.testing.expectEqual(@as(u16, std.elf.SHN_UNDEF), undefined_object.section_index);
385     try std.testing.expectEqual(@as(u16, std.elf.SHN_UNDEF), undefined_function.section_index);
386     try std.testing.expect(!Symbol.weakUndefinedFunction("maybe").isLocal());
387 }
388 
389 test "ELF object relocations pack their symbol and type into one info word" {
390     const rela = Relocation.x86_64(1, 0x10, 3, .@"64", -8);
391     try std.testing.expectEqual(@as(u16, 1), rela.section);
392     try std.testing.expectEqual(@as(u64, 0x10), rela.offset);
393     try std.testing.expectEqual(@as(i64, -8), rela.addend);
394     try std.testing.expectEqual(@as(u64, (3 << 32) | 1), rela.info());
395     try std.testing.expectEqual(@as(u32, 3), format.Rela.symbolIndex(.{ .info = rela.info() }));
396     try std.testing.expectEqual(@as(u32, 1), format.Rela.relocationType(.{ .info = rela.info() }));
397 }