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

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const elf = @import("../root.zig");
  3 const model = @import("model.zig");
  4 const layout_plan = @import("layout.zig");
  5 
  6 const Allocator = std.mem.Allocator;
  7 const format = elf.format;
  8 const shdr_size = format.shdr_size;
  9 const sym_size = format.sym_size;
 10 const rela_size = format.rela_size;
 11 const copyInto = format.copyInto;
 12 const Description = model.Description;
 13 const Error = model.Error;
 14 const Layout = layout_plan.Layout;
 15 
 16 /// Writes one described object into `out`, which is exactly `layout.size`
 17 /// bytes, using the layout (the planned file offsets for every record). A
 18 /// caller that owns its output buffer calls this function after `plan` to write
 19 /// the description (a value listing its header fields, sections, symbols and
 20 /// relocations), and `build` calls it for everyone else. The call allocates
 21 /// nothing and does no arithmetic that can fail, because `plan` already proved
 22 /// every offset. The function zeroes `out` first, so the padding between
 23 /// records is zero. Each described record is written in one bounded pass.
 24 pub fn write(description: Description, layout: Layout, out: []u8) void {
 25     std.debug.assert(out.len == layout.size);
 26     @memset(out, 0);
 27 
 28     (format.Header{
 29         .type = description.header.type,
 30         .machine = description.header.machine,
 31         .entry = description.header.entry,
 32         .flags = description.header.flags,
 33         .shoff = layout.shoff,
 34         .shnum = layout.shnum,
 35         .shstrndx = layout.shstrtab_index,
 36     }).write(out);
 37 
 38     for (description.sections, 0..) |section, index| {
 39         copyInto(out, @intCast(layout.section_offsets[index]), section.bytes);
 40     }
 41     writeRelocations(description, layout, out);
 42     writeSymbols(description, layout, out);
 43     writeSectionHeaders(description, layout, out);
 44 }
 45 
 46 /// Plans the layout (the planned file offsets for every record), allocates the
 47 /// output once at the planned size, writes it, and frees the layout, for tests
 48 /// and tools that want the object's bytes in one call. The returned bytes
 49 /// belong to the caller, who frees them with the same allocator.
 50 pub fn build(allocator: Allocator, description: Description) Error![]u8 {
 51     var layout = try Layout.plan(allocator, description);
 52     defer layout.deinit(allocator);
 53     const out = try allocator.alloc(u8, layout.size);
 54     write(description, layout, out);
 55     return out;
 56 }
 57 
 58 /// Writes the relocation section of each described section that has
 59 /// relocations, for `write` to emit every relocation section. The function
 60 /// walks `layout.order` once, because that order already groups relocations by
 61 /// ascending target and keeps input order within a target.
 62 fn writeRelocations(description: Description, layout: Layout, out: []u8) void {
 63     var placed: usize = 0;
 64     for (description.sections, 0..) |_, index| {
 65         const count: usize = @intCast(layout.rela_counts[index]);
 66         if (count == 0) continue;
 67         const start: usize = @intCast(layout.rela_starts[index]);
 68         var slot: usize = 0;
 69         while (slot < count) : (slot += 1) {
 70             const relocation = description.relocations[@intCast(layout.order[placed + slot])];
 71             (format.Rela{
 72                 .offset = relocation.offset,
 73                 .info = relocation.info(),
 74                 .addend = relocation.addend,
 75             }).write(out, start + slot * rela_size);
 76         }
 77         placed += count;
 78     }
 79     std.debug.assert(placed == layout.order.len);
 80 }
 81 
 82 /// Writes `.symtab` and `.strtab` in one pass, for `write` to emit the symbol
 83 /// table. One cursor gives both each name's position in the name table and the
 84 /// offset stored in its symbol entry, so the two always agree.
 85 fn writeSymbols(description: Description, layout: Layout, out: []u8) void {
 86     const table = out[@intCast(layout.symtab_offset)..];
 87     const names = out[@intCast(layout.strtab_offset)..];
 88     (format.SymbolRecord{}).write(table, 0);
 89 
 90     var cursor: usize = 1;
 91     for (description.symbols, 0..) |symbol, index| {
 92         var name_offset: u32 = 0;
 93         if (symbol.name.len != 0) {
 94             name_offset = @intCast(cursor);
 95             copyInto(names, cursor, symbol.name);
 96             cursor += symbol.name.len + 1;
 97         }
 98         (format.SymbolRecord{
 99             .name_offset = name_offset,
100             .info = symbol.info(),
101             .other = symbol.other,
102             .section_index = symbol.section_index,
103             .value = symbol.value,
104             .size = symbol.size,
105         }).write(table, (index + 1) * sym_size);
106     }
107     std.debug.assert(cursor == layout.strtab_size);
108 }
109 
110 /// Writes the section headers and `.shstrtab` in one pass, with one cursor for
111 /// both, the same way `writeSymbols` pairs its two tables, for `write` to emit
112 /// the section header table.
113 fn writeSectionHeaders(description: Description, layout: Layout, out: []u8) void {
114     const table = out[@intCast(layout.shoff)..];
115     const names = out[@intCast(layout.shstrtab_offset)..];
116     (format.SectionHeader{}).write(table, 0);
117 
118     var cursor: usize = 1;
119     for (description.sections, 0..) |section, index| {
120         copyInto(names, cursor, section.name);
121         (format.SectionHeader{
122             .name_offset = @intCast(cursor),
123             .section_type = section.section_type,
124             .flags = section.flags,
125             .address = section.address,
126             .offset = layout.section_offsets[index],
127             .size = section.wireSize(),
128             .link = sectionLink(section, layout),
129             .info = section.info,
130             .alignment = section.alignment,
131             .entry_size = section.entry_size,
132         }).write(table, (index + 1) * shdr_size);
133         cursor += section.name.len + 1;
134     }
135 
136     var rela_index: usize = description.sections.len + 1;
137     for (description.sections, 0..) |section, index| {
138         const count = layout.rela_counts[index];
139         if (count == 0) continue;
140         copyInto(names, cursor, layout_plan.rela_name_prefix);
141         copyInto(names, cursor + layout_plan.rela_name_prefix.len, section.name);
142         (format.SectionHeader{
143             .name_offset = @intCast(cursor),
144             .section_type = std.elf.SHT_RELA,
145             .offset = layout.rela_starts[index],
146             .size = count * rela_size,
147             .link = layout.symtab_index,
148             .info = @intCast(index + 1),
149             .alignment = 8,
150             .entry_size = rela_size,
151         }).write(table, rela_index * shdr_size);
152         cursor += layout_plan.rela_name_prefix.len + section.name.len + 1;
153         rela_index += 1;
154     }
155     std.debug.assert(rela_index == layout.symtab_index);
156 
157     writeTrailingHeaders(layout, table, names, cursor);
158 }
159 
160 /// Writes the three headers every object ends with, for `writeSectionHeaders`
161 /// to finish the table, in this fixed order: those of `.symtab`, `.strtab` and
162 /// `.shstrtab`.
163 fn writeTrailingHeaders(layout: Layout, table: []u8, names: []u8, start: usize) void {
164     var cursor = start;
165     copyInto(names, cursor, layout_plan.symtab_name);
166     (format.SectionHeader{
167         .name_offset = @intCast(cursor),
168         .section_type = std.elf.SHT_SYMTAB,
169         .offset = layout.symtab_offset,
170         .size = layout.symtab_size,
171         .link = layout.strtab_index,
172         .info = layout.first_global,
173         .alignment = 8,
174         .entry_size = sym_size,
175     }).write(table, @as(usize, layout.symtab_index) * shdr_size);
176     cursor += layout_plan.symtab_name.len + 1;
177 
178     copyInto(names, cursor, layout_plan.strtab_name);
179     (format.SectionHeader{
180         .name_offset = @intCast(cursor),
181         .section_type = std.elf.SHT_STRTAB,
182         .offset = layout.strtab_offset,
183         .size = layout.strtab_size,
184         .alignment = 1,
185     }).write(table, @as(usize, layout.strtab_index) * shdr_size);
186     cursor += layout_plan.strtab_name.len + 1;
187 
188     copyInto(names, cursor, layout_plan.shstrtab_name);
189     (format.SectionHeader{
190         .name_offset = @intCast(cursor),
191         .section_type = std.elf.SHT_STRTAB,
192         .offset = layout.shstrtab_offset,
193         .size = layout.shstrtab_size,
194         .alignment = 1,
195     }).write(table, @as(usize, layout.shstrtab_index) * shdr_size);
196     cursor += layout_plan.shstrtab_name.len + 1;
197     std.debug.assert(cursor == layout.shstrtab_size);
198 }
199 
200 /// Returns the symbol table's index for a group section, because the group's
201 /// `sh_info` names its signature symbol by an index into that table.
202 /// `writeSectionHeaders` calls this function for each described section's
203 /// `sh_link` field. Every other section keeps the link stated in its
204 /// description (a value listing the object's header fields, sections, symbols
205 /// and relocations).
206 fn sectionLink(section: Section, layout: Layout) u32 {
207     if (section.section_type == std.elf.SHT_GROUP) return layout.symtab_index;
208     return section.link;
209 }
210 
211 const Section = model.Section;
212 const Symbol = model.Symbol;
213 const Relocation = model.Relocation;
214 
215 test "ELF object writer emits a relocatable header without program headers" {
216     const allocator = std.testing.allocator;
217     const sections = [_]Section{Section.progbits(".text", "\xc3", 0, 1)};
218     const bytes = try build(allocator, .{ .sections = &sections });
219     defer allocator.free(bytes);
220 
221     const header = try format.readHeader(bytes);
222     try std.testing.expectEqual(std.elf.ET.REL, header.type);
223     try std.testing.expectEqual(std.elf.EM.X86_64, header.machine);
224     try std.testing.expectEqual(@as(u64, 0), header.entry);
225     try std.testing.expectEqual(@as(u64, 0), header.phoff);
226     try std.testing.expectEqual(@as(u16, 0), header.phnum);
227     try std.testing.expectEqual(@as(u16, 0), format.readU16(bytes, 54));
228     try std.testing.expectEqual(@as(u16, 1 + layout_plan.fixed_section_count), header.shnum);
229 }
230 
231 test "ELF object writer places sections, symbols, and relocations where the layout says" {
232     const allocator = std.testing.allocator;
233     const description = Description{
234         .sections = &.{
235             Section.progbits(".text", "\xc3\x90\x90\x90", std.elf.SHF_EXECINSTR, 16),
236             Section.nobits(".bss", 128, std.elf.SHF_WRITE, 8),
237         },
238         .symbols = &.{
239             Symbol.section(1),
240             Symbol.function("_start", 1, 0, 4),
241             Symbol.undefinedFunction("helper"),
242         },
243         .relocations = &.{Relocation.x86_64(1, 1, 3, .PLT32, -4)},
244     };
245     const bytes = try build(allocator, description);
246     defer allocator.free(bytes);
247 
248     const view = try elf.view.View.parse(bytes);
249     const text = (try view.find(".text")).?;
250     try std.testing.expectEqualStrings("\xc3\x90\x90\x90", try view.sectionBytes(text.header));
251     try std.testing.expectEqual(@as(u64, 16), text.header.alignment);
252 
253     const reserved = (try view.find(".bss")).?;
254     try std.testing.expectEqual(@as(u64, 128), reserved.header.size);
255     try std.testing.expectEqual(@as(usize, 0), (try view.sectionBytes(reserved.header)).len);
256 
257     const symtab = (try view.find(".symtab")).?;
258     try std.testing.expectEqual(@as(u32, 2), symtab.header.info);
259     try std.testing.expectEqual(@as(usize, 4), try view.symbolCount(symtab.header));
260     const strtab = view.section(@intCast(symtab.header.link));
261     const start = try view.symbol(symtab.header, 2);
262     try std.testing.expectEqualStrings("_start", try view.string(strtab, start.name_offset));
263     try std.testing.expectEqual(@as(u8, 0x12), start.info);
264     const section_symbol = try view.symbol(symtab.header, 1);
265     try std.testing.expectEqualStrings("", try view.string(strtab, section_symbol.name_offset));
266 
267     const rela = (try view.find(".rela.text")).?;
268     try std.testing.expectEqual(@as(u32, std.elf.SHT_RELA), rela.header.section_type);
269     try std.testing.expectEqual(@as(u32, 1), rela.header.info);
270     try std.testing.expectEqual(@as(u32, symtab.index), rela.header.link);
271     const rela_bytes = try view.sectionBytes(rela.header);
272     try std.testing.expectEqual(@as(usize, rela_size), rela_bytes.len);
273     try std.testing.expectEqual(@as(u64, 1), format.readU64(rela_bytes, 0));
274     try std.testing.expectEqual(@as(u64, (3 << 32) | 4), format.readU64(rela_bytes, 8));
275     try std.testing.expectEqual(@as(i64, -4), @as(i64, @bitCast(format.readU64(rela_bytes, 16))));
276 }
277 
278 test "ELF object writer points a group section at the symbol table" {
279     const allocator = std.testing.allocator;
280     var payload: [2 * 4]u8 = undefined;
281     const description = Description{
282         .sections = &.{
283             Section.progbits(".text.inline", "\xc3", std.elf.SHF_EXECINSTR, 1),
284             Section.group(".group", model.groupBytes(&payload, &.{1}), 1),
285         },
286         .symbols = &.{Symbol.function("inlined", 1, 0, 1)},
287     };
288     const bytes = try build(allocator, description);
289     defer allocator.free(bytes);
290 
291     const view = try elf.view.View.parse(bytes);
292     const group = (try view.find(".group")).?;
293     const symtab = (try view.find(".symtab")).?;
294     try std.testing.expectEqual(@as(u32, symtab.index), group.header.link);
295     try std.testing.expectEqual(@as(u32, 1), group.header.info);
296     try std.testing.expectEqual(@as(u64, 4), group.header.entry_size);
297     const group_bytes = try view.sectionBytes(group.header);
298     try std.testing.expectEqual(@as(u32, std.elf.GRP_COMDAT), format.readU32(group_bytes, 0));
299     try std.testing.expectEqual(@as(u32, 1), format.readU32(group_bytes, 4));
300 }
301 
302 test "ELF object writer zero fills the padding it leaves between sections" {
303     const allocator = std.testing.allocator;
304     const description = Description{
305         .sections = &.{
306             Section.progbits(".text", "\xc3", std.elf.SHF_EXECINSTR, 1),
307             Section.progbits(".data", "\x01", std.elf.SHF_WRITE, 64),
308         },
309     };
310     const bytes = try build(allocator, description);
311     defer allocator.free(bytes);
312 
313     const view = try elf.view.View.parse(bytes);
314     const data = (try view.find(".data")).?;
315     try std.testing.expectEqual(@as(u64, 128), data.header.offset);
316     for (bytes[65..128]) |pad| try std.testing.expectEqual(@as(u8, 0), pad);
317 }