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 = §ions });
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 }