authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-05-13 08:56:15+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-05-13 08:56:15+02:00
log5d619da2de1e6f906082640b75322553f8fef7c0
tree7eb11d6f3a4b3d93320ef577248c118ac1488273
parent6f117dbca41e4dfede925d046b572df40849a9c4

Step.CheckObject: put MachO object parsing logic in ObjectContext wrapper


1 files changed, 850 insertions(+), 859 deletions(-)

lib/std/Build/Step/CheckObject.zig+850-859
......@@ -695,7 +695,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
695695}
696696
697697const MachODumper = struct {
698 const LoadCommandIterator = macho.LoadCommandIterator;
699698 const dyld_rebase_label = "dyld rebase data";
700699 const dyld_bind_label = "dyld bind data";
701700 const dyld_weak_bind_label = "dyld weak bind data";
......@@ -704,80 +703,52 @@ const MachODumper = struct {
704703 const symtab_label = "symbol table";
705704 const indirect_symtab_label = "indirect symbol table";
706705
707 const Symtab = struct {
708 symbols: []align(1) const macho.nlist_64 = &[0]macho.nlist_64{},
709 strings: []const u8 = &[0]u8{},
710 indirect_symbols: []align(1) const u32 = &[0]u32{},
711
712 fn getString(symtab: Symtab, off: u32) []const u8 {
713 assert(off < symtab.strings.len);
714 return mem.sliceTo(@as([*:0]const u8, @ptrCast(symtab.strings.ptr + off)), 0);
715 }
716 };
717
718706 fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
719 const gpa = step.owner.allocator;
720 var stream = std.io.fixedBufferStream(bytes);
721 const reader = stream.reader();
722
723 const hdr = try reader.readStruct(macho.mach_header_64);
724 if (hdr.magic != macho.MH_MAGIC_64) {
725 return error.InvalidMagicNumber;
726 }
727
728 var output = std.ArrayList(u8).init(gpa);
729 const writer = output.writer();
707 // TODO: handle archives and fat files
708 return parseAndDumpObject(step, check, bytes);
709 }
730710
731 var symtab: Symtab = .{};
732 var segments = std.ArrayList(macho.segment_command_64).init(gpa);
733 defer segments.deinit();
734 var sections = std.ArrayList(macho.section_64).init(gpa);
735 defer sections.deinit();
736 var imports = std.ArrayList([]const u8).init(gpa);
737 defer imports.deinit();
738 var text_seg: ?u8 = null;
739 var dyld_info_lc: ?macho.dyld_info_command = null;
740
741 {
742 var it: LoadCommandIterator = .{
743 .ncmds = hdr.ncmds,
744 .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
745 };
711 const ObjectContext = struct {
712 gpa: Allocator,
713 data: []const u8,
714 header: macho.mach_header_64,
715 segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
716 sections: std.ArrayListUnmanaged(macho.section_64) = .{},
717 symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
718 strtab: std.ArrayListUnmanaged(u8) = .{},
719 indsymtab: std.ArrayListUnmanaged(u32) = .{},
720 imports: std.ArrayListUnmanaged([]const u8) = .{},
721
722 fn parse(ctx: *ObjectContext) !void {
723 var it = ctx.getLoadCommandIterator();
746724 var i: usize = 0;
747725 while (it.next()) |cmd| {
748726 switch (cmd.cmd()) {
749727 .SEGMENT_64 => {
750728 const seg = cmd.cast(macho.segment_command_64).?;
751 try sections.ensureUnusedCapacity(seg.nsects);
729 try ctx.segments.append(ctx.gpa, seg);
730 try ctx.sections.ensureUnusedCapacity(ctx.gpa, seg.nsects);
752731 for (cmd.getSections()) |sect| {
753 sections.appendAssumeCapacity(sect);
754 }
755 const seg_id: u8 = @intCast(segments.items.len);
756 try segments.append(seg);
757 if (mem.eql(u8, seg.segName(), "__TEXT")) {
758 text_seg = seg_id;
732 ctx.sections.appendAssumeCapacity(sect);
759733 }
760734 },
761735 .SYMTAB => {
762736 const lc = cmd.cast(macho.symtab_command).?;
763 const symbols = @as([*]align(1) const macho.nlist_64, @ptrCast(bytes.ptr + lc.symoff))[0..lc.nsyms];
764 const strings = bytes[lc.stroff..][0..lc.strsize];
765 symtab.symbols = symbols;
766 symtab.strings = strings;
737 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(ctx.data.ptr + lc.symoff))[0..lc.nsyms];
738 const strtab = ctx.data[lc.stroff..][0..lc.strsize];
739 try ctx.symtab.appendUnalignedSlice(ctx.gpa, symtab);
740 try ctx.strtab.appendSlice(ctx.gpa, strtab);
767741 },
768742 .DYSYMTAB => {
769743 const lc = cmd.cast(macho.dysymtab_command).?;
770 const indexes = @as([*]align(1) const u32, @ptrCast(bytes.ptr + lc.indirectsymoff))[0..lc.nindirectsyms];
771 symtab.indirect_symbols = indexes;
744 const indexes = @as([*]align(1) const u32, @ptrCast(ctx.data.ptr + lc.indirectsymoff))[0..lc.nindirectsyms];
745 try ctx.indsymtab.appendUnalignedSlice(ctx.gpa, indexes);
772746 },
773747 .LOAD_DYLIB,
774748 .LOAD_WEAK_DYLIB,
775749 .REEXPORT_DYLIB,
776750 => {
777 try imports.append(cmd.getDylibPathName());
778 },
779 .DYLD_INFO_ONLY => {
780 dyld_info_lc = cmd.cast(macho.dyld_info_command).?;
751 try ctx.imports.append(ctx.gpa, cmd.getDylibPathName());
781752 },
782753 else => {},
783754 }
......@@ -786,899 +757,919 @@ const MachODumper = struct {
786757 }
787758 }
788759
789 switch (check.kind) {
790 .headers => {
791 try dumpHeader(hdr, writer);
792
793 var it: LoadCommandIterator = .{
794 .ncmds = hdr.ncmds,
795 .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
796 };
797 var i: usize = 0;
798 while (it.next()) |cmd| {
799 try dumpLoadCommand(cmd, i, writer);
800 try writer.writeByte('\n');
801
802 i += 1;
803 }
804 },
805
806 .symtab => if (symtab.symbols.len > 0) {
807 try dumpSymtab(sections.items, imports.items, symtab, writer);
808 } else return step.fail("no symbol table found", .{}),
809
810 .indirect_symtab => if (symtab.symbols.len > 0 and symtab.indirect_symbols.len > 0) {
811 try dumpIndirectSymtab(gpa, sections.items, symtab, writer);
812 } else return step.fail("no indirect symbol table found", .{}),
813
814 .dyld_rebase,
815 .dyld_bind,
816 .dyld_weak_bind,
817 .dyld_lazy_bind,
818 => {
819 if (dyld_info_lc == null) return step.fail("no dyld info found", .{});
820 const lc = dyld_info_lc.?;
821
822 switch (check.kind) {
823 .dyld_rebase => if (lc.rebase_size > 0) {
824 const data = bytes[lc.rebase_off..][0..lc.rebase_size];
825 try writer.writeAll(dyld_rebase_label ++ "\n");
826 try dumpRebaseInfo(gpa, data, segments.items, writer);
827 } else return step.fail("no rebase data found", .{}),
828
829 .dyld_bind => if (lc.bind_size > 0) {
830 const data = bytes[lc.bind_off..][0..lc.bind_size];
831 try writer.writeAll(dyld_bind_label ++ "\n");
832 try dumpBindInfo(gpa, data, segments.items, imports.items, writer);
833 } else return step.fail("no bind data found", .{}),
834
835 .dyld_weak_bind => if (lc.weak_bind_size > 0) {
836 const data = bytes[lc.weak_bind_off..][0..lc.weak_bind_size];
837 try writer.writeAll(dyld_weak_bind_label ++ "\n");
838 try dumpBindInfo(gpa, data, segments.items, imports.items, writer);
839 } else return step.fail("no weak bind data found", .{}),
760 fn getString(ctx: ObjectContext, off: u32) [:0]const u8 {
761 assert(off < ctx.strtab.items.len);
762 return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + off)), 0);
763 }
840764
841 .dyld_lazy_bind => if (lc.lazy_bind_size > 0) {
842 const data = bytes[lc.lazy_bind_off..][0..lc.lazy_bind_size];
843 try writer.writeAll(dyld_lazy_bind_label ++ "\n");
844 try dumpBindInfo(gpa, data, segments.items, imports.items, writer);
845 } else return step.fail("no lazy bind data found", .{}),
765 fn getLoadCommandIterator(ctx: ObjectContext) macho.LoadCommandIterator {
766 const data = ctx.data[@sizeOf(macho.mach_header_64)..][0..ctx.header.sizeofcmds];
767 return .{ .ncmds = ctx.header.ncmds, .buffer = data };
768 }
846769
847 else => unreachable,
848 }
849 },
770 fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) ?macho.LoadCommandIterator.LoadCommand {
771 var it = ctx.getLoadCommandIterator();
772 while (it.next()) |lc| if (lc.cmd() == cmd) {
773 return lc;
774 };
775 return null;
776 }
850777
851 .exports => blk: {
852 if (dyld_info_lc) |lc| {
853 if (lc.export_size > 0) {
854 const data = bytes[lc.export_off..][0..lc.export_size];
855 try writer.writeAll(exports_label ++ "\n");
856 try dumpExportsTrie(gpa, data, segments.items[text_seg.?], writer);
857 break :blk;
858 }
859 }
860 return step.fail("no exports data found", .{});
861 },
778 fn getSegmentByName(ctx: ObjectContext, name: []const u8) ?macho.segment_command_64 {
779 for (ctx.segments.items) |seg| {
780 if (mem.eql(u8, seg.segName(), name)) return seg;
781 }
782 return null;
783 }
862784
863 else => return step.fail("invalid check kind for MachO file format: {s}", .{@tagName(check.kind)}),
785 fn getSectionByName(ctx: ObjectContext, segname: []const u8, sectname: []const u8) ?macho.section_64 {
786 for (ctx.sections.items) |sect| {
787 if (mem.eql(u8, sect.segName(), segname) and mem.eql(u8, sect.sectName(), sectname)) return sect;
788 }
789 return null;
864790 }
865791
866 return output.toOwnedSlice();
867 }
792 fn dumpHeader(hdr: macho.mach_header_64, writer: anytype) !void {
793 const cputype = switch (hdr.cputype) {
794 macho.CPU_TYPE_ARM64 => "ARM64",
795 macho.CPU_TYPE_X86_64 => "X86_64",
796 else => "Unknown",
797 };
798 const filetype = switch (hdr.filetype) {
799 macho.MH_OBJECT => "MH_OBJECT",
800 macho.MH_EXECUTE => "MH_EXECUTE",
801 macho.MH_FVMLIB => "MH_FVMLIB",
802 macho.MH_CORE => "MH_CORE",
803 macho.MH_PRELOAD => "MH_PRELOAD",
804 macho.MH_DYLIB => "MH_DYLIB",
805 macho.MH_DYLINKER => "MH_DYLINKER",
806 macho.MH_BUNDLE => "MH_BUNDLE",
807 macho.MH_DYLIB_STUB => "MH_DYLIB_STUB",
808 macho.MH_DSYM => "MH_DSYM",
809 macho.MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE",
810 else => "Unknown",
811 };
868812
869 fn dumpHeader(hdr: macho.mach_header_64, writer: anytype) !void {
870 const cputype = switch (hdr.cputype) {
871 macho.CPU_TYPE_ARM64 => "ARM64",
872 macho.CPU_TYPE_X86_64 => "X86_64",
873 else => "Unknown",
874 };
875 const filetype = switch (hdr.filetype) {
876 macho.MH_OBJECT => "MH_OBJECT",
877 macho.MH_EXECUTE => "MH_EXECUTE",
878 macho.MH_FVMLIB => "MH_FVMLIB",
879 macho.MH_CORE => "MH_CORE",
880 macho.MH_PRELOAD => "MH_PRELOAD",
881 macho.MH_DYLIB => "MH_DYLIB",
882 macho.MH_DYLINKER => "MH_DYLINKER",
883 macho.MH_BUNDLE => "MH_BUNDLE",
884 macho.MH_DYLIB_STUB => "MH_DYLIB_STUB",
885 macho.MH_DSYM => "MH_DSYM",
886 macho.MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE",
887 else => "Unknown",
888 };
813 try writer.print(
814 \\header
815 \\cputype {s}
816 \\filetype {s}
817 \\ncmds {d}
818 \\sizeofcmds {x}
819 \\flags
820 , .{
821 cputype,
822 filetype,
823 hdr.ncmds,
824 hdr.sizeofcmds,
825 });
826
827 if (hdr.flags > 0) {
828 if (hdr.flags & macho.MH_NOUNDEFS != 0) try writer.writeAll(" NOUNDEFS");
829 if (hdr.flags & macho.MH_INCRLINK != 0) try writer.writeAll(" INCRLINK");
830 if (hdr.flags & macho.MH_DYLDLINK != 0) try writer.writeAll(" DYLDLINK");
831 if (hdr.flags & macho.MH_BINDATLOAD != 0) try writer.writeAll(" BINDATLOAD");
832 if (hdr.flags & macho.MH_PREBOUND != 0) try writer.writeAll(" PREBOUND");
833 if (hdr.flags & macho.MH_SPLIT_SEGS != 0) try writer.writeAll(" SPLIT_SEGS");
834 if (hdr.flags & macho.MH_LAZY_INIT != 0) try writer.writeAll(" LAZY_INIT");
835 if (hdr.flags & macho.MH_TWOLEVEL != 0) try writer.writeAll(" TWOLEVEL");
836 if (hdr.flags & macho.MH_FORCE_FLAT != 0) try writer.writeAll(" FORCE_FLAT");
837 if (hdr.flags & macho.MH_NOMULTIDEFS != 0) try writer.writeAll(" NOMULTIDEFS");
838 if (hdr.flags & macho.MH_NOFIXPREBINDING != 0) try writer.writeAll(" NOFIXPREBINDING");
839 if (hdr.flags & macho.MH_PREBINDABLE != 0) try writer.writeAll(" PREBINDABLE");
840 if (hdr.flags & macho.MH_ALLMODSBOUND != 0) try writer.writeAll(" ALLMODSBOUND");
841 if (hdr.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) try writer.writeAll(" SUBSECTIONS_VIA_SYMBOLS");
842 if (hdr.flags & macho.MH_CANONICAL != 0) try writer.writeAll(" CANONICAL");
843 if (hdr.flags & macho.MH_WEAK_DEFINES != 0) try writer.writeAll(" WEAK_DEFINES");
844 if (hdr.flags & macho.MH_BINDS_TO_WEAK != 0) try writer.writeAll(" BINDS_TO_WEAK");
845 if (hdr.flags & macho.MH_ALLOW_STACK_EXECUTION != 0) try writer.writeAll(" ALLOW_STACK_EXECUTION");
846 if (hdr.flags & macho.MH_ROOT_SAFE != 0) try writer.writeAll(" ROOT_SAFE");
847 if (hdr.flags & macho.MH_SETUID_SAFE != 0) try writer.writeAll(" SETUID_SAFE");
848 if (hdr.flags & macho.MH_NO_REEXPORTED_DYLIBS != 0) try writer.writeAll(" NO_REEXPORTED_DYLIBS");
849 if (hdr.flags & macho.MH_PIE != 0) try writer.writeAll(" PIE");
850 if (hdr.flags & macho.MH_DEAD_STRIPPABLE_DYLIB != 0) try writer.writeAll(" DEAD_STRIPPABLE_DYLIB");
851 if (hdr.flags & macho.MH_HAS_TLV_DESCRIPTORS != 0) try writer.writeAll(" HAS_TLV_DESCRIPTORS");
852 if (hdr.flags & macho.MH_NO_HEAP_EXECUTION != 0) try writer.writeAll(" NO_HEAP_EXECUTION");
853 if (hdr.flags & macho.MH_APP_EXTENSION_SAFE != 0) try writer.writeAll(" APP_EXTENSION_SAFE");
854 if (hdr.flags & macho.MH_NLIST_OUTOFSYNC_WITH_DYLDINFO != 0) try writer.writeAll(" NLIST_OUTOFSYNC_WITH_DYLDINFO");
855 }
889856
890 try writer.print(
891 \\header
892 \\cputype {s}
893 \\filetype {s}
894 \\ncmds {d}
895 \\sizeofcmds {x}
896 \\flags
897 , .{
898 cputype,
899 filetype,
900 hdr.ncmds,
901 hdr.sizeofcmds,
902 });
903
904 if (hdr.flags > 0) {
905 if (hdr.flags & macho.MH_NOUNDEFS != 0) try writer.writeAll(" NOUNDEFS");
906 if (hdr.flags & macho.MH_INCRLINK != 0) try writer.writeAll(" INCRLINK");
907 if (hdr.flags & macho.MH_DYLDLINK != 0) try writer.writeAll(" DYLDLINK");
908 if (hdr.flags & macho.MH_BINDATLOAD != 0) try writer.writeAll(" BINDATLOAD");
909 if (hdr.flags & macho.MH_PREBOUND != 0) try writer.writeAll(" PREBOUND");
910 if (hdr.flags & macho.MH_SPLIT_SEGS != 0) try writer.writeAll(" SPLIT_SEGS");
911 if (hdr.flags & macho.MH_LAZY_INIT != 0) try writer.writeAll(" LAZY_INIT");
912 if (hdr.flags & macho.MH_TWOLEVEL != 0) try writer.writeAll(" TWOLEVEL");
913 if (hdr.flags & macho.MH_FORCE_FLAT != 0) try writer.writeAll(" FORCE_FLAT");
914 if (hdr.flags & macho.MH_NOMULTIDEFS != 0) try writer.writeAll(" NOMULTIDEFS");
915 if (hdr.flags & macho.MH_NOFIXPREBINDING != 0) try writer.writeAll(" NOFIXPREBINDING");
916 if (hdr.flags & macho.MH_PREBINDABLE != 0) try writer.writeAll(" PREBINDABLE");
917 if (hdr.flags & macho.MH_ALLMODSBOUND != 0) try writer.writeAll(" ALLMODSBOUND");
918 if (hdr.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) try writer.writeAll(" SUBSECTIONS_VIA_SYMBOLS");
919 if (hdr.flags & macho.MH_CANONICAL != 0) try writer.writeAll(" CANONICAL");
920 if (hdr.flags & macho.MH_WEAK_DEFINES != 0) try writer.writeAll(" WEAK_DEFINES");
921 if (hdr.flags & macho.MH_BINDS_TO_WEAK != 0) try writer.writeAll(" BINDS_TO_WEAK");
922 if (hdr.flags & macho.MH_ALLOW_STACK_EXECUTION != 0) try writer.writeAll(" ALLOW_STACK_EXECUTION");
923 if (hdr.flags & macho.MH_ROOT_SAFE != 0) try writer.writeAll(" ROOT_SAFE");
924 if (hdr.flags & macho.MH_SETUID_SAFE != 0) try writer.writeAll(" SETUID_SAFE");
925 if (hdr.flags & macho.MH_NO_REEXPORTED_DYLIBS != 0) try writer.writeAll(" NO_REEXPORTED_DYLIBS");
926 if (hdr.flags & macho.MH_PIE != 0) try writer.writeAll(" PIE");
927 if (hdr.flags & macho.MH_DEAD_STRIPPABLE_DYLIB != 0) try writer.writeAll(" DEAD_STRIPPABLE_DYLIB");
928 if (hdr.flags & macho.MH_HAS_TLV_DESCRIPTORS != 0) try writer.writeAll(" HAS_TLV_DESCRIPTORS");
929 if (hdr.flags & macho.MH_NO_HEAP_EXECUTION != 0) try writer.writeAll(" NO_HEAP_EXECUTION");
930 if (hdr.flags & macho.MH_APP_EXTENSION_SAFE != 0) try writer.writeAll(" APP_EXTENSION_SAFE");
931 if (hdr.flags & macho.MH_NLIST_OUTOFSYNC_WITH_DYLDINFO != 0) try writer.writeAll(" NLIST_OUTOFSYNC_WITH_DYLDINFO");
857 try writer.writeByte('\n');
932858 }
933859
934 try writer.writeByte('\n');
935 }
860 fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void {
861 // print header first
862 try writer.print(
863 \\LC {d}
864 \\cmd {s}
865 \\cmdsize {d}
866 , .{ index, @tagName(lc.cmd()), lc.cmdsize() });
867
868 switch (lc.cmd()) {
869 .SEGMENT_64 => {
870 const seg = lc.cast(macho.segment_command_64).?;
871 try writer.writeByte('\n');
872 try writer.print(
873 \\segname {s}
874 \\vmaddr {x}
875 \\vmsize {x}
876 \\fileoff {x}
877 \\filesz {x}
878 , .{
879 seg.segName(),
880 seg.vmaddr,
881 seg.vmsize,
882 seg.fileoff,
883 seg.filesize,
884 });
936885
937 fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void {
938 // print header first
939 try writer.print(
940 \\LC {d}
941 \\cmd {s}
942 \\cmdsize {d}
943 , .{ index, @tagName(lc.cmd()), lc.cmdsize() });
944
945 switch (lc.cmd()) {
946 .SEGMENT_64 => {
947 const seg = lc.cast(macho.segment_command_64).?;
948 try writer.writeByte('\n');
949 try writer.print(
950 \\segname {s}
951 \\vmaddr {x}
952 \\vmsize {x}
953 \\fileoff {x}
954 \\filesz {x}
955 , .{
956 seg.segName(),
957 seg.vmaddr,
958 seg.vmsize,
959 seg.fileoff,
960 seg.filesize,
961 });
886 for (lc.getSections()) |sect| {
887 try writer.writeByte('\n');
888 try writer.print(
889 \\sectname {s}
890 \\addr {x}
891 \\size {x}
892 \\offset {x}
893 \\align {x}
894 , .{
895 sect.sectName(),
896 sect.addr,
897 sect.size,
898 sect.offset,
899 sect.@"align",
900 });
901 }
902 },
962903
963 for (lc.getSections()) |sect| {
904 .ID_DYLIB,
905 .LOAD_DYLIB,
906 .LOAD_WEAK_DYLIB,
907 .REEXPORT_DYLIB,
908 => {
909 const dylib = lc.cast(macho.dylib_command).?;
964910 try writer.writeByte('\n');
965911 try writer.print(
966 \\sectname {s}
967 \\addr {x}
968 \\size {x}
969 \\offset {x}
970 \\align {x}
912 \\name {s}
913 \\timestamp {d}
914 \\current version {x}
915 \\compatibility version {x}
971916 , .{
972 sect.sectName(),
973 sect.addr,
974 sect.size,
975 sect.offset,
976 sect.@"align",
917 lc.getDylibPathName(),
918 dylib.dylib.timestamp,
919 dylib.dylib.current_version,
920 dylib.dylib.compatibility_version,
977921 });
978 }
979 },
980
981 .ID_DYLIB,
982 .LOAD_DYLIB,
983 .LOAD_WEAK_DYLIB,
984 .REEXPORT_DYLIB,
985 => {
986 const dylib = lc.cast(macho.dylib_command).?;
987 try writer.writeByte('\n');
988 try writer.print(
989 \\name {s}
990 \\timestamp {d}
991 \\current version {x}
992 \\compatibility version {x}
993 , .{
994 lc.getDylibPathName(),
995 dylib.dylib.timestamp,
996 dylib.dylib.current_version,
997 dylib.dylib.compatibility_version,
998 });
999 },
922 },
1000923
1001 .MAIN => {
1002 const main = lc.cast(macho.entry_point_command).?;
1003 try writer.writeByte('\n');
1004 try writer.print(
1005 \\entryoff {x}
1006 \\stacksize {x}
1007 , .{ main.entryoff, main.stacksize });
1008 },
924 .MAIN => {
925 const main = lc.cast(macho.entry_point_command).?;
926 try writer.writeByte('\n');
927 try writer.print(
928 \\entryoff {x}
929 \\stacksize {x}
930 , .{ main.entryoff, main.stacksize });
931 },
1009932
1010 .RPATH => {
1011 try writer.writeByte('\n');
1012 try writer.print(
1013 \\path {s}
1014 , .{
1015 lc.getRpathPathName(),
1016 });
1017 },
933 .RPATH => {
934 try writer.writeByte('\n');
935 try writer.print(
936 \\path {s}
937 , .{
938 lc.getRpathPathName(),
939 });
940 },
1018941
1019 .UUID => {
1020 const uuid = lc.cast(macho.uuid_command).?;
1021 try writer.writeByte('\n');
1022 try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)});
1023 },
942 .UUID => {
943 const uuid = lc.cast(macho.uuid_command).?;
944 try writer.writeByte('\n');
945 try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)});
946 },
1024947
1025 .DATA_IN_CODE,
1026 .FUNCTION_STARTS,
1027 .CODE_SIGNATURE,
1028 => {
1029 const llc = lc.cast(macho.linkedit_data_command).?;
1030 try writer.writeByte('\n');
1031 try writer.print(
1032 \\dataoff {x}
1033 \\datasize {x}
1034 , .{ llc.dataoff, llc.datasize });
1035 },
948 .DATA_IN_CODE,
949 .FUNCTION_STARTS,
950 .CODE_SIGNATURE,
951 => {
952 const llc = lc.cast(macho.linkedit_data_command).?;
953 try writer.writeByte('\n');
954 try writer.print(
955 \\dataoff {x}
956 \\datasize {x}
957 , .{ llc.dataoff, llc.datasize });
958 },
1036959
1037 .DYLD_INFO_ONLY => {
1038 const dlc = lc.cast(macho.dyld_info_command).?;
1039 try writer.writeByte('\n');
1040 try writer.print(
1041 \\rebaseoff {x}
1042 \\rebasesize {x}
1043 \\bindoff {x}
1044 \\bindsize {x}
1045 \\weakbindoff {x}
1046 \\weakbindsize {x}
1047 \\lazybindoff {x}
1048 \\lazybindsize {x}
1049 \\exportoff {x}
1050 \\exportsize {x}
1051 , .{
1052 dlc.rebase_off,
1053 dlc.rebase_size,
1054 dlc.bind_off,
1055 dlc.bind_size,
1056 dlc.weak_bind_off,
1057 dlc.weak_bind_size,
1058 dlc.lazy_bind_off,
1059 dlc.lazy_bind_size,
1060 dlc.export_off,
1061 dlc.export_size,
1062 });
1063 },
960 .DYLD_INFO_ONLY => {
961 const dlc = lc.cast(macho.dyld_info_command).?;
962 try writer.writeByte('\n');
963 try writer.print(
964 \\rebaseoff {x}
965 \\rebasesize {x}
966 \\bindoff {x}
967 \\bindsize {x}
968 \\weakbindoff {x}
969 \\weakbindsize {x}
970 \\lazybindoff {x}
971 \\lazybindsize {x}
972 \\exportoff {x}
973 \\exportsize {x}
974 , .{
975 dlc.rebase_off,
976 dlc.rebase_size,
977 dlc.bind_off,
978 dlc.bind_size,
979 dlc.weak_bind_off,
980 dlc.weak_bind_size,
981 dlc.lazy_bind_off,
982 dlc.lazy_bind_size,
983 dlc.export_off,
984 dlc.export_size,
985 });
986 },
1064987
1065 .SYMTAB => {
1066 const slc = lc.cast(macho.symtab_command).?;
1067 try writer.writeByte('\n');
1068 try writer.print(
1069 \\symoff {x}
1070 \\nsyms {x}
1071 \\stroff {x}
1072 \\strsize {x}
1073 , .{
1074 slc.symoff,
1075 slc.nsyms,
1076 slc.stroff,
1077 slc.strsize,
1078 });
1079 },
988 .SYMTAB => {
989 const slc = lc.cast(macho.symtab_command).?;
990 try writer.writeByte('\n');
991 try writer.print(
992 \\symoff {x}
993 \\nsyms {x}
994 \\stroff {x}
995 \\strsize {x}
996 , .{
997 slc.symoff,
998 slc.nsyms,
999 slc.stroff,
1000 slc.strsize,
1001 });
1002 },
10801003
1081 .DYSYMTAB => {
1082 const dlc = lc.cast(macho.dysymtab_command).?;
1083 try writer.writeByte('\n');
1084 try writer.print(
1085 \\ilocalsym {x}
1086 \\nlocalsym {x}
1087 \\iextdefsym {x}
1088 \\nextdefsym {x}
1089 \\iundefsym {x}
1090 \\nundefsym {x}
1091 \\indirectsymoff {x}
1092 \\nindirectsyms {x}
1093 , .{
1094 dlc.ilocalsym,
1095 dlc.nlocalsym,
1096 dlc.iextdefsym,
1097 dlc.nextdefsym,
1098 dlc.iundefsym,
1099 dlc.nundefsym,
1100 dlc.indirectsymoff,
1101 dlc.nindirectsyms,
1102 });
1103 },
1004 .DYSYMTAB => {
1005 const dlc = lc.cast(macho.dysymtab_command).?;
1006 try writer.writeByte('\n');
1007 try writer.print(
1008 \\ilocalsym {x}
1009 \\nlocalsym {x}
1010 \\iextdefsym {x}
1011 \\nextdefsym {x}
1012 \\iundefsym {x}
1013 \\nundefsym {x}
1014 \\indirectsymoff {x}
1015 \\nindirectsyms {x}
1016 , .{
1017 dlc.ilocalsym,
1018 dlc.nlocalsym,
1019 dlc.iextdefsym,
1020 dlc.nextdefsym,
1021 dlc.iundefsym,
1022 dlc.nundefsym,
1023 dlc.indirectsymoff,
1024 dlc.nindirectsyms,
1025 });
1026 },
11041027
1105 .BUILD_VERSION => {
1106 const blc = lc.cast(macho.build_version_command).?;
1107 try writer.writeByte('\n');
1108 try writer.print(
1109 \\platform {s}
1110 \\minos {d}.{d}.{d}
1111 \\sdk {d}.{d}.{d}
1112 \\ntools {d}
1113 , .{
1114 @tagName(blc.platform),
1115 blc.minos >> 16,
1116 @as(u8, @truncate(blc.minos >> 8)),
1117 @as(u8, @truncate(blc.minos)),
1118 blc.sdk >> 16,
1119 @as(u8, @truncate(blc.sdk >> 8)),
1120 @as(u8, @truncate(blc.sdk)),
1121 blc.ntools,
1122 });
1123 for (lc.getBuildVersionTools()) |tool| {
1028 .BUILD_VERSION => {
1029 const blc = lc.cast(macho.build_version_command).?;
11241030 try writer.writeByte('\n');
1125 switch (tool.tool) {
1126 .CLANG, .SWIFT, .LD, .LLD, .ZIG => try writer.print("tool {s}\n", .{@tagName(tool.tool)}),
1127 else => |x| try writer.print("tool {d}\n", .{@intFromEnum(x)}),
1031 try writer.print(
1032 \\platform {s}
1033 \\minos {d}.{d}.{d}
1034 \\sdk {d}.{d}.{d}
1035 \\ntools {d}
1036 , .{
1037 @tagName(blc.platform),
1038 blc.minos >> 16,
1039 @as(u8, @truncate(blc.minos >> 8)),
1040 @as(u8, @truncate(blc.minos)),
1041 blc.sdk >> 16,
1042 @as(u8, @truncate(blc.sdk >> 8)),
1043 @as(u8, @truncate(blc.sdk)),
1044 blc.ntools,
1045 });
1046 for (lc.getBuildVersionTools()) |tool| {
1047 try writer.writeByte('\n');
1048 switch (tool.tool) {
1049 .CLANG, .SWIFT, .LD, .LLD, .ZIG => try writer.print("tool {s}\n", .{@tagName(tool.tool)}),
1050 else => |x| try writer.print("tool {d}\n", .{@intFromEnum(x)}),
1051 }
1052 try writer.print(
1053 \\version {d}.{d}.{d}
1054 , .{
1055 tool.version >> 16,
1056 @as(u8, @truncate(tool.version >> 8)),
1057 @as(u8, @truncate(tool.version)),
1058 });
11281059 }
1060 },
1061
1062 .VERSION_MIN_MACOSX,
1063 .VERSION_MIN_IPHONEOS,
1064 .VERSION_MIN_WATCHOS,
1065 .VERSION_MIN_TVOS,
1066 => {
1067 const vlc = lc.cast(macho.version_min_command).?;
1068 try writer.writeByte('\n');
11291069 try writer.print(
11301070 \\version {d}.{d}.{d}
1071 \\sdk {d}.{d}.{d}
11311072 , .{
1132 tool.version >> 16,
1133 @as(u8, @truncate(tool.version >> 8)),
1134 @as(u8, @truncate(tool.version)),
1073 vlc.version >> 16,
1074 @as(u8, @truncate(vlc.version >> 8)),
1075 @as(u8, @truncate(vlc.version)),
1076 vlc.sdk >> 16,
1077 @as(u8, @truncate(vlc.sdk >> 8)),
1078 @as(u8, @truncate(vlc.sdk)),
11351079 });
1136 }
1137 },
1138
1139 .VERSION_MIN_MACOSX,
1140 .VERSION_MIN_IPHONEOS,
1141 .VERSION_MIN_WATCHOS,
1142 .VERSION_MIN_TVOS,
1143 => {
1144 const vlc = lc.cast(macho.version_min_command).?;
1145 try writer.writeByte('\n');
1146 try writer.print(
1147 \\version {d}.{d}.{d}
1148 \\sdk {d}.{d}.{d}
1149 , .{
1150 vlc.version >> 16,
1151 @as(u8, @truncate(vlc.version >> 8)),
1152 @as(u8, @truncate(vlc.version)),
1153 vlc.sdk >> 16,
1154 @as(u8, @truncate(vlc.sdk >> 8)),
1155 @as(u8, @truncate(vlc.sdk)),
1156 });
1157 },
1080 },
11581081
1159 else => {},
1082 else => {},
1083 }
11601084 }
1161 }
11621085
1163 fn dumpSymtab(
1164 sections: []const macho.section_64,
1165 imports: []const []const u8,
1166 symtab: Symtab,
1167 writer: anytype,
1168 ) !void {
1169 try writer.writeAll(symtab_label ++ "\n");
1170
1171 for (symtab.symbols) |sym| {
1172 if (sym.stab()) continue;
1173 const sym_name = symtab.getString(sym.n_strx);
1174 if (sym.sect()) {
1175 const sect = sections[sym.n_sect - 1];
1176 try writer.print("{x} ({s},{s})", .{
1177 sym.n_value,
1178 sect.segName(),
1179 sect.sectName(),
1180 });
1181 if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]");
1182 if (sym.weakDef()) try writer.writeAll(" weak");
1183 if (sym.weakRef()) try writer.writeAll(" weakref");
1184 if (sym.ext()) {
1185 if (sym.pext()) try writer.writeAll(" private");
1186 try writer.writeAll(" external");
1187 } else if (sym.pext()) try writer.writeAll(" (was private external)");
1188 try writer.print(" {s}\n", .{sym_name});
1189 } else if (sym.tentative()) {
1190 const alignment = (sym.n_desc >> 8) & 0x0F;
1191 try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment });
1192 if (sym.ext()) try writer.writeAll(" external");
1193 try writer.print(" {s}\n", .{sym_name});
1194 } else if (sym.undf()) {
1195 const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
1196 const import_name = blk: {
1197 if (ordinal <= 0) {
1198 if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF)
1199 break :blk "self import";
1200 if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE)
1201 break :blk "main executable";
1202 if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)
1203 break :blk "flat lookup";
1204 unreachable;
1205 }
1206 const full_path = imports[@as(u16, @bitCast(ordinal)) - 1];
1207 const basename = fs.path.basename(full_path);
1208 assert(basename.len > 0);
1209 const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len;
1210 break :blk basename[0..ext];
1211 };
1212 try writer.writeAll("(undefined)");
1213 if (sym.weakRef()) try writer.writeAll(" weakref");
1214 if (sym.ext()) try writer.writeAll(" external");
1215 try writer.print(" {s} (from {s})\n", .{
1216 sym_name,
1217 import_name,
1218 });
1086 fn dumpSymtab(ctx: ObjectContext, writer: anytype) !void {
1087 try writer.writeAll(symtab_label ++ "\n");
1088
1089 for (ctx.symtab.items) |sym| {
1090 if (sym.stab()) continue;
1091 const sym_name = ctx.getString(sym.n_strx);
1092 if (sym.sect()) {
1093 const sect = ctx.sections.items[sym.n_sect - 1];
1094 try writer.print("{x} ({s},{s})", .{
1095 sym.n_value,
1096 sect.segName(),
1097 sect.sectName(),
1098 });
1099 if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]");
1100 if (sym.weakDef()) try writer.writeAll(" weak");
1101 if (sym.weakRef()) try writer.writeAll(" weakref");
1102 if (sym.ext()) {
1103 if (sym.pext()) try writer.writeAll(" private");
1104 try writer.writeAll(" external");
1105 } else if (sym.pext()) try writer.writeAll(" (was private external)");
1106 try writer.print(" {s}\n", .{sym_name});
1107 } else if (sym.tentative()) {
1108 const alignment = (sym.n_desc >> 8) & 0x0F;
1109 try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment });
1110 if (sym.ext()) try writer.writeAll(" external");
1111 try writer.print(" {s}\n", .{sym_name});
1112 } else if (sym.undf()) {
1113 const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
1114 const import_name = blk: {
1115 if (ordinal <= 0) {
1116 if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF)
1117 break :blk "self import";
1118 if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE)
1119 break :blk "main executable";
1120 if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)
1121 break :blk "flat lookup";
1122 unreachable;
1123 }
1124 const full_path = ctx.imports.items[@as(u16, @bitCast(ordinal)) - 1];
1125 const basename = fs.path.basename(full_path);
1126 assert(basename.len > 0);
1127 const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len;
1128 break :blk basename[0..ext];
1129 };
1130 try writer.writeAll("(undefined)");
1131 if (sym.weakRef()) try writer.writeAll(" weakref");
1132 if (sym.ext()) try writer.writeAll(" external");
1133 try writer.print(" {s} (from {s})\n", .{
1134 sym_name,
1135 import_name,
1136 });
1137 }
12191138 }
12201139 }
1221 }
12221140
1223 fn dumpIndirectSymtab(
1224 gpa: Allocator,
1225 sections: []const macho.section_64,
1226 symtab: Symtab,
1227 writer: anytype,
1228 ) !void {
1229 try writer.writeAll(indirect_symtab_label ++ "\n");
1141 fn dumpIndirectSymtab(ctx: ObjectContext, writer: anytype) !void {
1142 try writer.writeAll(indirect_symtab_label ++ "\n");
12301143
1231 var sects = std.ArrayList(macho.section_64).init(gpa);
1232 defer sects.deinit();
1233 try sects.ensureUnusedCapacity(3);
1144 var sects_buffer: [3]macho.section_64 = undefined;
1145 const sects = blk: {
1146 var count: usize = 0;
1147 if (ctx.getSectionByName("__TEXT", "__stubs")) |sect| {
1148 sects_buffer[count] = sect;
1149 count += 1;
1150 }
1151 if (ctx.getSectionByName("__DATA_CONST", "__got")) |sect| {
1152 sects_buffer[count] = sect;
1153 count += 1;
1154 }
1155 if (ctx.getSectionByName("__DATA", "__la_symbol_ptr")) |sect| {
1156 sects_buffer[count] = sect;
1157 count += 1;
1158 }
1159 break :blk sects_buffer[0..count];
1160 };
12341161
1235 for (sections) |sect| {
1236 if (mem.eql(u8, sect.sectName(), "__stubs")) sects.appendAssumeCapacity(sect);
1237 if (mem.eql(u8, sect.sectName(), "__got")) sects.appendAssumeCapacity(sect);
1238 if (mem.eql(u8, sect.sectName(), "__la_symbol_ptr")) sects.appendAssumeCapacity(sect);
1239 }
1162 const sortFn = struct {
1163 fn sortFn(c: void, lhs: macho.section_64, rhs: macho.section_64) bool {
1164 _ = c;
1165 return lhs.reserved1 < rhs.reserved1;
1166 }
1167 }.sortFn;
1168 mem.sort(macho.section_64, sects, {}, sortFn);
12401169
1241 const sortFn = struct {
1242 fn sortFn(ctx: void, lhs: macho.section_64, rhs: macho.section_64) bool {
1243 _ = ctx;
1244 return lhs.reserved1 < rhs.reserved1;
1245 }
1246 }.sortFn;
1247 mem.sort(macho.section_64, sects.items, {}, sortFn);
1248
1249 var i: usize = 0;
1250 while (i < sects.items.len) : (i += 1) {
1251 const sect = sects.items[i];
1252 const start = sect.reserved1;
1253 const end = if (i + 1 >= sects.items.len) symtab.indirect_symbols.len else sects.items[i + 1].reserved1;
1254 const entry_size = blk: {
1255 if (mem.eql(u8, sect.sectName(), "__stubs")) break :blk sect.reserved2;
1256 break :blk @sizeOf(u64);
1257 };
1170 var i: usize = 0;
1171 while (i < sects.len) : (i += 1) {
1172 const sect = sects[i];
1173 const start = sect.reserved1;
1174 const end = if (i + 1 >= sects.len) ctx.indsymtab.items.len else sects[i + 1].reserved1;
1175 const entry_size = blk: {
1176 if (mem.eql(u8, sect.sectName(), "__stubs")) break :blk sect.reserved2;
1177 break :blk @sizeOf(u64);
1178 };
12581179
1259 try writer.print("{s},{s}\n", .{ sect.segName(), sect.sectName() });
1260 try writer.print("nentries {d}\n", .{end - start});
1261 for (symtab.indirect_symbols[start..end], 0..) |index, j| {
1262 const sym = symtab.symbols[index];
1263 const addr = sect.addr + entry_size * j;
1264 try writer.print("0x{x} {d} {s}\n", .{ addr, index, symtab.getString(sym.n_strx) });
1180 try writer.print("{s},{s}\n", .{ sect.segName(), sect.sectName() });
1181 try writer.print("nentries {d}\n", .{end - start});
1182 for (ctx.indsymtab.items[start..end], 0..) |index, j| {
1183 const sym = ctx.symtab.items[index];
1184 const addr = sect.addr + entry_size * j;
1185 try writer.print("0x{x} {d} {s}\n", .{ addr, index, ctx.getString(sym.n_strx) });
1186 }
12651187 }
12661188 }
1267 }
12681189
1269 fn dumpRebaseInfo(
1270 gpa: Allocator,
1271 data: []const u8,
1272 segments: []const macho.segment_command_64,
1273 writer: anytype,
1274 ) !void {
1275 var rebases = std.ArrayList(u64).init(gpa);
1276 defer rebases.deinit();
1277 try parseRebaseInfo(data, segments, &rebases);
1278 mem.sort(u64, rebases.items, {}, std.sort.asc(u64));
1279 for (rebases.items) |addr| {
1280 try writer.print("0x{x}\n", .{addr});
1190 fn dumpRebaseInfo(ctx: ObjectContext, data: []const u8, writer: anytype) !void {
1191 var rebases = std.ArrayList(u64).init(ctx.gpa);
1192 defer rebases.deinit();
1193 try ctx.parseRebaseInfo(data, &rebases);
1194 mem.sort(u64, rebases.items, {}, std.sort.asc(u64));
1195 for (rebases.items) |addr| {
1196 try writer.print("0x{x}\n", .{addr});
1197 }
12811198 }
1282 }
12831199
1284 fn parseRebaseInfo(
1285 data: []const u8,
1286 segments: []const macho.segment_command_64,
1287 rebases: *std.ArrayList(u64),
1288 ) !void {
1289 var stream = std.io.fixedBufferStream(data);
1290 var creader = std.io.countingReader(stream.reader());
1291 const reader = creader.reader();
1200 fn parseRebaseInfo(ctx: ObjectContext, data: []const u8, rebases: *std.ArrayList(u64)) !void {
1201 var stream = std.io.fixedBufferStream(data);
1202 var creader = std.io.countingReader(stream.reader());
1203 const reader = creader.reader();
12921204
1293 var seg_id: ?u8 = null;
1294 var offset: u64 = 0;
1295 while (true) {
1296 const byte = reader.readByte() catch break;
1297 const opc = byte & macho.REBASE_OPCODE_MASK;
1298 const imm = byte & macho.REBASE_IMMEDIATE_MASK;
1299 switch (opc) {
1300 macho.REBASE_OPCODE_DONE => break,
1301 macho.REBASE_OPCODE_SET_TYPE_IMM => {},
1302 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
1303 seg_id = imm;
1304 offset = try std.leb.readULEB128(u64, reader);
1305 },
1306 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED => {
1307 offset += imm * @sizeOf(u64);
1308 },
1309 macho.REBASE_OPCODE_ADD_ADDR_ULEB => {
1310 const addend = try std.leb.readULEB128(u64, reader);
1311 offset += addend;
1312 },
1313 macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB => {
1314 const addend = try std.leb.readULEB128(u64, reader);
1315 const seg = segments[seg_id.?];
1316 const addr = seg.vmaddr + offset;
1317 try rebases.append(addr);
1318 offset += addend + @sizeOf(u64);
1319 },
1320 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES,
1321 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
1322 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
1323 => {
1324 var ntimes: u64 = 1;
1325 var skip: u64 = 0;
1326 switch (opc) {
1327 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES => {
1328 ntimes = imm;
1329 },
1330 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES => {
1331 ntimes = try std.leb.readULEB128(u64, reader);
1332 },
1333 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB => {
1334 ntimes = try std.leb.readULEB128(u64, reader);
1335 skip = try std.leb.readULEB128(u64, reader);
1336 },
1337 else => unreachable,
1338 }
1339 const seg = segments[seg_id.?];
1340 const base_addr = seg.vmaddr;
1341 var count: usize = 0;
1342 while (count < ntimes) : (count += 1) {
1343 const addr = base_addr + offset;
1205 var seg_id: ?u8 = null;
1206 var offset: u64 = 0;
1207 while (true) {
1208 const byte = reader.readByte() catch break;
1209 const opc = byte & macho.REBASE_OPCODE_MASK;
1210 const imm = byte & macho.REBASE_IMMEDIATE_MASK;
1211 switch (opc) {
1212 macho.REBASE_OPCODE_DONE => break,
1213 macho.REBASE_OPCODE_SET_TYPE_IMM => {},
1214 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
1215 seg_id = imm;
1216 offset = try std.leb.readULEB128(u64, reader);
1217 },
1218 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED => {
1219 offset += imm * @sizeOf(u64);
1220 },
1221 macho.REBASE_OPCODE_ADD_ADDR_ULEB => {
1222 const addend = try std.leb.readULEB128(u64, reader);
1223 offset += addend;
1224 },
1225 macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB => {
1226 const addend = try std.leb.readULEB128(u64, reader);
1227 const seg = ctx.segments.items[seg_id.?];
1228 const addr = seg.vmaddr + offset;
13441229 try rebases.append(addr);
1345 offset += skip + @sizeOf(u64);
1346 }
1347 },
1348 else => break,
1230 offset += addend + @sizeOf(u64);
1231 },
1232 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES,
1233 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
1234 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
1235 => {
1236 var ntimes: u64 = 1;
1237 var skip: u64 = 0;
1238 switch (opc) {
1239 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES => {
1240 ntimes = imm;
1241 },
1242 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES => {
1243 ntimes = try std.leb.readULEB128(u64, reader);
1244 },
1245 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB => {
1246 ntimes = try std.leb.readULEB128(u64, reader);
1247 skip = try std.leb.readULEB128(u64, reader);
1248 },
1249 else => unreachable,
1250 }
1251 const seg = ctx.segments.items[seg_id.?];
1252 const base_addr = seg.vmaddr;
1253 var count: usize = 0;
1254 while (count < ntimes) : (count += 1) {
1255 const addr = base_addr + offset;
1256 try rebases.append(addr);
1257 offset += skip + @sizeOf(u64);
1258 }
1259 },
1260 else => break,
1261 }
13491262 }
13501263 }
1351 }
13521264
1353 const Binding = struct {
1354 address: u64,
1355 addend: i64,
1356 ordinal: u16,
1357 tag: Tag,
1358 name: []const u8,
1265 const Binding = struct {
1266 address: u64,
1267 addend: i64,
1268 ordinal: u16,
1269 tag: Tag,
1270 name: []const u8,
13591271
1360 fn deinit(binding: *Binding, gpa: Allocator) void {
1361 gpa.free(binding.name);
1362 }
1272 fn deinit(binding: *Binding, gpa: Allocator) void {
1273 gpa.free(binding.name);
1274 }
13631275
1364 fn lessThan(ctx: void, lhs: Binding, rhs: Binding) bool {
1365 _ = ctx;
1366 return lhs.address < rhs.address;
1367 }
1276 fn lessThan(ctx: void, lhs: Binding, rhs: Binding) bool {
1277 _ = ctx;
1278 return lhs.address < rhs.address;
1279 }
13681280
1369 const Tag = enum {
1370 ord,
1371 self,
1372 exe,
1373 flat,
1281 const Tag = enum {
1282 ord,
1283 self,
1284 exe,
1285 flat,
1286 };
13741287 };
1375 };
13761288
1377 fn dumpBindInfo(
1378 gpa: Allocator,
1379 data: []const u8,
1380 segments: []const macho.segment_command_64,
1381 dylibs: []const []const u8,
1382 writer: anytype,
1383 ) !void {
1384 var bindings = std.ArrayList(Binding).init(gpa);
1385 defer {
1386 for (bindings.items) |*b| {
1387 b.deinit(gpa);
1289 fn dumpBindInfo(ctx: ObjectContext, data: []const u8, writer: anytype) !void {
1290 var bindings = std.ArrayList(Binding).init(ctx.gpa);
1291 defer {
1292 for (bindings.items) |*b| {
1293 b.deinit(ctx.gpa);
1294 }
1295 bindings.deinit();
1296 }
1297 try ctx.parseBindInfo(data, &bindings);
1298 mem.sort(Binding, bindings.items, {}, Binding.lessThan);
1299 for (bindings.items) |binding| {
1300 try writer.print("0x{x} [addend: {d}]", .{ binding.address, binding.addend });
1301 try writer.writeAll(" (");
1302 switch (binding.tag) {
1303 .self => try writer.writeAll("self"),
1304 .exe => try writer.writeAll("main executable"),
1305 .flat => try writer.writeAll("flat lookup"),
1306 .ord => try writer.writeAll(std.fs.path.basename(ctx.imports.items[binding.ordinal - 1])),
1307 }
1308 try writer.print(") {s}\n", .{binding.name});
13881309 }
1389 bindings.deinit();
13901310 }
1391 try parseBindInfo(gpa, data, segments, &bindings);
1392 mem.sort(Binding, bindings.items, {}, Binding.lessThan);
1393 for (bindings.items) |binding| {
1394 try writer.print("0x{x} [addend: {d}]", .{ binding.address, binding.addend });
1395 try writer.writeAll(" (");
1396 switch (binding.tag) {
1397 .self => try writer.writeAll("self"),
1398 .exe => try writer.writeAll("main executable"),
1399 .flat => try writer.writeAll("flat lookup"),
1400 .ord => try writer.writeAll(std.fs.path.basename(dylibs[binding.ordinal - 1])),
1311
1312 fn parseBindInfo(ctx: ObjectContext, data: []const u8, bindings: *std.ArrayList(Binding)) !void {
1313 var stream = std.io.fixedBufferStream(data);
1314 var creader = std.io.countingReader(stream.reader());
1315 const reader = creader.reader();
1316
1317 var seg_id: ?u8 = null;
1318 var tag: Binding.Tag = .self;
1319 var ordinal: u16 = 0;
1320 var offset: u64 = 0;
1321 var addend: i64 = 0;
1322
1323 var name_buf = std.ArrayList(u8).init(ctx.gpa);
1324 defer name_buf.deinit();
1325
1326 while (true) {
1327 const byte = reader.readByte() catch break;
1328 const opc = byte & macho.BIND_OPCODE_MASK;
1329 const imm = byte & macho.BIND_IMMEDIATE_MASK;
1330 switch (opc) {
1331 macho.BIND_OPCODE_DONE,
1332 macho.BIND_OPCODE_SET_TYPE_IMM,
1333 => {},
1334 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
1335 tag = .ord;
1336 ordinal = imm;
1337 },
1338 macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM => {
1339 switch (imm) {
1340 0 => tag = .self,
1341 0xf => tag = .exe,
1342 0xe => tag = .flat,
1343 else => unreachable,
1344 }
1345 },
1346 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
1347 seg_id = imm;
1348 offset = try std.leb.readULEB128(u64, reader);
1349 },
1350 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
1351 name_buf.clearRetainingCapacity();
1352 try reader.readUntilDelimiterArrayList(&name_buf, 0, std.math.maxInt(u32));
1353 try name_buf.append(0);
1354 },
1355 macho.BIND_OPCODE_SET_ADDEND_SLEB => {
1356 addend = try std.leb.readILEB128(i64, reader);
1357 },
1358 macho.BIND_OPCODE_ADD_ADDR_ULEB => {
1359 const x = try std.leb.readULEB128(u64, reader);
1360 offset = @intCast(@as(i64, @intCast(offset)) + @as(i64, @bitCast(x)));
1361 },
1362 macho.BIND_OPCODE_DO_BIND,
1363 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB,
1364 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED,
1365 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
1366 => {
1367 var add_addr: u64 = 0;
1368 var count: u64 = 1;
1369 var skip: u64 = 0;
1370
1371 switch (opc) {
1372 macho.BIND_OPCODE_DO_BIND => {},
1373 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => {
1374 add_addr = try std.leb.readULEB128(u64, reader);
1375 },
1376 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED => {
1377 add_addr = imm * @sizeOf(u64);
1378 },
1379 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => {
1380 count = try std.leb.readULEB128(u64, reader);
1381 skip = try std.leb.readULEB128(u64, reader);
1382 },
1383 else => unreachable,
1384 }
1385
1386 const seg = ctx.segments.items[seg_id.?];
1387 var i: u64 = 0;
1388 while (i < count) : (i += 1) {
1389 const addr: u64 = @intCast(@as(i64, @intCast(seg.vmaddr + offset)));
1390 try bindings.append(.{
1391 .address = addr,
1392 .addend = addend,
1393 .tag = tag,
1394 .ordinal = ordinal,
1395 .name = try ctx.gpa.dupe(u8, name_buf.items),
1396 });
1397 offset += skip + @sizeOf(u64) + add_addr;
1398 }
1399 },
1400 else => break,
1401 }
14011402 }
1402 try writer.print(") {s}\n", .{binding.name});
14031403 }
1404 }
14051404
1406 fn parseBindInfo(
1407 gpa: Allocator,
1408 data: []const u8,
1409 segments: []const macho.segment_command_64,
1410 bindings: *std.ArrayList(Binding),
1411 ) !void {
1412 var stream = std.io.fixedBufferStream(data);
1413 var creader = std.io.countingReader(stream.reader());
1414 const reader = creader.reader();
1405 fn dumpExportsTrie(ctx: ObjectContext, data: []const u8, writer: anytype) !void {
1406 const seg = ctx.getSegmentByName("__TEXT") orelse return;
14151407
1416 var seg_id: ?u8 = null;
1417 var tag: Binding.Tag = .self;
1418 var ordinal: u16 = 0;
1419 var offset: u64 = 0;
1420 var addend: i64 = 0;
1408 var arena = std.heap.ArenaAllocator.init(ctx.gpa);
1409 defer arena.deinit();
14211410
1422 var name_buf = std.ArrayList(u8).init(gpa);
1423 defer name_buf.deinit();
1411 var exports = std.ArrayList(Export).init(arena.allocator());
1412 var it = TrieIterator{ .data = data };
1413 try parseTrieNode(arena.allocator(), &it, "", &exports);
14241414
1425 while (true) {
1426 const byte = reader.readByte() catch break;
1427 const opc = byte & macho.BIND_OPCODE_MASK;
1428 const imm = byte & macho.BIND_IMMEDIATE_MASK;
1429 switch (opc) {
1430 macho.BIND_OPCODE_DONE,
1431 macho.BIND_OPCODE_SET_TYPE_IMM,
1432 => {},
1433 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
1434 tag = .ord;
1435 ordinal = imm;
1436 },
1437 macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM => {
1438 switch (imm) {
1439 0 => tag = .self,
1440 0xf => tag = .exe,
1441 0xe => tag = .flat,
1442 else => unreachable,
1443 }
1444 },
1445 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
1446 seg_id = imm;
1447 offset = try std.leb.readULEB128(u64, reader);
1448 },
1449 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
1450 name_buf.clearRetainingCapacity();
1451 try reader.readUntilDelimiterArrayList(&name_buf, 0, std.math.maxInt(u32));
1452 try name_buf.append(0);
1453 },
1454 macho.BIND_OPCODE_SET_ADDEND_SLEB => {
1455 addend = try std.leb.readILEB128(i64, reader);
1456 },
1457 macho.BIND_OPCODE_ADD_ADDR_ULEB => {
1458 const x = try std.leb.readULEB128(u64, reader);
1459 offset = @intCast(@as(i64, @intCast(offset)) + @as(i64, @bitCast(x)));
1460 },
1461 macho.BIND_OPCODE_DO_BIND,
1462 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB,
1463 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED,
1464 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
1465 => {
1466 var add_addr: u64 = 0;
1467 var count: u64 = 1;
1468 var skip: u64 = 0;
1469
1470 switch (opc) {
1471 macho.BIND_OPCODE_DO_BIND => {},
1472 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => {
1473 add_addr = try std.leb.readULEB128(u64, reader);
1474 },
1475 macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED => {
1476 add_addr = imm * @sizeOf(u64);
1477 },
1478 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => {
1479 count = try std.leb.readULEB128(u64, reader);
1480 skip = try std.leb.readULEB128(u64, reader);
1481 },
1482 else => unreachable,
1483 }
1415 mem.sort(Export, exports.items, {}, Export.lessThan);
14841416
1485 const seg = segments[seg_id.?];
1486 var i: u64 = 0;
1487 while (i < count) : (i += 1) {
1488 const addr: u64 = @intCast(@as(i64, @intCast(seg.vmaddr + offset)));
1489 try bindings.append(.{
1490 .address = addr,
1491 .addend = addend,
1492 .tag = tag,
1493 .ordinal = ordinal,
1494 .name = try gpa.dupe(u8, name_buf.items),
1495 });
1496 offset += skip + @sizeOf(u64) + add_addr;
1497 }
1498 },
1499 else => break,
1417 for (exports.items) |exp| {
1418 switch (exp.tag) {
1419 .@"export" => {
1420 const info = exp.data.@"export";
1421 if (info.kind != .regular or info.weak) {
1422 try writer.writeByte('[');
1423 }
1424 switch (info.kind) {
1425 .regular => {},
1426 .absolute => try writer.writeAll("ABS, "),
1427 .tlv => try writer.writeAll("THREAD_LOCAL, "),
1428 }
1429 if (info.weak) try writer.writeAll("WEAK");
1430 if (info.kind != .regular or info.weak) {
1431 try writer.writeAll("] ");
1432 }
1433 try writer.print("{x} ", .{seg.vmaddr + info.vmoffset});
1434 },
1435 else => {},
1436 }
1437
1438 try writer.print("{s}\n", .{exp.name});
15001439 }
15011440 }
1502 }
15031441
1504 fn dumpExportsTrie(
1505 gpa: Allocator,
1506 data: []const u8,
1507 seg: macho.segment_command_64,
1508 writer: anytype,
1509 ) !void {
1510 var arena = std.heap.ArenaAllocator.init(gpa);
1511 defer arena.deinit();
1442 const TrieIterator = struct {
1443 data: []const u8,
1444 pos: usize = 0,
15121445
1513 var exports = std.ArrayList(Export).init(arena.allocator());
1514 var it = TrieIterator{ .data = data };
1515 try parseTrieNode(arena.allocator(), &it, "", &exports);
1446 fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) {
1447 return std.io.fixedBufferStream(it.data[it.pos..]);
1448 }
15161449
1517 mem.sort(Export, exports.items, {}, Export.lessThan);
1450 fn readULEB128(it: *TrieIterator) !u64 {
1451 var stream = it.getStream();
1452 var creader = std.io.countingReader(stream.reader());
1453 const reader = creader.reader();
1454 const value = try std.leb.readULEB128(u64, reader);
1455 it.pos += creader.bytes_read;
1456 return value;
1457 }
15181458
1519 for (exports.items) |exp| {
1520 switch (exp.tag) {
1521 .@"export" => {
1522 const info = exp.data.@"export";
1523 if (info.kind != .regular or info.weak) {
1524 try writer.writeByte('[');
1525 }
1526 switch (info.kind) {
1527 .regular => {},
1528 .absolute => try writer.writeAll("ABS, "),
1529 .tlv => try writer.writeAll("THREAD_LOCAL, "),
1530 }
1531 if (info.weak) try writer.writeAll("WEAK");
1532 if (info.kind != .regular or info.weak) {
1533 try writer.writeAll("] ");
1534 }
1535 try writer.print("{x} ", .{seg.vmaddr + info.vmoffset});
1459 fn readString(it: *TrieIterator) ![:0]const u8 {
1460 var stream = it.getStream();
1461 const reader = stream.reader();
1462
1463 var count: usize = 0;
1464 while (true) : (count += 1) {
1465 const byte = try reader.readByte();
1466 if (byte == 0) break;
1467 }
1468
1469 const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0];
1470 it.pos += count + 1;
1471 return str;
1472 }
1473
1474 fn readByte(it: *TrieIterator) !u8 {
1475 var stream = it.getStream();
1476 const value = try stream.reader().readByte();
1477 it.pos += 1;
1478 return value;
1479 }
1480 };
1481
1482 const Export = struct {
1483 name: []const u8,
1484 tag: enum { @"export", reexport, stub_resolver },
1485 data: union {
1486 @"export": struct {
1487 kind: enum { regular, absolute, tlv },
1488 weak: bool = false,
1489 vmoffset: u64,
15361490 },
1537 else => {},
1491 reexport: u64,
1492 stub_resolver: struct {
1493 stub_offset: u64,
1494 resolver_offset: u64,
1495 },
1496 },
1497
1498 inline fn rankByTag(@"export": Export) u3 {
1499 return switch (@"export".tag) {
1500 .@"export" => 1,
1501 .reexport => 2,
1502 .stub_resolver => 3,
1503 };
15381504 }
15391505
1540 try writer.print("{s}\n", .{exp.name});
1541 }
1542 }
1506 fn lessThan(ctx: void, lhs: Export, rhs: Export) bool {
1507 _ = ctx;
1508 if (lhs.rankByTag() == rhs.rankByTag()) {
1509 return switch (lhs.tag) {
1510 .@"export" => lhs.data.@"export".vmoffset < rhs.data.@"export".vmoffset,
1511 .reexport => lhs.data.reexport < rhs.data.reexport,
1512 .stub_resolver => lhs.data.stub_resolver.stub_offset < rhs.data.stub_resolver.stub_offset,
1513 };
1514 }
1515 return lhs.rankByTag() < rhs.rankByTag();
1516 }
1517 };
15431518
1544 const TrieIterator = struct {
1545 data: []const u8,
1546 pos: usize = 0,
1519 fn parseTrieNode(
1520 arena: Allocator,
1521 it: *TrieIterator,
1522 prefix: []const u8,
1523 exports: *std.ArrayList(Export),
1524 ) !void {
1525 const size = try it.readULEB128();
1526 if (size > 0) {
1527 const flags = try it.readULEB128();
1528 switch (flags) {
1529 macho.EXPORT_SYMBOL_FLAGS_REEXPORT => {
1530 const ord = try it.readULEB128();
1531 const name = try arena.dupe(u8, try it.readString());
1532 try exports.append(.{
1533 .name = if (name.len > 0) name else prefix,
1534 .tag = .reexport,
1535 .data = .{ .reexport = ord },
1536 });
1537 },
1538 macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER => {
1539 const stub_offset = try it.readULEB128();
1540 const resolver_offset = try it.readULEB128();
1541 try exports.append(.{
1542 .name = prefix,
1543 .tag = .stub_resolver,
1544 .data = .{ .stub_resolver = .{
1545 .stub_offset = stub_offset,
1546 .resolver_offset = resolver_offset,
1547 } },
1548 });
1549 },
1550 else => {
1551 const vmoff = try it.readULEB128();
1552 try exports.append(.{
1553 .name = prefix,
1554 .tag = .@"export",
1555 .data = .{ .@"export" = .{
1556 .kind = switch (flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK) {
1557 macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR => .regular,
1558 macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE => .absolute,
1559 macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL => .tlv,
1560 else => unreachable,
1561 },
1562 .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0,
1563 .vmoffset = vmoff,
1564 } },
1565 });
1566 },
1567 }
1568 }
15471569
1548 fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) {
1549 return std.io.fixedBufferStream(it.data[it.pos..]);
1570 const nedges = try it.readByte();
1571 for (0..nedges) |_| {
1572 const label = try it.readString();
1573 const off = try it.readULEB128();
1574 const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label });
1575 const curr = it.pos;
1576 it.pos = off;
1577 try parseTrieNode(arena, it, prefix_label, exports);
1578 it.pos = curr;
1579 }
15501580 }
1581 };
15511582
1552 fn readULEB128(it: *TrieIterator) !u64 {
1553 var stream = it.getStream();
1554 var creader = std.io.countingReader(stream.reader());
1555 const reader = creader.reader();
1556 const value = try std.leb.readULEB128(u64, reader);
1557 it.pos += creader.bytes_read;
1558 return value;
1583 fn parseAndDumpObject(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
1584 const gpa = step.owner.allocator;
1585 const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(bytes.ptr)).*;
1586 if (hdr.magic != macho.MH_MAGIC_64) {
1587 return error.InvalidMagicNumber;
15591588 }
15601589
1561 fn readString(it: *TrieIterator) ![:0]const u8 {
1562 var stream = it.getStream();
1563 const reader = stream.reader();
1590 var ctx = ObjectContext{ .gpa = gpa, .data = bytes, .header = hdr };
1591 try ctx.parse();
15641592
1565 var count: usize = 0;
1566 while (true) : (count += 1) {
1567 const byte = try reader.readByte();
1568 if (byte == 0) break;
1569 }
1593 var output = std.ArrayList(u8).init(gpa);
1594 const writer = output.writer();
15701595
1571 const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0];
1572 it.pos += count + 1;
1573 return str;
1574 }
1596 switch (check.kind) {
1597 .headers => {
1598 try ObjectContext.dumpHeader(ctx.header, writer);
15751599
1576 fn readByte(it: *TrieIterator) !u8 {
1577 var stream = it.getStream();
1578 const value = try stream.reader().readByte();
1579 it.pos += 1;
1580 return value;
1581 }
1582 };
1600 var it = ctx.getLoadCommandIterator();
1601 var i: usize = 0;
1602 while (it.next()) |cmd| {
1603 try ObjectContext.dumpLoadCommand(cmd, i, writer);
1604 try writer.writeByte('\n');
15831605
1584 const Export = struct {
1585 name: []const u8,
1586 tag: enum { @"export", reexport, stub_resolver },
1587 data: union {
1588 @"export": struct {
1589 kind: enum { regular, absolute, tlv },
1590 weak: bool = false,
1591 vmoffset: u64,
1592 },
1593 reexport: u64,
1594 stub_resolver: struct {
1595 stub_offset: u64,
1596 resolver_offset: u64,
1606 i += 1;
1607 }
15971608 },
1598 },
15991609
1600 inline fn rankByTag(@"export": Export) u3 {
1601 return switch (@"export".tag) {
1602 .@"export" => 1,
1603 .reexport => 2,
1604 .stub_resolver => 3,
1605 };
1606 }
1610 .symtab => if (ctx.symtab.items.len > 0) {
1611 try ctx.dumpSymtab(writer);
1612 } else return step.fail("no symbol table found", .{}),
16071613
1608 fn lessThan(ctx: void, lhs: Export, rhs: Export) bool {
1609 _ = ctx;
1610 if (lhs.rankByTag() == rhs.rankByTag()) {
1611 return switch (lhs.tag) {
1612 .@"export" => lhs.data.@"export".vmoffset < rhs.data.@"export".vmoffset,
1613 .reexport => lhs.data.reexport < rhs.data.reexport,
1614 .stub_resolver => lhs.data.stub_resolver.stub_offset < rhs.data.stub_resolver.stub_offset,
1615 };
1616 }
1617 return lhs.rankByTag() < rhs.rankByTag();
1618 }
1619 };
1614 .indirect_symtab => if (ctx.symtab.items.len > 0 and ctx.indsymtab.items.len > 0) {
1615 try ctx.dumpIndirectSymtab(writer);
1616 } else return step.fail("no indirect symbol table found", .{}),
16201617
1621 fn parseTrieNode(
1622 arena: Allocator,
1623 it: *TrieIterator,
1624 prefix: []const u8,
1625 exports: *std.ArrayList(Export),
1626 ) !void {
1627 const size = try it.readULEB128();
1628 if (size > 0) {
1629 const flags = try it.readULEB128();
1630 switch (flags) {
1631 macho.EXPORT_SYMBOL_FLAGS_REEXPORT => {
1632 const ord = try it.readULEB128();
1633 const name = try arena.dupe(u8, try it.readString());
1634 try exports.append(.{
1635 .name = if (name.len > 0) name else prefix,
1636 .tag = .reexport,
1637 .data = .{ .reexport = ord },
1638 });
1639 },
1640 macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER => {
1641 const stub_offset = try it.readULEB128();
1642 const resolver_offset = try it.readULEB128();
1643 try exports.append(.{
1644 .name = prefix,
1645 .tag = .stub_resolver,
1646 .data = .{ .stub_resolver = .{
1647 .stub_offset = stub_offset,
1648 .resolver_offset = resolver_offset,
1649 } },
1650 });
1651 },
1652 else => {
1653 const vmoff = try it.readULEB128();
1654 try exports.append(.{
1655 .name = prefix,
1656 .tag = .@"export",
1657 .data = .{ .@"export" = .{
1658 .kind = switch (flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK) {
1659 macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR => .regular,
1660 macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE => .absolute,
1661 macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL => .tlv,
1662 else => unreachable,
1663 },
1664 .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0,
1665 .vmoffset = vmoff,
1666 } },
1667 });
1668 },
1669 }
1670 }
1618 .dyld_rebase,
1619 .dyld_bind,
1620 .dyld_weak_bind,
1621 .dyld_lazy_bind,
1622 => {
1623 const cmd = ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse
1624 return step.fail("no dyld info found", .{});
1625 const lc = cmd.cast(macho.dyld_info_command).?;
16711626
1672 const nedges = try it.readByte();
1673 for (0..nedges) |_| {
1674 const label = try it.readString();
1675 const off = try it.readULEB128();
1676 const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label });
1677 const curr = it.pos;
1678 it.pos = off;
1679 try parseTrieNode(arena, it, prefix_label, exports);
1680 it.pos = curr;
1627 switch (check.kind) {
1628 .dyld_rebase => if (lc.rebase_size > 0) {
1629 const data = ctx.data[lc.rebase_off..][0..lc.rebase_size];
1630 try writer.writeAll(dyld_rebase_label ++ "\n");
1631 try ctx.dumpRebaseInfo(data, writer);
1632 } else return step.fail("no rebase data found", .{}),
1633
1634 .dyld_bind => if (lc.bind_size > 0) {
1635 const data = ctx.data[lc.bind_off..][0..lc.bind_size];
1636 try writer.writeAll(dyld_bind_label ++ "\n");
1637 try ctx.dumpBindInfo(data, writer);
1638 } else return step.fail("no bind data found", .{}),
1639
1640 .dyld_weak_bind => if (lc.weak_bind_size > 0) {
1641 const data = ctx.data[lc.weak_bind_off..][0..lc.weak_bind_size];
1642 try writer.writeAll(dyld_weak_bind_label ++ "\n");
1643 try ctx.dumpBindInfo(data, writer);
1644 } else return step.fail("no weak bind data found", .{}),
1645
1646 .dyld_lazy_bind => if (lc.lazy_bind_size > 0) {
1647 const data = ctx.data[lc.lazy_bind_off..][0..lc.lazy_bind_size];
1648 try writer.writeAll(dyld_lazy_bind_label ++ "\n");
1649 try ctx.dumpBindInfo(data, writer);
1650 } else return step.fail("no lazy bind data found", .{}),
1651
1652 else => unreachable,
1653 }
1654 },
1655
1656 .exports => blk: {
1657 if (ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| {
1658 const lc = cmd.cast(macho.dyld_info_command).?;
1659 if (lc.export_size > 0) {
1660 const data = ctx.data[lc.export_off..][0..lc.export_size];
1661 try writer.writeAll(exports_label ++ "\n");
1662 try ctx.dumpExportsTrie(data, writer);
1663 break :blk;
1664 }
1665 }
1666 return step.fail("no exports data found", .{});
1667 },
1668
1669 else => return step.fail("invalid check kind for MachO file format: {s}", .{@tagName(check.kind)}),
16811670 }
1671
1672 return output.toOwnedSlice();
16821673 }
16831674};
16841675