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