lib/tldr/src/formats/elf/ehframe/header/writer.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const root = @import("../../../../root.zig");
3 const elf = @import("../../root.zig");
4 const ehframe = @import("../root.zig");
5 const reservation = @import("reservation.zig");
6
7 const Allocator = std.mem.Allocator;
8 const model = root.model;
9 const addressing = elf.addressing;
10 const format = elf.format;
11 const layout = elf.layout;
12 const output_section = elf.output_section;
13 const parser = elf.parser;
14 const ObjectFile = parser.ObjectFile;
15 const ObjectLayout = layout.ObjectLayout;
16 const OutputSection = layout.OutputSection;
17 const GlobalSymbol = layout.GlobalSymbol;
18 const SymbolAddressCache = addressing.Cache;
19 const Rela = format.Rela;
20 const OutputSectionKind = output_section.OutputSectionKind;
21 const eh_frame_hdr_fixed_size = format.eh_frame_hdr_fixed_size;
22 const eh_frame_hdr_entry_size = format.eh_frame_hdr_entry_size;
23 const dwarf_eh_pe_udata4 = format.dwarf_eh_pe_udata4;
24 const dwarf_eh_pe_sdata4 = format.dwarf_eh_pe_sdata4;
25 const dwarf_eh_pe_pcrel = format.dwarf_eh_pe_pcrel;
26 const dwarf_eh_pe_datarel = format.dwarf_eh_pe_datarel;
27 const contributionAt = layout.contributionAt;
28 const ehFrameSectionLayout = layout.ehFrameSection;
29 const sectionBytes = format.sectionBytes;
30 const readU32 = format.readU32;
31 const writeU32 = format.writeU32;
32 const requireRange = format.requireRange;
33 const relocationTargetAddress = addressing.relocationTarget;
34 const record = ehframe.record;
35 const section = ehframe.section;
36
37 const Entry = struct {
38 start_ip: u64,
39 fde_address: u64,
40 };
41
42 pub fn write(
43 scratch: Allocator,
44 image: []u8,
45 objects: []const ObjectFile,
46 layouts: []const ObjectLayout,
47 output_sections: []const OutputSection,
48 globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
49 symbol_addresses: *SymbolAddressCache,
50 options: model.LinkOptions,
51 ) model.Error!void {
52 if (!options.eh_frame_header) return;
53 const header = output_sections[@backingInt(OutputSectionKind.eh_frame_hdr)];
54 if (header.logicalSize() == 0) return;
55 const eh_frame_start = startAddress(objects, layouts, output_sections) orelse return error.MissingSection;
56
57 var entries = std.ArrayListUnmanaged(Entry).empty;
58 defer entries.deinit(scratch);
59 try collectEntries(scratch, &entries, objects, layouts, output_sections, globals, symbol_addresses);
60 if (try reservation.size(entries.items.len) != header.logicalSize()) return error.InvalidRange;
61
62 std.mem.sort(Entry, entries.items, {}, compareEntries);
63 try requireRange(image, header.file_offset, header.logicalSize());
64 const start: usize = @intCast(header.file_offset);
65 image[start + 0] = 1;
66 image[start + 1] = dwarf_eh_pe_pcrel | dwarf_eh_pe_sdata4;
67 image[start + 2] = dwarf_eh_pe_udata4;
68 image[start + 3] = dwarf_eh_pe_datarel | dwarf_eh_pe_sdata4;
69 writeU32(image, start + 4, @bitCast(try elf.relocation.checkedI32(@as(i128, @intCast(eh_frame_start)) - @as(i128, @intCast(header.address + 4)))));
70 writeU32(image, start + 8, try elf.relocation.checkedU32(@intCast(entries.items.len)));
71
72 var cursor = start + eh_frame_hdr_fixed_size;
73 for (entries.items) |entry| {
74 writeU32(image, cursor + 0, @bitCast(try elf.relocation.checkedI32(@as(i128, @intCast(entry.start_ip)) - @as(i128, @intCast(header.address)))));
75 writeU32(image, cursor + 4, @bitCast(try elf.relocation.checkedI32(@as(i128, @intCast(entry.fde_address)) - @as(i128, @intCast(header.address)))));
76 cursor += eh_frame_hdr_entry_size;
77 }
78 }
79
80 fn collectEntries(
81 allocator: Allocator,
82 entries: *std.ArrayListUnmanaged(Entry),
83 objects: []const ObjectFile,
84 layouts: []const ObjectLayout,
85 output_sections: []const OutputSection,
86 globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
87 symbol_addresses: *SymbolAddressCache,
88 ) model.Error!void {
89 for (objects, 0..) |object, object_index| {
90 const object_layout = layouts[object_index];
91 for (object.sections, 0..) |input_section, section_index| {
92 const contribution = contributionAt(object_layout.sections, section_index) orelse continue;
93 if (!section.is(object, section_index, input_section)) continue;
94 const output = output_sections[contribution.outputIndex()];
95 const section_address = output.address + contribution.offset;
96 const bytes = try sectionBytes(object.bytes, input_section);
97 if (ehFrameSectionLayout(object_layout, section_index)) |eh_frame_section| {
98 var cursor = record.Cursor.init(eh_frame_section.relocations);
99 for (eh_frame_section.pieces) |piece| {
100 try appendEntry(
101 allocator,
102 entries,
103 objects,
104 layouts,
105 output_sections,
106 globals,
107 symbol_addresses,
108 object_index,
109 bytes,
110 piece.input_offset,
111 piece.size,
112 piece.output_offset,
113 &cursor,
114 section_address,
115 );
116 }
117 continue;
118 }
119 try collectRawEntries(
120 allocator,
121 entries,
122 objects,
123 layouts,
124 output_sections,
125 globals,
126 symbol_addresses,
127 object_index,
128 bytes,
129 object.relocationsForSection(section_index),
130 section_address,
131 );
132 }
133 }
134 }
135
136 fn collectRawEntries(
137 allocator: Allocator,
138 entries: *std.ArrayListUnmanaged(Entry),
139 objects: []const ObjectFile,
140 layouts: []const ObjectLayout,
141 output_sections: []const OutputSection,
142 globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
143 symbol_addresses: *SymbolAddressCache,
144 object_index: usize,
145 bytes: []const u8,
146 relocations: []const Rela,
147 section_address: u64,
148 ) model.Error!void {
149 var cursor = record.Cursor.init(relocations);
150 var input_offset: u64 = 0;
151 while (input_offset < bytes.len) {
152 const record_start: usize = @intCast(input_offset);
153 if (bytes.len - record_start < 4) return error.InvalidObject;
154 const record_length = readU32(bytes, record_start);
155 if (record_length == 0xffffffff) return error.UnsupportedFormat;
156 const record_size = @as(u64, record_length) + 4;
157 if (record_size < 4) return error.InvalidObject;
158 if (input_offset > std.math.maxInt(u64) - record_size) return error.InvalidObject;
159 const record_end = input_offset + record_size;
160 if (record_end > bytes.len) return error.InvalidObject;
161 try appendEntry(
162 allocator,
163 entries,
164 objects,
165 layouts,
166 output_sections,
167 globals,
168 symbol_addresses,
169 object_index,
170 bytes,
171 input_offset,
172 record_size,
173 input_offset,
174 &cursor,
175 section_address,
176 );
177 if (record_length == 0) break;
178 input_offset = record_end;
179 }
180 }
181
182 fn appendEntry(
183 allocator: Allocator,
184 entries: *std.ArrayListUnmanaged(Entry),
185 objects: []const ObjectFile,
186 layouts: []const ObjectLayout,
187 output_sections: []const OutputSection,
188 globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
189 symbol_addresses: *SymbolAddressCache,
190 object_index: usize,
191 bytes: []const u8,
192 input_offset: u64,
193 entry_size: u64,
194 output_offset: u64,
195 cursor: *record.Cursor,
196 section_address: u64,
197 ) model.Error!void {
198 if (!try record.hasHeaderEntry(bytes, input_offset, entry_size, output_offset, cursor)) return;
199 const relocation = cursor.at(output_offset + 8) orelse return;
200 const symbol_index: usize = @intCast(relocation.symbolIndex());
201 const target = try relocationTargetAddress(.serial, objects, layouts, output_sections, globals, symbol_addresses, object_index, symbol_index, relocation, false);
202 try entries.append(allocator, .{
203 .start_ip = try elf.relocation.checkedU64(target.address + target.addend),
204 .fde_address = section_address + output_offset,
205 });
206 }
207
208 fn startAddress(
209 objects: []const ObjectFile,
210 layouts: []const ObjectLayout,
211 output_sections: []const OutputSection,
212 ) ?u64 {
213 var start: ?u64 = null;
214 for (objects, 0..) |object, object_index| {
215 const object_layout = layouts[object_index];
216 for (object.sections, 0..) |input_section, section_index| {
217 const contribution = contributionAt(object_layout.sections, section_index) orelse continue;
218 if (!section.is(object, section_index, input_section)) continue;
219 const output = output_sections[contribution.outputIndex()];
220 const address = output.address + contribution.offset;
221 if (start == null or address < start.?) start = address;
222 }
223 }
224 return start;
225 }
226
227 fn compareEntries(_: void, left: Entry, right: Entry) bool {
228 if (left.start_ip != right.start_ip) return left.start_ip < right.start_ip;
229 return left.fde_address < right.fde_address;
230 }