authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-01-21 13:26:27+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-01-21 13:37:48+01:00
log3dff040ca58effc5aaf1c7313a7baa28ec2ac6dd
treed8a184dc36e16755633529c7fe4bb88591a91fc7
parent24f6c07653eaadb70901ef251d091d3c3c0e010f

macho: synthesise unwind records in absence of compact unwind section

Unlike Apple ld, we will not do any DWARF CFI parsing and simply output DWARF type unwind records.

3 files changed, 155 insertions(+), 117 deletions(-)

src/link/MachO/Object.zig+12-1
...@@ -725,7 +725,18 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {...@@ -725,7 +725,18 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
725}725}
726726
727fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {727fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
728 const sect = self.unwind_info_sect orelse return;728 const sect = self.unwind_info_sect orelse {
729 // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`,
730 // we will try fully synthesising unwind info records to somewhat match Apple ld's
731 // approach. However, we will only synthesise DWARF records and nothing more. For this reason,
732 // we still create the output `__TEXT,__unwind_info` section.
733 if (self.eh_frame_sect != null) {
734 if (zld.getSectionByName("__TEXT", "__unwind_info") == null) {
735 _ = try zld.initSection("__TEXT", "__unwind_info", .{});
736 }
737 }
738 return;
739 };
729740
730 log.debug("parsing unwind info in {s}", .{self.name});741 log.debug("parsing unwind info in {s}", .{self.name});
731742
src/link/MachO/UnwindInfo.zig+63-52
...@@ -253,37 +253,13 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {...@@ -253,37 +253,13 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
253 try records.ensureUnusedCapacity(object.exec_atoms.items.len);253 try records.ensureUnusedCapacity(object.exec_atoms.items.len);
254 try atom_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);254 try atom_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
255255
256 var it = object.getEhFrameRecordsIterator();
257
258 for (object.exec_atoms.items) |atom_index| {256 for (object.exec_atoms.items) |atom_index| {
259 var record = if (object.unwind_records_lookup.get(atom_index)) |record_id| blk: {257 var record = if (object.unwind_records_lookup.get(atom_index)) |record_id| blk: {
260 if (object.unwind_relocs_lookup[record_id].dead) continue;258 if (object.unwind_relocs_lookup[record_id].dead) continue;
261 var record = unwind_records[record_id];259 var record = unwind_records[record_id];
262260
263 if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {261 if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
264 const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;262 try info.collectPersonalityFromDwarf(zld, @intCast(u32, object_id), atom_index, &record);
265 it.seekTo(fde_offset);
266 const fde = (try it.next()).?;
267 const cie_ptr = fde.getCiePointer();
268 const cie_offset = fde_offset + 4 - cie_ptr;
269 it.seekTo(cie_offset);
270 const cie = (try it.next()).?;
271
272 if (cie.getPersonalityPointerReloc(
273 zld,
274 @intCast(u32, object_id),
275 cie_offset,
276 )) |target| {
277 const personality_index = info.getPersonalityFunction(target) orelse inner: {
278 const personality_index = info.personalities_count;
279 info.personalities[personality_index] = target;
280 info.personalities_count += 1;
281 break :inner personality_index;
282 };
283
284 record.personalityFunction = personality_index + 1;
285 UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
286 }
287 } else {263 } else {
288 if (getPersonalityFunctionReloc(264 if (getPersonalityFunctionReloc(
289 zld,265 zld,
...@@ -324,6 +300,21 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {...@@ -324,6 +300,21 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
324 const atom = zld.getAtom(atom_index);300 const atom = zld.getAtom(atom_index);
325 const sym = zld.getSymbol(atom.getSymbolWithLoc());301 const sym = zld.getSymbol(atom.getSymbolWithLoc());
326 if (sym.n_desc == N_DEAD) continue;302 if (sym.n_desc == N_DEAD) continue;
303
304 if (!object.hasUnwindRecords()) {
305 if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
306 if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
307 var record = nullRecord();
308 try info.collectPersonalityFromDwarf(zld, @intCast(u32, object_id), atom_index, &record);
309 switch (cpu_arch) {
310 .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
311 .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
312 else => unreachable,
313 }
314 break :blk record;
315 }
316 }
317
327 break :blk nullRecord();318 break :blk nullRecord();
328 };319 };
329320
...@@ -499,6 +490,40 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {...@@ -499,6 +490,40 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
499 }490 }
500}491}
501492
493fn collectPersonalityFromDwarf(
494 info: *UnwindInfo,
495 zld: *Zld,
496 object_id: u32,
497 atom_index: u32,
498 record: *macho.compact_unwind_entry,
499) !void {
500 const object = &zld.objects.items[object_id];
501 var it = object.getEhFrameRecordsIterator();
502 const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;
503 it.seekTo(fde_offset);
504 const fde = (try it.next()).?;
505 const cie_ptr = fde.getCiePointer();
506 const cie_offset = fde_offset + 4 - cie_ptr;
507 it.seekTo(cie_offset);
508 const cie = (try it.next()).?;
509
510 if (cie.getPersonalityPointerReloc(
511 zld,
512 @intCast(u32, object_id),
513 cie_offset,
514 )) |target| {
515 const personality_index = info.getPersonalityFunction(target) orelse inner: {
516 const personality_index = info.personalities_count;
517 info.personalities[personality_index] = target;
518 info.personalities_count += 1;
519 break :inner personality_index;
520 };
521
522 record.personalityFunction = personality_index + 1;
523 UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
524 }
525}
526
502pub fn calcSectionSize(info: UnwindInfo, zld: *Zld) !void {527pub fn calcSectionSize(info: UnwindInfo, zld: *Zld) !void {
503 const sect_id = zld.getSectionByName("__TEXT", "__unwind_info") orelse return;528 const sect_id = zld.getSectionByName("__TEXT", "__unwind_info") orelse return;
504 const sect = &zld.sections.items(.header)[sect_id];529 const sect = &zld.sections.items(.header)[sect_id];
...@@ -766,40 +791,26 @@ fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7...@@ -766,40 +791,26 @@ fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7
766}791}
767792
768pub const UnwindEncoding = struct {793pub const UnwindEncoding = struct {
769 pub const UNWIND_X86_64_MODE = enum(u4) {
770 none = 0,
771 ebp_frame = 1,
772 stack_immd = 2,
773 stack_ind = 3,
774 dwarf = 4,
775 };
776
777 pub const UNWIND_ARM64_MODE = enum(u4) {
778 none = 0,
779 frameless = 2,
780 dwarf = 3,
781 frame = 4,
782 };
783
784 pub const UNWIND_MODE_MASK: u32 = 0x0F000000;
785 pub const UNWIND_PERSONALITY_INDEX_MASK: u32 = 0x30000000;
786 pub const UNWIND_HAS_LSDA_MASK: u32 = 0x40000000;
787
788 pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 {794 pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 {
789 const mode = @truncate(u4, (enc & UNWIND_MODE_MASK) >> 24);795 comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
790 return mode;796 return @truncate(u4, (enc & macho.UNWIND_ARM64_MODE_MASK) >> 24);
791 }797 }
792798
793 pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool {799 pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool {
794 switch (cpu_arch) {800 const mode = getMode(enc);
795 .aarch64 => return @intToEnum(UNWIND_ARM64_MODE, getMode(enc)) == .dwarf,801 return switch (cpu_arch) {
796 .x86_64 => return @intToEnum(UNWIND_X86_64_MODE, getMode(enc)) == .dwarf,802 .aarch64 => @intToEnum(macho.UNWIND_ARM64_MODE, mode) == .DWARF,
803 .x86_64 => @intToEnum(macho.UNWIND_X86_64_MODE, mode) == .DWARF,
797 else => unreachable,804 else => unreachable,
798 }805 };
806 }
807
808 pub fn setMode(enc: *macho.compact_unwind_encoding_t, mode: anytype) void {
809 enc.* |= @intCast(u32, @enumToInt(mode)) << 24;
799 }810 }
800811
801 pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool {812 pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool {
802 const has_lsda = @truncate(u1, (enc & UNWIND_HAS_LSDA_MASK) >> 31);813 const has_lsda = @truncate(u1, (enc & macho.UNWIND_HAS_LSDA) >> 31);
803 return has_lsda == 1;814 return has_lsda == 1;
804 }815 }
805816
...@@ -809,7 +820,7 @@ pub const UnwindEncoding = struct {...@@ -809,7 +820,7 @@ pub const UnwindEncoding = struct {
809 }820 }
810821
811 pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 {822 pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 {
812 const index = @truncate(u2, (enc & UNWIND_PERSONALITY_INDEX_MASK) >> 28);823 const index = @truncate(u2, (enc & macho.UNWIND_PERSONALITY_MASK) >> 28);
813 return index;824 return index;
814 }825 }
815826
src/link/MachO/dead_strip.zig+80-64
...@@ -238,16 +238,10 @@ fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {...@@ -238,16 +238,10 @@ fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
238 }238 }
239 }239 }
240240
241 for (zld.objects.items) |object, object_id| {241 for (zld.objects.items) |_, object_id| {
242 // Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so,242 // Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so,
243 // marking all references as live.243 // marking all references as live.
244 // TODO I am currently assuming there will always be __unwind_info section emitted which implies244 try markUnwindRecords(zld, @intCast(u32, object_id), alive);
245 // we will not traverse __eh_frame in isolation. This however is only true for more recent versions
246 // of macOS so if there is a feature request to handle earlier versions of macOS, the following
247 // bit code needs updating as well.
248 if (object.hasUnwindRecords()) {
249 try markUnwindRecords(zld, @intCast(u32, object_id), alive);
250 }
251 }245 }
252}246}
253247
...@@ -256,9 +250,23 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {...@@ -256,9 +250,23 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
256 const cpu_arch = zld.options.target.cpu.arch;250 const cpu_arch = zld.options.target.cpu.arch;
257251
258 const unwind_records = object.getUnwindRecords();252 const unwind_records = object.getUnwindRecords();
259 var it = object.getEhFrameRecordsIterator();
260253
261 for (object.exec_atoms.items) |atom_index| {254 for (object.exec_atoms.items) |atom_index| {
255 if (!object.hasUnwindRecords()) {
256 if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
257 const ptr = object.eh_frame_relocs_lookup.getPtr(fde_offset).?;
258 if (ptr.dead) continue; // already marked
259 if (!alive.contains(atom_index)) {
260 // Mark dead and continue.
261 ptr.dead = true;
262 } else {
263 // Mark references live and continue.
264 try markEhFrameRecord(zld, object_id, atom_index, alive);
265 }
266 continue;
267 }
268 }
269
262 const record_id = object.unwind_records_lookup.get(atom_index) orelse continue;270 const record_id = object.unwind_records_lookup.get(atom_index) orelse continue;
263 if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do271 if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
264 if (!alive.contains(atom_index)) {272 if (!alive.contains(atom_index)) {
...@@ -272,61 +280,7 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {...@@ -272,61 +280,7 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
272280
273 const record = unwind_records[record_id];281 const record = unwind_records[record_id];
274 if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {282 if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
275 const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;283 try markEhFrameRecord(zld, object_id, atom_index, alive);
276 it.seekTo(fde_offset);
277 const fde = (try it.next()).?;
278
279 const cie_ptr = fde.getCiePointer();
280 const cie_offset = fde_offset + 4 - cie_ptr;
281 it.seekTo(cie_offset);
282 const cie = (try it.next()).?;
283
284 switch (cpu_arch) {
285 .aarch64 => {
286 // Mark FDE references which should include any referenced LSDA record
287 const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
288 for (relocs) |rel| {
289 const target = UnwindInfo.parseRelocTarget(
290 zld,
291 object_id,
292 rel,
293 fde.data,
294 @intCast(i32, fde_offset) + 4,
295 );
296 const target_sym = zld.getSymbol(target);
297 if (!target_sym.undf()) blk: {
298 const target_object = zld.objects.items[target.getFile().?];
299 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
300 break :blk;
301 markLive(zld, target_atom_index, alive);
302 }
303 }
304 },
305 .x86_64 => {
306 const lsda_ptr = try fde.getLsdaPointer(cie, .{
307 .base_addr = object.eh_frame_sect.?.addr,
308 .base_offset = fde_offset,
309 });
310 if (lsda_ptr) |lsda_address| {
311 // Mark LSDA record as live
312 const sym_index = object.getSymbolByAddress(lsda_address, null);
313 const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
314 markLive(zld, target_atom_index, alive);
315 }
316 },
317 else => unreachable,
318 }
319
320 // Mark CIE references which should include any referenced personalities
321 // that are defined locally.
322 if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
323 const target_sym = zld.getSymbol(target);
324 if (!target_sym.undf()) {
325 const target_object = zld.objects.items[target.getFile().?];
326 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
327 markLive(zld, target_atom_index, alive);
328 }
329 }
330 } else {284 } else {
331 if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {285 if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
332 const target = UnwindInfo.parseRelocTarget(286 const target = UnwindInfo.parseRelocTarget(
...@@ -360,6 +314,68 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {...@@ -360,6 +314,68 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
360 }314 }
361}315}
362316
317fn markEhFrameRecord(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *AtomTable) !void {
318 const cpu_arch = zld.options.target.cpu.arch;
319 const object = &zld.objects.items[object_id];
320 var it = object.getEhFrameRecordsIterator();
321
322 const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;
323 it.seekTo(fde_offset);
324 const fde = (try it.next()).?;
325
326 const cie_ptr = fde.getCiePointer();
327 const cie_offset = fde_offset + 4 - cie_ptr;
328 it.seekTo(cie_offset);
329 const cie = (try it.next()).?;
330
331 switch (cpu_arch) {
332 .aarch64 => {
333 // Mark FDE references which should include any referenced LSDA record
334 const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
335 for (relocs) |rel| {
336 const target = UnwindInfo.parseRelocTarget(
337 zld,
338 object_id,
339 rel,
340 fde.data,
341 @intCast(i32, fde_offset) + 4,
342 );
343 const target_sym = zld.getSymbol(target);
344 if (!target_sym.undf()) blk: {
345 const target_object = zld.objects.items[target.getFile().?];
346 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
347 break :blk;
348 markLive(zld, target_atom_index, alive);
349 }
350 }
351 },
352 .x86_64 => {
353 const lsda_ptr = try fde.getLsdaPointer(cie, .{
354 .base_addr = object.eh_frame_sect.?.addr,
355 .base_offset = fde_offset,
356 });
357 if (lsda_ptr) |lsda_address| {
358 // Mark LSDA record as live
359 const sym_index = object.getSymbolByAddress(lsda_address, null);
360 const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
361 markLive(zld, target_atom_index, alive);
362 }
363 },
364 else => unreachable,
365 }
366
367 // Mark CIE references which should include any referenced personalities
368 // that are defined locally.
369 if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
370 const target_sym = zld.getSymbol(target);
371 if (!target_sym.undf()) {
372 const target_object = zld.objects.items[target.getFile().?];
373 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
374 markLive(zld, target_atom_index, alive);
375 }
376 }
377}
378
363fn prune(zld: *Zld, alive: AtomTable) void {379fn prune(zld: *Zld, alive: AtomTable) void {
364 log.debug("pruning dead atoms", .{});380 log.debug("pruning dead atoms", .{});
365 for (zld.objects.items) |*object| {381 for (zld.objects.items) |*object| {