master
1pub const Cie = struct {
2 /// Includes 4byte size cell.
3 offset: u32,
4 out_offset: u32 = 0,
5 size: u32,
6 lsda_size: ?enum { p32, p64 } = null,
7 personality: ?Personality = null,
8 file: File.Index = 0,
9 alive: bool = false,
10
11 pub fn parse(cie: *Cie, macho_file: *MachO) !void {
12 const tracy = trace(@src());
13 defer tracy.end();
14
15 const data = cie.getData(macho_file);
16 const aug = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(data.ptr + 9)), 0);
17
18 if (aug[0] != 'z') return; // TODO should we error out?
19
20 var reader: std.Io.Reader = .fixed(data[9 + aug.len + 1 ..]);
21
22 _ = try reader.takeLeb128(u64); // code alignment factor
23 _ = try reader.takeLeb128(u64); // data alignment factor
24 _ = try reader.takeLeb128(u64); // return address register
25 _ = try reader.takeLeb128(u64); // augmentation data length
26
27 for (aug[1..]) |ch| switch (ch) {
28 'R' => {
29 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
30 if (enc != @as(DW.EH.PE, .{ .type = .absptr, .rel = .pcrel })) {
31 @panic("unexpected pointer encoding"); // TODO error
32 }
33 },
34 'P' => {
35 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
36 if (enc != @as(DW.EH.PE, .{ .type = .sdata4, .rel = .pcrel, .indirect = true })) {
37 @panic("unexpected personality pointer encoding"); // TODO error
38 }
39 _ = try reader.takeInt(u32, .little); // personality pointer
40 },
41 'L' => {
42 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
43 switch (enc.type) {
44 .sdata4 => cie.lsda_size = .p32,
45 .absptr => cie.lsda_size = .p64,
46 else => @panic("unexpected lsda encoding"), // TODO error
47 }
48 },
49 else => @panic("unexpected augmentation string"), // TODO error
50 };
51 }
52
53 pub inline fn getSize(cie: Cie) u32 {
54 return cie.size + 4;
55 }
56
57 pub fn getObject(cie: Cie, macho_file: *MachO) *Object {
58 const file = macho_file.getFile(cie.file).?;
59 return file.object;
60 }
61
62 pub fn getData(cie: Cie, macho_file: *MachO) []const u8 {
63 const object = cie.getObject(macho_file);
64 return object.eh_frame_data.items[cie.offset..][0..cie.getSize()];
65 }
66
67 pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol {
68 const personality = cie.personality orelse return null;
69 const object = cie.getObject(macho_file);
70 return object.getSymbolRef(personality.index, macho_file).getSymbol(macho_file);
71 }
72
73 pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool {
74 if (!std.mem.eql(u8, cie.getData(macho_file), other.getData(macho_file))) return false;
75 if (cie.personality != null and other.personality != null) {
76 if (cie.personality.?.index != other.personality.?.index) return false;
77 }
78 if (cie.personality != null or other.personality != null) return false;
79 return true;
80 }
81
82 pub fn fmt(cie: Cie, macho_file: *MachO) std.fmt.Alt(Format, Format.default) {
83 return .{ .data = .{
84 .cie = cie,
85 .macho_file = macho_file,
86 } };
87 }
88
89 const Format = struct {
90 cie: Cie,
91 macho_file: *MachO,
92
93 fn default(f: Format, w: *Writer) Writer.Error!void {
94 const cie = f.cie;
95 try w.print("@{x} : size({x})", .{
96 cie.offset,
97 cie.getSize(),
98 });
99 if (!cie.alive) try w.writeAll(" : [*]");
100 }
101 };
102
103 pub const Index = u32;
104
105 pub const Personality = struct {
106 index: Symbol.Index = 0,
107 offset: u32 = 0,
108 };
109};
110
111pub const Fde = struct {
112 /// Includes 4byte size cell.
113 offset: u32,
114 out_offset: u32 = 0,
115 size: u32,
116 cie: Cie.Index,
117 atom: Atom.Index = 0,
118 atom_offset: u32 = 0,
119 lsda: Atom.Index = 0,
120 lsda_offset: u32 = 0,
121 lsda_ptr_offset: u32 = 0,
122 file: File.Index = 0,
123 alive: bool = true,
124
125 pub fn parse(fde: *Fde, macho_file: *MachO) !void {
126 const tracy = trace(@src());
127 defer tracy.end();
128
129 const data = fde.getData(macho_file);
130 const object = fde.getObject(macho_file);
131 const sect = object.sections.items(.header)[object.eh_frame_sect_index.?];
132
133 // Parse target atom index
134 const pc_begin = std.mem.readInt(i64, data[8..][0..8], .little);
135 const taddr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + 8)) + pc_begin);
136 fde.atom = object.findAtom(taddr) orelse {
137 try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid function reference in FDE", .{
138 sect.segName(), sect.sectName(), fde.offset + 8,
139 });
140 return error.MalformedObject;
141 };
142 const atom = fde.getAtom(macho_file);
143 fde.atom_offset = @intCast(taddr - atom.getInputAddress(macho_file));
144
145 // Associate with a CIE
146 const cie_ptr = std.mem.readInt(u32, data[4..8], .little);
147 const cie_offset = fde.offset + 4 - cie_ptr;
148 const cie_index = for (object.cies.items, 0..) |cie, cie_index| {
149 if (cie.offset == cie_offset) break @as(Cie.Index, @intCast(cie_index));
150 } else null;
151 if (cie_index) |cie| {
152 fde.cie = cie;
153 } else {
154 try macho_file.reportParseError2(object.index, "no matching CIE found for FDE at offset {x}", .{
155 fde.offset,
156 });
157 return error.MalformedObject;
158 }
159
160 const cie = fde.getCie(macho_file);
161
162 // Parse LSDA atom index if any
163 if (cie.lsda_size) |lsda_size| {
164 var reader: std.Io.Reader = .fixed(data);
165 reader.seek = 24;
166 _ = try reader.takeLeb128(u64); // augmentation length
167 fde.lsda_ptr_offset = @intCast(reader.seek);
168 const lsda_ptr = switch (lsda_size) {
169 .p32 => try reader.takeInt(i32, .little),
170 .p64 => try reader.takeInt(i64, .little),
171 };
172 const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr);
173 fde.lsda = object.findAtom(lsda_addr) orelse {
174 try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid LSDA reference in FDE", .{
175 sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset,
176 });
177 return error.MalformedObject;
178 };
179 const lsda_atom = fde.getLsdaAtom(macho_file).?;
180 fde.lsda_offset = @intCast(lsda_addr - lsda_atom.getInputAddress(macho_file));
181 }
182 }
183
184 pub inline fn getSize(fde: Fde) u32 {
185 return fde.size + 4;
186 }
187
188 pub fn getObject(fde: Fde, macho_file: *MachO) *Object {
189 const file = macho_file.getFile(fde.file).?;
190 return file.object;
191 }
192
193 pub fn getData(fde: Fde, macho_file: *MachO) []const u8 {
194 const object = fde.getObject(macho_file);
195 return object.eh_frame_data.items[fde.offset..][0..fde.getSize()];
196 }
197
198 pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie {
199 const object = fde.getObject(macho_file);
200 return &object.cies.items[fde.cie];
201 }
202
203 pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom {
204 return fde.getObject(macho_file).getAtom(fde.atom).?;
205 }
206
207 pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom {
208 return fde.getObject(macho_file).getAtom(fde.lsda);
209 }
210
211 pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Alt(Format, Format.default) {
212 return .{ .data = .{
213 .fde = fde,
214 .macho_file = macho_file,
215 } };
216 }
217
218 const Format = struct {
219 fde: Fde,
220 macho_file: *MachO,
221
222 fn default(f: Format, writer: *Writer) Writer.Error!void {
223 const fde = f.fde;
224 const macho_file = f.macho_file;
225 try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{
226 fde.offset,
227 fde.getSize(),
228 fde.cie,
229 fde.getAtom(macho_file).getName(macho_file),
230 });
231 if (!fde.alive) try writer.writeAll(" : [*]");
232 }
233 };
234
235 pub const Index = u32;
236};
237
238pub const Iterator = struct {
239 data: []const u8,
240 pos: u32 = 0,
241
242 pub const Record = struct {
243 tag: enum { fde, cie },
244 offset: u32,
245 size: u32,
246 };
247
248 pub fn next(it: *Iterator) !?Record {
249 if (it.pos >= it.data.len) return null;
250
251 var reader: std.Io.Reader = .fixed(it.data[it.pos..]);
252
253 const size = try reader.takeInt(u32, .little);
254 if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS");
255
256 const id = try reader.takeInt(u32, .little);
257 const record = Record{
258 .tag = if (id == 0) .cie else .fde,
259 .offset = it.pos,
260 .size = size,
261 };
262 it.pos += size + 4;
263
264 return record;
265 }
266};
267
268pub fn calcSize(macho_file: *MachO) !u32 {
269 const tracy = trace(@src());
270 defer tracy.end();
271
272 var offset: u32 = 0;
273
274 var cies = std.array_list.Managed(Cie).init(macho_file.base.comp.gpa);
275 defer cies.deinit();
276
277 for (macho_file.objects.items) |index| {
278 const object = macho_file.getFile(index).?.object;
279
280 outer: for (object.cies.items) |*cie| {
281 for (cies.items) |other| {
282 if (other.eql(cie.*, macho_file)) {
283 // We already have a CIE record that has the exact same contents, so instead of
284 // duplicating them, we mark this one dead and set its output offset to be
285 // equal to that of the alive record. This way, we won't have to rewrite
286 // Fde.cie_index field when committing the records to file.
287 cie.out_offset = other.out_offset;
288 continue :outer;
289 }
290 }
291 cie.alive = true;
292 cie.out_offset = offset;
293 offset += cie.getSize();
294 try cies.append(cie.*);
295 }
296 }
297
298 for (macho_file.objects.items) |index| {
299 const object = macho_file.getFile(index).?.object;
300 for (object.fdes.items) |*fde| {
301 if (!fde.alive) continue;
302 fde.out_offset = offset;
303 offset += fde.getSize();
304 }
305 }
306
307 return offset;
308}
309
310pub fn calcNumRelocs(macho_file: *MachO) u32 {
311 const tracy = trace(@src());
312 defer tracy.end();
313
314 var nreloc: u32 = 0;
315
316 for (macho_file.objects.items) |index| {
317 const object = macho_file.getFile(index).?.object;
318 for (object.cies.items) |cie| {
319 if (!cie.alive) continue;
320 if (cie.getPersonality(macho_file)) |_| {
321 nreloc += 1; // personality
322 }
323 }
324 }
325
326 return nreloc;
327}
328
329pub fn write(macho_file: *MachO, buffer: []u8) void {
330 const tracy = trace(@src());
331 defer tracy.end();
332
333 const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
334 const addend: i64 = switch (macho_file.getTarget().cpu.arch) {
335 .x86_64 => 4,
336 else => 0,
337 };
338
339 for (macho_file.objects.items) |index| {
340 const object = macho_file.getFile(index).?.object;
341 for (object.cies.items) |cie| {
342 if (!cie.alive) continue;
343
344 @memcpy(buffer[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
345
346 if (cie.getPersonality(macho_file)) |sym| {
347 const offset = cie.out_offset + cie.personality.?.offset;
348 const saddr = sect.addr + offset;
349 const taddr = sym.getGotAddress(macho_file);
350 std.mem.writeInt(
351 i32,
352 buffer[offset..][0..4],
353 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
354 .little,
355 );
356 }
357 }
358 }
359
360 for (macho_file.objects.items) |index| {
361 const object = macho_file.getFile(index).?.object;
362 for (object.fdes.items) |fde| {
363 if (!fde.alive) continue;
364
365 @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
366
367 {
368 const offset = fde.out_offset + 4;
369 const value = offset - fde.getCie(macho_file).out_offset;
370 std.mem.writeInt(u32, buffer[offset..][0..4], value, .little);
371 }
372
373 {
374 const offset = fde.out_offset + 8;
375 const saddr = sect.addr + offset;
376 const taddr = fde.getAtom(macho_file).getAddress(macho_file);
377 std.mem.writeInt(
378 i64,
379 buffer[offset..][0..8],
380 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
381 .little,
382 );
383 }
384
385 if (fde.getLsdaAtom(macho_file)) |atom| {
386 const offset = fde.out_offset + fde.lsda_ptr_offset;
387 const saddr = sect.addr + offset;
388 const taddr = atom.getAddress(macho_file) + fde.lsda_offset;
389 switch (fde.getCie(macho_file).lsda_size.?) {
390 .p32 => std.mem.writeInt(
391 i32,
392 buffer[offset..][0..4],
393 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
394 .little,
395 ),
396 .p64 => std.mem.writeInt(
397 i64,
398 buffer[offset..][0..8],
399 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
400 .little,
401 ),
402 }
403 }
404 }
405 }
406}
407
408pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: []macho.relocation_info) error{Overflow}!void {
409 const tracy = trace(@src());
410 defer tracy.end();
411
412 const cpu_arch = macho_file.getTarget().cpu.arch;
413 const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
414 const addend: i64 = switch (cpu_arch) {
415 .x86_64 => 4,
416 else => 0,
417 };
418
419 var i: usize = 0;
420 for (macho_file.objects.items) |index| {
421 const object = macho_file.getFile(index).?.object;
422 for (object.cies.items) |cie| {
423 if (!cie.alive) continue;
424
425 @memcpy(code[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
426
427 if (cie.getPersonality(macho_file)) |sym| {
428 const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow;
429 const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
430 relocs[i] = .{
431 .r_address = r_address,
432 .r_symbolnum = r_symbolnum,
433 .r_length = 2,
434 .r_extern = 1,
435 .r_pcrel = 1,
436 .r_type = switch (cpu_arch) {
437 .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_POINTER_TO_GOT),
438 .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT),
439 else => unreachable,
440 },
441 };
442 i += 1;
443 }
444 }
445 }
446
447 for (macho_file.objects.items) |index| {
448 const object = macho_file.getFile(index).?.object;
449 for (object.fdes.items) |fde| {
450 if (!fde.alive) continue;
451
452 @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
453
454 {
455 const offset = fde.out_offset + 4;
456 const value = offset - fde.getCie(macho_file).out_offset;
457 std.mem.writeInt(u32, code[offset..][0..4], value, .little);
458 }
459
460 {
461 const offset = fde.out_offset + 8;
462 const saddr = sect.addr + offset;
463 const taddr = fde.getAtom(macho_file).getAddress(macho_file);
464 std.mem.writeInt(
465 i64,
466 code[offset..][0..8],
467 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
468 .little,
469 );
470 }
471
472 if (fde.getLsdaAtom(macho_file)) |atom| {
473 const offset = fde.out_offset + fde.lsda_ptr_offset;
474 const saddr = sect.addr + offset;
475 const taddr = atom.getAddress(macho_file) + fde.lsda_offset;
476 switch (fde.getCie(macho_file).lsda_size.?) {
477 .p32 => std.mem.writeInt(
478 i32,
479 code[offset..][0..4],
480 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
481 .little,
482 ),
483 .p64 => std.mem.writeInt(
484 i64,
485 code[offset..][0..8],
486 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
487 .little,
488 ),
489 }
490 }
491 }
492 }
493
494 assert(relocs.len == i);
495}
496
497const assert = std.debug.assert;
498const leb = std.leb;
499const macho = std.macho;
500const math = std.math;
501const mem = std.mem;
502const std = @import("std");
503const trace = @import("../../tracy.zig").trace;
504const Writer = std.Io.Writer;
505
506const Allocator = std.mem.Allocator;
507const Atom = @import("Atom.zig");
508const DW = std.dwarf;
509const File = @import("file.zig").File;
510const MachO = @import("../MachO.zig");
511const Object = @import("Object.zig");
512const Symbol = @import("Symbol.zig");