| author | |
| committer | |
| log | 2f94dc939e8acf0dab4cf1f3cf01b132ac6fdd3c |
| tree | 5d9ccf480d9194c6b09fcd519e872e2df9c7ae0e |
| parent | 92211135f1424aaca0de131cfe3646248730b1ca |
27 files changed, 8104 insertions(+), 8302 deletions(-)
CMakeLists.txt+7-3| ... | @@ -603,20 +603,24 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -603,20 +603,24 @@ set(ZIG_STAGE2_SOURCES |
| 603 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" | 603 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" |
| 604 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig" | 604 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig" |
| 605 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" | 605 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" |
| 606 | "${CMAKE_SOURCE_DIR}/src/link/MachO/InternalObject.zig" | ||
| 606 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" | 607 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" |
| 607 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig" | 608 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig" |
| 608 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" | 609 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig" |
| 609 | "${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig" | 610 | "${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig" |
| 611 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig" | ||
| 610 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig" | 612 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig" |
| 611 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig" | 613 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig" |
| 612 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig" | 614 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Trie.zig" |
| 613 | "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig" | 615 | "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig" |
| 614 | "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" | 616 | "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" |
| 617 | "${CMAKE_SOURCE_DIR}/src/link/MachO/file.zig" | ||
| 615 | "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig" | 618 | "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig" |
| 616 | "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" | 619 | "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" |
| 620 | "${CMAKE_SOURCE_DIR}/src/link/MachO/relocatable.zig" | ||
| 621 | "${CMAKE_SOURCE_DIR}/src/link/MachO/synthetic.zig" | ||
| 617 | "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" | 622 | "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" |
| 618 | "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" | 623 | "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" |
| 619 | "${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig" | ||
| 620 | "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" | 624 | "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" |
| 621 | "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" | 625 | "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" |
| 622 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" | 626 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" |
src/link/MachO/Archive.zig+62-151| ... | @@ -1,20 +1,15 @@ | ... | @@ -1,20 +1,15 @@ |
| 1 | file: fs.File, | 1 | path: []const u8, |
| 2 | fat_offset: u64, | 2 | data: []const u8, |
| 3 | name: []const u8, | ||
| 4 | header: ar_hdr = undefined, | ||
| 5 | 3 | ||
| 6 | /// Parsed table of contents. | 4 | objects: std.ArrayListUnmanaged(Object) = .{}, |
| 7 | /// Each symbol name points to a list of all definition | ||
| 8 | /// sites within the current static archive. | ||
| 9 | toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{}, | ||
| 10 | 5 | ||
| 11 | // Archive files start with the ARMAG identifying string. Then follows a | 6 | // Archive files start with the ARMAG identifying string. Then follows a |
| 12 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' | 7 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' |
| 13 | // member indicates, for each member file. | 8 | // member indicates, for each member file. |
| 14 | /// String that begins an archive file. | 9 | /// String that begins an archive file. |
| 15 | const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; | 10 | pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; |
| 16 | /// Size of that string. | 11 | /// Size of that string. |
| 17 | const SARMAG: u4 = 8; | 12 | pub const SARMAG: u4 = 8; |
| 18 | 13 | ||
| 19 | /// String in ar_fmag at the end of each header. | 14 | /// String in ar_fmag at the end of each header. |
| 20 | const ARFMAG: *const [2:0]u8 = "`\n"; | 15 | const ARFMAG: *const [2:0]u8 = "`\n"; |
| ... | @@ -41,177 +36,93 @@ const ar_hdr = extern struct { | ... | @@ -41,177 +36,93 @@ const ar_hdr = extern struct { |
| 41 | /// Always contains ARFMAG. | 36 | /// Always contains ARFMAG. |
| 42 | ar_fmag: [2]u8, | 37 | ar_fmag: [2]u8, |
| 43 | 38 | ||
| 44 | const NameOrLength = union(enum) { | ||
| 45 | Name: []const u8, | ||
| 46 | Length: u32, | ||
| 47 | }; | ||
| 48 | fn nameOrLength(self: ar_hdr) !NameOrLength { | ||
| 49 | const value = getValue(&self.ar_name); | ||
| 50 | const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive; | ||
| 51 | const len = value.len; | ||
| 52 | if (slash_index == len - 1) { | ||
| 53 | // Name stored directly | ||
| 54 | return NameOrLength{ .Name = value }; | ||
| 55 | } else { | ||
| 56 | // Name follows the header directly and its length is encoded in | ||
| 57 | // the name field. | ||
| 58 | const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10); | ||
| 59 | return NameOrLength{ .Length = length }; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | fn date(self: ar_hdr) !u64 { | 39 | fn date(self: ar_hdr) !u64 { |
| 64 | const value = getValue(&self.ar_date); | 40 | const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); |
| 65 | return std.fmt.parseInt(u64, value, 10); | 41 | return std.fmt.parseInt(u64, value, 10); |
| 66 | } | 42 | } |
| 67 | 43 | ||
| 68 | fn size(self: ar_hdr) !u32 { | 44 | fn size(self: ar_hdr) !u32 { |
| 69 | const value = getValue(&self.ar_size); | 45 | const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)}); |
| 70 | return std.fmt.parseInt(u32, value, 10); | 46 | return std.fmt.parseInt(u32, value, 10); |
| 71 | } | 47 | } |
| 72 | 48 | ||
| 73 | fn getValue(raw: []const u8) []const u8 { | 49 | fn name(self: *const ar_hdr) ?[]const u8 { |
| 74 | return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); | 50 | const value = &self.ar_name; |
| 51 | if (mem.startsWith(u8, value, "#1/")) return null; | ||
| 52 | const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len; | ||
| 53 | return value[0..sentinel]; | ||
| 75 | } | 54 | } |
| 76 | }; | ||
| 77 | 55 | ||
| 78 | pub fn isArchive(file: fs.File, fat_offset: u64) bool { | 56 | fn nameLength(self: ar_hdr) !?u32 { |
| 79 | const reader = file.reader(); | 57 | const value = &self.ar_name; |
| 80 | const magic = reader.readBytesNoEof(SARMAG) catch return false; | 58 | if (!mem.startsWith(u8, value, "#1/")) return null; |
| 81 | defer file.seekTo(fat_offset) catch {}; | 59 | const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); |
| 82 | return mem.eql(u8, &magic, ARMAG); | 60 | return try std.fmt.parseInt(u32, trimmed, 10); |
| 83 | } | ||
| 84 | |||
| 85 | pub fn deinit(self: *Archive, allocator: Allocator) void { | ||
| 86 | self.file.close(); | ||
| 87 | for (self.toc.keys()) |*key| { | ||
| 88 | allocator.free(key.*); | ||
| 89 | } | ||
| 90 | for (self.toc.values()) |*value| { | ||
| 91 | value.deinit(allocator); | ||
| 92 | } | 61 | } |
| 93 | self.toc.deinit(allocator); | 62 | }; |
| 94 | allocator.free(self.name); | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void { | ||
| 98 | _ = try reader.readBytesNoEof(SARMAG); | ||
| 99 | self.header = try reader.readStruct(ar_hdr); | ||
| 100 | const name_or_length = try self.header.nameOrLength(); | ||
| 101 | const embedded_name = try parseName(allocator, name_or_length, reader); | ||
| 102 | log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name }); | ||
| 103 | defer allocator.free(embedded_name); | ||
| 104 | |||
| 105 | try self.parseTableOfContents(allocator, reader); | ||
| 106 | } | ||
| 107 | 63 | ||
| 108 | fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 { | 64 | pub fn deinit(self: *Archive, allocator: Allocator) void { |
| 109 | var name: []u8 = undefined; | 65 | self.objects.deinit(allocator); |
| 110 | switch (name_or_length) { | ||
| 111 | .Name => |n| { | ||
| 112 | name = try allocator.dupe(u8, n); | ||
| 113 | }, | ||
| 114 | .Length => |len| { | ||
| 115 | var n = try allocator.alloc(u8, len); | ||
| 116 | defer allocator.free(n); | ||
| 117 | try reader.readNoEof(n); | ||
| 118 | const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len; | ||
| 119 | name = try allocator.dupe(u8, n[0..actual_len]); | ||
| 120 | }, | ||
| 121 | } | ||
| 122 | return name; | ||
| 123 | } | 66 | } |
| 124 | 67 | ||
| 125 | fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void { | 68 | pub fn parse(self: *Archive, arena: Allocator, macho_file: *MachO) !void { |
| 126 | const symtab_size = try reader.readInt(u32, .little); | 69 | const gpa = macho_file.base.allocator; |
| 127 | const symtab = try allocator.alloc(u8, symtab_size); | ||
| 128 | defer allocator.free(symtab); | ||
| 129 | |||
| 130 | reader.readNoEof(symtab) catch { | ||
| 131 | log.debug("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size}); | ||
| 132 | return error.MalformedArchive; | ||
| 133 | }; | ||
| 134 | 70 | ||
| 135 | const strtab_size = try reader.readInt(u32, .little); | 71 | var stream = std.io.fixedBufferStream(self.data); |
| 136 | const strtab = try allocator.alloc(u8, strtab_size); | 72 | const reader = stream.reader(); |
| 137 | defer allocator.free(strtab); | ||
| 138 | |||
| 139 | reader.readNoEof(strtab) catch { | ||
| 140 | log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size}); | ||
| 141 | return error.MalformedArchive; | ||
| 142 | }; | ||
| 143 | |||
| 144 | var symtab_stream = std.io.fixedBufferStream(symtab); | ||
| 145 | var symtab_reader = symtab_stream.reader(); | ||
| 146 | 73 | ||
| 147 | while (true) { | 74 | while (true) { |
| 148 | const n_strx = symtab_reader.readInt(u32, .little) catch |err| switch (err) { | 75 | if (stream.pos >= self.data.len) break; |
| 149 | error.EndOfStream => break, | 76 | if (!mem.isAligned(stream.pos, 2)) stream.pos += 1; |
| 150 | else => |e| return e, | ||
| 151 | }; | ||
| 152 | const object_offset = try symtab_reader.readInt(u32, .little); | ||
| 153 | 77 | ||
| 154 | const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + n_strx)), 0); | 78 | const hdr = try reader.readStruct(ar_hdr); |
| 155 | const owned_name = try allocator.dupe(u8, sym_name); | ||
| 156 | const res = try self.toc.getOrPut(allocator, owned_name); | ||
| 157 | defer if (res.found_existing) allocator.free(owned_name); | ||
| 158 | 79 | ||
| 159 | if (!res.found_existing) { | 80 | if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { |
| 160 | res.value_ptr.* = .{}; | 81 | macho_file.base.fatal("{s}: invalid header delimiter: expected '{s}', found '{s}'", .{ |
| 82 | self.path, std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag), | ||
| 83 | }); | ||
| 84 | return error.ParseFailed; | ||
| 161 | } | 85 | } |
| 162 | 86 | ||
| 163 | try res.value_ptr.append(allocator, object_offset); | 87 | var size = try hdr.size(); |
| 164 | } | 88 | const name = name: { |
| 165 | } | 89 | if (hdr.name()) |n| break :name try arena.dupe(u8, n); |
| 90 | if (try hdr.nameLength()) |len| { | ||
| 91 | size -= len; | ||
| 92 | const buf = try arena.alloc(u8, len); | ||
| 93 | try reader.readNoEof(buf); | ||
| 94 | const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len; | ||
| 95 | break :name buf[0..actual_len]; | ||
| 96 | } | ||
| 97 | unreachable; | ||
| 98 | }; | ||
| 99 | defer { | ||
| 100 | _ = stream.seekBy(size) catch {}; | ||
| 101 | } | ||
| 166 | 102 | ||
| 167 | pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object { | 103 | if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue; |
| 168 | const reader = self.file.reader(); | ||
| 169 | try reader.context.seekTo(self.fat_offset + offset); | ||
| 170 | |||
| 171 | const object_header = try reader.readStruct(ar_hdr); | ||
| 172 | |||
| 173 | const name_or_length = try object_header.nameOrLength(); | ||
| 174 | const object_name = try parseName(gpa, name_or_length, reader); | ||
| 175 | defer gpa.free(object_name); | ||
| 176 | |||
| 177 | log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name }); | ||
| 178 | |||
| 179 | const name = name: { | ||
| 180 | var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; | ||
| 181 | const path = try std.os.realpath(self.name, &buffer); | ||
| 182 | break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name }); | ||
| 183 | }; | ||
| 184 | |||
| 185 | const object_name_len = switch (name_or_length) { | ||
| 186 | .Name => 0, | ||
| 187 | .Length => |len| len, | ||
| 188 | }; | ||
| 189 | const object_size = (try object_header.size()) - object_name_len; | ||
| 190 | const contents = try gpa.allocWithOptions(u8, object_size, @alignOf(u64), null); | ||
| 191 | const amt = try reader.readAll(contents); | ||
| 192 | if (amt != object_size) { | ||
| 193 | return error.InputOutput; | ||
| 194 | } | ||
| 195 | 104 | ||
| 196 | var object = Object{ | 105 | const object = Object{ |
| 197 | .name = name, | 106 | .archive = self.path, |
| 198 | .mtime = object_header.date() catch 0, | 107 | .path = name, |
| 199 | .contents = contents, | 108 | .data = self.data[stream.pos..][0..size], |
| 200 | }; | 109 | .index = undefined, |
| 110 | .alive = false, | ||
| 111 | .mtime = hdr.date() catch 0, | ||
| 112 | }; | ||
| 201 | 113 | ||
| 202 | try object.parse(gpa); | 114 | log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path }); |
| 203 | 115 | ||
| 204 | return object; | 116 | try self.objects.append(gpa, object); |
| 117 | } | ||
| 205 | } | 118 | } |
| 206 | 119 | ||
| 207 | const Archive = @This(); | ||
| 208 | |||
| 209 | const std = @import("std"); | ||
| 210 | const assert = std.debug.assert; | ||
| 211 | const fs = std.fs; | ||
| 212 | const log = std.log.scoped(.link); | 120 | const log = std.log.scoped(.link); |
| 213 | const macho = std.macho; | 121 | const macho = std.macho; |
| 214 | const mem = std.mem; | 122 | const mem = std.mem; |
| 123 | const std = @import("std"); | ||
| 215 | 124 | ||
| 216 | const Allocator = mem.Allocator; | 125 | const Allocator = mem.Allocator; |
| 126 | const Archive = @This(); | ||
| 127 | const MachO = @import("../MachO.zig"); | ||
| 217 | const Object = @import("Object.zig"); | 128 | const Object = @import("Object.zig"); |
src/link/MachO/Atom.zig+751-1117| ... | @@ -1,1271 +1,905 @@ | ... | @@ -1,1271 +1,905 @@ |
| 1 | /// Each Atom always gets a symbol with the fully qualified name. | 1 | /// Address allocated for this Atom. |
| 2 | /// The symbol can reside in any object file context structure in `symtab` array | 2 | value: u64 = 0, |
| 3 | /// (see `Object`), or if the symbol is a synthetic symbol such as a GOT cell or | 3 | |
| 4 | /// a stub trampoline, it can be found in the linkers `locals` arraylist. | 4 | /// Name of this Atom. |
| 5 | /// If this field is 0 and file is 0, it means the codegen size = 0 and there is no symbol or | 5 | name: u32 = 0, |
| 6 | /// offset table entry. | 6 | |
| 7 | sym_index: u32 = 0, | 7 | /// Index into linker's input file table. |
| 8 | 8 | file: File.Index = 0, | |
| 9 | /// 0 means an Atom is a synthetic Atom such as a GOT cell defined by the linker. | 9 | |
| 10 | /// Otherwise, it is the index into appropriate object file (indexing from 1). | 10 | /// Size of this atom |
| 11 | /// Prefer using `getFile()` helper to get the file index out rather than using | ||
| 12 | /// the field directly. | ||
| 13 | file: u32 = 0, | ||
| 14 | |||
| 15 | /// If this Atom is not a synthetic Atom, i.e., references a subsection in an | ||
| 16 | /// Object file, `inner_sym_index` and `inner_nsyms_trailing` tell where and if | ||
| 17 | /// this Atom contains any additional symbol references that fall within this Atom's | ||
| 18 | /// address range. These could for example be an alias symbol which can be used | ||
| 19 | /// internally by the relocation records, or if the Object file couldn't be split | ||
| 20 | /// into subsections, this Atom may encompass an entire input section. | ||
| 21 | inner_sym_index: u32 = 0, | ||
| 22 | inner_nsyms_trailing: u32 = 0, | ||
| 23 | |||
| 24 | /// Size and alignment of this atom | ||
| 25 | /// Unlike in Elf, we need to store the size of this symbol as part of | ||
| 26 | /// the atom since macho.nlist_64 lacks this information. | ||
| 27 | size: u64 = 0, | 11 | size: u64 = 0, |
| 28 | 12 | ||
| 29 | /// Alignment of this atom as a power of 2. | 13 | /// Alignment of this atom as a power of two. |
| 30 | /// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned. | 14 | alignment: u32 = 0, |
| 31 | alignment: Alignment = .@"1", | ||
| 32 | 15 | ||
| 33 | /// Points to the previous and next neighbours | 16 | /// Index of the input section. |
| 34 | /// TODO use the same trick as with symbols: reserve index 0 as null atom | 17 | n_sect: u32 = 0, |
| 35 | next_index: ?Index = null, | ||
| 36 | prev_index: ?Index = null, | ||
| 37 | 18 | ||
| 38 | pub const Alignment = @import("../../InternPool.zig").Alignment; | 19 | /// Index of the output section. |
| 20 | out_n_sect: u8 = 0, | ||
| 39 | 21 | ||
| 40 | pub const Index = u32; | 22 | /// Offset within the parent section pointed to by n_sect. |
| 23 | /// off + size <= parent section size. | ||
| 24 | off: u64 = 0, | ||
| 41 | 25 | ||
| 42 | pub const Binding = struct { | 26 | /// Relocations of this atom. |
| 43 | target: SymbolWithLoc, | 27 | relocs: Loc = .{}, |
| 44 | offset: u64, | 28 | |
| 45 | }; | 29 | /// Index of this atom in the linker's atoms table. |
| 30 | atom_index: Index = 0, | ||
| 46 | 31 | ||
| 47 | /// Returns `null` if the Atom is a synthetic Atom. | 32 | /// Index of the thunk for this atom. |
| 48 | /// Otherwise, returns an index into an array of Objects. | 33 | thunk_index: Thunk.Index = 0, |
| 49 | pub fn getFile(self: Atom) ?u32 { | 34 | |
| 50 | if (self.file == 0) return null; | 35 | /// Unwind records associated with this atom. |
| 51 | return self.file - 1; | 36 | unwind_records: Loc = .{}, |
| 37 | |||
| 38 | flags: Flags = .{}, | ||
| 39 | |||
| 40 | pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 { | ||
| 41 | return macho_file.string_intern.getAssumeExists(self.name); | ||
| 52 | } | 42 | } |
| 53 | 43 | ||
| 54 | pub fn getSymbolIndex(self: Atom) ?u32 { | 44 | pub fn getFile(self: Atom, macho_file: *MachO) File { |
| 55 | if (self.getFile() == null and self.sym_index == 0) return null; | 45 | return macho_file.getFile(self.file).?; |
| 56 | return self.sym_index; | ||
| 57 | } | 46 | } |
| 58 | 47 | ||
| 59 | /// Returns symbol referencing this atom. | 48 | pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 { |
| 60 | pub fn getSymbol(self: Atom, macho_file: *MachO) macho.nlist_64 { | 49 | return switch (self.getFile(macho_file)) { |
| 61 | return self.getSymbolPtr(macho_file).*; | 50 | .dylib => unreachable, |
| 51 | inline else => |x| x.sections.items(.header)[self.n_sect], | ||
| 52 | }; | ||
| 62 | } | 53 | } |
| 63 | 54 | ||
| 64 | /// Returns pointer-to-symbol referencing this atom. | 55 | pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 { |
| 65 | pub fn getSymbolPtr(self: Atom, macho_file: *MachO) *macho.nlist_64 { | 56 | return self.getInputSection(macho_file).addr + self.off; |
| 66 | const sym_index = self.getSymbolIndex().?; | ||
| 67 | return macho_file.getSymbolPtr(.{ .sym_index = sym_index, .file = self.file }); | ||
| 68 | } | 57 | } |
| 69 | 58 | ||
| 70 | pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { | 59 | pub fn getPriority(self: Atom, macho_file: *MachO) u64 { |
| 71 | const sym_index = self.getSymbolIndex().?; | 60 | const file = self.getFile(macho_file); |
| 72 | return .{ .sym_index = sym_index, .file = self.file }; | 61 | return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect)); |
| 73 | } | 62 | } |
| 74 | 63 | ||
| 75 | /// Returns the name of this atom. | 64 | pub fn getCode(self: Atom, macho_file: *MachO) []const u8 { |
| 76 | pub fn getName(self: Atom, macho_file: *MachO) []const u8 { | 65 | const code = switch (self.getFile(macho_file)) { |
| 77 | const sym_index = self.getSymbolIndex().?; | 66 | .dylib => unreachable, |
| 78 | return macho_file.getSymbolName(.{ .sym_index = sym_index, .file = self.file }); | 67 | inline else => |x| x.getSectionData(self.n_sect), |
| 68 | }; | ||
| 69 | return code[self.off..][0..self.size]; | ||
| 79 | } | 70 | } |
| 80 | 71 | ||
| 81 | /// Returns how much room there is to grow in virtual address space. | 72 | pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation { |
| 82 | /// File offset relocation happens transparently, so it is not included in | 73 | const relocs = switch (self.getFile(macho_file)) { |
| 83 | /// this calculation. | 74 | .dylib => unreachable, |
| 84 | pub fn capacity(self: Atom, macho_file: *MachO) u64 { | 75 | inline else => |x| x.sections.items(.relocs)[self.n_sect], |
| 85 | const self_sym = self.getSymbol(macho_file); | 76 | }; |
| 86 | if (self.next_index) |next_index| { | 77 | return relocs.items[self.relocs.pos..][0..self.relocs.len]; |
| 87 | const next = macho_file.getAtom(next_index); | ||
| 88 | const next_sym = next.getSymbol(macho_file); | ||
| 89 | return next_sym.n_value - self_sym.n_value; | ||
| 90 | } else { | ||
| 91 | // We are the last atom. | ||
| 92 | // The capacity is limited only by virtual address space. | ||
| 93 | return macho_file.allocatedVirtualSize(self_sym.n_value); | ||
| 94 | } | ||
| 95 | } | 78 | } |
| 96 | 79 | ||
| 97 | pub fn freeListEligible(self: Atom, macho_file: *MachO) bool { | 80 | pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index { |
| 98 | // No need to keep a free list node for the last atom. | 81 | return switch (self.getFile(macho_file)) { |
| 99 | const next_index = self.next_index orelse return false; | 82 | .dylib => unreachable, |
| 100 | const next = macho_file.getAtom(next_index); | 83 | .internal => &[0]UnwindInfo.Record.Index{}, |
| 101 | const self_sym = self.getSymbol(macho_file); | 84 | .object => |x| x.unwind_records.items[self.unwind_records.pos..][0..self.unwind_records.len], |
| 102 | const next_sym = next.getSymbol(macho_file); | 85 | }; |
| 103 | const cap = next_sym.n_value - self_sym.n_value; | ||
| 104 | const ideal_cap = MachO.padToIdeal(self.size); | ||
| 105 | if (cap <= ideal_cap) return false; | ||
| 106 | const surplus = cap - ideal_cap; | ||
| 107 | return surplus >= MachO.min_text_capacity; | ||
| 108 | } | 86 | } |
| 109 | 87 | ||
| 110 | pub fn getOutputSection(macho_file: *MachO, sect: macho.section_64) !?u8 { | 88 | pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void { |
| 111 | const segname = sect.segName(); | 89 | for (self.getUnwindRecords(macho_file)) |cu_index| { |
| 112 | const sectname = sect.sectName(); | 90 | const cu = macho_file.getUnwindRecord(cu_index); |
| 113 | const res: ?u8 = blk: { | 91 | cu.alive = false; |
| 114 | if (mem.eql(u8, "__LLVM", segname)) { | ||
| 115 | log.debug("TODO LLVM section: type 0x{x}, name '{s},{s}'", .{ | ||
| 116 | sect.flags, segname, sectname, | ||
| 117 | }); | ||
| 118 | break :blk null; | ||
| 119 | } | ||
| 120 | 92 | ||
| 121 | // We handle unwind info separately. | 93 | if (cu.getFdePtr(macho_file)) |fde| { |
| 122 | if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) { | 94 | fde.alive = false; |
| 123 | break :blk null; | ||
| 124 | } | ||
| 125 | if (mem.eql(u8, "__LD", segname) and mem.eql(u8, "__compact_unwind", sectname)) { | ||
| 126 | break :blk null; | ||
| 127 | } | 95 | } |
| 96 | } | ||
| 97 | } | ||
| 128 | 98 | ||
| 129 | if (sect.isCode()) { | 99 | pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { |
| 130 | if (macho_file.text_section_index == null) { | 100 | return macho_file.getThunk(self.thunk_index); |
| 131 | macho_file.text_section_index = try macho_file.initSection("__TEXT", "__text", .{ | 101 | } |
| 132 | .flags = macho.S_REGULAR | | ||
| 133 | macho.S_ATTR_PURE_INSTRUCTIONS | | ||
| 134 | macho.S_ATTR_SOME_INSTRUCTIONS, | ||
| 135 | }); | ||
| 136 | } | ||
| 137 | break :blk macho_file.text_section_index.?; | ||
| 138 | } | ||
| 139 | 102 | ||
| 140 | if (sect.isDebug()) { | 103 | pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { |
| 141 | break :blk null; | 104 | const segname, const sectname, const flags = blk: { |
| 142 | } | 105 | if (sect.isCode()) break :blk .{ |
| 106 | "__TEXT", | ||
| 107 | "__text", | ||
| 108 | macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, | ||
| 109 | }; | ||
| 143 | 110 | ||
| 144 | switch (sect.type()) { | 111 | switch (sect.type()) { |
| 145 | macho.S_4BYTE_LITERALS, | 112 | macho.S_4BYTE_LITERALS, |
| 146 | macho.S_8BYTE_LITERALS, | 113 | macho.S_8BYTE_LITERALS, |
| 147 | macho.S_16BYTE_LITERALS, | 114 | macho.S_16BYTE_LITERALS, |
| 148 | => { | 115 | => break :blk .{ "__TEXT", "__const", macho.S_REGULAR }, |
| 149 | break :blk macho_file.getSectionByName("__TEXT", "__const") orelse | 116 | |
| 150 | try macho_file.initSection("__TEXT", "__const", .{}); | ||
| 151 | }, | ||
| 152 | macho.S_CSTRING_LITERALS => { | 117 | macho.S_CSTRING_LITERALS => { |
| 153 | if (mem.startsWith(u8, sectname, "__objc")) { | 118 | if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{ |
| 154 | break :blk macho_file.getSectionByName(segname, sectname) orelse | 119 | sect.segName(), sect.sectName(), macho.S_REGULAR, |
| 155 | try macho_file.initSection(segname, sectname, .{}); | 120 | }; |
| 156 | } | 121 | break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS }; |
| 157 | break :blk macho_file.getSectionByName("__TEXT", "__cstring") orelse | ||
| 158 | try macho_file.initSection("__TEXT", "__cstring", .{ | ||
| 159 | .flags = macho.S_CSTRING_LITERALS, | ||
| 160 | }); | ||
| 161 | }, | 122 | }, |
| 123 | |||
| 162 | macho.S_MOD_INIT_FUNC_POINTERS, | 124 | macho.S_MOD_INIT_FUNC_POINTERS, |
| 163 | macho.S_MOD_TERM_FUNC_POINTERS, | 125 | macho.S_MOD_TERM_FUNC_POINTERS, |
| 164 | => { | 126 | => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags }, |
| 165 | break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse | 127 | |
| 166 | try macho_file.initSection("__DATA_CONST", sectname, .{ | ||
| 167 | .flags = sect.flags, | ||
| 168 | }); | ||
| 169 | }, | ||
| 170 | macho.S_LITERAL_POINTERS, | 128 | macho.S_LITERAL_POINTERS, |
| 171 | macho.S_ZEROFILL, | 129 | macho.S_ZEROFILL, |
| 130 | macho.S_GB_ZEROFILL, | ||
| 172 | macho.S_THREAD_LOCAL_VARIABLES, | 131 | macho.S_THREAD_LOCAL_VARIABLES, |
| 173 | macho.S_THREAD_LOCAL_VARIABLE_POINTERS, | 132 | macho.S_THREAD_LOCAL_VARIABLE_POINTERS, |
| 174 | macho.S_THREAD_LOCAL_REGULAR, | 133 | macho.S_THREAD_LOCAL_REGULAR, |
| 175 | macho.S_THREAD_LOCAL_ZEROFILL, | 134 | macho.S_THREAD_LOCAL_ZEROFILL, |
| 176 | => { | 135 | => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, |
| 177 | break :blk macho_file.getSectionByName(segname, sectname) orelse | 136 | |
| 178 | try macho_file.initSection(segname, sectname, .{ | 137 | macho.S_COALESCED => break :blk .{ |
| 179 | .flags = sect.flags, | 138 | sect.segName(), |
| 180 | }); | 139 | sect.sectName(), |
| 181 | }, | 140 | macho.S_REGULAR, |
| 182 | macho.S_COALESCED => { | ||
| 183 | break :blk macho_file.getSectionByName(segname, sectname) orelse | ||
| 184 | try macho_file.initSection(segname, sectname, .{}); | ||
| 185 | }, | 141 | }, |
| 142 | |||
| 186 | macho.S_REGULAR => { | 143 | macho.S_REGULAR => { |
| 187 | if (mem.eql(u8, segname, "__TEXT")) { | 144 | const segname = sect.segName(); |
| 188 | if (mem.eql(u8, sectname, "__rodata") or | 145 | const sectname = sect.sectName(); |
| 189 | mem.eql(u8, sectname, "__typelink") or | ||
| 190 | mem.eql(u8, sectname, "__itablink") or | ||
| 191 | mem.eql(u8, sectname, "__gosymtab") or | ||
| 192 | mem.eql(u8, sectname, "__gopclntab")) | ||
| 193 | { | ||
| 194 | break :blk macho_file.getSectionByName("__TEXT", sectname) orelse | ||
| 195 | try macho_file.initSection("__TEXT", sectname, .{}); | ||
| 196 | } | ||
| 197 | } | ||
| 198 | if (mem.eql(u8, segname, "__DATA")) { | 146 | if (mem.eql(u8, segname, "__DATA")) { |
| 199 | if (mem.eql(u8, sectname, "__const") or | 147 | if (mem.eql(u8, sectname, "__const") or |
| 200 | mem.eql(u8, sectname, "__cfstring") or | 148 | mem.eql(u8, sectname, "__cfstring") or |
| 201 | mem.eql(u8, sectname, "__objc_classlist") or | 149 | mem.eql(u8, sectname, "__objc_classlist") or |
| 202 | mem.eql(u8, sectname, "__objc_imageinfo")) | 150 | mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{ |
| 203 | { | 151 | "__DATA_CONST", |
| 204 | break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse | 152 | sectname, |
| 205 | try macho_file.initSection("__DATA_CONST", sectname, .{}); | 153 | macho.S_REGULAR, |
| 206 | } else if (mem.eql(u8, sectname, "__data")) { | 154 | }; |
| 207 | if (macho_file.data_section_index == null) { | ||
| 208 | macho_file.data_section_index = try macho_file.initSection("__DATA", "__data", .{}); | ||
| 209 | } | ||
| 210 | break :blk macho_file.data_section_index.?; | ||
| 211 | } | ||
| 212 | } | 155 | } |
| 213 | break :blk macho_file.getSectionByName(segname, sectname) orelse | 156 | break :blk .{ segname, sectname, sect.flags }; |
| 214 | try macho_file.initSection(segname, sectname, .{}); | ||
| 215 | }, | 157 | }, |
| 216 | else => break :blk null, | ||
| 217 | } | ||
| 218 | }; | ||
| 219 | 158 | ||
| 220 | // TODO we can do this directly in the selection logic above. | 159 | else => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, |
| 221 | // Or is it not worth it? | ||
| 222 | if (macho_file.data_const_section_index == null) { | ||
| 223 | if (macho_file.getSectionByName("__DATA_CONST", "__const")) |index| { | ||
| 224 | macho_file.data_const_section_index = index; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | if (macho_file.thread_vars_section_index == null) { | ||
| 228 | if (macho_file.getSectionByName("__DATA", "__thread_vars")) |index| { | ||
| 229 | macho_file.thread_vars_section_index = index; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | if (macho_file.thread_data_section_index == null) { | ||
| 233 | if (macho_file.getSectionByName("__DATA", "__thread_data")) |index| { | ||
| 234 | macho_file.thread_data_section_index = index; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | if (macho_file.thread_bss_section_index == null) { | ||
| 238 | if (macho_file.getSectionByName("__DATA", "__thread_bss")) |index| { | ||
| 239 | macho_file.thread_bss_section_index = index; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | if (macho_file.bss_section_index == null) { | ||
| 243 | if (macho_file.getSectionByName("__DATA", "__bss")) |index| { | ||
| 244 | macho_file.bss_section_index = index; | ||
| 245 | } | 160 | } |
| 246 | } | ||
| 247 | |||
| 248 | return res; | ||
| 249 | } | ||
| 250 | |||
| 251 | pub fn addRelocation(macho_file: *MachO, atom_index: Index, reloc: Relocation) !void { | ||
| 252 | return addRelocations(macho_file, atom_index, &[_]Relocation{reloc}); | ||
| 253 | } | ||
| 254 | |||
| 255 | pub fn addRelocations(macho_file: *MachO, atom_index: Index, relocs: []const Relocation) !void { | ||
| 256 | const comp = macho_file.base.comp; | ||
| 257 | const gpa = comp.gpa; | ||
| 258 | const gop = try macho_file.relocs.getOrPut(gpa, atom_index); | ||
| 259 | if (!gop.found_existing) { | ||
| 260 | gop.value_ptr.* = .{}; | ||
| 261 | } | ||
| 262 | try gop.value_ptr.ensureUnusedCapacity(gpa, relocs.len); | ||
| 263 | for (relocs) |reloc| { | ||
| 264 | log.debug(" (adding reloc of type {s} to target %{d})", .{ | ||
| 265 | @tagName(reloc.type), | ||
| 266 | reloc.target.sym_index, | ||
| 267 | }); | ||
| 268 | gop.value_ptr.appendAssumeCapacity(reloc); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | pub fn addRebase(macho_file: *MachO, atom_index: Index, offset: u32) !void { | ||
| 273 | const comp = macho_file.base.comp; | ||
| 274 | const gpa = comp.gpa; | ||
| 275 | const atom = macho_file.getAtom(atom_index); | ||
| 276 | log.debug(" (adding rebase at offset 0x{x} in %{?d})", .{ offset, atom.getSymbolIndex() }); | ||
| 277 | const gop = try macho_file.rebases.getOrPut(gpa, atom_index); | ||
| 278 | if (!gop.found_existing) { | ||
| 279 | gop.value_ptr.* = .{}; | ||
| 280 | } | ||
| 281 | try gop.value_ptr.append(gpa, offset); | ||
| 282 | } | ||
| 283 | |||
| 284 | pub fn addBinding(macho_file: *MachO, atom_index: Index, binding: Binding) !void { | ||
| 285 | const comp = macho_file.base.comp; | ||
| 286 | const gpa = comp.gpa; | ||
| 287 | const atom = macho_file.getAtom(atom_index); | ||
| 288 | log.debug(" (adding binding to symbol {s} at offset 0x{x} in %{?d})", .{ | ||
| 289 | macho_file.getSymbolName(binding.target), | ||
| 290 | binding.offset, | ||
| 291 | atom.getSymbolIndex(), | ||
| 292 | }); | ||
| 293 | const gop = try macho_file.bindings.getOrPut(gpa, atom_index); | ||
| 294 | if (!gop.found_existing) { | ||
| 295 | gop.value_ptr.* = .{}; | ||
| 296 | } | ||
| 297 | try gop.value_ptr.append(gpa, binding); | ||
| 298 | } | ||
| 299 | |||
| 300 | pub fn resolveRelocations( | ||
| 301 | macho_file: *MachO, | ||
| 302 | atom_index: Index, | ||
| 303 | relocs: []*const Relocation, | ||
| 304 | code: []u8, | ||
| 305 | ) void { | ||
| 306 | relocs_log.debug("relocating '{s}'", .{macho_file.getAtom(atom_index).getName(macho_file)}); | ||
| 307 | for (relocs) |reloc| { | ||
| 308 | reloc.resolve(macho_file, atom_index, code); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 312 | pub fn freeRelocations(macho_file: *MachO, atom_index: Index) void { | ||
| 313 | const comp = macho_file.base.comp; | ||
| 314 | const gpa = comp.gpa; | ||
| 315 | var removed_relocs = macho_file.relocs.fetchOrderedRemove(atom_index); | ||
| 316 | if (removed_relocs) |*relocs| relocs.value.deinit(gpa); | ||
| 317 | var removed_rebases = macho_file.rebases.fetchOrderedRemove(atom_index); | ||
| 318 | if (removed_rebases) |*rebases| rebases.value.deinit(gpa); | ||
| 319 | var removed_bindings = macho_file.bindings.fetchOrderedRemove(atom_index); | ||
| 320 | if (removed_bindings) |*bindings| bindings.value.deinit(gpa); | ||
| 321 | } | ||
| 322 | |||
| 323 | const InnerSymIterator = struct { | ||
| 324 | sym_index: u32, | ||
| 325 | nsyms: u32, | ||
| 326 | file: u32, | ||
| 327 | pos: u32 = 0, | ||
| 328 | |||
| 329 | pub fn next(it: *@This()) ?SymbolWithLoc { | ||
| 330 | if (it.pos == it.nsyms) return null; | ||
| 331 | const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file }; | ||
| 332 | it.pos += 1; | ||
| 333 | return res; | ||
| 334 | } | ||
| 335 | }; | ||
| 336 | |||
| 337 | /// Returns an iterator over potentially contained symbols. | ||
| 338 | /// Panics when called on a synthetic Atom. | ||
| 339 | pub fn getInnerSymbolsIterator(macho_file: *MachO, atom_index: Index) InnerSymIterator { | ||
| 340 | const atom = macho_file.getAtom(atom_index); | ||
| 341 | assert(atom.getFile() != null); | ||
| 342 | return .{ | ||
| 343 | .sym_index = atom.inner_sym_index, | ||
| 344 | .nsyms = atom.inner_nsyms_trailing, | ||
| 345 | .file = atom.file, | ||
| 346 | }; | 161 | }; |
| 347 | } | 162 | const osec = macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection( |
| 348 | 163 | segname, | |
| 349 | /// Returns a section alias symbol if one is defined. | 164 | sectname, |
| 350 | /// An alias symbol is used to represent the start of an input section | 165 | .{ .flags = flags }, |
| 351 | /// if there were no symbols defined within that range. | 166 | ); |
| 352 | /// Alias symbols are only used on x86_64. | 167 | if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__data")) { |
| 353 | pub fn getSectionAlias(macho_file: *MachO, atom_index: Index) ?SymbolWithLoc { | 168 | macho_file.data_sect_index = osec; |
| 354 | const atom = macho_file.getAtom(atom_index); | ||
| 355 | assert(atom.getFile() != null); | ||
| 356 | |||
| 357 | const object = macho_file.objects.items[atom.getFile().?]; | ||
| 358 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | ||
| 359 | const ntotal = @as(u32, @intCast(object.symtab.len)); | ||
| 360 | var sym_index: u32 = nbase; | ||
| 361 | while (sym_index < ntotal) : (sym_index += 1) { | ||
| 362 | if (object.getAtomIndexForSymbol(sym_index)) |other_atom_index| { | ||
| 363 | if (other_atom_index == atom_index) return SymbolWithLoc{ | ||
| 364 | .sym_index = sym_index, | ||
| 365 | .file = atom.file, | ||
| 366 | }; | ||
| 367 | } | ||
| 368 | } | 169 | } |
| 369 | return null; | 170 | return osec; |
| 370 | } | 171 | } |
| 371 | 172 | ||
| 372 | /// Given an index into a contained symbol within, calculates an offset wrt | 173 | pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { |
| 373 | /// the start of this Atom. | ||
| 374 | pub fn calcInnerSymbolOffset(macho_file: *MachO, atom_index: Index, sym_index: u32) u64 { | ||
| 375 | const atom = macho_file.getAtom(atom_index); | ||
| 376 | assert(atom.getFile() != null); | ||
| 377 | |||
| 378 | if (atom.sym_index == sym_index) return 0; | ||
| 379 | |||
| 380 | const object = macho_file.objects.items[atom.getFile().?]; | ||
| 381 | const source_sym = object.getSourceSymbol(sym_index).?; | ||
| 382 | const base_addr = if (object.getSourceSymbol(atom.sym_index)) |sym| | ||
| 383 | sym.n_value | ||
| 384 | else blk: { | ||
| 385 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | ||
| 386 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | ||
| 387 | const source_sect = object.getSourceSection(sect_id); | ||
| 388 | break :blk source_sect.addr; | ||
| 389 | }; | ||
| 390 | return source_sym.n_value - base_addr; | ||
| 391 | } | ||
| 392 | |||
| 393 | pub fn scanAtomRelocs(macho_file: *MachO, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void { | ||
| 394 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 395 | const arch = target.cpu.arch; | ||
| 396 | const atom = macho_file.getAtom(atom_index); | ||
| 397 | assert(atom.getFile() != null); // synthetic atoms do not have relocs | ||
| 398 | |||
| 399 | return switch (arch) { | ||
| 400 | .aarch64 => scanAtomRelocsArm64(macho_file, atom_index, relocs), | ||
| 401 | .x86_64 => scanAtomRelocsX86(macho_file, atom_index, relocs), | ||
| 402 | else => unreachable, | ||
| 403 | }; | ||
| 404 | } | ||
| 405 | |||
| 406 | const RelocContext = struct { | ||
| 407 | base_addr: i64 = 0, | ||
| 408 | base_offset: i32 = 0, | ||
| 409 | }; | ||
| 410 | |||
| 411 | pub fn getRelocContext(macho_file: *MachO, atom_index: Index) RelocContext { | ||
| 412 | const atom = macho_file.getAtom(atom_index); | ||
| 413 | assert(atom.getFile() != null); // synthetic atoms do not have relocs | ||
| 414 | |||
| 415 | const object = macho_file.objects.items[atom.getFile().?]; | ||
| 416 | if (object.getSourceSymbol(atom.sym_index)) |source_sym| { | ||
| 417 | const source_sect = object.getSourceSection(source_sym.n_sect - 1); | ||
| 418 | return .{ | ||
| 419 | .base_addr = @as(i64, @intCast(source_sect.addr)), | ||
| 420 | .base_offset = @as(i32, @intCast(source_sym.n_value - source_sect.addr)), | ||
| 421 | }; | ||
| 422 | } | ||
| 423 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | ||
| 424 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | ||
| 425 | const source_sect = object.getSourceSection(sect_id); | ||
| 426 | return .{ | ||
| 427 | .base_addr = @as(i64, @intCast(source_sect.addr)), | ||
| 428 | .base_offset = 0, | ||
| 429 | }; | ||
| 430 | } | ||
| 431 | |||
| 432 | pub fn parseRelocTarget(macho_file: *MachO, ctx: struct { | ||
| 433 | object_id: u32, | ||
| 434 | rel: macho.relocation_info, | ||
| 435 | code: []const u8, | ||
| 436 | base_addr: i64 = 0, | ||
| 437 | base_offset: i32 = 0, | ||
| 438 | }) SymbolWithLoc { | ||
| 439 | const tracy = trace(@src()); | 174 | const tracy = trace(@src()); |
| 440 | defer tracy.end(); | 175 | defer tracy.end(); |
| 441 | 176 | ||
| 442 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 177 | const object = self.getFile(macho_file).object; |
| 443 | const object = &macho_file.objects.items[ctx.object_id]; | 178 | const relocs = self.getRelocs(macho_file); |
| 444 | log.debug("parsing reloc target in object({d}) '{s}' ", .{ ctx.object_id, object.name }); | ||
| 445 | |||
| 446 | const sym_index = if (ctx.rel.r_extern == 0) sym_index: { | ||
| 447 | const sect_id = @as(u8, @intCast(ctx.rel.r_symbolnum - 1)); | ||
| 448 | const rel_offset = @as(u32, @intCast(ctx.rel.r_address - ctx.base_offset)); | ||
| 449 | |||
| 450 | const address_in_section = if (ctx.rel.r_pcrel == 0) blk: { | ||
| 451 | break :blk if (ctx.rel.r_length == 3) | ||
| 452 | mem.readInt(u64, ctx.code[rel_offset..][0..8], .little) | ||
| 453 | else | ||
| 454 | mem.readInt(u32, ctx.code[rel_offset..][0..4], .little); | ||
| 455 | } else blk: { | ||
| 456 | assert(target.cpu.arch == .x86_64); | ||
| 457 | const correction: u3 = switch (@as(macho.reloc_type_x86_64, @enumFromInt(ctx.rel.r_type))) { | ||
| 458 | .X86_64_RELOC_SIGNED => 0, | ||
| 459 | .X86_64_RELOC_SIGNED_1 => 1, | ||
| 460 | .X86_64_RELOC_SIGNED_2 => 2, | ||
| 461 | .X86_64_RELOC_SIGNED_4 => 4, | ||
| 462 | else => unreachable, | ||
| 463 | }; | ||
| 464 | const addend = mem.readInt(i32, ctx.code[rel_offset..][0..4], .little); | ||
| 465 | const target_address = @as(i64, @intCast(ctx.base_addr)) + ctx.rel.r_address + 4 + correction + addend; | ||
| 466 | break :blk @as(u64, @intCast(target_address)); | ||
| 467 | }; | ||
| 468 | |||
| 469 | // Find containing atom | ||
| 470 | log.debug(" | locating symbol by address @{x} in section {d}", .{ address_in_section, sect_id }); | ||
| 471 | break :sym_index object.getSymbolByAddress(address_in_section, sect_id); | ||
| 472 | } else object.reverse_symtab_lookup[ctx.rel.r_symbolnum]; | ||
| 473 | |||
| 474 | const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 }; | ||
| 475 | const sym = macho_file.getSymbol(sym_loc); | ||
| 476 | const reloc_target = if (sym.sect() and !sym.ext()) | ||
| 477 | sym_loc | ||
| 478 | else if (object.getGlobal(sym_index)) |global_index| | ||
| 479 | macho_file.globals.items[global_index] | ||
| 480 | else | ||
| 481 | sym_loc; | ||
| 482 | log.debug(" | target %{d} ('{s}') in object({?d})", .{ | ||
| 483 | reloc_target.sym_index, | ||
| 484 | macho_file.getSymbolName(reloc_target), | ||
| 485 | reloc_target.getFile(), | ||
| 486 | }); | ||
| 487 | return reloc_target; | ||
| 488 | } | ||
| 489 | |||
| 490 | pub fn getRelocTargetAtomIndex(macho_file: *MachO, target: SymbolWithLoc) ?Index { | ||
| 491 | if (target.getFile() == null) { | ||
| 492 | const target_sym_name = macho_file.getSymbolName(target); | ||
| 493 | if (mem.eql(u8, "__mh_execute_header", target_sym_name)) return null; | ||
| 494 | if (mem.eql(u8, "___dso_handle", target_sym_name)) return null; | ||
| 495 | 179 | ||
| 496 | unreachable; // referenced symbol not found | ||
| 497 | } | ||
| 498 | |||
| 499 | const object = macho_file.objects.items[target.getFile().?]; | ||
| 500 | return object.getAtomIndexForSymbol(target.sym_index); | ||
| 501 | } | ||
| 502 | |||
| 503 | fn scanAtomRelocsArm64( | ||
| 504 | macho_file: *MachO, | ||
| 505 | atom_index: Index, | ||
| 506 | relocs: []align(1) const macho.relocation_info, | ||
| 507 | ) !void { | ||
| 508 | for (relocs) |rel| { | 180 | for (relocs) |rel| { |
| 509 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | 181 | if (try self.reportUndefSymbol(rel, macho_file)) continue; |
| 510 | 182 | ||
| 511 | switch (rel_type) { | 183 | switch (rel.type) { |
| 512 | .ARM64_RELOC_ADDEND, .ARM64_RELOC_SUBTRACTOR => continue, | 184 | .branch => { |
| 513 | else => {}, | 185 | const symbol = rel.getTargetSymbol(macho_file); |
| 514 | } | 186 | if (symbol.flags.import or (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable))) { |
| 515 | 187 | symbol.flags.stubs = true; | |
| 516 | if (rel.r_extern == 0) continue; | 188 | if (symbol.flags.weak) { |
| 517 | 189 | macho_file.binds_to_weak = true; | |
| 518 | const atom = macho_file.getAtom(atom_index); | 190 | } |
| 519 | const object = &macho_file.objects.items[atom.getFile().?]; | 191 | } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) { |
| 520 | const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum]; | 192 | symbol.flags.objc_stubs = true; |
| 521 | const sym_loc = SymbolWithLoc{ | 193 | } |
| 522 | .sym_index = sym_index, | 194 | }, |
| 523 | .file = atom.file, | ||
| 524 | }; | ||
| 525 | 195 | ||
| 526 | const target = if (object.getGlobal(sym_index)) |global_index| | 196 | .got_load, |
| 527 | macho_file.globals.items[global_index] | 197 | .got_load_page, |
| 528 | else | 198 | .got_load_pageoff, |
| 529 | sym_loc; | 199 | => { |
| 200 | const symbol = rel.getTargetSymbol(macho_file); | ||
| 201 | if (symbol.flags.import or | ||
| 202 | (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable)) or | ||
| 203 | macho_file.options.cpu_arch.? == .aarch64) // TODO relax on arm64 | ||
| 204 | { | ||
| 205 | symbol.flags.got = true; | ||
| 206 | if (symbol.flags.weak) { | ||
| 207 | macho_file.binds_to_weak = true; | ||
| 208 | } | ||
| 209 | } | ||
| 210 | }, | ||
| 530 | 211 | ||
| 531 | switch (rel_type) { | 212 | .got => { |
| 532 | .ARM64_RELOC_BRANCH26 => { | 213 | rel.getTargetSymbol(macho_file).flags.got = true; |
| 533 | // TODO rewrite relocation | ||
| 534 | const sym = macho_file.getSymbol(target); | ||
| 535 | if (sym.undf()) try macho_file.addStubEntry(target); | ||
| 536 | }, | 214 | }, |
| 537 | .ARM64_RELOC_GOT_LOAD_PAGE21, | 215 | |
| 538 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | 216 | .tlv, |
| 539 | .ARM64_RELOC_POINTER_TO_GOT, | 217 | .tlvp_page, |
| 218 | .tlvp_pageoff, | ||
| 540 | => { | 219 | => { |
| 541 | // TODO rewrite relocation | 220 | const symbol = rel.getTargetSymbol(macho_file); |
| 542 | try macho_file.addGotEntry(target); | 221 | if (!symbol.flags.tlv) { |
| 222 | macho_file.base.fatal( | ||
| 223 | "{}: {s}: illegal thread-local variable reference to regular symbol {s}", | ||
| 224 | .{ object.fmtPath(), self.getName(macho_file), symbol.getName(macho_file) }, | ||
| 225 | ); | ||
| 226 | } | ||
| 227 | if (symbol.flags.import or (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable))) { | ||
| 228 | symbol.flags.tlv_ptr = true; | ||
| 229 | if (symbol.flags.weak) { | ||
| 230 | macho_file.binds_to_weak = true; | ||
| 231 | } | ||
| 232 | } | ||
| 543 | }, | 233 | }, |
| 544 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | 234 | |
| 545 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | 235 | .unsigned => { |
| 546 | => { | 236 | if (rel.meta.length == 3) { // TODO this really should check if this is pointer width |
| 547 | const sym = macho_file.getSymbol(target); | 237 | if (rel.tag == .@"extern") { |
| 548 | if (sym.undf()) try macho_file.addTlvPtrEntry(target); | 238 | const symbol = rel.getTargetSymbol(macho_file); |
| 239 | if (symbol.isTlvInit(macho_file)) { | ||
| 240 | macho_file.has_tlv = true; | ||
| 241 | continue; | ||
| 242 | } | ||
| 243 | if (symbol.flags.import) { | ||
| 244 | object.num_bind_relocs += 1; | ||
| 245 | if (symbol.flags.weak) { | ||
| 246 | object.num_weak_bind_relocs += 1; | ||
| 247 | macho_file.binds_to_weak = true; | ||
| 248 | } | ||
| 249 | continue; | ||
| 250 | } | ||
| 251 | if (symbol.flags.@"export") { | ||
| 252 | if (symbol.flags.weak) { | ||
| 253 | object.num_weak_bind_relocs += 1; | ||
| 254 | macho_file.binds_to_weak = true; | ||
| 255 | } else if (symbol.flags.interposable) { | ||
| 256 | object.num_bind_relocs += 1; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | object.num_rebase_relocs += 1; | ||
| 261 | } | ||
| 549 | }, | 262 | }, |
| 263 | |||
| 550 | else => {}, | 264 | else => {}, |
| 551 | } | 265 | } |
| 552 | } | 266 | } |
| 553 | } | 267 | } |
| 554 | 268 | ||
| 555 | fn scanAtomRelocsX86( | 269 | fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool { |
| 556 | macho_file: *MachO, | 270 | if (rel.tag == .local) return false; |
| 557 | atom_index: Index, | ||
| 558 | relocs: []align(1) const macho.relocation_info, | ||
| 559 | ) !void { | ||
| 560 | for (relocs) |rel| { | ||
| 561 | const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type)); | ||
| 562 | 271 | ||
| 563 | switch (rel_type) { | 272 | const sym = rel.getTargetSymbol(macho_file); |
| 564 | .X86_64_RELOC_SUBTRACTOR => continue, | 273 | if (sym.getFile(macho_file) == null) { |
| 565 | else => {}, | 274 | const gpa = macho_file.base.allocator; |
| 275 | const gop = try macho_file.undefs.getOrPut(gpa, rel.target); | ||
| 276 | if (!gop.found_existing) { | ||
| 277 | gop.value_ptr.* = .{}; | ||
| 566 | } | 278 | } |
| 279 | try gop.value_ptr.append(gpa, self.atom_index); | ||
| 280 | return true; | ||
| 281 | } | ||
| 567 | 282 | ||
| 568 | if (rel.r_extern == 0) continue; | 283 | return false; |
| 284 | } | ||
| 569 | 285 | ||
| 570 | const atom = macho_file.getAtom(atom_index); | 286 | pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void { |
| 571 | const object = &macho_file.objects.items[atom.getFile().?]; | 287 | const tracy = trace(@src()); |
| 572 | const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum]; | 288 | defer tracy.end(); |
| 573 | const sym_loc = SymbolWithLoc{ | ||
| 574 | .sym_index = sym_index, | ||
| 575 | .file = atom.file, | ||
| 576 | }; | ||
| 577 | 289 | ||
| 578 | const target = if (object.getGlobal(sym_index)) |global_index| | 290 | assert(!self.getInputSection(macho_file).isZerofill()); |
| 579 | macho_file.globals.items[global_index] | 291 | const relocs = self.getRelocs(macho_file); |
| 580 | else | 292 | const file = self.getFile(macho_file); |
| 581 | sym_loc; | 293 | const name = self.getName(macho_file); |
| 294 | @memcpy(buffer, self.getCode(macho_file)); | ||
| 582 | 295 | ||
| 583 | switch (rel_type) { | 296 | relocs_log.debug("{x}: {s}", .{ self.value, name }); |
| 584 | .X86_64_RELOC_BRANCH => { | ||
| 585 | // TODO rewrite relocation | ||
| 586 | const sym = macho_file.getSymbol(target); | ||
| 587 | if (sym.undf()) try macho_file.addStubEntry(target); | ||
| 588 | }, | ||
| 589 | .X86_64_RELOC_GOT, .X86_64_RELOC_GOT_LOAD => { | ||
| 590 | // TODO rewrite relocation | ||
| 591 | try macho_file.addGotEntry(target); | ||
| 592 | }, | ||
| 593 | .X86_64_RELOC_TLV => { | ||
| 594 | const sym = macho_file.getSymbol(target); | ||
| 595 | if (sym.undf()) try macho_file.addTlvPtrEntry(target); | ||
| 596 | }, | ||
| 597 | else => {}, | ||
| 598 | } | ||
| 599 | } | ||
| 600 | } | ||
| 601 | 297 | ||
| 602 | pub fn resolveRelocs( | 298 | var stream = std.io.fixedBufferStream(buffer); |
| 603 | macho_file: *MachO, | ||
| 604 | atom_index: Index, | ||
| 605 | atom_code: []u8, | ||
| 606 | atom_relocs: []align(1) const macho.relocation_info, | ||
| 607 | ) !void { | ||
| 608 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 609 | const arch = target.cpu.arch; | ||
| 610 | const atom = macho_file.getAtom(atom_index); | ||
| 611 | assert(atom.getFile() != null); // synthetic atoms do not have relocs | ||
| 612 | |||
| 613 | relocs_log.debug("resolving relocations in ATOM(%{d}, '{s}')", .{ | ||
| 614 | atom.sym_index, | ||
| 615 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | ||
| 616 | }); | ||
| 617 | 299 | ||
| 618 | const ctx = getRelocContext(macho_file, atom_index); | 300 | var i: usize = 0; |
| 301 | while (i < relocs.len) : (i += 1) { | ||
| 302 | const rel = relocs[i]; | ||
| 303 | const rel_offset = rel.offset - self.off; | ||
| 304 | const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null; | ||
| 619 | 305 | ||
| 620 | return switch (arch) { | 306 | if (rel.tag == .@"extern") { |
| 621 | .aarch64 => resolveRelocsArm64(macho_file, atom_index, atom_code, atom_relocs, ctx), | 307 | if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue; |
| 622 | .x86_64 => resolveRelocsX86(macho_file, atom_index, atom_code, atom_relocs, ctx), | 308 | } |
| 623 | else => unreachable, | ||
| 624 | }; | ||
| 625 | } | ||
| 626 | |||
| 627 | pub fn getRelocTargetAddress(macho_file: *MachO, target: SymbolWithLoc, is_tlv: bool) u64 { | ||
| 628 | const target_atom_index = getRelocTargetAtomIndex(macho_file, target) orelse { | ||
| 629 | // If there is no atom for target, we still need to check for special, atom-less | ||
| 630 | // symbols such as `___dso_handle`. | ||
| 631 | const target_name = macho_file.getSymbolName(target); | ||
| 632 | const atomless_sym = macho_file.getSymbol(target); | ||
| 633 | log.debug(" | atomless target '{s}'", .{target_name}); | ||
| 634 | return atomless_sym.n_value; | ||
| 635 | }; | ||
| 636 | const target_atom = macho_file.getAtom(target_atom_index); | ||
| 637 | log.debug(" | target ATOM(%{d}, '{s}') in object({?})", .{ | ||
| 638 | target_atom.sym_index, | ||
| 639 | macho_file.getSymbolName(target_atom.getSymbolWithLoc()), | ||
| 640 | target_atom.getFile(), | ||
| 641 | }); | ||
| 642 | 309 | ||
| 643 | const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc()); | 310 | try stream.seekTo(rel_offset); |
| 644 | assert(target_sym.n_desc != MachO.N_DEAD); | 311 | self.resolveRelocInner(rel, subtractor, buffer, macho_file, stream.writer()) catch |err| { |
| 645 | 312 | switch (err) { | |
| 646 | // If `target` is contained within the target atom, pull its address value. | 313 | error.RelaxFail => macho_file.base.fatal( |
| 647 | const offset = if (target_atom.getFile() != null) blk: { | 314 | "{}: {s}: 0x{x}: failed to relax relocation: in {s}", |
| 648 | const object = macho_file.objects.items[target_atom.getFile().?]; | 315 | .{ file.fmtPath(), name, rel.offset, @tagName(rel.type) }, |
| 649 | break :blk if (object.getSourceSymbol(target.sym_index)) |_| | 316 | ), |
| 650 | Atom.calcInnerSymbolOffset(macho_file, target_atom_index, target.sym_index) | 317 | else => |e| return e, |
| 651 | else | 318 | } |
| 652 | 0; // section alias | 319 | return error.ResolveFailed; |
| 653 | } else 0; | ||
| 654 | const base_address: u64 = if (is_tlv) base_address: { | ||
| 655 | // For TLV relocations, the value specified as a relocation is the displacement from the | ||
| 656 | // TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first | ||
| 657 | // defined TLV template init section in the following order: | ||
| 658 | // * wrt to __thread_data if defined, then | ||
| 659 | // * wrt to __thread_bss | ||
| 660 | // TODO remember to check what the mechanism was prior to HAS_TLV_INITIALIZERS in earlier versions of macOS | ||
| 661 | const sect_id: u16 = sect_id: { | ||
| 662 | if (macho_file.thread_data_section_index) |i| { | ||
| 663 | break :sect_id i; | ||
| 664 | } else if (macho_file.thread_bss_section_index) |i| { | ||
| 665 | break :sect_id i; | ||
| 666 | } else break :base_address 0; | ||
| 667 | }; | 320 | }; |
| 668 | break :base_address macho_file.sections.items(.header)[sect_id].addr; | 321 | } |
| 669 | } else 0; | ||
| 670 | return target_sym.n_value + offset - base_address; | ||
| 671 | } | 322 | } |
| 672 | 323 | ||
| 673 | fn resolveRelocsArm64( | 324 | const ResolveError = error{ |
| 674 | macho_file: *MachO, | 325 | RelaxFail, |
| 675 | atom_index: Index, | 326 | NoSpaceLeft, |
| 676 | atom_code: []u8, | 327 | DivisionByZero, |
| 677 | atom_relocs: []align(1) const macho.relocation_info, | 328 | UnexpectedRemainder, |
| 678 | context: RelocContext, | 329 | Overflow, |
| 679 | ) !void { | 330 | }; |
| 680 | const atom = macho_file.getAtom(atom_index); | ||
| 681 | const object = macho_file.objects.items[atom.getFile().?]; | ||
| 682 | |||
| 683 | var addend: ?i64 = null; | ||
| 684 | var subtractor: ?SymbolWithLoc = null; | ||
| 685 | |||
| 686 | for (atom_relocs) |rel| { | ||
| 687 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | ||
| 688 | |||
| 689 | switch (rel_type) { | ||
| 690 | .ARM64_RELOC_ADDEND => { | ||
| 691 | assert(addend == null); | ||
| 692 | 331 | ||
| 693 | relocs_log.debug(" RELA({s}) @ {x} => {x}", .{ @tagName(rel_type), rel.r_address, rel.r_symbolnum }); | 332 | fn resolveRelocInner( |
| 333 | self: Atom, | ||
| 334 | rel: Relocation, | ||
| 335 | subtractor: ?Relocation, | ||
| 336 | code: []u8, | ||
| 337 | macho_file: *MachO, | ||
| 338 | writer: anytype, | ||
| 339 | ) ResolveError!void { | ||
| 340 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 341 | const rel_offset = rel.offset - self.off; | ||
| 342 | const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect]; | ||
| 343 | const seg = macho_file.segments.items[seg_id]; | ||
| 344 | const P = @as(i64, @intCast(self.value)) + @as(i64, @intCast(rel_offset)); | ||
| 345 | const A = rel.addend + rel.getRelocAddend(cpu_arch); | ||
| 346 | const S: i64 = @intCast(rel.getTargetAddress(macho_file)); | ||
| 347 | const G: i64 = @intCast(rel.getGotTargetAddress(macho_file)); | ||
| 348 | const TLS = @as(i64, @intCast(macho_file.getTlsAddress())); | ||
| 349 | const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(macho_file))) else 0; | ||
| 350 | |||
| 351 | switch (rel.tag) { | ||
| 352 | .local => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] atom({d})", .{ | ||
| 353 | P, | ||
| 354 | rel_offset, | ||
| 355 | @tagName(rel.type), | ||
| 356 | S + A - SUB, | ||
| 357 | rel.getTargetAtom(macho_file).atom_index, | ||
| 358 | }), | ||
| 359 | .@"extern" => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] G({x}) ({s})", .{ | ||
| 360 | P, | ||
| 361 | rel_offset, | ||
| 362 | @tagName(rel.type), | ||
| 363 | S + A - SUB, | ||
| 364 | G + A, | ||
| 365 | rel.getTargetSymbol(macho_file).getName(macho_file), | ||
| 366 | }), | ||
| 367 | } | ||
| 694 | 368 | ||
| 695 | addend = rel.r_symbolnum; | 369 | switch (rel.type) { |
| 696 | continue; | 370 | .subtractor => {}, |
| 697 | }, | 371 | |
| 698 | .ARM64_RELOC_SUBTRACTOR => { | 372 | .unsigned => { |
| 699 | assert(subtractor == null); | 373 | assert(!rel.meta.pcrel); |
| 700 | 374 | if (rel.meta.length == 3) { | |
| 701 | relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{ | 375 | if (rel.tag == .@"extern") { |
| 702 | @tagName(rel_type), | 376 | const sym = rel.getTargetSymbol(macho_file); |
| 703 | rel.r_address, | 377 | if (sym.isTlvInit(macho_file)) { |
| 704 | rel.r_symbolnum, | 378 | try writer.writeInt(u64, @intCast(S - TLS), .little); |
| 705 | atom.getFile(), | 379 | return; |
| 380 | } | ||
| 381 | const entry = bind.Entry{ | ||
| 382 | .target = rel.target, | ||
| 383 | .offset = @as(u64, @intCast(P)) - seg.vmaddr, | ||
| 384 | .segment_id = seg_id, | ||
| 385 | .addend = A, | ||
| 386 | }; | ||
| 387 | if (sym.flags.import) { | ||
| 388 | macho_file.bind.entries.appendAssumeCapacity(entry); | ||
| 389 | if (sym.flags.weak) { | ||
| 390 | macho_file.weak_bind.entries.appendAssumeCapacity(entry); | ||
| 391 | } | ||
| 392 | return; | ||
| 393 | } | ||
| 394 | if (sym.flags.@"export") { | ||
| 395 | if (sym.flags.weak) { | ||
| 396 | macho_file.weak_bind.entries.appendAssumeCapacity(entry); | ||
| 397 | } else if (sym.flags.interposable) { | ||
| 398 | macho_file.bind.entries.appendAssumeCapacity(entry); | ||
| 399 | } | ||
| 400 | } | ||
| 401 | } | ||
| 402 | macho_file.rebase.entries.appendAssumeCapacity(.{ | ||
| 403 | .offset = @as(u64, @intCast(P)) - seg.vmaddr, | ||
| 404 | .segment_id = seg_id, | ||
| 706 | }); | 405 | }); |
| 406 | try writer.writeInt(u64, @bitCast(S + A - SUB), .little); | ||
| 407 | } else if (rel.meta.length == 2) { | ||
| 408 | try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little); | ||
| 409 | } else unreachable; | ||
| 410 | }, | ||
| 707 | 411 | ||
| 708 | subtractor = parseRelocTarget(macho_file, .{ | 412 | .got => { |
| 709 | .object_id = atom.getFile().?, | 413 | assert(rel.tag == .@"extern"); |
| 710 | .rel = rel, | 414 | assert(rel.meta.length == 2); |
| 711 | .code = atom_code, | 415 | assert(rel.meta.pcrel); |
| 712 | .base_addr = context.base_addr, | 416 | try writer.writeInt(i32, @intCast(G + A - P), .little); |
| 713 | .base_offset = context.base_offset, | 417 | }, |
| 714 | }); | ||
| 715 | continue; | ||
| 716 | }, | ||
| 717 | else => {}, | ||
| 718 | } | ||
| 719 | 418 | ||
| 720 | const target = parseRelocTarget(macho_file, .{ | 419 | .branch => { |
| 721 | .object_id = atom.getFile().?, | 420 | assert(rel.meta.length == 2); |
| 722 | .rel = rel, | 421 | assert(rel.meta.pcrel); |
| 723 | .code = atom_code, | 422 | assert(rel.tag == .@"extern"); |
| 724 | .base_addr = context.base_addr, | 423 | |
| 725 | .base_offset = context.base_offset, | 424 | switch (cpu_arch) { |
| 726 | }); | 425 | .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little), |
| 727 | const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset)); | 426 | .aarch64 => { |
| 728 | 427 | const disp: i28 = math.cast(i28, S + A - P) orelse blk: { | |
| 729 | relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{ | 428 | const thunk = self.getThunk(macho_file); |
| 730 | @tagName(rel_type), | 429 | const S_: i64 = @intCast(thunk.getAddress(rel.target)); |
| 731 | rel.r_address, | 430 | break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow; |
| 732 | target.sym_index, | 431 | }; |
| 733 | macho_file.getSymbolName(target), | 432 | var inst = aarch64.Instruction{ |
| 734 | target.getFile(), | 433 | .unconditional_branch_immediate = mem.bytesToValue(std.meta.TagPayload( |
| 735 | }); | 434 | aarch64.Instruction, |
| 736 | 435 | aarch64.Instruction.unconditional_branch_immediate, | |
| 737 | const source_addr = blk: { | 436 | ), code[rel_offset..][0..4]), |
| 738 | const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | 437 | }; |
| 739 | break :blk source_sym.n_value + rel_offset; | 438 | inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(disp >> 2)))); |
| 740 | }; | 439 | try writer.writeInt(u32, inst.toU32(), .little); |
| 741 | const target_addr = blk: { | 440 | }, |
| 742 | if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?; | 441 | else => unreachable, |
| 743 | if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf()) | 442 | } |
| 744 | break :blk macho_file.getTlvPtrEntryAddress(target).?; | 443 | }, |
| 745 | if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf()) | ||
| 746 | break :blk macho_file.getStubsEntryAddress(target).?; | ||
| 747 | const is_tlv = is_tlv: { | ||
| 748 | const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 749 | const header = macho_file.sections.items(.header)[source_sym.n_sect - 1]; | ||
| 750 | break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES; | ||
| 751 | }; | ||
| 752 | break :blk getRelocTargetAddress(macho_file, target, is_tlv); | ||
| 753 | }; | ||
| 754 | 444 | ||
| 755 | relocs_log.debug(" | source_addr = 0x{x}", .{source_addr}); | 445 | .got_load => { |
| 446 | assert(rel.tag == .@"extern"); | ||
| 447 | assert(rel.meta.length == 2); | ||
| 448 | assert(rel.meta.pcrel); | ||
| 449 | if (rel.getTargetSymbol(macho_file).flags.got) { | ||
| 450 | try writer.writeInt(i32, @intCast(G + A - P), .little); | ||
| 451 | } else { | ||
| 452 | try relaxGotLoad(code[rel_offset - 3 ..]); | ||
| 453 | try writer.writeInt(i32, @intCast(S + A - P), .little); | ||
| 454 | } | ||
| 455 | }, | ||
| 756 | 456 | ||
| 757 | switch (rel_type) { | 457 | .tlv => { |
| 758 | .ARM64_RELOC_BRANCH26 => { | 458 | assert(rel.tag == .@"extern"); |
| 759 | relocs_log.debug(" source {s} (object({?})), target {s}", .{ | 459 | assert(rel.meta.length == 2); |
| 760 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | 460 | assert(rel.meta.pcrel); |
| 761 | atom.getFile(), | 461 | const sym = rel.getTargetSymbol(macho_file); |
| 762 | macho_file.getSymbolName(target), | 462 | if (sym.flags.tlv_ptr) { |
| 763 | }); | 463 | const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); |
| 464 | try writer.writeInt(i32, @intCast(S_ + A - P), .little); | ||
| 465 | } else { | ||
| 466 | try relaxTlv(code[rel_offset - 3 ..]); | ||
| 467 | try writer.writeInt(i32, @intCast(S + A - P), .little); | ||
| 468 | } | ||
| 469 | }, | ||
| 764 | 470 | ||
| 765 | const displacement = if (Relocation.calcPcRelativeDisplacementArm64( | 471 | .signed, .signed1, .signed2, .signed4 => { |
| 766 | source_addr, | 472 | assert(rel.meta.length == 2); |
| 767 | target_addr, | 473 | assert(rel.meta.pcrel); |
| 768 | )) |disp| blk: { | 474 | try writer.writeInt(i32, @intCast(S + A - P), .little); |
| 769 | relocs_log.debug(" | target_addr = 0x{x}", .{target_addr}); | 475 | }, |
| 770 | break :blk disp; | 476 | |
| 771 | } else |_| blk: { | 477 | .page, |
| 772 | const thunk_index = macho_file.thunk_table.get(atom_index).?; | 478 | .got_load_page, |
| 773 | const thunk = macho_file.thunks.items[thunk_index]; | 479 | .tlvp_page, |
| 774 | const thunk_sym_loc = if (macho_file.getSymbol(target).undf()) | 480 | => { |
| 775 | thunk.getTrampoline(macho_file, .stub, target).? | 481 | assert(rel.tag == .@"extern"); |
| 776 | else | 482 | assert(rel.meta.length == 2); |
| 777 | thunk.getTrampoline(macho_file, .atom, target).?; | 483 | assert(rel.meta.pcrel); |
| 778 | const thunk_addr = macho_file.getSymbol(thunk_sym_loc).n_value; | 484 | const sym = rel.getTargetSymbol(macho_file); |
| 779 | relocs_log.debug(" | target_addr = 0x{x} (thunk)", .{thunk_addr}); | 485 | const source = math.cast(u64, P) orelse return error.Overflow; |
| 780 | break :blk try Relocation.calcPcRelativeDisplacementArm64(source_addr, thunk_addr); | 486 | const target = target: { |
| 487 | const target = switch (rel.type) { | ||
| 488 | .page => S + A, | ||
| 489 | .got_load_page => G + A, | ||
| 490 | .tlvp_page => if (sym.flags.tlv_ptr) blk: { | ||
| 491 | const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); | ||
| 492 | break :blk S_ + A; | ||
| 493 | } else S + A, | ||
| 494 | else => unreachable, | ||
| 781 | }; | 495 | }; |
| 496 | break :target math.cast(u64, target) orelse return error.Overflow; | ||
| 497 | }; | ||
| 498 | const pages = @as(u21, @bitCast(try Relocation.calcNumberOfPages(source, target))); | ||
| 499 | var inst = aarch64.Instruction{ | ||
| 500 | .pc_relative_address = mem.bytesToValue(std.meta.TagPayload( | ||
| 501 | aarch64.Instruction, | ||
| 502 | aarch64.Instruction.pc_relative_address, | ||
| 503 | ), code[rel_offset..][0..4]), | ||
| 504 | }; | ||
| 505 | inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2)); | ||
| 506 | inst.pc_relative_address.immlo = @as(u2, @truncate(pages)); | ||
| 507 | try writer.writeInt(u32, inst.toU32(), .little); | ||
| 508 | }, | ||
| 782 | 509 | ||
| 783 | const code = atom_code[rel_offset..][0..4]; | 510 | .pageoff => { |
| 511 | assert(rel.tag == .@"extern"); | ||
| 512 | assert(rel.meta.length == 2); | ||
| 513 | assert(!rel.meta.pcrel); | ||
| 514 | const target = math.cast(u64, S + A) orelse return error.Overflow; | ||
| 515 | const inst_code = code[rel_offset..][0..4]; | ||
| 516 | if (Relocation.isArithmeticOp(inst_code)) { | ||
| 517 | const off = try Relocation.calcPageOffset(target, .arithmetic); | ||
| 784 | var inst = aarch64.Instruction{ | 518 | var inst = aarch64.Instruction{ |
| 785 | .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( | 519 | .add_subtract_immediate = mem.bytesToValue(std.meta.TagPayload( |
| 786 | aarch64.Instruction, | 520 | aarch64.Instruction, |
| 787 | aarch64.Instruction.unconditional_branch_immediate, | 521 | aarch64.Instruction.add_subtract_immediate, |
| 788 | ), code), | 522 | ), inst_code), |
| 789 | }; | 523 | }; |
| 790 | inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2)))); | 524 | inst.add_subtract_immediate.imm12 = off; |
| 791 | mem.writeInt(u32, code, inst.toU32(), .little); | 525 | try writer.writeInt(u32, inst.toU32(), .little); |
| 792 | }, | 526 | } else { |
| 793 | |||
| 794 | .ARM64_RELOC_PAGE21, | ||
| 795 | .ARM64_RELOC_GOT_LOAD_PAGE21, | ||
| 796 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | ||
| 797 | => { | ||
| 798 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0))); | ||
| 799 | |||
| 800 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | ||
| 801 | |||
| 802 | const pages = @as(u21, @bitCast(Relocation.calcNumberOfPages(source_addr, adjusted_target_addr))); | ||
| 803 | const code = atom_code[rel_offset..][0..4]; | ||
| 804 | var inst = aarch64.Instruction{ | 527 | var inst = aarch64.Instruction{ |
| 805 | .pc_relative_address = mem.bytesToValue(meta.TagPayload( | 528 | .load_store_register = mem.bytesToValue(std.meta.TagPayload( |
| 806 | aarch64.Instruction, | 529 | aarch64.Instruction, |
| 807 | aarch64.Instruction.pc_relative_address, | 530 | aarch64.Instruction.load_store_register, |
| 808 | ), code), | 531 | ), inst_code), |
| 809 | }; | 532 | }; |
| 810 | inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2)); | 533 | const off = try Relocation.calcPageOffset(target, switch (inst.load_store_register.size) { |
| 811 | inst.pc_relative_address.immlo = @as(u2, @truncate(pages)); | 534 | 0 => if (inst.load_store_register.v == 1) |
| 812 | mem.writeInt(u32, code, inst.toU32(), .little); | 535 | Relocation.PageOffsetInstKind.load_store_128 |
| 813 | addend = null; | 536 | else |
| 814 | }, | 537 | Relocation.PageOffsetInstKind.load_store_8, |
| 538 | 1 => .load_store_16, | ||
| 539 | 2 => .load_store_32, | ||
| 540 | 3 => .load_store_64, | ||
| 541 | }); | ||
| 542 | inst.load_store_register.offset = off; | ||
| 543 | try writer.writeInt(u32, inst.toU32(), .little); | ||
| 544 | } | ||
| 545 | }, | ||
| 815 | 546 | ||
| 816 | .ARM64_RELOC_PAGEOFF12 => { | 547 | .got_load_pageoff => { |
| 817 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0))); | 548 | assert(rel.tag == .@"extern"); |
| 549 | assert(rel.meta.length == 2); | ||
| 550 | assert(!rel.meta.pcrel); | ||
| 551 | const target = math.cast(u64, G + A) orelse return error.Overflow; | ||
| 552 | const off = try Relocation.calcPageOffset(target, .load_store_64); | ||
| 553 | var inst: aarch64.Instruction = .{ | ||
| 554 | .load_store_register = mem.bytesToValue(std.meta.TagPayload( | ||
| 555 | aarch64.Instruction, | ||
| 556 | aarch64.Instruction.load_store_register, | ||
| 557 | ), code[rel_offset..][0..4]), | ||
| 558 | }; | ||
| 559 | inst.load_store_register.offset = off; | ||
| 560 | try writer.writeInt(u32, inst.toU32(), .little); | ||
| 561 | }, | ||
| 818 | 562 | ||
| 819 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 563 | .tlvp_pageoff => { |
| 564 | assert(rel.tag == .@"extern"); | ||
| 565 | assert(rel.meta.length == 2); | ||
| 566 | assert(!rel.meta.pcrel); | ||
| 567 | |||
| 568 | const sym = rel.getTargetSymbol(macho_file); | ||
| 569 | const target = target: { | ||
| 570 | const target = if (sym.flags.tlv_ptr) blk: { | ||
| 571 | const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); | ||
| 572 | break :blk S_ + A; | ||
| 573 | } else S + A; | ||
| 574 | break :target math.cast(u64, target) orelse return error.Overflow; | ||
| 575 | }; | ||
| 820 | 576 | ||
| 821 | const code = atom_code[rel_offset..][0..4]; | 577 | const RegInfo = struct { |
| 822 | if (Relocation.isArithmeticOp(code)) { | 578 | rd: u5, |
| 823 | const off = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic); | 579 | rn: u5, |
| 824 | var inst = aarch64.Instruction{ | 580 | size: u2, |
| 825 | .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( | 581 | }; |
| 826 | aarch64.Instruction, | 582 | |
| 827 | aarch64.Instruction.add_subtract_immediate, | 583 | const inst_code = code[rel_offset..][0..4]; |
| 828 | ), code), | 584 | const reg_info: RegInfo = blk: { |
| 585 | if (Relocation.isArithmeticOp(inst_code)) { | ||
| 586 | const inst = mem.bytesToValue(std.meta.TagPayload( | ||
| 587 | aarch64.Instruction, | ||
| 588 | aarch64.Instruction.add_subtract_immediate, | ||
| 589 | ), inst_code); | ||
| 590 | break :blk .{ | ||
| 591 | .rd = inst.rd, | ||
| 592 | .rn = inst.rn, | ||
| 593 | .size = inst.sf, | ||
| 829 | }; | 594 | }; |
| 830 | inst.add_subtract_immediate.imm12 = off; | ||
| 831 | mem.writeInt(u32, code, inst.toU32(), .little); | ||
| 832 | } else { | 595 | } else { |
| 833 | var inst = aarch64.Instruction{ | 596 | const inst = mem.bytesToValue(std.meta.TagPayload( |
| 834 | .load_store_register = mem.bytesToValue(meta.TagPayload( | 597 | aarch64.Instruction, |
| 835 | aarch64.Instruction, | 598 | aarch64.Instruction.load_store_register, |
| 836 | aarch64.Instruction.load_store_register, | 599 | ), inst_code); |
| 837 | ), code), | 600 | break :blk .{ |
| 601 | .rd = inst.rt, | ||
| 602 | .rn = inst.rn, | ||
| 603 | .size = inst.size, | ||
| 838 | }; | 604 | }; |
| 839 | const off = try Relocation.calcPageOffset(adjusted_target_addr, switch (inst.load_store_register.size) { | ||
| 840 | 0 => if (inst.load_store_register.v == 1) | ||
| 841 | Relocation.PageOffsetInstKind.load_store_128 | ||
| 842 | else | ||
| 843 | Relocation.PageOffsetInstKind.load_store_8, | ||
| 844 | 1 => .load_store_16, | ||
| 845 | 2 => .load_store_32, | ||
| 846 | 3 => .load_store_64, | ||
| 847 | }); | ||
| 848 | inst.load_store_register.offset = off; | ||
| 849 | mem.writeInt(u32, code, inst.toU32(), .little); | ||
| 850 | } | 605 | } |
| 851 | addend = null; | 606 | }; |
| 852 | }, | ||
| 853 | |||
| 854 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => { | ||
| 855 | const code = atom_code[rel_offset..][0..4]; | ||
| 856 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0))); | ||
| 857 | 607 | ||
| 858 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 608 | var inst = if (sym.flags.tlv_ptr) aarch64.Instruction{ |
| 609 | .load_store_register = .{ | ||
| 610 | .rt = reg_info.rd, | ||
| 611 | .rn = reg_info.rn, | ||
| 612 | .offset = try Relocation.calcPageOffset(target, .load_store_64), | ||
| 613 | .opc = 0b01, | ||
| 614 | .op1 = 0b01, | ||
| 615 | .v = 0, | ||
| 616 | .size = reg_info.size, | ||
| 617 | }, | ||
| 618 | } else aarch64.Instruction{ | ||
| 619 | .add_subtract_immediate = .{ | ||
| 620 | .rd = reg_info.rd, | ||
| 621 | .rn = reg_info.rn, | ||
| 622 | .imm12 = try Relocation.calcPageOffset(target, .arithmetic), | ||
| 623 | .sh = 0, | ||
| 624 | .s = 0, | ||
| 625 | .op = 0, | ||
| 626 | .sf = @as(u1, @truncate(reg_info.size)), | ||
| 627 | }, | ||
| 628 | }; | ||
| 629 | try writer.writeInt(u32, inst.toU32(), .little); | ||
| 630 | }, | ||
| 631 | } | ||
| 632 | } | ||
| 859 | 633 | ||
| 860 | const off = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64); | 634 | fn relaxGotLoad(code: []u8) error{RelaxFail}!void { |
| 861 | var inst: aarch64.Instruction = .{ | 635 | const old_inst = disassemble(code) orelse return error.RelaxFail; |
| 862 | .load_store_register = mem.bytesToValue(meta.TagPayload( | 636 | switch (old_inst.encoding.mnemonic) { |
| 863 | aarch64.Instruction, | 637 | .mov => { |
| 864 | aarch64.Instruction.load_store_register, | 638 | const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail; |
| 865 | ), code), | 639 | relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); |
| 866 | }; | 640 | encode(&.{inst}, code) catch return error.RelaxFail; |
| 867 | inst.load_store_register.offset = off; | 641 | }, |
| 868 | mem.writeInt(u32, code, inst.toU32(), .little); | 642 | else => return error.RelaxFail, |
| 869 | addend = null; | 643 | } |
| 870 | }, | 644 | } |
| 871 | 645 | ||
| 872 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { | 646 | fn relaxTlv(code: []u8) error{RelaxFail}!void { |
| 873 | const code = atom_code[rel_offset..][0..4]; | 647 | const old_inst = disassemble(code) orelse return error.RelaxFail; |
| 874 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0))); | 648 | switch (old_inst.encoding.mnemonic) { |
| 649 | .mov => { | ||
| 650 | const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail; | ||
| 651 | relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); | ||
| 652 | encode(&.{inst}, code) catch return error.RelaxFail; | ||
| 653 | }, | ||
| 654 | else => return error.RelaxFail, | ||
| 655 | } | ||
| 656 | } | ||
| 875 | 657 | ||
| 876 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 658 | fn disassemble(code: []const u8) ?Instruction { |
| 659 | var disas = Disassembler.init(code); | ||
| 660 | const inst = disas.next() catch return null; | ||
| 661 | return inst; | ||
| 662 | } | ||
| 877 | 663 | ||
| 878 | const RegInfo = struct { | 664 | fn encode(insts: []const Instruction, code: []u8) !void { |
| 879 | rd: u5, | 665 | var stream = std.io.fixedBufferStream(code); |
| 880 | rn: u5, | 666 | const writer = stream.writer(); |
| 881 | size: u2, | 667 | for (insts) |inst| { |
| 882 | }; | 668 | try inst.encode(writer, .{}); |
| 883 | const reg_info: RegInfo = blk: { | 669 | } |
| 884 | if (Relocation.isArithmeticOp(code)) { | 670 | } |
| 885 | const inst = mem.bytesToValue(meta.TagPayload( | ||
| 886 | aarch64.Instruction, | ||
| 887 | aarch64.Instruction.add_subtract_immediate, | ||
| 888 | ), code); | ||
| 889 | break :blk .{ | ||
| 890 | .rd = inst.rd, | ||
| 891 | .rn = inst.rn, | ||
| 892 | .size = inst.sf, | ||
| 893 | }; | ||
| 894 | } else { | ||
| 895 | const inst = mem.bytesToValue(meta.TagPayload( | ||
| 896 | aarch64.Instruction, | ||
| 897 | aarch64.Instruction.load_store_register, | ||
| 898 | ), code); | ||
| 899 | break :blk .{ | ||
| 900 | .rd = inst.rt, | ||
| 901 | .rn = inst.rn, | ||
| 902 | .size = inst.size, | ||
| 903 | }; | ||
| 904 | } | ||
| 905 | }; | ||
| 906 | 671 | ||
| 907 | var inst = if (macho_file.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{ | 672 | pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 { |
| 908 | .load_store_register = .{ | 673 | switch (macho_file.options.cpu_arch.?) { |
| 909 | .rt = reg_info.rd, | 674 | .aarch64 => { |
| 910 | .rn = reg_info.rn, | 675 | var nreloc: u32 = 0; |
| 911 | .offset = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64), | 676 | for (self.getRelocs(macho_file)) |rel| { |
| 912 | .opc = 0b01, | 677 | nreloc += 1; |
| 913 | .op1 = 0b01, | 678 | switch (rel.type) { |
| 914 | .v = 0, | 679 | .page, .pageoff => if (rel.addend > 0) { |
| 915 | .size = reg_info.size, | 680 | nreloc += 1; |
| 916 | }, | ||
| 917 | } else aarch64.Instruction{ | ||
| 918 | .add_subtract_immediate = .{ | ||
| 919 | .rd = reg_info.rd, | ||
| 920 | .rn = reg_info.rn, | ||
| 921 | .imm12 = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic), | ||
| 922 | .sh = 0, | ||
| 923 | .s = 0, | ||
| 924 | .op = 0, | ||
| 925 | .sf = @as(u1, @truncate(reg_info.size)), | ||
| 926 | }, | 681 | }, |
| 927 | }; | 682 | else => {}, |
| 928 | mem.writeInt(u32, code, inst.toU32(), .little); | ||
| 929 | addend = null; | ||
| 930 | }, | ||
| 931 | |||
| 932 | .ARM64_RELOC_POINTER_TO_GOT => { | ||
| 933 | relocs_log.debug(" | target_addr = 0x{x}", .{target_addr}); | ||
| 934 | const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse | ||
| 935 | return error.Overflow; | ||
| 936 | mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @bitCast(result)), .little); | ||
| 937 | }, | ||
| 938 | |||
| 939 | .ARM64_RELOC_UNSIGNED => { | ||
| 940 | var ptr_addend = if (rel.r_length == 3) | ||
| 941 | mem.readInt(i64, atom_code[rel_offset..][0..8], .little) | ||
| 942 | else | ||
| 943 | mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | ||
| 944 | |||
| 945 | if (rel.r_extern == 0) { | ||
| 946 | const base_addr = if (target.sym_index >= object.source_address_lookup.len) | ||
| 947 | @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr)) | ||
| 948 | else | ||
| 949 | object.source_address_lookup[target.sym_index]; | ||
| 950 | ptr_addend -= base_addr; | ||
| 951 | } | 683 | } |
| 952 | 684 | } | |
| 953 | const result = blk: { | 685 | return nreloc; |
| 954 | if (subtractor) |sub| { | 686 | }, |
| 955 | const sym = macho_file.getSymbol(sub); | 687 | .x86_64 => return @intCast(self.getRelocs(macho_file).len), |
| 956 | break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + ptr_addend; | 688 | else => unreachable, |
| 957 | } else { | ||
| 958 | break :blk @as(i64, @intCast(target_addr)) + ptr_addend; | ||
| 959 | } | ||
| 960 | }; | ||
| 961 | relocs_log.debug(" | target_addr = 0x{x}", .{result}); | ||
| 962 | |||
| 963 | if (rel.r_length == 3) { | ||
| 964 | mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little); | ||
| 965 | } else { | ||
| 966 | mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little); | ||
| 967 | } | ||
| 968 | |||
| 969 | subtractor = null; | ||
| 970 | }, | ||
| 971 | |||
| 972 | .ARM64_RELOC_ADDEND => unreachable, | ||
| 973 | .ARM64_RELOC_SUBTRACTOR => unreachable, | ||
| 974 | } | ||
| 975 | } | 689 | } |
| 976 | } | 690 | } |
| 977 | 691 | ||
| 978 | fn resolveRelocsX86( | 692 | pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void { |
| 979 | macho_file: *MachO, | 693 | const tracy = trace(@src()); |
| 980 | atom_index: Index, | 694 | defer tracy.end(); |
| 981 | atom_code: []u8, | ||
| 982 | atom_relocs: []align(1) const macho.relocation_info, | ||
| 983 | context: RelocContext, | ||
| 984 | ) !void { | ||
| 985 | const atom = macho_file.getAtom(atom_index); | ||
| 986 | const object = macho_file.objects.items[atom.getFile().?]; | ||
| 987 | |||
| 988 | var subtractor: ?SymbolWithLoc = null; | ||
| 989 | |||
| 990 | for (atom_relocs) |rel| { | ||
| 991 | const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type)); | ||
| 992 | |||
| 993 | switch (rel_type) { | ||
| 994 | .X86_64_RELOC_SUBTRACTOR => { | ||
| 995 | assert(subtractor == null); | ||
| 996 | |||
| 997 | relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{ | ||
| 998 | @tagName(rel_type), | ||
| 999 | rel.r_address, | ||
| 1000 | rel.r_symbolnum, | ||
| 1001 | atom.getFile(), | ||
| 1002 | }); | ||
| 1003 | 695 | ||
| 1004 | subtractor = parseRelocTarget(macho_file, .{ | 696 | const cpu_arch = macho_file.options.cpu_arch.?; |
| 1005 | .object_id = atom.getFile().?, | 697 | const relocs = self.getRelocs(macho_file); |
| 1006 | .rel = rel, | 698 | const sect = macho_file.sections.items(.header)[self.out_n_sect]; |
| 1007 | .code = atom_code, | 699 | var stream = std.io.fixedBufferStream(code); |
| 1008 | .base_addr = context.base_addr, | ||
| 1009 | .base_offset = context.base_offset, | ||
| 1010 | }); | ||
| 1011 | continue; | ||
| 1012 | }, | ||
| 1013 | else => {}, | ||
| 1014 | } | ||
| 1015 | 700 | ||
| 1016 | const target = parseRelocTarget(macho_file, .{ | 701 | for (relocs) |rel| { |
| 1017 | .object_id = atom.getFile().?, | 702 | const rel_offset = rel.offset - self.off; |
| 1018 | .rel = rel, | 703 | const r_address: i32 = math.cast(i32, self.value + rel_offset - sect.addr) orelse return error.Overflow; |
| 1019 | .code = atom_code, | 704 | const r_symbolnum = r_symbolnum: { |
| 1020 | .base_addr = context.base_addr, | 705 | const r_symbolnum: u32 = switch (rel.tag) { |
| 1021 | .base_offset = context.base_offset, | 706 | .local => rel.getTargetAtom(macho_file).out_n_sect + 1, |
| 1022 | }); | 707 | .@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?, |
| 1023 | const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset)); | ||
| 1024 | |||
| 1025 | relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{ | ||
| 1026 | @tagName(rel_type), | ||
| 1027 | rel.r_address, | ||
| 1028 | target.sym_index, | ||
| 1029 | macho_file.getSymbolName(target), | ||
| 1030 | target.getFile(), | ||
| 1031 | }); | ||
| 1032 | |||
| 1033 | const source_addr = blk: { | ||
| 1034 | const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 1035 | break :blk source_sym.n_value + rel_offset; | ||
| 1036 | }; | ||
| 1037 | const target_addr = blk: { | ||
| 1038 | if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?; | ||
| 1039 | if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf()) | ||
| 1040 | break :blk macho_file.getStubsEntryAddress(target).?; | ||
| 1041 | if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf()) | ||
| 1042 | break :blk macho_file.getTlvPtrEntryAddress(target).?; | ||
| 1043 | const is_tlv = is_tlv: { | ||
| 1044 | const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 1045 | const header = macho_file.sections.items(.header)[source_sym.n_sect - 1]; | ||
| 1046 | break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES; | ||
| 1047 | }; | 708 | }; |
| 1048 | break :blk getRelocTargetAddress(macho_file, target, is_tlv); | 709 | break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow; |
| 1049 | }; | 710 | }; |
| 711 | const r_extern = rel.tag == .@"extern"; | ||
| 712 | var addend = rel.addend + rel.getRelocAddend(cpu_arch); | ||
| 713 | if (rel.tag == .local) { | ||
| 714 | const target: i64 = @intCast(rel.getTargetAddress(macho_file)); | ||
| 715 | addend += target; | ||
| 716 | } | ||
| 1050 | 717 | ||
| 1051 | relocs_log.debug(" | source_addr = 0x{x}", .{source_addr}); | 718 | try stream.seekTo(rel_offset); |
| 1052 | 719 | ||
| 1053 | switch (rel_type) { | 720 | switch (cpu_arch) { |
| 1054 | .X86_64_RELOC_BRANCH => { | 721 | .aarch64 => { |
| 1055 | const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | 722 | if (rel.type == .unsigned) switch (rel.meta.length) { |
| 1056 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | 723 | 0, 1 => unreachable, |
| 1057 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 724 | 2 => try stream.writer().writeInt(i32, @truncate(addend), .little), |
| 1058 | const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0); | 725 | 3 => try stream.writer().writeInt(i64, addend, .little), |
| 1059 | mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little); | 726 | } else if (addend > 0) { |
| 1060 | }, | 727 | buffer.appendAssumeCapacity(.{ |
| 1061 | 728 | .r_address = r_address, | |
| 1062 | .X86_64_RELOC_GOT, | 729 | .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow), |
| 1063 | .X86_64_RELOC_GOT_LOAD, | 730 | .r_pcrel = 0, |
| 1064 | => { | 731 | .r_length = 2, |
| 1065 | const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | 732 | .r_extern = 0, |
| 1066 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | 733 | .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND), |
| 1067 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 734 | }); |
| 1068 | const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0); | ||
| 1069 | mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little); | ||
| 1070 | }, | ||
| 1071 | |||
| 1072 | .X86_64_RELOC_TLV => { | ||
| 1073 | const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | ||
| 1074 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | ||
| 1075 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | ||
| 1076 | const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0); | ||
| 1077 | |||
| 1078 | if (macho_file.tlv_ptr_table.lookup.get(target) == null) { | ||
| 1079 | // We need to rewrite the opcode from movq to leaq. | ||
| 1080 | atom_code[rel_offset - 2] = 0x8d; | ||
| 1081 | } | 735 | } |
| 1082 | 736 | ||
| 1083 | mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little); | 737 | const r_type: macho.reloc_type_arm64 = switch (rel.type) { |
| 1084 | }, | 738 | .page => .ARM64_RELOC_PAGE21, |
| 1085 | 739 | .pageoff => .ARM64_RELOC_PAGEOFF12, | |
| 1086 | .X86_64_RELOC_SIGNED, | 740 | .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21, |
| 1087 | .X86_64_RELOC_SIGNED_1, | 741 | .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12, |
| 1088 | .X86_64_RELOC_SIGNED_2, | 742 | .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21, |
| 1089 | .X86_64_RELOC_SIGNED_4, | 743 | .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, |
| 1090 | => { | 744 | .branch => .ARM64_RELOC_BRANCH26, |
| 1091 | const correction: u3 = switch (rel_type) { | 745 | .got => .ARM64_RELOC_POINTER_TO_GOT, |
| 1092 | .X86_64_RELOC_SIGNED => 0, | 746 | .subtractor => .ARM64_RELOC_SUBTRACTOR, |
| 1093 | .X86_64_RELOC_SIGNED_1 => 1, | 747 | .unsigned => .ARM64_RELOC_UNSIGNED, |
| 1094 | .X86_64_RELOC_SIGNED_2 => 2, | 748 | |
| 1095 | .X86_64_RELOC_SIGNED_4 => 4, | 749 | .signed, |
| 1096 | else => unreachable, | 750 | .signed1, |
| 751 | .signed2, | ||
| 752 | .signed4, | ||
| 753 | .got_load, | ||
| 754 | .tlv, | ||
| 755 | => unreachable, | ||
| 1097 | }; | 756 | }; |
| 1098 | var addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little) + correction; | 757 | buffer.appendAssumeCapacity(.{ |
| 1099 | 758 | .r_address = r_address, | |
| 1100 | if (rel.r_extern == 0) { | 759 | .r_symbolnum = r_symbolnum, |
| 1101 | const base_addr = if (target.sym_index >= object.source_address_lookup.len) | 760 | .r_pcrel = @intFromBool(rel.meta.pcrel), |
| 1102 | @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr)) | 761 | .r_extern = @intFromBool(r_extern), |
| 1103 | else | 762 | .r_length = rel.meta.length, |
| 1104 | object.source_address_lookup[target.sym_index]; | 763 | .r_type = @intFromEnum(r_type), |
| 1105 | addend += @as(i32, @intCast(@as(i64, @intCast(context.base_addr)) + rel.r_address + 4 - | 764 | }); |
| 1106 | @as(i64, @intCast(base_addr)))); | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | ||
| 1110 | |||
| 1111 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | ||
| 1112 | |||
| 1113 | const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, correction); | ||
| 1114 | mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little); | ||
| 1115 | }, | 765 | }, |
| 1116 | 766 | .x86_64 => { | |
| 1117 | .X86_64_RELOC_UNSIGNED => { | 767 | if (rel.meta.pcrel) { |
| 1118 | var addend = if (rel.r_length == 3) | 768 | if (rel.tag == .local) { |
| 1119 | mem.readInt(i64, atom_code[rel_offset..][0..8], .little) | 769 | addend -= @as(i64, @intCast(self.value + rel_offset)); |
| 1120 | else | ||
| 1121 | mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | ||
| 1122 | |||
| 1123 | if (rel.r_extern == 0) { | ||
| 1124 | const base_addr = if (target.sym_index >= object.source_address_lookup.len) | ||
| 1125 | @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr)) | ||
| 1126 | else | ||
| 1127 | object.source_address_lookup[target.sym_index]; | ||
| 1128 | addend -= base_addr; | ||
| 1129 | } | ||
| 1130 | |||
| 1131 | const result = blk: { | ||
| 1132 | if (subtractor) |sub| { | ||
| 1133 | const sym = macho_file.getSymbol(sub); | ||
| 1134 | break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + addend; | ||
| 1135 | } else { | 770 | } else { |
| 1136 | break :blk @as(i64, @intCast(target_addr)) + addend; | 771 | addend += 4; |
| 1137 | } | 772 | } |
| 1138 | }; | 773 | } |
| 1139 | relocs_log.debug(" | target_addr = 0x{x}", .{result}); | 774 | switch (rel.meta.length) { |
| 1140 | 775 | 0, 1 => unreachable, | |
| 1141 | if (rel.r_length == 3) { | 776 | 2 => try stream.writer().writeInt(i32, @truncate(addend), .little), |
| 1142 | mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little); | 777 | 3 => try stream.writer().writeInt(i64, addend, .little), |
| 1143 | } else { | ||
| 1144 | mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little); | ||
| 1145 | } | 778 | } |
| 1146 | 779 | ||
| 1147 | subtractor = null; | 780 | const r_type: macho.reloc_type_x86_64 = switch (rel.type) { |
| 781 | .signed => .X86_64_RELOC_SIGNED, | ||
| 782 | .signed1 => .X86_64_RELOC_SIGNED_1, | ||
| 783 | .signed2 => .X86_64_RELOC_SIGNED_2, | ||
| 784 | .signed4 => .X86_64_RELOC_SIGNED_4, | ||
| 785 | .got_load => .X86_64_RELOC_GOT_LOAD, | ||
| 786 | .tlv => .X86_64_RELOC_TLV, | ||
| 787 | .branch => .X86_64_RELOC_BRANCH, | ||
| 788 | .got => .X86_64_RELOC_GOT, | ||
| 789 | .subtractor => .X86_64_RELOC_SUBTRACTOR, | ||
| 790 | .unsigned => .X86_64_RELOC_UNSIGNED, | ||
| 791 | |||
| 792 | .page, | ||
| 793 | .pageoff, | ||
| 794 | .got_load_page, | ||
| 795 | .got_load_pageoff, | ||
| 796 | .tlvp_page, | ||
| 797 | .tlvp_pageoff, | ||
| 798 | => unreachable, | ||
| 799 | }; | ||
| 800 | buffer.appendAssumeCapacity(.{ | ||
| 801 | .r_address = r_address, | ||
| 802 | .r_symbolnum = r_symbolnum, | ||
| 803 | .r_pcrel = @intFromBool(rel.meta.pcrel), | ||
| 804 | .r_extern = @intFromBool(r_extern), | ||
| 805 | .r_length = rel.meta.length, | ||
| 806 | .r_type = @intFromEnum(r_type), | ||
| 807 | }); | ||
| 1148 | }, | 808 | }, |
| 1149 | 809 | else => unreachable, | |
| 1150 | .X86_64_RELOC_SUBTRACTOR => unreachable, | ||
| 1151 | } | 810 | } |
| 1152 | } | 811 | } |
| 1153 | } | 812 | } |
| 1154 | 813 | ||
| 1155 | pub fn getAtomCode(macho_file: *MachO, atom_index: Index) []const u8 { | 814 | pub fn format( |
| 1156 | const atom = macho_file.getAtom(atom_index); | 815 | atom: Atom, |
| 1157 | assert(atom.getFile() != null); // Synthetic atom shouldn't need to inquire for code. | 816 | comptime unused_fmt_string: []const u8, |
| 1158 | const object = macho_file.objects.items[atom.getFile().?]; | 817 | options: std.fmt.FormatOptions, |
| 1159 | const source_sym = object.getSourceSymbol(atom.sym_index) orelse { | 818 | writer: anytype, |
| 1160 | // If there was no matching symbol present in the source symtab, this means | 819 | ) !void { |
| 1161 | // we are dealing with either an entire section, or part of it, but also | 820 | _ = atom; |
| 1162 | // starting at the beginning. | 821 | _ = unused_fmt_string; |
| 1163 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | 822 | _ = options; |
| 1164 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | 823 | _ = writer; |
| 1165 | const source_sect = object.getSourceSection(sect_id); | 824 | @compileError("do not format Atom directly"); |
| 1166 | assert(!source_sect.isZerofill()); | ||
| 1167 | const code = object.getSectionContents(source_sect); | ||
| 1168 | const code_len = @as(usize, @intCast(atom.size)); | ||
| 1169 | return code[0..code_len]; | ||
| 1170 | }; | ||
| 1171 | const source_sect = object.getSourceSection(source_sym.n_sect - 1); | ||
| 1172 | assert(!source_sect.isZerofill()); | ||
| 1173 | const code = object.getSectionContents(source_sect); | ||
| 1174 | const offset = @as(usize, @intCast(source_sym.n_value - source_sect.addr)); | ||
| 1175 | const code_len = @as(usize, @intCast(atom.size)); | ||
| 1176 | return code[offset..][0..code_len]; | ||
| 1177 | } | 825 | } |
| 1178 | 826 | ||
| 1179 | pub fn getAtomRelocs(macho_file: *MachO, atom_index: Index) []const macho.relocation_info { | 827 | pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 1180 | const atom = macho_file.getAtom(atom_index); | 828 | return .{ .data = .{ |
| 1181 | assert(atom.getFile() != null); // Synthetic atom shouldn't need to unique for relocs. | 829 | .atom = atom, |
| 1182 | const object = macho_file.objects.items[atom.getFile().?]; | 830 | .macho_file = macho_file, |
| 1183 | const cache = object.relocs_lookup[atom.sym_index]; | 831 | } }; |
| 1184 | |||
| 1185 | const source_sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: { | ||
| 1186 | break :blk source_sym.n_sect - 1; | ||
| 1187 | } else blk: { | ||
| 1188 | // If there was no matching symbol present in the source symtab, this means | ||
| 1189 | // we are dealing with either an entire section, or part of it, but also | ||
| 1190 | // starting at the beginning. | ||
| 1191 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | ||
| 1192 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | ||
| 1193 | break :blk sect_id; | ||
| 1194 | }; | ||
| 1195 | const source_sect = object.getSourceSection(source_sect_id); | ||
| 1196 | assert(!source_sect.isZerofill()); | ||
| 1197 | const relocs = object.getRelocs(source_sect_id); | ||
| 1198 | return relocs[cache.start..][0..cache.len]; | ||
| 1199 | } | 832 | } |
| 1200 | 833 | ||
| 1201 | pub fn relocRequiresGot(macho_file: *MachO, rel: macho.relocation_info) bool { | 834 | const FormatContext = struct { |
| 1202 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 835 | atom: Atom, |
| 1203 | switch (target.cpu.arch) { | 836 | macho_file: *MachO, |
| 1204 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | 837 | }; |
| 1205 | .ARM64_RELOC_GOT_LOAD_PAGE21, | ||
| 1206 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | ||
| 1207 | .ARM64_RELOC_POINTER_TO_GOT, | ||
| 1208 | => return true, | ||
| 1209 | else => return false, | ||
| 1210 | }, | ||
| 1211 | .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) { | ||
| 1212 | .X86_64_RELOC_GOT, | ||
| 1213 | .X86_64_RELOC_GOT_LOAD, | ||
| 1214 | => return true, | ||
| 1215 | else => return false, | ||
| 1216 | }, | ||
| 1217 | else => unreachable, | ||
| 1218 | } | ||
| 1219 | } | ||
| 1220 | 838 | ||
| 1221 | pub fn relocIsTlv(macho_file: *MachO, rel: macho.relocation_info) bool { | 839 | fn format2( |
| 1222 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 840 | ctx: FormatContext, |
| 1223 | switch (target.cpu.arch) { | 841 | comptime unused_fmt_string: []const u8, |
| 1224 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | 842 | options: std.fmt.FormatOptions, |
| 1225 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | 843 | writer: anytype, |
| 1226 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | 844 | ) !void { |
| 1227 | => return true, | 845 | _ = options; |
| 1228 | else => return false, | 846 | _ = unused_fmt_string; |
| 1229 | }, | 847 | const atom = ctx.atom; |
| 1230 | .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) { | 848 | const macho_file = ctx.macho_file; |
| 1231 | .X86_64_RELOC_TLV => return true, | 849 | try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{ |
| 1232 | else => return false, | 850 | atom.atom_index, atom.getName(macho_file), atom.value, |
| 1233 | }, | 851 | atom.out_n_sect, atom.alignment, atom.size, |
| 1234 | else => unreachable, | 852 | atom.thunk_index, |
| 853 | }); | ||
| 854 | if (!atom.flags.alive) try writer.writeAll(" : [*]"); | ||
| 855 | if (atom.unwind_records.len > 0) { | ||
| 856 | try writer.writeAll(" : unwind{ "); | ||
| 857 | for (atom.getUnwindRecords(macho_file), atom.unwind_records.pos..) |index, i| { | ||
| 858 | const rec = macho_file.getUnwindRecord(index); | ||
| 859 | try writer.print("{d}", .{index}); | ||
| 860 | if (!rec.alive) try writer.writeAll("([*])"); | ||
| 861 | if (i < atom.unwind_records.pos + atom.unwind_records.len - 1) try writer.writeAll(", "); | ||
| 862 | } | ||
| 863 | try writer.writeAll(" }"); | ||
| 1235 | } | 864 | } |
| 1236 | } | 865 | } |
| 1237 | 866 | ||
| 1238 | pub fn relocIsStub(macho_file: *MachO, rel: macho.relocation_info) bool { | 867 | pub const Index = u32; |
| 1239 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 1240 | switch (target.cpu.arch) { | ||
| 1241 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | ||
| 1242 | .ARM64_RELOC_BRANCH26 => return true, | ||
| 1243 | else => return false, | ||
| 1244 | }, | ||
| 1245 | .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) { | ||
| 1246 | .X86_64_RELOC_BRANCH => return true, | ||
| 1247 | else => return false, | ||
| 1248 | }, | ||
| 1249 | else => unreachable, | ||
| 1250 | } | ||
| 1251 | } | ||
| 1252 | 868 | ||
| 1253 | const Atom = @This(); | 869 | pub const Flags = packed struct { |
| 870 | /// Specifies whether this atom is alive or has been garbage collected. | ||
| 871 | alive: bool = true, | ||
| 1254 | 872 | ||
| 1255 | const std = @import("std"); | 873 | /// Specifies if the atom has been visited during garbage collection. |
| 1256 | const build_options = @import("build_options"); | 874 | visited: bool = false, |
| 1257 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | 875 | }; |
| 876 | |||
| 877 | pub const Loc = struct { | ||
| 878 | pos: usize = 0, | ||
| 879 | len: usize = 0, | ||
| 880 | }; | ||
| 881 | |||
| 882 | const aarch64 = @import("../aarch64.zig"); | ||
| 1258 | const assert = std.debug.assert; | 883 | const assert = std.debug.assert; |
| 1259 | const log = std.log.scoped(.link); | 884 | const bind = @import("dyld_info/bind.zig"); |
| 1260 | const relocs_log = std.log.scoped(.link_relocs); | 885 | const dis_x86_64 = @import("dis_x86_64"); |
| 1261 | const macho = std.macho; | 886 | const macho = std.macho; |
| 1262 | const math = std.math; | 887 | const math = std.math; |
| 1263 | const mem = std.mem; | 888 | const mem = std.mem; |
| 1264 | const meta = std.meta; | 889 | const log = std.log.scoped(.link); |
| 1265 | const trace = @import("../../tracy.zig").trace; | 890 | const relocs_log = std.log.scoped(.relocs); |
| 891 | const std = @import("std"); | ||
| 892 | const trace = @import("../tracy.zig").trace; | ||
| 1266 | 893 | ||
| 1267 | const Allocator = mem.Allocator; | 894 | const Allocator = mem.Allocator; |
| 1268 | const Arch = std.Target.Cpu.Arch; | 895 | const Atom = @This(); |
| 896 | const Disassembler = dis_x86_64.Disassembler; | ||
| 897 | const File = @import("file.zig").File; | ||
| 898 | const Instruction = dis_x86_64.Instruction; | ||
| 899 | const Immediate = dis_x86_64.Immediate; | ||
| 1269 | const MachO = @import("../MachO.zig"); | 900 | const MachO = @import("../MachO.zig"); |
| 1270 | pub const Relocation = @import("Relocation.zig"); | 901 | const Object = @import("Object.zig"); |
| 1271 | const SymbolWithLoc = MachO.SymbolWithLoc; | 902 | const Relocation = @import("Relocation.zig"); |
| 903 | const Symbol = @import("Symbol.zig"); | ||
| 904 | const Thunk = @import("thunks.zig").Thunk; | ||
| 905 | const UnwindInfo = @import("UnwindInfo.zig"); |
src/link/MachO/CodeSignature.zig+184-184| ... | @@ -1,175 +1,17 @@ | ... | @@ -1,175 +1,17 @@ |
| 1 | page_size: u16, | 1 | const CodeSignature = @This(); |
| 2 | code_directory: CodeDirectory, | ||
| 3 | requirements: ?Requirements = null, | ||
| 4 | entitlements: ?Entitlements = null, | ||
| 5 | signature: ?Signature = null, | ||
| 6 | |||
| 7 | pub fn init(page_size: u16) CodeSignature { | ||
| 8 | return .{ | ||
| 9 | .page_size = page_size, | ||
| 10 | .code_directory = CodeDirectory.init(page_size), | ||
| 11 | }; | ||
| 12 | } | ||
| 13 | |||
| 14 | pub fn deinit(self: *CodeSignature, allocator: Allocator) void { | ||
| 15 | self.code_directory.deinit(allocator); | ||
| 16 | if (self.requirements) |*req| { | ||
| 17 | req.deinit(allocator); | ||
| 18 | } | ||
| 19 | if (self.entitlements) |*ents| { | ||
| 20 | ents.deinit(allocator); | ||
| 21 | } | ||
| 22 | if (self.signature) |*sig| { | ||
| 23 | sig.deinit(allocator); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void { | ||
| 28 | const file = try fs.cwd().openFile(path, .{}); | ||
| 29 | defer file.close(); | ||
| 30 | const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); | ||
| 31 | self.entitlements = .{ .inner = inner }; | ||
| 32 | } | ||
| 33 | |||
| 34 | pub const WriteOpts = struct { | ||
| 35 | file: fs.File, | ||
| 36 | exec_seg_base: u64, | ||
| 37 | exec_seg_limit: u64, | ||
| 38 | file_size: u32, | ||
| 39 | output_mode: std.builtin.OutputMode, | ||
| 40 | }; | ||
| 41 | |||
| 42 | pub fn writeAdhocSignature( | ||
| 43 | self: *CodeSignature, | ||
| 44 | comp: *const Compilation, | ||
| 45 | opts: WriteOpts, | ||
| 46 | writer: anytype, | ||
| 47 | ) !void { | ||
| 48 | const gpa = comp.gpa; | ||
| 49 | |||
| 50 | var header: macho.SuperBlob = .{ | ||
| 51 | .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, | ||
| 52 | .length = @sizeOf(macho.SuperBlob), | ||
| 53 | .count = 0, | ||
| 54 | }; | ||
| 55 | |||
| 56 | var blobs = std.ArrayList(Blob).init(gpa); | ||
| 57 | defer blobs.deinit(); | ||
| 58 | |||
| 59 | self.code_directory.inner.execSegBase = opts.exec_seg_base; | ||
| 60 | self.code_directory.inner.execSegLimit = opts.exec_seg_limit; | ||
| 61 | self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0; | ||
| 62 | self.code_directory.inner.codeLimit = opts.file_size; | ||
| 63 | |||
| 64 | const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size)); | ||
| 65 | |||
| 66 | try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages); | ||
| 67 | self.code_directory.code_slots.items.len = total_pages; | ||
| 68 | self.code_directory.inner.nCodeSlots = total_pages; | ||
| 69 | |||
| 70 | // Calculate hash for each page (in file) and write it to the buffer | ||
| 71 | var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool }; | ||
| 72 | try hasher.hash(opts.file, self.code_directory.code_slots.items, .{ | ||
| 73 | .chunk_size = self.page_size, | ||
| 74 | .max_file_size = opts.file_size, | ||
| 75 | }); | ||
| 76 | |||
| 77 | try blobs.append(.{ .code_directory = &self.code_directory }); | ||
| 78 | header.length += @sizeOf(macho.BlobIndex); | ||
| 79 | header.count += 1; | ||
| 80 | |||
| 81 | var hash: [hash_size]u8 = undefined; | ||
| 82 | |||
| 83 | if (self.requirements) |*req| { | ||
| 84 | var buf = std.ArrayList(u8).init(gpa); | ||
| 85 | defer buf.deinit(); | ||
| 86 | try req.write(buf.writer()); | ||
| 87 | Sha256.hash(buf.items, &hash, .{}); | ||
| 88 | self.code_directory.addSpecialHash(req.slotType(), hash); | ||
| 89 | |||
| 90 | try blobs.append(.{ .requirements = req }); | ||
| 91 | header.count += 1; | ||
| 92 | header.length += @sizeOf(macho.BlobIndex) + req.size(); | ||
| 93 | } | ||
| 94 | |||
| 95 | if (self.entitlements) |*ents| { | ||
| 96 | var buf = std.ArrayList(u8).init(gpa); | ||
| 97 | defer buf.deinit(); | ||
| 98 | try ents.write(buf.writer()); | ||
| 99 | Sha256.hash(buf.items, &hash, .{}); | ||
| 100 | self.code_directory.addSpecialHash(ents.slotType(), hash); | ||
| 101 | |||
| 102 | try blobs.append(.{ .entitlements = ents }); | ||
| 103 | header.count += 1; | ||
| 104 | header.length += @sizeOf(macho.BlobIndex) + ents.size(); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (self.signature) |*sig| { | ||
| 108 | try blobs.append(.{ .signature = sig }); | ||
| 109 | header.count += 1; | ||
| 110 | header.length += @sizeOf(macho.BlobIndex) + sig.size(); | ||
| 111 | } | ||
| 112 | |||
| 113 | self.code_directory.inner.hashOffset = | ||
| 114 | @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size)); | ||
| 115 | self.code_directory.inner.length = self.code_directory.size(); | ||
| 116 | header.length += self.code_directory.size(); | ||
| 117 | |||
| 118 | try writer.writeInt(u32, header.magic, .big); | ||
| 119 | try writer.writeInt(u32, header.length, .big); | ||
| 120 | try writer.writeInt(u32, header.count, .big); | ||
| 121 | |||
| 122 | var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len)); | ||
| 123 | for (blobs.items) |blob| { | ||
| 124 | try writer.writeInt(u32, blob.slotType(), .big); | ||
| 125 | try writer.writeInt(u32, offset, .big); | ||
| 126 | offset += blob.size(); | ||
| 127 | } | ||
| 128 | |||
| 129 | for (blobs.items) |blob| { | ||
| 130 | try blob.write(writer); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | pub fn size(self: CodeSignature) u32 { | ||
| 135 | var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); | ||
| 136 | if (self.requirements) |req| { | ||
| 137 | ssize += @sizeOf(macho.BlobIndex) + req.size(); | ||
| 138 | } | ||
| 139 | if (self.entitlements) |ent| { | ||
| 140 | ssize += @sizeOf(macho.BlobIndex) + ent.size(); | ||
| 141 | } | ||
| 142 | if (self.signature) |sig| { | ||
| 143 | ssize += @sizeOf(macho.BlobIndex) + sig.size(); | ||
| 144 | } | ||
| 145 | return ssize; | ||
| 146 | } | ||
| 147 | |||
| 148 | pub fn estimateSize(self: CodeSignature, file_size: u64) u32 { | ||
| 149 | var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); | ||
| 150 | // Approx code slots | ||
| 151 | const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size; | ||
| 152 | ssize += total_pages * hash_size; | ||
| 153 | var n_special_slots: u32 = 0; | ||
| 154 | if (self.requirements) |req| { | ||
| 155 | ssize += @sizeOf(macho.BlobIndex) + req.size(); | ||
| 156 | n_special_slots = @max(n_special_slots, req.slotType()); | ||
| 157 | } | ||
| 158 | if (self.entitlements) |ent| { | ||
| 159 | ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size; | ||
| 160 | n_special_slots = @max(n_special_slots, ent.slotType()); | ||
| 161 | } | ||
| 162 | if (self.signature) |sig| { | ||
| 163 | ssize += @sizeOf(macho.BlobIndex) + sig.size(); | ||
| 164 | } | ||
| 165 | ssize += n_special_slots * hash_size; | ||
| 166 | return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64)))); | ||
| 167 | } | ||
| 168 | 2 | ||
| 169 | pub fn clear(self: *CodeSignature, allocator: Allocator) void { | 3 | const std = @import("std"); |
| 170 | self.code_directory.deinit(allocator); | 4 | const assert = std.debug.assert; |
| 171 | self.code_directory = CodeDirectory.init(self.page_size); | 5 | const fs = std.fs; |
| 172 | } | 6 | const log = std.log.scoped(.link); |
| 7 | const macho = std.macho; | ||
| 8 | const mem = std.mem; | ||
| 9 | const testing = std.testing; | ||
| 10 | const Allocator = mem.Allocator; | ||
| 11 | const Hasher = @import("hasher.zig").ParallelHasher; | ||
| 12 | const MachO = @import("../MachO.zig"); | ||
| 13 | const Sha256 = std.crypto.hash.sha2.Sha256; | ||
| 14 | const Zld = @import("../Zld.zig"); | ||
| 173 | 15 | ||
| 174 | const hash_size = Sha256.digest_length; | 16 | const hash_size = Sha256.digest_length; |
| 175 | 17 | ||
| ... | @@ -257,7 +99,7 @@ const CodeDirectory = struct { | ... | @@ -257,7 +99,7 @@ const CodeDirectory = struct { |
| 257 | fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { | 99 | fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { |
| 258 | assert(index > 0); | 100 | assert(index > 0); |
| 259 | self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index); | 101 | self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index); |
| 260 | self.special_slots[index - 1] = hash; | 102 | @memcpy(&self.special_slots[index - 1], &hash); |
| 261 | } | 103 | } |
| 262 | 104 | ||
| 263 | fn slotType(self: CodeDirectory) u32 { | 105 | fn slotType(self: CodeDirectory) u32 { |
| ... | @@ -376,17 +218,175 @@ const Signature = struct { | ... | @@ -376,17 +218,175 @@ const Signature = struct { |
| 376 | } | 218 | } |
| 377 | }; | 219 | }; |
| 378 | 220 | ||
| 379 | const CodeSignature = @This(); | 221 | page_size: u16, |
| 222 | code_directory: CodeDirectory, | ||
| 223 | requirements: ?Requirements = null, | ||
| 224 | entitlements: ?Entitlements = null, | ||
| 225 | signature: ?Signature = null, | ||
| 380 | 226 | ||
| 381 | const std = @import("std"); | 227 | pub fn init(page_size: u16) CodeSignature { |
| 382 | const assert = std.debug.assert; | 228 | return .{ |
| 383 | const fs = std.fs; | 229 | .page_size = page_size, |
| 384 | const log = std.log.scoped(.link); | 230 | .code_directory = CodeDirectory.init(page_size), |
| 385 | const macho = std.macho; | 231 | }; |
| 386 | const mem = std.mem; | 232 | } |
| 387 | const testing = std.testing; | ||
| 388 | 233 | ||
| 389 | const Allocator = mem.Allocator; | 234 | pub fn deinit(self: *CodeSignature, allocator: Allocator) void { |
| 390 | const Compilation = @import("../../Compilation.zig"); | 235 | self.code_directory.deinit(allocator); |
| 391 | const Hasher = @import("hasher.zig").ParallelHasher; | 236 | if (self.requirements) |*req| { |
| 392 | const Sha256 = std.crypto.hash.sha2.Sha256; | 237 | req.deinit(allocator); |
| 238 | } | ||
| 239 | if (self.entitlements) |*ents| { | ||
| 240 | ents.deinit(allocator); | ||
| 241 | } | ||
| 242 | if (self.signature) |*sig| { | ||
| 243 | sig.deinit(allocator); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void { | ||
| 248 | const file = try fs.cwd().openFile(path, .{}); | ||
| 249 | defer file.close(); | ||
| 250 | const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); | ||
| 251 | self.entitlements = .{ .inner = inner }; | ||
| 252 | } | ||
| 253 | |||
| 254 | pub const WriteOpts = struct { | ||
| 255 | file: fs.File, | ||
| 256 | exec_seg_base: u64, | ||
| 257 | exec_seg_limit: u64, | ||
| 258 | file_size: u32, | ||
| 259 | dylib: bool, | ||
| 260 | }; | ||
| 261 | |||
| 262 | pub fn writeAdhocSignature( | ||
| 263 | self: *CodeSignature, | ||
| 264 | macho_file: *MachO, | ||
| 265 | opts: WriteOpts, | ||
| 266 | writer: anytype, | ||
| 267 | ) !void { | ||
| 268 | const allocator = macho_file.base.allocator; | ||
| 269 | |||
| 270 | var header: macho.SuperBlob = .{ | ||
| 271 | .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, | ||
| 272 | .length = @sizeOf(macho.SuperBlob), | ||
| 273 | .count = 0, | ||
| 274 | }; | ||
| 275 | |||
| 276 | var blobs = std.ArrayList(Blob).init(allocator); | ||
| 277 | defer blobs.deinit(); | ||
| 278 | |||
| 279 | self.code_directory.inner.execSegBase = opts.exec_seg_base; | ||
| 280 | self.code_directory.inner.execSegLimit = opts.exec_seg_limit; | ||
| 281 | self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0; | ||
| 282 | self.code_directory.inner.codeLimit = opts.file_size; | ||
| 283 | |||
| 284 | const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size)); | ||
| 285 | |||
| 286 | try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages); | ||
| 287 | self.code_directory.code_slots.items.len = total_pages; | ||
| 288 | self.code_directory.inner.nCodeSlots = total_pages; | ||
| 289 | |||
| 290 | // Calculate hash for each page (in file) and write it to the buffer | ||
| 291 | var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.thread_pool }; | ||
| 292 | try hasher.hash(opts.file, self.code_directory.code_slots.items, .{ | ||
| 293 | .chunk_size = self.page_size, | ||
| 294 | .max_file_size = opts.file_size, | ||
| 295 | }); | ||
| 296 | |||
| 297 | try blobs.append(.{ .code_directory = &self.code_directory }); | ||
| 298 | header.length += @sizeOf(macho.BlobIndex); | ||
| 299 | header.count += 1; | ||
| 300 | |||
| 301 | var hash: [hash_size]u8 = undefined; | ||
| 302 | |||
| 303 | if (self.requirements) |*req| { | ||
| 304 | var buf = std.ArrayList(u8).init(allocator); | ||
| 305 | defer buf.deinit(); | ||
| 306 | try req.write(buf.writer()); | ||
| 307 | Sha256.hash(buf.items, &hash, .{}); | ||
| 308 | self.code_directory.addSpecialHash(req.slotType(), hash); | ||
| 309 | |||
| 310 | try blobs.append(.{ .requirements = req }); | ||
| 311 | header.count += 1; | ||
| 312 | header.length += @sizeOf(macho.BlobIndex) + req.size(); | ||
| 313 | } | ||
| 314 | |||
| 315 | if (self.entitlements) |*ents| { | ||
| 316 | var buf = std.ArrayList(u8).init(allocator); | ||
| 317 | defer buf.deinit(); | ||
| 318 | try ents.write(buf.writer()); | ||
| 319 | Sha256.hash(buf.items, &hash, .{}); | ||
| 320 | self.code_directory.addSpecialHash(ents.slotType(), hash); | ||
| 321 | |||
| 322 | try blobs.append(.{ .entitlements = ents }); | ||
| 323 | header.count += 1; | ||
| 324 | header.length += @sizeOf(macho.BlobIndex) + ents.size(); | ||
| 325 | } | ||
| 326 | |||
| 327 | if (self.signature) |*sig| { | ||
| 328 | try blobs.append(.{ .signature = sig }); | ||
| 329 | header.count += 1; | ||
| 330 | header.length += @sizeOf(macho.BlobIndex) + sig.size(); | ||
| 331 | } | ||
| 332 | |||
| 333 | self.code_directory.inner.hashOffset = | ||
| 334 | @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size)); | ||
| 335 | self.code_directory.inner.length = self.code_directory.size(); | ||
| 336 | header.length += self.code_directory.size(); | ||
| 337 | |||
| 338 | try writer.writeInt(u32, header.magic, .big); | ||
| 339 | try writer.writeInt(u32, header.length, .big); | ||
| 340 | try writer.writeInt(u32, header.count, .big); | ||
| 341 | |||
| 342 | var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len)); | ||
| 343 | for (blobs.items) |blob| { | ||
| 344 | try writer.writeInt(u32, blob.slotType(), .big); | ||
| 345 | try writer.writeInt(u32, offset, .big); | ||
| 346 | offset += blob.size(); | ||
| 347 | } | ||
| 348 | |||
| 349 | for (blobs.items) |blob| { | ||
| 350 | try blob.write(writer); | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | pub fn size(self: CodeSignature) u32 { | ||
| 355 | var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); | ||
| 356 | if (self.requirements) |req| { | ||
| 357 | ssize += @sizeOf(macho.BlobIndex) + req.size(); | ||
| 358 | } | ||
| 359 | if (self.entitlements) |ent| { | ||
| 360 | ssize += @sizeOf(macho.BlobIndex) + ent.size(); | ||
| 361 | } | ||
| 362 | if (self.signature) |sig| { | ||
| 363 | ssize += @sizeOf(macho.BlobIndex) + sig.size(); | ||
| 364 | } | ||
| 365 | return ssize; | ||
| 366 | } | ||
| 367 | |||
| 368 | pub fn estimateSize(self: CodeSignature, file_size: u64) u32 { | ||
| 369 | var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); | ||
| 370 | // Approx code slots | ||
| 371 | const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size; | ||
| 372 | ssize += total_pages * hash_size; | ||
| 373 | var n_special_slots: u32 = 0; | ||
| 374 | if (self.requirements) |req| { | ||
| 375 | ssize += @sizeOf(macho.BlobIndex) + req.size(); | ||
| 376 | n_special_slots = @max(n_special_slots, req.slotType()); | ||
| 377 | } | ||
| 378 | if (self.entitlements) |ent| { | ||
| 379 | ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size; | ||
| 380 | n_special_slots = @max(n_special_slots, ent.slotType()); | ||
| 381 | } | ||
| 382 | if (self.signature) |sig| { | ||
| 383 | ssize += @sizeOf(macho.BlobIndex) + sig.size(); | ||
| 384 | } | ||
| 385 | ssize += n_special_slots * hash_size; | ||
| 386 | return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64)))); | ||
| 387 | } | ||
| 388 | |||
| 389 | pub fn clear(self: *CodeSignature, allocator: Allocator) void { | ||
| 390 | self.code_directory.deinit(allocator); | ||
| 391 | self.code_directory = CodeDirectory.init(self.page_size); | ||
| 392 | } |
src/link/MachO/DwarfInfo.zig+351-387| ... | @@ -2,377 +2,175 @@ debug_info: []const u8, | ... | @@ -2,377 +2,175 @@ debug_info: []const u8, |
| 2 | debug_abbrev: []const u8, | 2 | debug_abbrev: []const u8, |
| 3 | debug_str: []const u8, | 3 | debug_str: []const u8, |
| 4 | 4 | ||
| 5 | pub fn getCompileUnitIterator(self: DwarfInfo) CompileUnitIterator { | 5 | /// Abbreviation table indexed by offset in the .debug_abbrev bytestream |
| 6 | return .{ .ctx = self }; | 6 | abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{}, |
| 7 | /// List of compile units as they appear in the .debug_info bytestream | ||
| 8 | compile_units: std.ArrayListUnmanaged(CompileUnit) = .{}, | ||
| 9 | |||
| 10 | pub fn init(dw: *DwarfInfo, allocator: Allocator) !void { | ||
| 11 | try dw.parseAbbrevTables(allocator); | ||
| 12 | try dw.parseCompileUnits(allocator); | ||
| 7 | } | 13 | } |
| 8 | 14 | ||
| 9 | const CompileUnitIterator = struct { | 15 | pub fn deinit(dw: *DwarfInfo, allocator: Allocator) void { |
| 10 | ctx: DwarfInfo, | 16 | dw.abbrev_tables.deinit(allocator); |
| 11 | pos: usize = 0, | 17 | for (dw.compile_units.items) |*cu| { |
| 12 | 18 | cu.deinit(allocator); | |
| 13 | pub fn next(self: *CompileUnitIterator) !?CompileUnit { | ||
| 14 | if (self.pos >= self.ctx.debug_info.len) return null; | ||
| 15 | |||
| 16 | var stream = std.io.fixedBufferStream(self.ctx.debug_info[self.pos..]); | ||
| 17 | var creader = std.io.countingReader(stream.reader()); | ||
| 18 | const reader = creader.reader(); | ||
| 19 | |||
| 20 | const cuh = try CompileUnit.Header.read(reader); | ||
| 21 | const total_length = cuh.length + @as(u64, if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32)); | ||
| 22 | const offset = math.cast(usize, creader.bytes_read) orelse return error.Overflow; | ||
| 23 | |||
| 24 | const cu = CompileUnit{ | ||
| 25 | .cuh = cuh, | ||
| 26 | .debug_info_off = self.pos + offset, | ||
| 27 | }; | ||
| 28 | |||
| 29 | self.pos += (math.cast(usize, total_length) orelse return error.Overflow); | ||
| 30 | |||
| 31 | return cu; | ||
| 32 | } | 19 | } |
| 33 | }; | 20 | dw.compile_units.deinit(allocator); |
| 34 | 21 | } | |
| 35 | pub fn genSubprogramLookupByName( | ||
| 36 | self: DwarfInfo, | ||
| 37 | compile_unit: CompileUnit, | ||
| 38 | abbrev_lookup: AbbrevLookupTable, | ||
| 39 | lookup: *SubprogramLookupByName, | ||
| 40 | ) !void { | ||
| 41 | var abbrev_it = compile_unit.getAbbrevEntryIterator(self); | ||
| 42 | while (try abbrev_it.next(abbrev_lookup)) |entry| switch (entry.tag) { | ||
| 43 | dwarf.TAG.subprogram => { | ||
| 44 | var attr_it = entry.getAttributeIterator(self, compile_unit.cuh); | ||
| 45 | |||
| 46 | var name: ?[]const u8 = null; | ||
| 47 | var low_pc: ?u64 = null; | ||
| 48 | var high_pc: ?u64 = null; | ||
| 49 | |||
| 50 | while (try attr_it.next()) |attr| switch (attr.name) { | ||
| 51 | dwarf.AT.name => if (attr.getString(self, compile_unit.cuh)) |str| { | ||
| 52 | name = str; | ||
| 53 | }, | ||
| 54 | dwarf.AT.low_pc => { | ||
| 55 | if (attr.getAddr(self, compile_unit.cuh)) |addr| { | ||
| 56 | low_pc = addr; | ||
| 57 | } | ||
| 58 | if (try attr.getConstant(self)) |constant| { | ||
| 59 | low_pc = @as(u64, @intCast(constant)); | ||
| 60 | } | ||
| 61 | }, | ||
| 62 | dwarf.AT.high_pc => { | ||
| 63 | if (attr.getAddr(self, compile_unit.cuh)) |addr| { | ||
| 64 | high_pc = addr; | ||
| 65 | } | ||
| 66 | if (try attr.getConstant(self)) |constant| { | ||
| 67 | high_pc = @as(u64, @intCast(constant)); | ||
| 68 | } | ||
| 69 | }, | ||
| 70 | else => {}, | ||
| 71 | }; | ||
| 72 | |||
| 73 | if (name == null or low_pc == null or high_pc == null) continue; | ||
| 74 | 22 | ||
| 75 | try lookup.putNoClobber(name.?, .{ .addr = low_pc.?, .size = high_pc.? }); | 23 | fn getString(dw: DwarfInfo, off: u64) [:0]const u8 { |
| 76 | }, | 24 | assert(off < dw.debug_str.len); |
| 77 | else => {}, | 25 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(dw.debug_str.ptr + off)), 0); |
| 78 | }; | ||
| 79 | } | 26 | } |
| 80 | 27 | ||
| 81 | pub fn genAbbrevLookupByKind(self: DwarfInfo, off: usize, lookup: *AbbrevLookupTable) !void { | 28 | fn parseAbbrevTables(dw: *DwarfInfo, allocator: Allocator) !void { |
| 82 | const data = self.debug_abbrev[off..]; | 29 | const tracy = trace(@src()); |
| 83 | var stream = std.io.fixedBufferStream(data); | 30 | defer tracy.end(); |
| 31 | |||
| 32 | const debug_abbrev = dw.debug_abbrev; | ||
| 33 | var stream = std.io.fixedBufferStream(debug_abbrev); | ||
| 84 | var creader = std.io.countingReader(stream.reader()); | 34 | var creader = std.io.countingReader(stream.reader()); |
| 85 | const reader = creader.reader(); | 35 | const reader = creader.reader(); |
| 86 | 36 | ||
| 87 | while (true) { | 37 | while (true) { |
| 88 | const kind = try leb.readULEB128(u64, reader); | 38 | if (creader.bytes_read >= debug_abbrev.len) break; |
| 89 | 39 | ||
| 90 | if (kind == 0) break; | 40 | try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1); |
| 91 | 41 | const table_gop = dw.abbrev_tables.getOrPutAssumeCapacity(@intCast(creader.bytes_read)); | |
| 92 | const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow; | 42 | assert(!table_gop.found_existing); |
| 93 | _ = try leb.readULEB128(u64, reader); // TAG | 43 | const table = table_gop.value_ptr; |
| 94 | _ = try reader.readByte(); // CHILDREN | 44 | table.* = .{}; |
| 95 | 45 | ||
| 96 | while (true) { | 46 | while (true) { |
| 97 | const name = try leb.readULEB128(u64, reader); | 47 | const code = try leb.readULEB128(Code, reader); |
| 98 | const form = try leb.readULEB128(u64, reader); | 48 | if (code == 0) break; |
| 99 | 49 | ||
| 100 | if (name == 0 and form == 0) break; | 50 | try table.decls.ensureUnusedCapacity(allocator, 1); |
| 101 | } | 51 | const decl_gop = table.decls.getOrPutAssumeCapacity(code); |
| 102 | 52 | assert(!decl_gop.found_existing); | |
| 103 | const next_pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow; | 53 | const decl = decl_gop.value_ptr; |
| 104 | 54 | decl.* = .{ | |
| 105 | try lookup.putNoClobber(kind, .{ | 55 | .code = code, |
| 106 | .pos = pos, | 56 | .tag = undefined, |
| 107 | .len = next_pos - pos - 2, | 57 | .children = false, |
| 108 | }); | 58 | }; |
| 109 | } | 59 | decl.tag = try leb.readULEB128(Tag, reader); |
| 110 | } | 60 | decl.children = (try reader.readByte()) > 0; |
| 111 | 61 | ||
| 112 | pub const CompileUnit = struct { | 62 | while (true) { |
| 113 | cuh: Header, | 63 | const at = try leb.readULEB128(At, reader); |
| 114 | debug_info_off: usize, | 64 | const form = try leb.readULEB128(Form, reader); |
| 115 | 65 | if (at == 0 and form == 0) break; | |
| 116 | pub const Header = struct { | 66 | |
| 117 | is_64bit: bool, | 67 | try decl.attrs.ensureUnusedCapacity(allocator, 1); |
| 118 | length: u64, | 68 | const attr_gop = decl.attrs.getOrPutAssumeCapacity(at); |
| 119 | version: u16, | 69 | assert(!attr_gop.found_existing); |
| 120 | debug_abbrev_offset: u64, | 70 | const attr = attr_gop.value_ptr; |
| 121 | address_size: u8, | 71 | attr.* = .{ |
| 122 | 72 | .at = at, | |
| 123 | fn read(reader: anytype) !Header { | 73 | .form = form, |
| 124 | var length: u64 = try reader.readInt(u32, .little); | 74 | }; |
| 125 | |||
| 126 | const is_64bit = length == 0xffffffff; | ||
| 127 | if (is_64bit) { | ||
| 128 | length = try reader.readInt(u64, .little); | ||
| 129 | } | 75 | } |
| 130 | |||
| 131 | const version = try reader.readInt(u16, .little); | ||
| 132 | const debug_abbrev_offset = if (is_64bit) | ||
| 133 | try reader.readInt(u64, .little) | ||
| 134 | else | ||
| 135 | try reader.readInt(u32, .little); | ||
| 136 | const address_size = try reader.readInt(u8, .little); | ||
| 137 | |||
| 138 | return Header{ | ||
| 139 | .is_64bit = is_64bit, | ||
| 140 | .length = length, | ||
| 141 | .version = version, | ||
| 142 | .debug_abbrev_offset = debug_abbrev_offset, | ||
| 143 | .address_size = address_size, | ||
| 144 | }; | ||
| 145 | } | 76 | } |
| 146 | }; | ||
| 147 | |||
| 148 | inline fn getDebugInfo(self: CompileUnit, ctx: DwarfInfo) []const u8 { | ||
| 149 | return ctx.debug_info[self.debug_info_off..][0..self.cuh.length]; | ||
| 150 | } | ||
| 151 | |||
| 152 | pub fn getAbbrevEntryIterator(self: CompileUnit, ctx: DwarfInfo) AbbrevEntryIterator { | ||
| 153 | return .{ .cu = self, .ctx = ctx }; | ||
| 154 | } | 77 | } |
| 155 | }; | 78 | } |
| 156 | |||
| 157 | const AbbrevEntryIterator = struct { | ||
| 158 | cu: CompileUnit, | ||
| 159 | ctx: DwarfInfo, | ||
| 160 | pos: usize = 0, | ||
| 161 | |||
| 162 | pub fn next(self: *AbbrevEntryIterator, lookup: AbbrevLookupTable) !?AbbrevEntry { | ||
| 163 | if (self.pos + self.cu.debug_info_off >= self.ctx.debug_info.len) return null; | ||
| 164 | |||
| 165 | const debug_info = self.ctx.debug_info[self.pos + self.cu.debug_info_off ..]; | ||
| 166 | var stream = std.io.fixedBufferStream(debug_info); | ||
| 167 | var creader = std.io.countingReader(stream.reader()); | ||
| 168 | const reader = creader.reader(); | ||
| 169 | 79 | ||
| 170 | const kind = try leb.readULEB128(u64, reader); | 80 | fn parseCompileUnits(dw: *DwarfInfo, allocator: Allocator) !void { |
| 171 | self.pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow); | 81 | const tracy = trace(@src()); |
| 82 | defer tracy.end(); | ||
| 172 | 83 | ||
| 173 | if (kind == 0) { | 84 | const debug_info = dw.debug_info; |
| 174 | return AbbrevEntry.null(); | 85 | var stream = std.io.fixedBufferStream(debug_info); |
| 175 | } | 86 | var creader = std.io.countingReader(stream.reader()); |
| 87 | const reader = creader.reader(); | ||
| 176 | 88 | ||
| 177 | const abbrev_pos = lookup.get(kind) orelse return null; | 89 | while (true) { |
| 178 | const len = try findAbbrevEntrySize( | 90 | if (creader.bytes_read == debug_info.len) break; |
| 179 | self.ctx, | ||
| 180 | abbrev_pos.pos, | ||
| 181 | abbrev_pos.len, | ||
| 182 | self.pos + self.cu.debug_info_off, | ||
| 183 | self.cu.cuh, | ||
| 184 | ); | ||
| 185 | const entry = try getAbbrevEntry( | ||
| 186 | self.ctx, | ||
| 187 | abbrev_pos.pos, | ||
| 188 | abbrev_pos.len, | ||
| 189 | self.pos + self.cu.debug_info_off, | ||
| 190 | len, | ||
| 191 | ); | ||
| 192 | |||
| 193 | self.pos += len; | ||
| 194 | |||
| 195 | return entry; | ||
| 196 | } | ||
| 197 | }; | ||
| 198 | 91 | ||
| 199 | pub const AbbrevEntry = struct { | 92 | const cu = try dw.compile_units.addOne(allocator); |
| 200 | tag: u64, | 93 | cu.* = .{ |
| 201 | children: u8, | 94 | .header = undefined, |
| 202 | debug_abbrev_off: usize, | 95 | .pos = creader.bytes_read, |
| 203 | debug_abbrev_len: usize, | ||
| 204 | debug_info_off: usize, | ||
| 205 | debug_info_len: usize, | ||
| 206 | |||
| 207 | fn @"null"() AbbrevEntry { | ||
| 208 | return .{ | ||
| 209 | .tag = 0, | ||
| 210 | .children = dwarf.CHILDREN.no, | ||
| 211 | .debug_abbrev_off = 0, | ||
| 212 | .debug_abbrev_len = 0, | ||
| 213 | .debug_info_off = 0, | ||
| 214 | .debug_info_len = 0, | ||
| 215 | }; | 96 | }; |
| 216 | } | ||
| 217 | |||
| 218 | pub fn hasChildren(self: AbbrevEntry) bool { | ||
| 219 | return self.children == dwarf.CHILDREN.yes; | ||
| 220 | } | ||
| 221 | |||
| 222 | inline fn getDebugInfo(self: AbbrevEntry, ctx: DwarfInfo) []const u8 { | ||
| 223 | return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len]; | ||
| 224 | } | ||
| 225 | |||
| 226 | inline fn getDebugAbbrev(self: AbbrevEntry, ctx: DwarfInfo) []const u8 { | ||
| 227 | return ctx.debug_abbrev[self.debug_abbrev_off..][0..self.debug_abbrev_len]; | ||
| 228 | } | ||
| 229 | |||
| 230 | pub fn getAttributeIterator(self: AbbrevEntry, ctx: DwarfInfo, cuh: CompileUnit.Header) AttributeIterator { | ||
| 231 | return .{ .entry = self, .ctx = ctx, .cuh = cuh }; | ||
| 232 | } | ||
| 233 | }; | ||
| 234 | |||
| 235 | pub const Attribute = struct { | ||
| 236 | name: u64, | ||
| 237 | form: u64, | ||
| 238 | debug_info_off: usize, | ||
| 239 | debug_info_len: usize, | ||
| 240 | |||
| 241 | inline fn getDebugInfo(self: Attribute, ctx: DwarfInfo) []const u8 { | ||
| 242 | return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len]; | ||
| 243 | } | ||
| 244 | |||
| 245 | pub fn getString(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?[]const u8 { | ||
| 246 | const debug_info = self.getDebugInfo(ctx); | ||
| 247 | 97 | ||
| 248 | switch (self.form) { | 98 | var length: u64 = try reader.readInt(u32, .little); |
| 249 | dwarf.FORM.string => { | 99 | const is_64bit = length == 0xffffffff; |
| 250 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(debug_info.ptr)), 0); | 100 | if (is_64bit) { |
| 251 | }, | 101 | length = try reader.readInt(u64, .little); |
| 252 | dwarf.FORM.strp => { | ||
| 253 | const off = if (cuh.is_64bit) | ||
| 254 | mem.readInt(u64, debug_info[0..8], .little) | ||
| 255 | else | ||
| 256 | mem.readInt(u32, debug_info[0..4], .little); | ||
| 257 | return ctx.getString(off); | ||
| 258 | }, | ||
| 259 | else => return null, | ||
| 260 | } | 102 | } |
| 103 | cu.header.format = if (is_64bit) .dwarf64 else .dwarf32; | ||
| 104 | cu.header.length = length; | ||
| 105 | cu.header.version = try reader.readInt(u16, .little); | ||
| 106 | cu.header.debug_abbrev_offset = try readOffset(cu.header.format, reader); | ||
| 107 | cu.header.address_size = try reader.readInt(u8, .little); | ||
| 108 | |||
| 109 | const table = dw.abbrev_tables.get(cu.header.debug_abbrev_offset).?; | ||
| 110 | try dw.parseDie(allocator, cu, table, null, &creader); | ||
| 261 | } | 111 | } |
| 112 | } | ||
| 262 | 113 | ||
| 263 | pub fn getConstant(self: Attribute, ctx: DwarfInfo) !?i128 { | 114 | fn parseDie( |
| 264 | const debug_info = self.getDebugInfo(ctx); | 115 | dw: *DwarfInfo, |
| 265 | var stream = std.io.fixedBufferStream(debug_info); | 116 | allocator: Allocator, |
| 266 | const reader = stream.reader(); | 117 | cu: *CompileUnit, |
| 267 | 118 | table: AbbrevTable, | |
| 268 | return switch (self.form) { | 119 | parent: ?u32, |
| 269 | dwarf.FORM.data1 => debug_info[0], | 120 | creader: anytype, |
| 270 | dwarf.FORM.data2 => mem.readInt(u16, debug_info[0..2], .little), | 121 | ) anyerror!void { |
| 271 | dwarf.FORM.data4 => mem.readInt(u32, debug_info[0..4], .little), | 122 | const tracy = trace(@src()); |
| 272 | dwarf.FORM.data8 => mem.readInt(u64, debug_info[0..8], .little), | 123 | defer tracy.end(); |
| 273 | dwarf.FORM.udata => try leb.readULEB128(u64, reader), | 124 | |
| 274 | dwarf.FORM.sdata => try leb.readILEB128(i64, reader), | 125 | while (creader.bytes_read < cu.nextCompileUnitOffset()) { |
| 275 | else => null, | 126 | const die = try cu.addDie(allocator); |
| 276 | }; | 127 | cu.diePtr(die).* = .{ .code = undefined }; |
| 277 | } | 128 | if (parent) |p| { |
| 278 | 129 | try cu.diePtr(p).children.append(allocator, die); | |
| 279 | pub fn getAddr(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?u64 { | 130 | } else { |
| 280 | if (self.form != dwarf.FORM.addr) return null; | 131 | try cu.children.append(allocator, die); |
| 281 | const debug_info = self.getDebugInfo(ctx); | 132 | } |
| 282 | return switch (cuh.address_size) { | ||
| 283 | 1 => debug_info[0], | ||
| 284 | 2 => mem.readInt(u16, debug_info[0..2], .little), | ||
| 285 | 4 => mem.readInt(u32, debug_info[0..4], .little), | ||
| 286 | 8 => mem.readInt(u64, debug_info[0..8], .little), | ||
| 287 | else => unreachable, | ||
| 288 | }; | ||
| 289 | } | ||
| 290 | }; | ||
| 291 | |||
| 292 | const AttributeIterator = struct { | ||
| 293 | entry: AbbrevEntry, | ||
| 294 | ctx: DwarfInfo, | ||
| 295 | cuh: CompileUnit.Header, | ||
| 296 | debug_abbrev_pos: usize = 0, | ||
| 297 | debug_info_pos: usize = 0, | ||
| 298 | 133 | ||
| 299 | pub fn next(self: *AttributeIterator) !?Attribute { | 134 | const code = try leb.readULEB128(Code, creader.reader()); |
| 300 | const debug_abbrev = self.entry.getDebugAbbrev(self.ctx); | 135 | cu.diePtr(die).code = code; |
| 301 | if (self.debug_abbrev_pos >= debug_abbrev.len) return null; | ||
| 302 | 136 | ||
| 303 | var stream = std.io.fixedBufferStream(debug_abbrev[self.debug_abbrev_pos..]); | 137 | if (code == 0) { |
| 304 | var creader = std.io.countingReader(stream.reader()); | 138 | if (parent == null) continue; |
| 305 | const reader = creader.reader(); | 139 | return; // Close scope |
| 140 | } | ||
| 306 | 141 | ||
| 307 | const name = try leb.readULEB128(u64, reader); | 142 | const decl = table.decls.get(code) orelse return error.MalformedDwarf; // TODO better errors |
| 308 | const form = try leb.readULEB128(u64, reader); | 143 | const data = dw.debug_info; |
| 309 | 144 | try cu.diePtr(die).values.ensureTotalCapacityPrecise(allocator, decl.attrs.values().len); | |
| 310 | self.debug_abbrev_pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow); | ||
| 311 | |||
| 312 | const len = try findFormSize( | ||
| 313 | self.ctx, | ||
| 314 | form, | ||
| 315 | self.debug_info_pos + self.entry.debug_info_off, | ||
| 316 | self.cuh, | ||
| 317 | ); | ||
| 318 | const attr = Attribute{ | ||
| 319 | .name = name, | ||
| 320 | .form = form, | ||
| 321 | .debug_info_off = self.debug_info_pos + self.entry.debug_info_off, | ||
| 322 | .debug_info_len = len, | ||
| 323 | }; | ||
| 324 | 145 | ||
| 325 | self.debug_info_pos += len; | 146 | for (decl.attrs.values()) |attr| { |
| 147 | const start = creader.bytes_read; | ||
| 148 | try advanceByFormSize(cu, attr.form, creader); | ||
| 149 | const end = creader.bytes_read; | ||
| 150 | cu.diePtr(die).values.appendAssumeCapacity(data[start..end]); | ||
| 151 | } | ||
| 326 | 152 | ||
| 327 | return attr; | 153 | if (decl.children) { |
| 154 | // Open scope | ||
| 155 | try dw.parseDie(allocator, cu, table, die, creader); | ||
| 156 | } | ||
| 328 | } | 157 | } |
| 329 | }; | ||
| 330 | |||
| 331 | fn getAbbrevEntry(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, di_len: usize) !AbbrevEntry { | ||
| 332 | const debug_abbrev = self.debug_abbrev[da_off..][0..da_len]; | ||
| 333 | var stream = std.io.fixedBufferStream(debug_abbrev); | ||
| 334 | var creader = std.io.countingReader(stream.reader()); | ||
| 335 | const reader = creader.reader(); | ||
| 336 | |||
| 337 | const tag = try leb.readULEB128(u64, reader); | ||
| 338 | const children = switch (tag) { | ||
| 339 | std.dwarf.TAG.const_type, | ||
| 340 | std.dwarf.TAG.packed_type, | ||
| 341 | std.dwarf.TAG.pointer_type, | ||
| 342 | std.dwarf.TAG.reference_type, | ||
| 343 | std.dwarf.TAG.restrict_type, | ||
| 344 | std.dwarf.TAG.rvalue_reference_type, | ||
| 345 | std.dwarf.TAG.shared_type, | ||
| 346 | std.dwarf.TAG.volatile_type, | ||
| 347 | => if (creader.bytes_read == da_len) std.dwarf.CHILDREN.no else try reader.readByte(), | ||
| 348 | else => try reader.readByte(), | ||
| 349 | }; | ||
| 350 | |||
| 351 | const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow; | ||
| 352 | |||
| 353 | return AbbrevEntry{ | ||
| 354 | .tag = tag, | ||
| 355 | .children = children, | ||
| 356 | .debug_abbrev_off = pos + da_off, | ||
| 357 | .debug_abbrev_len = da_len - pos, | ||
| 358 | .debug_info_off = di_off, | ||
| 359 | .debug_info_len = di_len, | ||
| 360 | }; | ||
| 361 | } | 158 | } |
| 362 | 159 | ||
| 363 | fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Header) !usize { | 160 | fn advanceByFormSize(cu: *CompileUnit, form: Form, creader: anytype) !void { |
| 364 | const debug_info = self.debug_info[di_off..]; | 161 | const tracy = trace(@src()); |
| 365 | var stream = std.io.fixedBufferStream(debug_info); | 162 | defer tracy.end(); |
| 366 | var creader = std.io.countingReader(stream.reader()); | ||
| 367 | const reader = creader.reader(); | ||
| 368 | 163 | ||
| 164 | const reader = creader.reader(); | ||
| 369 | switch (form) { | 165 | switch (form) { |
| 370 | dwarf.FORM.strp, | 166 | dwarf.FORM.strp, |
| 371 | dwarf.FORM.sec_offset, | 167 | dwarf.FORM.sec_offset, |
| 372 | dwarf.FORM.ref_addr, | 168 | dwarf.FORM.ref_addr, |
| 373 | => return if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32), | 169 | => { |
| 170 | _ = try readOffset(cu.header.format, reader); | ||
| 171 | }, | ||
| 374 | 172 | ||
| 375 | dwarf.FORM.addr => return cuh.address_size, | 173 | dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}), |
| 376 | 174 | ||
| 377 | dwarf.FORM.block1, | 175 | dwarf.FORM.block1, |
| 378 | dwarf.FORM.block2, | 176 | dwarf.FORM.block2, |
| ... | @@ -386,119 +184,285 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head | ... | @@ -386,119 +184,285 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head |
| 386 | dwarf.FORM.block => try leb.readULEB128(u64, reader), | 184 | dwarf.FORM.block => try leb.readULEB128(u64, reader), |
| 387 | else => unreachable, | 185 | else => unreachable, |
| 388 | }; | 186 | }; |
| 389 | var i: u64 = 0; | 187 | for (0..len) |_| { |
| 390 | while (i < len) : (i += 1) { | ||
| 391 | _ = try reader.readByte(); | 188 | _ = try reader.readByte(); |
| 392 | } | 189 | } |
| 393 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 394 | }, | 190 | }, |
| 395 | 191 | ||
| 396 | dwarf.FORM.exprloc => { | 192 | dwarf.FORM.exprloc => { |
| 397 | const expr_len = try leb.readULEB128(u64, reader); | 193 | const len = try leb.readULEB128(u64, reader); |
| 398 | var i: u64 = 0; | 194 | for (0..len) |_| { |
| 399 | while (i < expr_len) : (i += 1) { | ||
| 400 | _ = try reader.readByte(); | 195 | _ = try reader.readByte(); |
| 401 | } | 196 | } |
| 402 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 403 | }, | 197 | }, |
| 404 | dwarf.FORM.flag_present => return 0, | 198 | dwarf.FORM.flag_present => {}, |
| 405 | 199 | ||
| 406 | dwarf.FORM.data1, | 200 | dwarf.FORM.data1, |
| 407 | dwarf.FORM.ref1, | 201 | dwarf.FORM.ref1, |
| 408 | dwarf.FORM.flag, | 202 | dwarf.FORM.flag, |
| 409 | => return @sizeOf(u8), | 203 | => try reader.skipBytes(1, .{}), |
| 410 | 204 | ||
| 411 | dwarf.FORM.data2, | 205 | dwarf.FORM.data2, |
| 412 | dwarf.FORM.ref2, | 206 | dwarf.FORM.ref2, |
| 413 | => return @sizeOf(u16), | 207 | => try reader.skipBytes(2, .{}), |
| 414 | 208 | ||
| 415 | dwarf.FORM.data4, | 209 | dwarf.FORM.data4, |
| 416 | dwarf.FORM.ref4, | 210 | dwarf.FORM.ref4, |
| 417 | => return @sizeOf(u32), | 211 | => try reader.skipBytes(4, .{}), |
| 418 | 212 | ||
| 419 | dwarf.FORM.data8, | 213 | dwarf.FORM.data8, |
| 420 | dwarf.FORM.ref8, | 214 | dwarf.FORM.ref8, |
| 421 | dwarf.FORM.ref_sig8, | 215 | dwarf.FORM.ref_sig8, |
| 422 | => return @sizeOf(u64), | 216 | => try reader.skipBytes(8, .{}), |
| 423 | 217 | ||
| 424 | dwarf.FORM.udata, | 218 | dwarf.FORM.udata, |
| 425 | dwarf.FORM.ref_udata, | 219 | dwarf.FORM.ref_udata, |
| 426 | => { | 220 | => { |
| 427 | _ = try leb.readULEB128(u64, reader); | 221 | _ = try leb.readULEB128(u64, reader); |
| 428 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 429 | }, | 222 | }, |
| 430 | 223 | ||
| 431 | dwarf.FORM.sdata => { | 224 | dwarf.FORM.sdata => { |
| 432 | _ = try leb.readILEB128(i64, reader); | 225 | _ = try leb.readILEB128(i64, reader); |
| 433 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 434 | }, | 226 | }, |
| 435 | 227 | ||
| 436 | dwarf.FORM.string => { | 228 | dwarf.FORM.string => { |
| 437 | var count: usize = 0; | ||
| 438 | while (true) { | 229 | while (true) { |
| 439 | const byte = try reader.readByte(); | 230 | const byte = try reader.readByte(); |
| 440 | count += 1; | ||
| 441 | if (byte == 0x0) break; | 231 | if (byte == 0x0) break; |
| 442 | } | 232 | } |
| 443 | return count; | ||
| 444 | }, | 233 | }, |
| 445 | 234 | ||
| 446 | else => { | 235 | else => { |
| 447 | // TODO figure out how to handle this | 236 | // TODO better errors |
| 448 | log.debug("unhandled DW_FORM_* value with identifier {x}", .{form}); | 237 | log.err("unhandled DW_FORM_* value with identifier {x}", .{form}); |
| 449 | return error.UnhandledDwFormValue; | 238 | return error.UnhandledDwFormValue; |
| 450 | }, | 239 | }, |
| 451 | } | 240 | } |
| 452 | } | 241 | } |
| 453 | 242 | ||
| 454 | fn findAbbrevEntrySize(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, cuh: CompileUnit.Header) !usize { | 243 | fn readOffset(format: Format, reader: anytype) !u64 { |
| 455 | const debug_abbrev = self.debug_abbrev[da_off..][0..da_len]; | 244 | return switch (format) { |
| 456 | var stream = std.io.fixedBufferStream(debug_abbrev); | 245 | .dwarf32 => try reader.readInt(u32, .little), |
| 457 | var creader = std.io.countingReader(stream.reader()); | 246 | .dwarf64 => try reader.readInt(u64, .little), |
| 458 | const reader = creader.reader(); | 247 | }; |
| 248 | } | ||
| 459 | 249 | ||
| 460 | const tag = try leb.readULEB128(u64, reader); | 250 | pub const AbbrevTable = struct { |
| 461 | switch (tag) { | 251 | /// Table of abbreviation declarations indexed by their assigned code value |
| 462 | std.dwarf.TAG.const_type, | 252 | decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{}, |
| 463 | std.dwarf.TAG.packed_type, | 253 | |
| 464 | std.dwarf.TAG.pointer_type, | 254 | pub fn deinit(table: *AbbrevTable, gpa: Allocator) void { |
| 465 | std.dwarf.TAG.reference_type, | 255 | for (table.decls.values()) |*decl| { |
| 466 | std.dwarf.TAG.restrict_type, | 256 | decl.deinit(gpa); |
| 467 | std.dwarf.TAG.rvalue_reference_type, | 257 | } |
| 468 | std.dwarf.TAG.shared_type, | 258 | table.decls.deinit(gpa); |
| 469 | std.dwarf.TAG.volatile_type, | ||
| 470 | => if (creader.bytes_read != da_len) { | ||
| 471 | _ = try reader.readByte(); | ||
| 472 | }, | ||
| 473 | else => _ = try reader.readByte(), | ||
| 474 | } | 259 | } |
| 260 | }; | ||
| 475 | 261 | ||
| 476 | var len: usize = 0; | 262 | pub const Decl = struct { |
| 477 | while (creader.bytes_read < debug_abbrev.len) { | 263 | code: Code, |
| 478 | _ = try leb.readULEB128(u64, reader); | 264 | tag: Tag, |
| 479 | const form = try leb.readULEB128(u64, reader); | 265 | children: bool, |
| 480 | const form_len = try self.findFormSize(form, di_off + len, cuh); | 266 | |
| 481 | len += form_len; | 267 | /// Table of attributes indexed by their AT value |
| 268 | attrs: std.AutoArrayHashMapUnmanaged(At, Attr) = .{}, | ||
| 269 | |||
| 270 | pub fn deinit(decl: *Decl, gpa: Allocator) void { | ||
| 271 | decl.attrs.deinit(gpa); | ||
| 482 | } | 272 | } |
| 273 | }; | ||
| 483 | 274 | ||
| 484 | return len; | 275 | pub const Attr = struct { |
| 485 | } | 276 | at: At, |
| 277 | form: Form, | ||
| 278 | }; | ||
| 486 | 279 | ||
| 487 | fn getString(self: DwarfInfo, off: u64) []const u8 { | 280 | pub const At = u64; |
| 488 | assert(off < self.debug_str.len); | 281 | pub const Code = u64; |
| 489 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0); | 282 | pub const Form = u64; |
| 490 | } | 283 | pub const Tag = u64; |
| 284 | |||
| 285 | pub const CompileUnitHeader = struct { | ||
| 286 | format: Format, | ||
| 287 | length: u64, | ||
| 288 | version: u16, | ||
| 289 | debug_abbrev_offset: u64, | ||
| 290 | address_size: u8, | ||
| 291 | }; | ||
| 491 | 292 | ||
| 492 | const DwarfInfo = @This(); | 293 | pub const CompileUnit = struct { |
| 294 | header: CompileUnitHeader, | ||
| 295 | pos: usize, | ||
| 296 | dies: std.ArrayListUnmanaged(Die) = .{}, | ||
| 297 | children: std.ArrayListUnmanaged(Die.Index) = .{}, | ||
| 298 | |||
| 299 | pub fn deinit(cu: *CompileUnit, gpa: Allocator) void { | ||
| 300 | for (cu.dies.items) |*die| { | ||
| 301 | die.deinit(gpa); | ||
| 302 | } | ||
| 303 | cu.dies.deinit(gpa); | ||
| 304 | cu.children.deinit(gpa); | ||
| 305 | } | ||
| 306 | |||
| 307 | pub fn addDie(cu: *CompileUnit, gpa: Allocator) !Die.Index { | ||
| 308 | const index = @as(Die.Index, @intCast(cu.dies.items.len)); | ||
| 309 | _ = try cu.dies.addOne(gpa); | ||
| 310 | return index; | ||
| 311 | } | ||
| 312 | |||
| 313 | pub fn diePtr(cu: *CompileUnit, index: Die.Index) *Die { | ||
| 314 | return &cu.dies.items[index]; | ||
| 315 | } | ||
| 316 | |||
| 317 | pub fn getCompileDir(cu: CompileUnit, ctx: DwarfInfo) ?[:0]const u8 { | ||
| 318 | assert(cu.dies.items.len > 0); | ||
| 319 | const die = cu.dies.items[0]; | ||
| 320 | const res = die.find(dwarf.AT.comp_dir, cu, ctx) orelse return null; | ||
| 321 | return res.getString(cu.header.format, ctx); | ||
| 322 | } | ||
| 323 | |||
| 324 | pub fn getSourceFile(cu: CompileUnit, ctx: DwarfInfo) ?[:0]const u8 { | ||
| 325 | assert(cu.dies.items.len > 0); | ||
| 326 | const die = cu.dies.items[0]; | ||
| 327 | const res = die.find(dwarf.AT.name, cu, ctx) orelse return null; | ||
| 328 | return res.getString(cu.header.format, ctx); | ||
| 329 | } | ||
| 330 | |||
| 331 | pub fn nextCompileUnitOffset(cu: CompileUnit) u64 { | ||
| 332 | return cu.pos + switch (cu.header.format) { | ||
| 333 | .dwarf32 => @as(u64, 4), | ||
| 334 | .dwarf64 => 12, | ||
| 335 | } + cu.header.length; | ||
| 336 | } | ||
| 337 | }; | ||
| 338 | |||
| 339 | pub const Die = struct { | ||
| 340 | code: Code, | ||
| 341 | values: std.ArrayListUnmanaged([]const u8) = .{}, | ||
| 342 | children: std.ArrayListUnmanaged(Die.Index) = .{}, | ||
| 343 | |||
| 344 | pub fn deinit(die: *Die, gpa: Allocator) void { | ||
| 345 | die.values.deinit(gpa); | ||
| 346 | die.children.deinit(gpa); | ||
| 347 | } | ||
| 348 | |||
| 349 | pub fn find(die: Die, at: At, cu: CompileUnit, ctx: DwarfInfo) ?DieValue { | ||
| 350 | const table = ctx.abbrev_tables.get(cu.header.debug_abbrev_offset) orelse return null; | ||
| 351 | const decl = table.decls.get(die.code).?; | ||
| 352 | const index = decl.attrs.getIndex(at) orelse return null; | ||
| 353 | const attr = decl.attrs.values()[index]; | ||
| 354 | const value = die.values.items[index]; | ||
| 355 | return .{ .attr = attr, .bytes = value }; | ||
| 356 | } | ||
| 357 | |||
| 358 | pub const Index = u32; | ||
| 359 | }; | ||
| 360 | |||
| 361 | pub const DieValue = struct { | ||
| 362 | attr: Attr, | ||
| 363 | bytes: []const u8, | ||
| 364 | |||
| 365 | pub fn getFlag(value: DieValue) ?bool { | ||
| 366 | return switch (value.attr.form) { | ||
| 367 | dwarf.FORM.flag => value.bytes[0] == 1, | ||
| 368 | dwarf.FORM.flag_present => true, | ||
| 369 | else => null, | ||
| 370 | }; | ||
| 371 | } | ||
| 372 | |||
| 373 | pub fn getString(value: DieValue, format: Format, ctx: DwarfInfo) ?[:0]const u8 { | ||
| 374 | switch (value.attr.form) { | ||
| 375 | dwarf.FORM.string => { | ||
| 376 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.bytes.ptr)), 0); | ||
| 377 | }, | ||
| 378 | dwarf.FORM.strp => { | ||
| 379 | const off = switch (format) { | ||
| 380 | .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little), | ||
| 381 | .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little), | ||
| 382 | }; | ||
| 383 | return ctx.getString(off); | ||
| 384 | }, | ||
| 385 | else => return null, | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | pub fn getSecOffset(value: DieValue, format: Format) ?u64 { | ||
| 390 | return switch (value.attr.form) { | ||
| 391 | dwarf.FORM.sec_offset => switch (format) { | ||
| 392 | .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little), | ||
| 393 | .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little), | ||
| 394 | }, | ||
| 395 | else => null, | ||
| 396 | }; | ||
| 397 | } | ||
| 398 | |||
| 399 | pub fn getConstant(value: DieValue) !?i128 { | ||
| 400 | var stream = std.io.fixedBufferStream(value.bytes); | ||
| 401 | const reader = stream.reader(); | ||
| 402 | return switch (value.attr.form) { | ||
| 403 | dwarf.FORM.data1 => value.bytes[0], | ||
| 404 | dwarf.FORM.data2 => mem.readInt(u16, value.bytes[0..2], .little), | ||
| 405 | dwarf.FORM.data4 => mem.readInt(u32, value.bytes[0..4], .little), | ||
| 406 | dwarf.FORM.data8 => mem.readInt(u64, value.bytes[0..8], .little), | ||
| 407 | dwarf.FORM.udata => try leb.readULEB128(u64, reader), | ||
| 408 | dwarf.FORM.sdata => try leb.readILEB128(i64, reader), | ||
| 409 | else => null, | ||
| 410 | }; | ||
| 411 | } | ||
| 412 | |||
| 413 | pub fn getReference(value: DieValue, format: Format) !?u64 { | ||
| 414 | var stream = std.io.fixedBufferStream(value.bytes); | ||
| 415 | const reader = stream.reader(); | ||
| 416 | return switch (value.attr.form) { | ||
| 417 | dwarf.FORM.ref1 => value.bytes[0], | ||
| 418 | dwarf.FORM.ref2 => mem.readInt(u16, value.bytes[0..2], .little), | ||
| 419 | dwarf.FORM.ref4 => mem.readInt(u32, value.bytes[0..4], .little), | ||
| 420 | dwarf.FORM.ref8 => mem.readInt(u64, value.bytes[0..8], .little), | ||
| 421 | dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader), | ||
| 422 | dwarf.FORM.ref_addr => switch (format) { | ||
| 423 | .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little), | ||
| 424 | .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little), | ||
| 425 | }, | ||
| 426 | else => null, | ||
| 427 | }; | ||
| 428 | } | ||
| 429 | |||
| 430 | pub fn getAddr(value: DieValue, header: CompileUnitHeader) ?u64 { | ||
| 431 | return switch (value.attr.form) { | ||
| 432 | dwarf.FORM.addr => switch (header.address_size) { | ||
| 433 | 1 => value.bytes[0], | ||
| 434 | 2 => mem.readInt(u16, value.bytes[0..2], .little), | ||
| 435 | 4 => mem.readInt(u32, value.bytes[0..4], .little), | ||
| 436 | 8 => mem.readInt(u64, value.bytes[0..8], .little), | ||
| 437 | else => null, | ||
| 438 | }, | ||
| 439 | else => null, | ||
| 440 | }; | ||
| 441 | } | ||
| 442 | |||
| 443 | pub fn getExprloc(value: DieValue) !?[]const u8 { | ||
| 444 | if (value.attr.form != dwarf.FORM.exprloc) return null; | ||
| 445 | var stream = std.io.fixedBufferStream(value.bytes); | ||
| 446 | var creader = std.io.countingReader(stream.reader()); | ||
| 447 | const reader = creader.reader(); | ||
| 448 | const expr_len = try leb.readULEB128(u64, reader); | ||
| 449 | return value.bytes[creader.bytes_read..][0..expr_len]; | ||
| 450 | } | ||
| 451 | }; | ||
| 452 | |||
| 453 | pub const Format = enum { | ||
| 454 | dwarf32, | ||
| 455 | dwarf64, | ||
| 456 | }; | ||
| 493 | 457 | ||
| 494 | const std = @import("std"); | ||
| 495 | const assert = std.debug.assert; | 458 | const assert = std.debug.assert; |
| 496 | const dwarf = std.dwarf; | 459 | const dwarf = std.dwarf; |
| 497 | const leb = std.leb; | 460 | const leb = std.leb; |
| 498 | const log = std.log.scoped(.macho); | 461 | const log = std.log.scoped(.link); |
| 499 | const math = std.math; | ||
| 500 | const mem = std.mem; | 462 | const mem = std.mem; |
| 463 | const std = @import("std"); | ||
| 464 | const trace = @import("../tracy.zig").trace; | ||
| 501 | 465 | ||
| 502 | const Allocator = mem.Allocator; | 466 | const Allocator = mem.Allocator; |
| 503 | pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize }); | 467 | const DwarfInfo = @This(); |
| 504 | pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 }); | 468 | const MachO = @import("../MachO.zig"); |
src/link/MachO/Dylib.zig+593-321| ... | @@ -1,340 +1,207 @@ | ... | @@ -1,340 +1,207 @@ |
| 1 | path: []const u8, | 1 | path: []const u8, |
| 2 | id: ?Id = null, | 2 | data: []const u8, |
| 3 | weak: bool = false, | 3 | index: File.Index, |
| 4 | /// Header is only set if Dylib is parsed directly from a binary and not a stub file. | ||
| 5 | header: ?macho.mach_header_64 = null, | ||
| 6 | |||
| 7 | /// Parsed symbol table represented as hash map of symbols' | ||
| 8 | /// names. We can and should defer creating *Symbols until | ||
| 9 | /// a symbol is referenced by an object file. | ||
| 10 | /// | ||
| 11 | /// The value for each parsed symbol represents whether the | ||
| 12 | /// symbol is defined as a weak symbol or strong. | ||
| 13 | /// TODO when the referenced symbol is weak, ld64 marks it as | ||
| 14 | /// N_REF_TO_WEAK but need to investigate if there's more to it | ||
| 15 | /// such as weak binding entry or simply weak. For now, we generate | ||
| 16 | /// standard bind or lazy bind. | ||
| 17 | symbols: std.StringArrayHashMapUnmanaged(bool) = .{}, | ||
| 18 | |||
| 19 | pub const Id = struct { | ||
| 20 | name: []const u8, | ||
| 21 | timestamp: u32, | ||
| 22 | current_version: u32, | ||
| 23 | compatibility_version: u32, | ||
| 24 | |||
| 25 | pub fn default(allocator: Allocator, name: []const u8) !Id { | ||
| 26 | return Id{ | ||
| 27 | .name = try allocator.dupe(u8, name), | ||
| 28 | .timestamp = 2, | ||
| 29 | .current_version = 0x10000, | ||
| 30 | .compatibility_version = 0x10000, | ||
| 31 | }; | ||
| 32 | } | ||
| 33 | |||
| 34 | pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id { | ||
| 35 | return Id{ | ||
| 36 | .name = try allocator.dupe(u8, name), | ||
| 37 | .timestamp = lc.dylib.timestamp, | ||
| 38 | .current_version = lc.dylib.current_version, | ||
| 39 | .compatibility_version = lc.dylib.compatibility_version, | ||
| 40 | }; | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn deinit(id: Id, allocator: Allocator) void { | ||
| 44 | allocator.free(id.name); | ||
| 45 | } | ||
| 46 | |||
| 47 | pub const ParseError = fmt.ParseIntError || fmt.BufPrintError; | ||
| 48 | |||
| 49 | pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { | ||
| 50 | id.current_version = try parseVersion(version); | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void { | ||
| 54 | id.compatibility_version = try parseVersion(version); | ||
| 55 | } | ||
| 56 | |||
| 57 | fn parseVersion(version: anytype) ParseError!u32 { | ||
| 58 | const string = blk: { | ||
| 59 | switch (version) { | ||
| 60 | .int => |int| { | ||
| 61 | var out: u32 = 0; | ||
| 62 | const major = math.cast(u16, int) orelse return error.Overflow; | ||
| 63 | out += @as(u32, @intCast(major)) << 16; | ||
| 64 | return out; | ||
| 65 | }, | ||
| 66 | .float => |float| { | ||
| 67 | var buf: [256]u8 = undefined; | ||
| 68 | break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float}); | ||
| 69 | }, | ||
| 70 | .string => |string| { | ||
| 71 | break :blk string; | ||
| 72 | }, | ||
| 73 | } | ||
| 74 | }; | ||
| 75 | |||
| 76 | var out: u32 = 0; | ||
| 77 | var values: [3][]const u8 = undefined; | ||
| 78 | 4 | ||
| 79 | var split = mem.splitScalar(u8, string, '.'); | 5 | header: ?macho.mach_header_64 = null, |
| 80 | var count: u4 = 0; | 6 | exports: std.MultiArrayList(Export) = .{}, |
| 81 | while (split.next()) |value| { | 7 | strtab: std.ArrayListUnmanaged(u8) = .{}, |
| 82 | if (count > 2) { | 8 | id: ?Id = null, |
| 83 | log.debug("malformed version field: {s}", .{string}); | 9 | ordinal: u16 = 0, |
| 84 | return 0x10000; | ||
| 85 | } | ||
| 86 | values[count] = value; | ||
| 87 | count += 1; | ||
| 88 | } | ||
| 89 | 10 | ||
| 90 | if (count > 2) { | 11 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, |
| 91 | out += try fmt.parseInt(u8, values[2], 10); | 12 | dependents: std.ArrayListUnmanaged(Id) = .{}, |
| 92 | } | 13 | rpaths: std.StringArrayHashMapUnmanaged(void) = .{}, |
| 93 | if (count > 1) { | 14 | umbrella: File.Index = 0, |
| 94 | out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8; | 15 | platform: ?MachO.Options.Platform = null, |
| 95 | } | ||
| 96 | out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16; | ||
| 97 | 16 | ||
| 98 | return out; | 17 | needed: bool, |
| 99 | } | 18 | weak: bool, |
| 100 | }; | 19 | reexport: bool, |
| 20 | explicit: bool, | ||
| 21 | hoisted: bool = true, | ||
| 22 | referenced: bool = false, | ||
| 101 | 23 | ||
| 102 | pub fn isDylib(file: std.fs.File, fat_offset: u64) bool { | 24 | output_symtab_ctx: MachO.SymtabCtx = .{}, |
| 103 | const reader = file.reader(); | ||
| 104 | const hdr = reader.readStruct(macho.mach_header_64) catch return false; | ||
| 105 | defer file.seekTo(fat_offset) catch {}; | ||
| 106 | return hdr.filetype == macho.MH_DYLIB; | ||
| 107 | } | ||
| 108 | 25 | ||
| 109 | pub fn deinit(self: *Dylib, allocator: Allocator) void { | 26 | pub fn deinit(self: *Dylib, allocator: Allocator) void { |
| 110 | allocator.free(self.path); | 27 | self.exports.deinit(allocator); |
| 111 | for (self.symbols.keys()) |key| { | 28 | self.strtab.deinit(allocator); |
| 112 | allocator.free(key); | 29 | if (self.id) |*id| id.deinit(allocator); |
| 113 | } | ||
| 114 | self.symbols.deinit(allocator); | 30 | self.symbols.deinit(allocator); |
| 115 | if (self.id) |*id| { | 31 | for (self.dependents.items) |*id| { |
| 116 | id.deinit(allocator); | 32 | id.deinit(allocator); |
| 117 | } | 33 | } |
| 34 | self.dependents.deinit(allocator); | ||
| 35 | self.rpaths.deinit(allocator); | ||
| 118 | } | 36 | } |
| 119 | 37 | ||
| 120 | pub fn parseFromBinary( | 38 | pub fn parse(self: *Dylib, macho_file: *MachO) !void { |
| 121 | self: *Dylib, | 39 | const tracy = trace(@src()); |
| 122 | allocator: Allocator, | 40 | defer tracy.end(); |
| 123 | dylib_id: u16, | 41 | |
| 124 | dependent_libs: anytype, | 42 | const gpa = macho_file.base.allocator; |
| 125 | name: []const u8, | 43 | var stream = std.io.fixedBufferStream(self.data); |
| 126 | data: []align(@alignOf(u64)) const u8, | ||
| 127 | ) !void { | ||
| 128 | var stream = std.io.fixedBufferStream(data); | ||
| 129 | const reader = stream.reader(); | 44 | const reader = stream.reader(); |
| 130 | 45 | ||
| 131 | log.debug("parsing shared library '{s}'", .{name}); | 46 | log.debug("parsing dylib from binary", .{}); |
| 132 | 47 | ||
| 133 | self.header = try reader.readStruct(macho.mach_header_64); | 48 | self.header = try reader.readStruct(macho.mach_header_64); |
| 134 | 49 | ||
| 135 | const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; | 50 | const lc_id = self.getLoadCommand(.ID_DYLIB) orelse { |
| 136 | var it = LoadCommandIterator{ | 51 | macho_file.base.fatal("{s}: missing LC_ID_DYLIB load command", .{self.path}); |
| 137 | .ncmds = self.header.?.ncmds, | 52 | return error.ParseFailed; |
| 138 | .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | ||
| 139 | }; | 53 | }; |
| 140 | while (it.next()) |cmd| { | 54 | self.id = try Id.fromLoadCommand(gpa, lc_id.cast(macho.dylib_command).?, lc_id.getDylibPathName()); |
| 141 | switch (cmd.cmd()) { | ||
| 142 | .SYMTAB => { | ||
| 143 | const symtab_cmd = cmd.cast(macho.symtab_command).?; | ||
| 144 | const symtab = @as( | ||
| 145 | [*]const macho.nlist_64, | ||
| 146 | // Alignment is guaranteed as a dylib is a final linked image and has to have sections | ||
| 147 | // properly aligned in order to be correctly loaded by the loader. | ||
| 148 | @ptrCast(@alignCast(&data[symtab_cmd.symoff])), | ||
| 149 | )[0..symtab_cmd.nsyms]; | ||
| 150 | const strtab = data[symtab_cmd.stroff..][0..symtab_cmd.strsize]; | ||
| 151 | |||
| 152 | for (symtab) |sym| { | ||
| 153 | const add_to_symtab = sym.ext() and (sym.sect() or sym.indr()); | ||
| 154 | if (!add_to_symtab) continue; | ||
| 155 | |||
| 156 | const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + sym.n_strx)), 0); | ||
| 157 | try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false); | ||
| 158 | } | ||
| 159 | }, | ||
| 160 | .ID_DYLIB => { | ||
| 161 | self.id = try Id.fromLoadCommand( | ||
| 162 | allocator, | ||
| 163 | cmd.cast(macho.dylib_command).?, | ||
| 164 | cmd.getDylibPathName(), | ||
| 165 | ); | ||
| 166 | }, | ||
| 167 | .REEXPORT_DYLIB => { | ||
| 168 | if (should_lookup_reexports) { | ||
| 169 | // Parse install_name to dependent dylib. | ||
| 170 | const id = try Id.fromLoadCommand( | ||
| 171 | allocator, | ||
| 172 | cmd.cast(macho.dylib_command).?, | ||
| 173 | cmd.getDylibPathName(), | ||
| 174 | ); | ||
| 175 | try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id }); | ||
| 176 | } | ||
| 177 | }, | ||
| 178 | else => {}, | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | 55 | ||
| 183 | /// Returns Platform composed from the first encountered build version type load command: | ||
| 184 | /// either LC_BUILD_VERSION or LC_VERSION_MIN_*. | ||
| 185 | pub fn getPlatform(self: Dylib, data: []align(@alignOf(u64)) const u8) ?Platform { | ||
| 186 | var it = LoadCommandIterator{ | 56 | var it = LoadCommandIterator{ |
| 187 | .ncmds = self.header.?.ncmds, | 57 | .ncmds = self.header.?.ncmds, |
| 188 | .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | 58 | .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], |
| 189 | }; | 59 | }; |
| 190 | while (it.next()) |cmd| { | 60 | while (it.next()) |cmd| switch (cmd.cmd()) { |
| 191 | switch (cmd.cmd()) { | 61 | .REEXPORT_DYLIB => if (self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0) { |
| 192 | .BUILD_VERSION, | 62 | const id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName()); |
| 193 | .VERSION_MIN_MACOSX, | 63 | try self.dependents.append(gpa, id); |
| 194 | .VERSION_MIN_IPHONEOS, | 64 | }, |
| 195 | .VERSION_MIN_TVOS, | 65 | .DYLD_INFO_ONLY => { |
| 196 | .VERSION_MIN_WATCHOS, | 66 | const dyld_cmd = cmd.cast(macho.dyld_info_command).?; |
| 197 | => return Platform.fromLoadCommand(cmd), | 67 | const data = self.data[dyld_cmd.export_off..][0..dyld_cmd.export_size]; |
| 198 | else => {}, | 68 | try self.parseTrie(data, macho_file); |
| 199 | } | 69 | }, |
| 200 | } else return null; | 70 | .DYLD_EXPORTS_TRIE => { |
| 201 | } | 71 | const ld_cmd = cmd.cast(macho.linkedit_data_command).?; |
| 202 | 72 | const data = self.data[ld_cmd.dataoff..][0..ld_cmd.datasize]; | |
| 203 | fn addObjCClassSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 73 | try self.parseTrie(data, macho_file); |
| 204 | const expanded = &[_][]const u8{ | 74 | }, |
| 205 | try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}), | 75 | .RPATH => { |
| 206 | try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}), | 76 | const path = cmd.getRpathPathName(); |
| 77 | try self.rpaths.put(gpa, path, {}); | ||
| 78 | }, | ||
| 79 | else => {}, | ||
| 207 | }; | 80 | }; |
| 208 | 81 | ||
| 209 | for (expanded) |sym| { | 82 | self.initPlatform(); |
| 210 | if (self.symbols.contains(sym)) continue; | ||
| 211 | try self.symbols.putNoClobber(allocator, sym, false); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | fn addObjCIVarSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | ||
| 216 | const expanded = try std.fmt.allocPrint(allocator, "_OBJC_IVAR_$_{s}", .{sym_name}); | ||
| 217 | if (self.symbols.contains(expanded)) return; | ||
| 218 | try self.symbols.putNoClobber(allocator, expanded, false); | ||
| 219 | } | 83 | } |
| 220 | 84 | ||
| 221 | fn addObjCEhTypeSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 85 | const TrieIterator = struct { |
| 222 | const expanded = try std.fmt.allocPrint(allocator, "_OBJC_EHTYPE_$_{s}", .{sym_name}); | 86 | data: []const u8, |
| 223 | if (self.symbols.contains(expanded)) return; | 87 | pos: usize = 0, |
| 224 | try self.symbols.putNoClobber(allocator, expanded, false); | ||
| 225 | } | ||
| 226 | |||
| 227 | fn addSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | ||
| 228 | if (self.symbols.contains(sym_name)) return; | ||
| 229 | try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false); | ||
| 230 | } | ||
| 231 | 88 | ||
| 232 | fn addWeakSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 89 | fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) { |
| 233 | if (self.symbols.contains(sym_name)) return; | 90 | return std.io.fixedBufferStream(it.data[it.pos..]); |
| 234 | try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), true); | 91 | } |
| 235 | } | ||
| 236 | 92 | ||
| 237 | pub const TargetMatcher = struct { | 93 | fn readULEB128(it: *TrieIterator) !u64 { |
| 238 | allocator: Allocator, | 94 | var stream = it.getStream(); |
| 239 | cpu_arch: std.Target.Cpu.Arch, | 95 | var creader = std.io.countingReader(stream.reader()); |
| 240 | os_tag: std.Target.Os.Tag, | 96 | const reader = creader.reader(); |
| 241 | abi: std.Target.Abi, | 97 | const value = try std.leb.readULEB128(u64, reader); |
| 242 | target_strings: std.ArrayListUnmanaged([]const u8) = .{}, | 98 | it.pos += creader.bytes_read; |
| 99 | return value; | ||
| 100 | } | ||
| 243 | 101 | ||
| 244 | pub fn init(allocator: Allocator, target: std.Target) !TargetMatcher { | 102 | fn readString(it: *TrieIterator) ![:0]const u8 { |
| 245 | var self = TargetMatcher{ | 103 | var stream = it.getStream(); |
| 246 | .allocator = allocator, | 104 | const reader = stream.reader(); |
| 247 | .cpu_arch = target.cpu.arch, | ||
| 248 | .os_tag = target.os.tag, | ||
| 249 | .abi = target.abi, | ||
| 250 | }; | ||
| 251 | const apple_string = try toAppleTargetTriple(allocator, self.cpu_arch, self.os_tag, self.abi); | ||
| 252 | try self.target_strings.append(allocator, apple_string); | ||
| 253 | 105 | ||
| 254 | if (self.abi == .simulator) { | 106 | var count: usize = 0; |
| 255 | // For Apple simulator targets, linking gets tricky as we need to link against the simulator | 107 | while (true) : (count += 1) { |
| 256 | // hosts dylibs too. | 108 | const byte = try reader.readByte(); |
| 257 | const host_target = try toAppleTargetTriple(allocator, self.cpu_arch, .macos, .none); | 109 | if (byte == 0) break; |
| 258 | try self.target_strings.append(allocator, host_target); | ||
| 259 | } | 110 | } |
| 260 | 111 | ||
| 261 | return self; | 112 | const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0]; |
| 113 | it.pos += count + 1; | ||
| 114 | return str; | ||
| 262 | } | 115 | } |
| 263 | 116 | ||
| 264 | pub fn deinit(self: *TargetMatcher) void { | 117 | fn readByte(it: *TrieIterator) !u8 { |
| 265 | for (self.target_strings.items) |t| { | 118 | var stream = it.getStream(); |
| 266 | self.allocator.free(t); | 119 | const value = try stream.reader().readByte(); |
| 267 | } | 120 | it.pos += 1; |
| 268 | self.target_strings.deinit(self.allocator); | 121 | return value; |
| 269 | } | 122 | } |
| 123 | }; | ||
| 270 | 124 | ||
| 271 | inline fn fmtCpuArch(cpu_arch: std.Target.Cpu.Arch) []const u8 { | 125 | pub fn addExport(self: *Dylib, allocator: Allocator, name: []const u8, flags: Export.Flags) !void { |
| 272 | return switch (cpu_arch) { | 126 | try self.exports.append(allocator, .{ |
| 273 | .aarch64 => "arm64", | 127 | .name = try self.insertString(allocator, name), |
| 274 | .x86_64 => "x86_64", | 128 | .flags = flags, |
| 275 | else => unreachable, | 129 | }); |
| 276 | }; | 130 | } |
| 277 | } | ||
| 278 | 131 | ||
| 279 | inline fn fmtAbi(abi: std.Target.Abi) ?[]const u8 { | 132 | fn parseTrieNode( |
| 280 | return switch (abi) { | 133 | self: *Dylib, |
| 281 | .none => null, | 134 | it: *TrieIterator, |
| 282 | .simulator => "simulator", | 135 | allocator: Allocator, |
| 283 | .macabi => "maccatalyst", | 136 | arena: Allocator, |
| 284 | else => unreachable, | 137 | prefix: []const u8, |
| 138 | ) !void { | ||
| 139 | const tracy = trace(@src()); | ||
| 140 | defer tracy.end(); | ||
| 141 | const size = try it.readULEB128(); | ||
| 142 | if (size > 0) { | ||
| 143 | const flags = try it.readULEB128(); | ||
| 144 | const kind = flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK; | ||
| 145 | const out_flags = Export.Flags{ | ||
| 146 | .abs = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE, | ||
| 147 | .tlv = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL, | ||
| 148 | .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0, | ||
| 285 | }; | 149 | }; |
| 286 | } | 150 | if (flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT != 0) { |
| 287 | 151 | _ = try it.readULEB128(); // dylib ordinal | |
| 288 | pub fn toAppleTargetTriple( | 152 | const name = try it.readString(); |
| 289 | allocator: Allocator, | 153 | try self.addExport(allocator, if (name.len > 0) name else prefix, out_flags); |
| 290 | cpu_arch: std.Target.Cpu.Arch, | 154 | } else if (flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0) { |
| 291 | os_tag: std.Target.Os.Tag, | 155 | _ = try it.readULEB128(); // stub offset |
| 292 | abi: std.Target.Abi, | 156 | _ = try it.readULEB128(); // resolver offset |
| 293 | ) ![]const u8 { | 157 | try self.addExport(allocator, prefix, out_flags); |
| 294 | const cpu_arch_s = fmtCpuArch(cpu_arch); | 158 | } else { |
| 295 | const os_tag_s = @tagName(os_tag); | 159 | _ = try it.readULEB128(); // VM offset |
| 296 | if (fmtAbi(abi)) |abi_s| { | 160 | try self.addExport(allocator, prefix, out_flags); |
| 297 | return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ cpu_arch_s, os_tag_s, abi_s }); | ||
| 298 | } | 161 | } |
| 299 | return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch_s, os_tag_s }); | ||
| 300 | } | 162 | } |
| 301 | 163 | ||
| 302 | fn hasValue(stack: []const []const u8, needle: []const u8) bool { | 164 | const nedges = try it.readByte(); |
| 303 | for (stack) |v| { | ||
| 304 | if (mem.eql(u8, v, needle)) return true; | ||
| 305 | } | ||
| 306 | return false; | ||
| 307 | } | ||
| 308 | 165 | ||
| 309 | pub fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { | 166 | for (0..nedges) |_| { |
| 310 | for (self.target_strings.items) |t| { | 167 | const label = try it.readString(); |
| 311 | if (hasValue(targets, t)) return true; | 168 | const off = try it.readULEB128(); |
| 312 | } | 169 | const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label }); |
| 313 | return false; | 170 | const curr = it.pos; |
| 171 | it.pos = off; | ||
| 172 | try self.parseTrieNode(it, allocator, arena, prefix_label); | ||
| 173 | it.pos = curr; | ||
| 314 | } | 174 | } |
| 175 | } | ||
| 315 | 176 | ||
| 316 | fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { | 177 | fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void { |
| 317 | return hasValue(archs, fmtCpuArch(self.cpu_arch)); | 178 | const tracy = trace(@src()); |
| 318 | } | 179 | defer tracy.end(); |
| 319 | }; | 180 | const gpa = macho_file.base.allocator; |
| 181 | var arena = std.heap.ArenaAllocator.init(gpa); | ||
| 182 | defer arena.deinit(); | ||
| 183 | |||
| 184 | var it: TrieIterator = .{ .data = data }; | ||
| 185 | try self.parseTrieNode(&it, gpa, arena.allocator(), ""); | ||
| 186 | } | ||
| 320 | 187 | ||
| 321 | pub fn parseFromStub( | 188 | pub fn parseTbd( |
| 322 | self: *Dylib, | 189 | self: *Dylib, |
| 323 | allocator: Allocator, | 190 | cpu_arch: std.Target.Cpu.Arch, |
| 324 | target: std.Target, | 191 | platform: ?MachO.Options.Platform, |
| 325 | lib_stub: LibStub, | 192 | lib_stub: LibStub, |
| 326 | dylib_id: u16, | 193 | macho_file: *MachO, |
| 327 | dependent_libs: anytype, | ||
| 328 | name: []const u8, | ||
| 329 | ) !void { | 194 | ) !void { |
| 330 | if (lib_stub.inner.len == 0) return error.NotLibStub; | 195 | const tracy = trace(@src()); |
| 196 | defer tracy.end(); | ||
| 197 | const gpa = macho_file.base.allocator; | ||
| 331 | 198 | ||
| 332 | log.debug("parsing shared library from stub '{s}'", .{name}); | 199 | log.debug("parsing dylib from stub", .{}); |
| 333 | 200 | ||
| 334 | const umbrella_lib = lib_stub.inner[0]; | 201 | const umbrella_lib = lib_stub.inner[0]; |
| 335 | 202 | ||
| 336 | { | 203 | { |
| 337 | var id = try Id.default(allocator, umbrella_lib.installName()); | 204 | var id = try Id.default(gpa, umbrella_lib.installName()); |
| 338 | if (umbrella_lib.currentVersion()) |version| { | 205 | if (umbrella_lib.currentVersion()) |version| { |
| 339 | try id.parseCurrentVersion(version); | 206 | try id.parseCurrentVersion(version); |
| 340 | } | 207 | } |
| ... | @@ -344,21 +211,21 @@ pub fn parseFromStub( | ... | @@ -344,21 +211,21 @@ pub fn parseFromStub( |
| 344 | self.id = id; | 211 | self.id = id; |
| 345 | } | 212 | } |
| 346 | 213 | ||
| 347 | var umbrella_libs = std.StringHashMap(void).init(allocator); | 214 | var umbrella_libs = std.StringHashMap(void).init(gpa); |
| 348 | defer umbrella_libs.deinit(); | 215 | defer umbrella_libs.deinit(); |
| 349 | 216 | ||
| 350 | log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); | 217 | log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); |
| 351 | 218 | ||
| 352 | var matcher = try TargetMatcher.init(allocator, target); | 219 | self.platform = platform orelse .{ |
| 220 | .platform = .MACOS, | ||
| 221 | .version = .{ .value = 0 }, | ||
| 222 | }; | ||
| 223 | |||
| 224 | var matcher = try TargetMatcher.init(gpa, cpu_arch, self.platform.?.platform); | ||
| 353 | defer matcher.deinit(); | 225 | defer matcher.deinit(); |
| 354 | 226 | ||
| 355 | for (lib_stub.inner, 0..) |elem, stub_index| { | 227 | for (lib_stub.inner, 0..) |elem, stub_index| { |
| 356 | const targets = try elem.targets(allocator); | 228 | if (!(try matcher.matchesTargetTbd(elem))) continue; |
| 357 | defer { | ||
| 358 | for (targets) |t| allocator.free(t); | ||
| 359 | allocator.free(targets); | ||
| 360 | } | ||
| 361 | if (!matcher.matchesTarget(targets)) continue; | ||
| 362 | 229 | ||
| 363 | if (stub_index > 0) { | 230 | if (stub_index > 0) { |
| 364 | // TODO I thought that we could switch on presence of `parent-umbrella` map; | 231 | // TODO I thought that we could switch on presence of `parent-umbrella` map; |
| ... | @@ -375,43 +242,42 @@ pub fn parseFromStub( | ... | @@ -375,43 +242,42 @@ pub fn parseFromStub( |
| 375 | 242 | ||
| 376 | if (exp.symbols) |symbols| { | 243 | if (exp.symbols) |symbols| { |
| 377 | for (symbols) |sym_name| { | 244 | for (symbols) |sym_name| { |
| 378 | try self.addSymbol(allocator, sym_name); | 245 | try self.addExport(gpa, sym_name, .{}); |
| 379 | } | 246 | } |
| 380 | } | 247 | } |
| 381 | 248 | ||
| 382 | if (exp.weak_symbols) |symbols| { | 249 | if (exp.weak_symbols) |symbols| { |
| 383 | for (symbols) |sym_name| { | 250 | for (symbols) |sym_name| { |
| 384 | try self.addWeakSymbol(allocator, sym_name); | 251 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 385 | } | 252 | } |
| 386 | } | 253 | } |
| 387 | 254 | ||
| 388 | if (exp.objc_classes) |objc_classes| { | 255 | if (exp.objc_classes) |objc_classes| { |
| 389 | for (objc_classes) |class_name| { | 256 | for (objc_classes) |class_name| { |
| 390 | try self.addObjCClassSymbol(allocator, class_name); | 257 | try self.addObjCClass(gpa, class_name); |
| 391 | } | 258 | } |
| 392 | } | 259 | } |
| 393 | 260 | ||
| 394 | if (exp.objc_ivars) |objc_ivars| { | 261 | if (exp.objc_ivars) |objc_ivars| { |
| 395 | for (objc_ivars) |ivar| { | 262 | for (objc_ivars) |ivar| { |
| 396 | try self.addObjCIVarSymbol(allocator, ivar); | 263 | try self.addObjCIVar(gpa, ivar); |
| 397 | } | 264 | } |
| 398 | } | 265 | } |
| 399 | 266 | ||
| 400 | if (exp.objc_eh_types) |objc_eh_types| { | 267 | if (exp.objc_eh_types) |objc_eh_types| { |
| 401 | for (objc_eh_types) |eht| { | 268 | for (objc_eh_types) |eht| { |
| 402 | try self.addObjCEhTypeSymbol(allocator, eht); | 269 | try self.addObjCEhType(gpa, eht); |
| 403 | } | 270 | } |
| 404 | } | 271 | } |
| 405 | 272 | ||
| 406 | // TODO track which libs were already parsed in different steps | ||
| 407 | if (exp.re_exports) |re_exports| { | 273 | if (exp.re_exports) |re_exports| { |
| 408 | for (re_exports) |lib| { | 274 | for (re_exports) |lib| { |
| 409 | if (umbrella_libs.contains(lib)) continue; | 275 | if (umbrella_libs.contains(lib)) continue; |
| 410 | 276 | ||
| 411 | log.debug(" (found re-export '{s}')", .{lib}); | 277 | log.debug(" (found re-export '{s}')", .{lib}); |
| 412 | 278 | ||
| 413 | const dep_id = try Id.default(allocator, lib); | 279 | const dep_id = try Id.default(gpa, lib); |
| 414 | try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); | 280 | try self.dependents.append(gpa, dep_id); |
| 415 | } | 281 | } |
| 416 | } | 282 | } |
| 417 | } | 283 | } |
| ... | @@ -424,31 +290,31 @@ pub fn parseFromStub( | ... | @@ -424,31 +290,31 @@ pub fn parseFromStub( |
| 424 | 290 | ||
| 425 | if (exp.symbols) |symbols| { | 291 | if (exp.symbols) |symbols| { |
| 426 | for (symbols) |sym_name| { | 292 | for (symbols) |sym_name| { |
| 427 | try self.addSymbol(allocator, sym_name); | 293 | try self.addExport(gpa, sym_name, .{}); |
| 428 | } | 294 | } |
| 429 | } | 295 | } |
| 430 | 296 | ||
| 431 | if (exp.weak_symbols) |symbols| { | 297 | if (exp.weak_symbols) |symbols| { |
| 432 | for (symbols) |sym_name| { | 298 | for (symbols) |sym_name| { |
| 433 | try self.addWeakSymbol(allocator, sym_name); | 299 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 434 | } | 300 | } |
| 435 | } | 301 | } |
| 436 | 302 | ||
| 437 | if (exp.objc_classes) |classes| { | 303 | if (exp.objc_classes) |classes| { |
| 438 | for (classes) |sym_name| { | 304 | for (classes) |sym_name| { |
| 439 | try self.addObjCClassSymbol(allocator, sym_name); | 305 | try self.addObjCClass(gpa, sym_name); |
| 440 | } | 306 | } |
| 441 | } | 307 | } |
| 442 | 308 | ||
| 443 | if (exp.objc_ivars) |objc_ivars| { | 309 | if (exp.objc_ivars) |objc_ivars| { |
| 444 | for (objc_ivars) |ivar| { | 310 | for (objc_ivars) |ivar| { |
| 445 | try self.addObjCIVarSymbol(allocator, ivar); | 311 | try self.addObjCIVar(gpa, ivar); |
| 446 | } | 312 | } |
| 447 | } | 313 | } |
| 448 | 314 | ||
| 449 | if (exp.objc_eh_types) |objc_eh_types| { | 315 | if (exp.objc_eh_types) |objc_eh_types| { |
| 450 | for (objc_eh_types) |eht| { | 316 | for (objc_eh_types) |eht| { |
| 451 | try self.addObjCEhTypeSymbol(allocator, eht); | 317 | try self.addObjCEhType(gpa, eht); |
| 452 | } | 318 | } |
| 453 | } | 319 | } |
| 454 | } | 320 | } |
| ... | @@ -460,31 +326,31 @@ pub fn parseFromStub( | ... | @@ -460,31 +326,31 @@ pub fn parseFromStub( |
| 460 | 326 | ||
| 461 | if (reexp.symbols) |symbols| { | 327 | if (reexp.symbols) |symbols| { |
| 462 | for (symbols) |sym_name| { | 328 | for (symbols) |sym_name| { |
| 463 | try self.addSymbol(allocator, sym_name); | 329 | try self.addExport(gpa, sym_name, .{}); |
| 464 | } | 330 | } |
| 465 | } | 331 | } |
| 466 | 332 | ||
| 467 | if (reexp.weak_symbols) |symbols| { | 333 | if (reexp.weak_symbols) |symbols| { |
| 468 | for (symbols) |sym_name| { | 334 | for (symbols) |sym_name| { |
| 469 | try self.addWeakSymbol(allocator, sym_name); | 335 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 470 | } | 336 | } |
| 471 | } | 337 | } |
| 472 | 338 | ||
| 473 | if (reexp.objc_classes) |classes| { | 339 | if (reexp.objc_classes) |classes| { |
| 474 | for (classes) |sym_name| { | 340 | for (classes) |sym_name| { |
| 475 | try self.addObjCClassSymbol(allocator, sym_name); | 341 | try self.addObjCClass(gpa, sym_name); |
| 476 | } | 342 | } |
| 477 | } | 343 | } |
| 478 | 344 | ||
| 479 | if (reexp.objc_ivars) |objc_ivars| { | 345 | if (reexp.objc_ivars) |objc_ivars| { |
| 480 | for (objc_ivars) |ivar| { | 346 | for (objc_ivars) |ivar| { |
| 481 | try self.addObjCIVarSymbol(allocator, ivar); | 347 | try self.addObjCIVar(gpa, ivar); |
| 482 | } | 348 | } |
| 483 | } | 349 | } |
| 484 | 350 | ||
| 485 | if (reexp.objc_eh_types) |objc_eh_types| { | 351 | if (reexp.objc_eh_types) |objc_eh_types| { |
| 486 | for (objc_eh_types) |eht| { | 352 | for (objc_eh_types) |eht| { |
| 487 | try self.addObjCEhTypeSymbol(allocator, eht); | 353 | try self.addObjCEhType(gpa, eht); |
| 488 | } | 354 | } |
| 489 | } | 355 | } |
| 490 | } | 356 | } |
| ... | @@ -492,19 +358,19 @@ pub fn parseFromStub( | ... | @@ -492,19 +358,19 @@ pub fn parseFromStub( |
| 492 | 358 | ||
| 493 | if (stub.objc_classes) |classes| { | 359 | if (stub.objc_classes) |classes| { |
| 494 | for (classes) |sym_name| { | 360 | for (classes) |sym_name| { |
| 495 | try self.addObjCClassSymbol(allocator, sym_name); | 361 | try self.addObjCClass(gpa, sym_name); |
| 496 | } | 362 | } |
| 497 | } | 363 | } |
| 498 | 364 | ||
| 499 | if (stub.objc_ivars) |objc_ivars| { | 365 | if (stub.objc_ivars) |objc_ivars| { |
| 500 | for (objc_ivars) |ivar| { | 366 | for (objc_ivars) |ivar| { |
| 501 | try self.addObjCIVarSymbol(allocator, ivar); | 367 | try self.addObjCIVar(gpa, ivar); |
| 502 | } | 368 | } |
| 503 | } | 369 | } |
| 504 | 370 | ||
| 505 | if (stub.objc_eh_types) |objc_eh_types| { | 371 | if (stub.objc_eh_types) |objc_eh_types| { |
| 506 | for (objc_eh_types) |eht| { | 372 | for (objc_eh_types) |eht| { |
| 507 | try self.addObjCEhTypeSymbol(allocator, eht); | 373 | try self.addObjCEhType(gpa, eht); |
| 508 | } | 374 | } |
| 509 | } | 375 | } |
| 510 | }, | 376 | }, |
| ... | @@ -514,10 +380,9 @@ pub fn parseFromStub( | ... | @@ -514,10 +380,9 @@ pub fn parseFromStub( |
| 514 | // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include | 380 | // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include |
| 515 | // re-exports directly in the stub file. | 381 | // re-exports directly in the stub file. |
| 516 | for (lib_stub.inner) |elem| { | 382 | for (lib_stub.inner) |elem| { |
| 517 | if (elem == .v3) break; | 383 | if (elem == .v3) continue; |
| 518 | const stub = elem.v4; | 384 | const stub = elem.v4; |
| 519 | 385 | ||
| 520 | // TODO track which libs were already parsed in different steps | ||
| 521 | if (stub.reexported_libraries) |reexports| { | 386 | if (stub.reexported_libraries) |reexports| { |
| 522 | for (reexports) |reexp| { | 387 | for (reexports) |reexp| { |
| 523 | if (!matcher.matchesTarget(reexp.targets)) continue; | 388 | if (!matcher.matchesTarget(reexp.targets)) continue; |
| ... | @@ -527,30 +392,437 @@ pub fn parseFromStub( | ... | @@ -527,30 +392,437 @@ pub fn parseFromStub( |
| 527 | 392 | ||
| 528 | log.debug(" (found re-export '{s}')", .{lib}); | 393 | log.debug(" (found re-export '{s}')", .{lib}); |
| 529 | 394 | ||
| 530 | const dep_id = try Id.default(allocator, lib); | 395 | const dep_id = try Id.default(gpa, lib); |
| 531 | try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); | 396 | try self.dependents.append(gpa, dep_id); |
| 532 | } | 397 | } |
| 533 | } | 398 | } |
| 534 | } | 399 | } |
| 535 | } | 400 | } |
| 536 | } | 401 | } |
| 537 | 402 | ||
| 538 | const Dylib = @This(); | 403 | fn addObjCClass(self: *Dylib, allocator: Allocator, name: []const u8) !void { |
| 404 | try self.addObjCExport(allocator, "_OBJC_CLASS_", name); | ||
| 405 | try self.addObjCExport(allocator, "_OBJC_METACLASS_", name); | ||
| 406 | } | ||
| 407 | |||
| 408 | fn addObjCIVar(self: *Dylib, allocator: Allocator, name: []const u8) !void { | ||
| 409 | try self.addObjCExport(allocator, "_OBJC_IVAR_", name); | ||
| 410 | } | ||
| 411 | |||
| 412 | fn addObjCEhType(self: *Dylib, allocator: Allocator, name: []const u8) !void { | ||
| 413 | try self.addObjCExport(allocator, "_OBJC_EHTYPE_", name); | ||
| 414 | } | ||
| 415 | |||
| 416 | fn addObjCExport( | ||
| 417 | self: *Dylib, | ||
| 418 | allocator: Allocator, | ||
| 419 | comptime prefix: []const u8, | ||
| 420 | name: []const u8, | ||
| 421 | ) !void { | ||
| 422 | const full_name = try std.fmt.allocPrint(allocator, prefix ++ "$_{s}", .{name}); | ||
| 423 | defer allocator.free(full_name); | ||
| 424 | try self.addExport(allocator, full_name, .{}); | ||
| 425 | } | ||
| 426 | |||
| 427 | pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void { | ||
| 428 | const gpa = macho_file.base.allocator; | ||
| 429 | |||
| 430 | try self.symbols.ensureTotalCapacityPrecise(gpa, self.exports.items(.name).len); | ||
| 431 | |||
| 432 | for (self.exports.items(.name)) |noff| { | ||
| 433 | const name = self.getString(noff); | ||
| 434 | const off = try macho_file.string_intern.insert(gpa, name); | ||
| 435 | const gop = try macho_file.getOrCreateGlobal(off); | ||
| 436 | self.symbols.addOneAssumeCapacity().* = gop.index; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | fn initPlatform(self: *Dylib) void { | ||
| 441 | var it = LoadCommandIterator{ | ||
| 442 | .ncmds = self.header.?.ncmds, | ||
| 443 | .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | ||
| 444 | }; | ||
| 445 | self.platform = while (it.next()) |cmd| { | ||
| 446 | switch (cmd.cmd()) { | ||
| 447 | .BUILD_VERSION, | ||
| 448 | .VERSION_MIN_MACOSX, | ||
| 449 | .VERSION_MIN_IPHONEOS, | ||
| 450 | .VERSION_MIN_TVOS, | ||
| 451 | .VERSION_MIN_WATCHOS, | ||
| 452 | => break MachO.Options.Platform.fromLoadCommand(cmd), | ||
| 453 | else => {}, | ||
| 454 | } | ||
| 455 | } else null; | ||
| 456 | } | ||
| 457 | |||
| 458 | pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) void { | ||
| 459 | const tracy = trace(@src()); | ||
| 460 | defer tracy.end(); | ||
| 461 | |||
| 462 | if (!self.explicit and !self.hoisted) return; | ||
| 463 | |||
| 464 | for (self.symbols.items, self.exports.items(.flags)) |index, flags| { | ||
| 465 | const global = macho_file.getSymbol(index); | ||
| 466 | if (self.asFile().getSymbolRank(.{ | ||
| 467 | .weak = flags.weak, | ||
| 468 | }) < global.getSymbolRank(macho_file)) { | ||
| 469 | global.value = 0; | ||
| 470 | global.atom = 0; | ||
| 471 | global.nlist_idx = 0; | ||
| 472 | global.file = self.index; | ||
| 473 | global.flags.weak = flags.weak; | ||
| 474 | global.flags.weak_ref = false; | ||
| 475 | global.flags.tlv = flags.tlv; | ||
| 476 | global.flags.dyn_ref = false; | ||
| 477 | global.flags.tentative = false; | ||
| 478 | global.visibility = .global; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | pub fn resetGlobals(self: *Dylib, macho_file: *MachO) void { | ||
| 484 | for (self.symbols.items) |sym_index| { | ||
| 485 | const sym = macho_file.getSymbol(sym_index); | ||
| 486 | const name = sym.name; | ||
| 487 | sym.* = .{}; | ||
| 488 | sym.name = name; | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | pub fn isAlive(self: Dylib, macho_file: *MachO) bool { | ||
| 493 | if (!macho_file.options.dead_strip_dylibs) return self.explicit or self.referenced or self.needed; | ||
| 494 | return self.referenced or self.needed; | ||
| 495 | } | ||
| 496 | |||
| 497 | pub fn markReferenced(self: *Dylib, macho_file: *MachO) void { | ||
| 498 | const tracy = trace(@src()); | ||
| 499 | defer tracy.end(); | ||
| 500 | |||
| 501 | for (self.symbols.items) |global_index| { | ||
| 502 | const global = macho_file.getSymbol(global_index); | ||
| 503 | const file_ptr = global.getFile(macho_file) orelse continue; | ||
| 504 | if (file_ptr.getIndex() != self.index) continue; | ||
| 505 | if (global.isLocal()) continue; | ||
| 506 | self.referenced = true; | ||
| 507 | break; | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) !void { | ||
| 512 | const tracy = trace(@src()); | ||
| 513 | defer tracy.end(); | ||
| 514 | |||
| 515 | for (self.symbols.items) |global_index| { | ||
| 516 | const global = macho_file.getSymbol(global_index); | ||
| 517 | const file_ptr = global.getFile(macho_file) orelse continue; | ||
| 518 | if (file_ptr.getIndex() != self.index) continue; | ||
| 519 | if (global.isLocal()) continue; | ||
| 520 | assert(global.flags.import); | ||
| 521 | global.flags.output_symtab = true; | ||
| 522 | try global.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file); | ||
| 523 | self.output_symtab_ctx.nimports += 1; | ||
| 524 | self.output_symtab_ctx.strsize += @as(u32, @intCast(global.getName(macho_file).len + 1)); | ||
| 525 | } | ||
| 526 | } | ||
| 527 | |||
| 528 | pub fn writeSymtab(self: Dylib, macho_file: *MachO) void { | ||
| 529 | const tracy = trace(@src()); | ||
| 530 | defer tracy.end(); | ||
| 531 | |||
| 532 | for (self.symbols.items) |global_index| { | ||
| 533 | const global = macho_file.getSymbol(global_index); | ||
| 534 | const file = global.getFile(macho_file) orelse continue; | ||
| 535 | if (file.getIndex() != self.index) continue; | ||
| 536 | const idx = global.getOutputSymtabIndex(macho_file) orelse continue; | ||
| 537 | const n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 538 | macho_file.strtab.appendSliceAssumeCapacity(global.getName(macho_file)); | ||
| 539 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 540 | const out_sym = &macho_file.symtab.items[idx]; | ||
| 541 | out_sym.n_strx = n_strx; | ||
| 542 | global.setOutputSym(macho_file, out_sym); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib { | ||
| 547 | return macho_file.getFile(self.umbrella).?.dylib; | ||
| 548 | } | ||
| 549 | |||
| 550 | fn getLoadCommand(self: Dylib, lc: macho.LC) ?LoadCommandIterator.LoadCommand { | ||
| 551 | var it = LoadCommandIterator{ | ||
| 552 | .ncmds = self.header.?.ncmds, | ||
| 553 | .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | ||
| 554 | }; | ||
| 555 | while (it.next()) |cmd| { | ||
| 556 | if (cmd.cmd() == lc) return cmd; | ||
| 557 | } else return null; | ||
| 558 | } | ||
| 559 | |||
| 560 | fn insertString(self: *Dylib, allocator: Allocator, name: []const u8) !u32 { | ||
| 561 | const off = @as(u32, @intCast(self.strtab.items.len)); | ||
| 562 | try self.strtab.writer(allocator).print("{s}\x00", .{name}); | ||
| 563 | return off; | ||
| 564 | } | ||
| 565 | |||
| 566 | pub inline fn getString(self: Dylib, off: u32) [:0]const u8 { | ||
| 567 | assert(off < self.strtab.items.len); | ||
| 568 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0); | ||
| 569 | } | ||
| 570 | |||
| 571 | pub fn asFile(self: *Dylib) File { | ||
| 572 | return .{ .dylib = self }; | ||
| 573 | } | ||
| 574 | |||
| 575 | pub fn format( | ||
| 576 | self: *Dylib, | ||
| 577 | comptime unused_fmt_string: []const u8, | ||
| 578 | options: std.fmt.FormatOptions, | ||
| 579 | writer: anytype, | ||
| 580 | ) !void { | ||
| 581 | _ = self; | ||
| 582 | _ = unused_fmt_string; | ||
| 583 | _ = options; | ||
| 584 | _ = writer; | ||
| 585 | @compileError("do not format dylib directly"); | ||
| 586 | } | ||
| 587 | |||
| 588 | pub fn fmtSymtab(self: *Dylib, macho_file: *MachO) std.fmt.Formatter(formatSymtab) { | ||
| 589 | return .{ .data = .{ | ||
| 590 | .dylib = self, | ||
| 591 | .macho_file = macho_file, | ||
| 592 | } }; | ||
| 593 | } | ||
| 594 | |||
| 595 | const FormatContext = struct { | ||
| 596 | dylib: *Dylib, | ||
| 597 | macho_file: *MachO, | ||
| 598 | }; | ||
| 599 | |||
| 600 | fn formatSymtab( | ||
| 601 | ctx: FormatContext, | ||
| 602 | comptime unused_fmt_string: []const u8, | ||
| 603 | options: std.fmt.FormatOptions, | ||
| 604 | writer: anytype, | ||
| 605 | ) !void { | ||
| 606 | _ = unused_fmt_string; | ||
| 607 | _ = options; | ||
| 608 | const dylib = ctx.dylib; | ||
| 609 | try writer.writeAll(" globals\n"); | ||
| 610 | for (dylib.symbols.items) |index| { | ||
| 611 | const global = ctx.macho_file.getSymbol(index); | ||
| 612 | try writer.print(" {}\n", .{global.fmt(ctx.macho_file)}); | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | pub const TargetMatcher = struct { | ||
| 617 | allocator: Allocator, | ||
| 618 | cpu_arch: std.Target.Cpu.Arch, | ||
| 619 | platform: macho.PLATFORM, | ||
| 620 | target_strings: std.ArrayListUnmanaged([]const u8) = .{}, | ||
| 621 | |||
| 622 | pub fn init(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) !TargetMatcher { | ||
| 623 | var self = TargetMatcher{ | ||
| 624 | .allocator = allocator, | ||
| 625 | .cpu_arch = cpu_arch, | ||
| 626 | .platform = platform, | ||
| 627 | }; | ||
| 628 | const apple_string = try targetToAppleString(allocator, cpu_arch, platform); | ||
| 629 | try self.target_strings.append(allocator, apple_string); | ||
| 630 | |||
| 631 | switch (platform) { | ||
| 632 | .IOSSIMULATOR, .TVOSSIMULATOR, .WATCHOSSIMULATOR => { | ||
| 633 | // For Apple simulator targets, linking gets tricky as we need to link against the simulator | ||
| 634 | // hosts dylibs too. | ||
| 635 | const host_target = try targetToAppleString(allocator, cpu_arch, .MACOS); | ||
| 636 | try self.target_strings.append(allocator, host_target); | ||
| 637 | }, | ||
| 638 | else => {}, | ||
| 639 | } | ||
| 640 | |||
| 641 | return self; | ||
| 642 | } | ||
| 643 | |||
| 644 | pub fn deinit(self: *TargetMatcher) void { | ||
| 645 | for (self.target_strings.items) |t| { | ||
| 646 | self.allocator.free(t); | ||
| 647 | } | ||
| 648 | self.target_strings.deinit(self.allocator); | ||
| 649 | } | ||
| 650 | |||
| 651 | inline fn cpuArchToAppleString(cpu_arch: std.Target.Cpu.Arch) []const u8 { | ||
| 652 | return switch (cpu_arch) { | ||
| 653 | .aarch64 => "arm64", | ||
| 654 | .x86_64 => "x86_64", | ||
| 655 | else => unreachable, | ||
| 656 | }; | ||
| 657 | } | ||
| 658 | |||
| 659 | pub fn targetToAppleString(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) ![]const u8 { | ||
| 660 | const arch = cpuArchToAppleString(cpu_arch); | ||
| 661 | const plat = switch (platform) { | ||
| 662 | .MACOS => "macos", | ||
| 663 | .IOS => "ios", | ||
| 664 | .TVOS => "tvos", | ||
| 665 | .WATCHOS => "watchos", | ||
| 666 | .IOSSIMULATOR => "ios-simulator", | ||
| 667 | .TVOSSIMULATOR => "tvos-simulator", | ||
| 668 | .WATCHOSSIMULATOR => "watchos-simulator", | ||
| 669 | .BRIDGEOS => "bridgeos", | ||
| 670 | .MACCATALYST => "maccatalyst", | ||
| 671 | .DRIVERKIT => "driverkit", | ||
| 672 | else => unreachable, | ||
| 673 | }; | ||
| 674 | return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, plat }); | ||
| 675 | } | ||
| 676 | |||
| 677 | fn hasValue(stack: []const []const u8, needle: []const u8) bool { | ||
| 678 | for (stack) |v| { | ||
| 679 | if (mem.eql(u8, v, needle)) return true; | ||
| 680 | } | ||
| 681 | return false; | ||
| 682 | } | ||
| 683 | |||
| 684 | fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { | ||
| 685 | return hasValue(archs, cpuArchToAppleString(self.cpu_arch)); | ||
| 686 | } | ||
| 687 | |||
| 688 | fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { | ||
| 689 | for (self.target_strings.items) |t| { | ||
| 690 | if (hasValue(targets, t)) return true; | ||
| 691 | } | ||
| 692 | return false; | ||
| 693 | } | ||
| 694 | |||
| 695 | pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool { | ||
| 696 | var arena = std.heap.ArenaAllocator.init(self.allocator); | ||
| 697 | defer arena.deinit(); | ||
| 698 | |||
| 699 | const targets = switch (tbd) { | ||
| 700 | .v3 => |v3| blk: { | ||
| 701 | var targets = std.ArrayList([]const u8).init(arena.allocator()); | ||
| 702 | for (v3.archs) |arch| { | ||
| 703 | const target = try std.fmt.allocPrint(arena.allocator(), "{s}-{s}", .{ arch, v3.platform }); | ||
| 704 | try targets.append(target); | ||
| 705 | } | ||
| 706 | break :blk targets.items; | ||
| 707 | }, | ||
| 708 | .v4 => |v4| v4.targets, | ||
| 709 | }; | ||
| 710 | |||
| 711 | return self.matchesTarget(targets); | ||
| 712 | } | ||
| 713 | }; | ||
| 714 | |||
| 715 | pub const Id = struct { | ||
| 716 | name: []const u8, | ||
| 717 | timestamp: u32, | ||
| 718 | current_version: u32, | ||
| 719 | compatibility_version: u32, | ||
| 720 | |||
| 721 | pub fn default(allocator: Allocator, name: []const u8) !Id { | ||
| 722 | return Id{ | ||
| 723 | .name = try allocator.dupe(u8, name), | ||
| 724 | .timestamp = 2, | ||
| 725 | .current_version = 0x10000, | ||
| 726 | .compatibility_version = 0x10000, | ||
| 727 | }; | ||
| 728 | } | ||
| 729 | |||
| 730 | pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id { | ||
| 731 | return Id{ | ||
| 732 | .name = try allocator.dupe(u8, name), | ||
| 733 | .timestamp = lc.dylib.timestamp, | ||
| 734 | .current_version = lc.dylib.current_version, | ||
| 735 | .compatibility_version = lc.dylib.compatibility_version, | ||
| 736 | }; | ||
| 737 | } | ||
| 738 | |||
| 739 | pub fn deinit(id: Id, allocator: Allocator) void { | ||
| 740 | allocator.free(id.name); | ||
| 741 | } | ||
| 742 | |||
| 743 | pub const ParseError = fmt.ParseIntError || fmt.BufPrintError; | ||
| 744 | |||
| 745 | pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { | ||
| 746 | id.current_version = try parseVersion(version); | ||
| 747 | } | ||
| 748 | |||
| 749 | pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void { | ||
| 750 | id.compatibility_version = try parseVersion(version); | ||
| 751 | } | ||
| 752 | |||
| 753 | fn parseVersion(version: anytype) ParseError!u32 { | ||
| 754 | const string = blk: { | ||
| 755 | switch (version) { | ||
| 756 | .int => |int| { | ||
| 757 | var out: u32 = 0; | ||
| 758 | const major = math.cast(u16, int) orelse return error.Overflow; | ||
| 759 | out += @as(u32, @intCast(major)) << 16; | ||
| 760 | return out; | ||
| 761 | }, | ||
| 762 | .float => |float| { | ||
| 763 | var buf: [256]u8 = undefined; | ||
| 764 | break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float}); | ||
| 765 | }, | ||
| 766 | .string => |string| { | ||
| 767 | break :blk string; | ||
| 768 | }, | ||
| 769 | } | ||
| 770 | }; | ||
| 771 | |||
| 772 | var out: u32 = 0; | ||
| 773 | var values: [3][]const u8 = undefined; | ||
| 774 | |||
| 775 | var split = mem.split(u8, string, "."); | ||
| 776 | var count: u4 = 0; | ||
| 777 | while (split.next()) |value| { | ||
| 778 | if (count > 2) { | ||
| 779 | log.debug("malformed version field: {s}", .{string}); | ||
| 780 | return 0x10000; | ||
| 781 | } | ||
| 782 | values[count] = value; | ||
| 783 | count += 1; | ||
| 784 | } | ||
| 785 | |||
| 786 | if (count > 2) { | ||
| 787 | out += try fmt.parseInt(u8, values[2], 10); | ||
| 788 | } | ||
| 789 | if (count > 1) { | ||
| 790 | out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8; | ||
| 791 | } | ||
| 792 | out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16; | ||
| 793 | |||
| 794 | return out; | ||
| 795 | } | ||
| 796 | }; | ||
| 797 | |||
| 798 | const Export = struct { | ||
| 799 | name: u32, | ||
| 800 | flags: Flags, | ||
| 801 | |||
| 802 | const Flags = packed struct { | ||
| 803 | abs: bool = false, | ||
| 804 | weak: bool = false, | ||
| 805 | tlv: bool = false, | ||
| 806 | }; | ||
| 807 | }; | ||
| 539 | 808 | ||
| 540 | const std = @import("std"); | ||
| 541 | const assert = std.debug.assert; | 809 | const assert = std.debug.assert; |
| 810 | const fat = @import("fat.zig"); | ||
| 542 | const fs = std.fs; | 811 | const fs = std.fs; |
| 543 | const fmt = std.fmt; | 812 | const fmt = std.fmt; |
| 544 | const log = std.log.scoped(.link); | 813 | const log = std.log.scoped(.link); |
| 545 | const macho = std.macho; | 814 | const macho = std.macho; |
| 546 | const math = std.math; | 815 | const math = std.math; |
| 547 | const mem = std.mem; | 816 | const mem = std.mem; |
| 548 | const fat = @import("fat.zig"); | ||
| 549 | const tapi = @import("../tapi.zig"); | 817 | const tapi = @import("../tapi.zig"); |
| 818 | const trace = @import("../tracy.zig").trace; | ||
| 819 | const std = @import("std"); | ||
| 550 | 820 | ||
| 551 | const Allocator = mem.Allocator; | 821 | const Allocator = mem.Allocator; |
| 822 | const Dylib = @This(); | ||
| 823 | const File = @import("file.zig").File; | ||
| 552 | const LibStub = tapi.LibStub; | 824 | const LibStub = tapi.LibStub; |
| 553 | const LoadCommandIterator = macho.LoadCommandIterator; | 825 | const LoadCommandIterator = macho.LoadCommandIterator; |
| 554 | const MachO = @import("../MachO.zig"); | 826 | const MachO = @import("../MachO.zig"); |
| 555 | const Platform = @import("load_commands.zig").Platform; | 827 | const Symbol = @import("Symbol.zig"); |
| 556 | const Tbd = tapi.Tbd; | 828 | const Tbd = tapi.Tbd; |
src/link/MachO/InternalObject.zig created+249| ... | @@ -0,0 +1,249 @@ | ||
| 1 | index: File.Index, | ||
| 2 | |||
| 3 | sections: std.MultiArrayList(Section) = .{}, | ||
| 4 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | ||
| 5 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 6 | |||
| 7 | objc_methnames: std.ArrayListUnmanaged(u8) = .{}, | ||
| 8 | objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64), | ||
| 9 | |||
| 10 | output_symtab_ctx: MachO.SymtabCtx = .{}, | ||
| 11 | |||
| 12 | pub fn deinit(self: *InternalObject, allocator: Allocator) void { | ||
| 13 | for (self.sections.items(.relocs)) |*relocs| { | ||
| 14 | relocs.deinit(allocator); | ||
| 15 | } | ||
| 16 | self.sections.deinit(allocator); | ||
| 17 | self.atoms.deinit(allocator); | ||
| 18 | self.symbols.deinit(allocator); | ||
| 19 | self.objc_methnames.deinit(allocator); | ||
| 20 | } | ||
| 21 | |||
| 22 | pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO) !Symbol.Index { | ||
| 23 | const gpa = macho_file.base.allocator; | ||
| 24 | try self.symbols.ensureUnusedCapacity(gpa, 1); | ||
| 25 | const off = try macho_file.string_intern.insert(gpa, name); | ||
| 26 | const gop = try macho_file.getOrCreateGlobal(off); | ||
| 27 | self.symbols.addOneAssumeCapacity().* = gop.index; | ||
| 28 | const sym = macho_file.getSymbol(gop.index); | ||
| 29 | sym.* = .{ .name = off, .file = self.index }; | ||
| 30 | return gop.index; | ||
| 31 | } | ||
| 32 | |||
| 33 | /// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs. | ||
| 34 | pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !u32 { | ||
| 35 | const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file); | ||
| 36 | return try self.addObjcSelrefsSection(sym_name, methname_atom_index, macho_file); | ||
| 37 | } | ||
| 38 | |||
| 39 | fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index { | ||
| 40 | const gpa = macho_file.base.allocator; | ||
| 41 | const atom_index = try macho_file.addAtom(); | ||
| 42 | try self.atoms.append(gpa, atom_index); | ||
| 43 | |||
| 44 | const name = try std.fmt.allocPrintZ(gpa, "__TEXT$__objc_methname${s}", .{methname}); | ||
| 45 | defer gpa.free(name); | ||
| 46 | const atom = macho_file.getAtom(atom_index).?; | ||
| 47 | atom.atom_index = atom_index; | ||
| 48 | atom.name = try macho_file.string_intern.insert(gpa, name); | ||
| 49 | atom.file = self.index; | ||
| 50 | atom.size = methname.len + 1; | ||
| 51 | atom.alignment = 0; | ||
| 52 | |||
| 53 | const n_sect = try self.addSection(gpa, "__TEXT", "__objc_methname"); | ||
| 54 | const sect = &self.sections.items(.header)[n_sect]; | ||
| 55 | sect.flags = macho.S_CSTRING_LITERALS; | ||
| 56 | sect.size = atom.size; | ||
| 57 | sect.@"align" = 0; | ||
| 58 | atom.n_sect = n_sect; | ||
| 59 | self.sections.items(.extra)[n_sect].is_objc_methname = true; | ||
| 60 | |||
| 61 | sect.offset = @intCast(self.objc_methnames.items.len); | ||
| 62 | try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1); | ||
| 63 | self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable; | ||
| 64 | |||
| 65 | return atom_index; | ||
| 66 | } | ||
| 67 | |||
| 68 | fn addObjcSelrefsSection( | ||
| 69 | self: *InternalObject, | ||
| 70 | methname: []const u8, | ||
| 71 | methname_atom_index: Atom.Index, | ||
| 72 | macho_file: *MachO, | ||
| 73 | ) !Atom.Index { | ||
| 74 | const gpa = macho_file.base.allocator; | ||
| 75 | const atom_index = try macho_file.addAtom(); | ||
| 76 | try self.atoms.append(gpa, atom_index); | ||
| 77 | |||
| 78 | const name = try std.fmt.allocPrintZ(gpa, "__DATA$__objc_selrefs${s}", .{methname}); | ||
| 79 | defer gpa.free(name); | ||
| 80 | const atom = macho_file.getAtom(atom_index).?; | ||
| 81 | atom.atom_index = atom_index; | ||
| 82 | atom.name = try macho_file.string_intern.insert(gpa, name); | ||
| 83 | atom.file = self.index; | ||
| 84 | atom.size = @sizeOf(u64); | ||
| 85 | atom.alignment = 3; | ||
| 86 | |||
| 87 | const n_sect = try self.addSection(gpa, "__DATA", "__objc_selrefs"); | ||
| 88 | const sect = &self.sections.items(.header)[n_sect]; | ||
| 89 | sect.flags = macho.S_LITERAL_POINTERS | macho.S_ATTR_NO_DEAD_STRIP; | ||
| 90 | sect.offset = 0; | ||
| 91 | sect.size = atom.size; | ||
| 92 | sect.@"align" = 3; | ||
| 93 | atom.n_sect = n_sect; | ||
| 94 | self.sections.items(.extra)[n_sect].is_objc_selref = true; | ||
| 95 | |||
| 96 | const relocs = &self.sections.items(.relocs)[n_sect]; | ||
| 97 | try relocs.ensureUnusedCapacity(gpa, 1); | ||
| 98 | relocs.appendAssumeCapacity(.{ | ||
| 99 | .tag = .local, | ||
| 100 | .offset = 0, | ||
| 101 | .target = methname_atom_index, | ||
| 102 | .addend = 0, | ||
| 103 | .type = .unsigned, | ||
| 104 | .meta = .{ | ||
| 105 | .pcrel = false, | ||
| 106 | .length = 3, | ||
| 107 | .symbolnum = 0, // Only used when synthesising unwind records so can be anything | ||
| 108 | .has_subtractor = false, | ||
| 109 | }, | ||
| 110 | }); | ||
| 111 | atom.relocs = .{ .pos = 0, .len = 1 }; | ||
| 112 | |||
| 113 | return atom_index; | ||
| 114 | } | ||
| 115 | |||
| 116 | pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void { | ||
| 117 | for (self.symbols.items) |sym_index| { | ||
| 118 | const sym = macho_file.getSymbol(sym_index); | ||
| 119 | if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue; | ||
| 120 | sym.flags.output_symtab = true; | ||
| 121 | if (sym.isLocal()) { | ||
| 122 | try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file); | ||
| 123 | self.output_symtab_ctx.nlocals += 1; | ||
| 124 | } else if (sym.flags.@"export") { | ||
| 125 | try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file); | ||
| 126 | self.output_symtab_ctx.nexports += 1; | ||
| 127 | } else { | ||
| 128 | assert(sym.flags.import); | ||
| 129 | try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file); | ||
| 130 | self.output_symtab_ctx.nimports += 1; | ||
| 131 | } | ||
| 132 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | pub fn writeSymtab(self: InternalObject, macho_file: *MachO) void { | ||
| 137 | for (self.symbols.items) |sym_index| { | ||
| 138 | const sym = macho_file.getSymbol(sym_index); | ||
| 139 | if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue; | ||
| 140 | const idx = sym.getOutputSymtabIndex(macho_file) orelse continue; | ||
| 141 | const n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 142 | macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file)); | ||
| 143 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 144 | const out_sym = &macho_file.symtab.items[idx]; | ||
| 145 | out_sym.n_strx = n_strx; | ||
| 146 | sym.setOutputSym(macho_file, out_sym); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 { | ||
| 151 | const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator))); | ||
| 152 | self.sections.set(n_sect, .{ | ||
| 153 | .header = .{ | ||
| 154 | .sectname = MachO.makeStaticString(sectname), | ||
| 155 | .segname = MachO.makeStaticString(segname), | ||
| 156 | }, | ||
| 157 | }); | ||
| 158 | return n_sect; | ||
| 159 | } | ||
| 160 | |||
| 161 | pub fn getSectionData(self: *const InternalObject, index: u32) []const u8 { | ||
| 162 | const slice = self.sections.slice(); | ||
| 163 | assert(index < slice.items(.header).len); | ||
| 164 | const sect = slice.items(.header)[index]; | ||
| 165 | const extra = slice.items(.extra)[index]; | ||
| 166 | if (extra.is_objc_methname) { | ||
| 167 | return self.objc_methnames.items[sect.offset..][0..sect.size]; | ||
| 168 | } else if (extra.is_objc_selref) { | ||
| 169 | return &self.objc_selrefs; | ||
| 170 | } else @panic("ref to non-existent section"); | ||
| 171 | } | ||
| 172 | |||
| 173 | pub fn asFile(self: *InternalObject) File { | ||
| 174 | return .{ .internal = self }; | ||
| 175 | } | ||
| 176 | |||
| 177 | const FormatContext = struct { | ||
| 178 | self: *InternalObject, | ||
| 179 | macho_file: *MachO, | ||
| 180 | }; | ||
| 181 | |||
| 182 | pub fn fmtAtoms(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) { | ||
| 183 | return .{ .data = .{ | ||
| 184 | .self = self, | ||
| 185 | .macho_file = macho_file, | ||
| 186 | } }; | ||
| 187 | } | ||
| 188 | |||
| 189 | fn formatAtoms( | ||
| 190 | ctx: FormatContext, | ||
| 191 | comptime unused_fmt_string: []const u8, | ||
| 192 | options: std.fmt.FormatOptions, | ||
| 193 | writer: anytype, | ||
| 194 | ) !void { | ||
| 195 | _ = unused_fmt_string; | ||
| 196 | _ = options; | ||
| 197 | try writer.writeAll(" atoms\n"); | ||
| 198 | for (ctx.self.atoms.items) |atom_index| { | ||
| 199 | const atom = ctx.macho_file.getAtom(atom_index).?; | ||
| 200 | try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)}); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | pub fn fmtSymtab(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) { | ||
| 205 | return .{ .data = .{ | ||
| 206 | .self = self, | ||
| 207 | .macho_file = macho_file, | ||
| 208 | } }; | ||
| 209 | } | ||
| 210 | |||
| 211 | fn formatSymtab( | ||
| 212 | ctx: FormatContext, | ||
| 213 | comptime unused_fmt_string: []const u8, | ||
| 214 | options: std.fmt.FormatOptions, | ||
| 215 | writer: anytype, | ||
| 216 | ) !void { | ||
| 217 | _ = unused_fmt_string; | ||
| 218 | _ = options; | ||
| 219 | try writer.writeAll(" symbols\n"); | ||
| 220 | for (ctx.self.symbols.items) |index| { | ||
| 221 | const global = ctx.macho_file.getSymbol(index); | ||
| 222 | try writer.print(" {}\n", .{global.fmt(ctx.macho_file)}); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | const Section = struct { | ||
| 227 | header: macho.section_64, | ||
| 228 | relocs: std.ArrayListUnmanaged(Relocation) = .{}, | ||
| 229 | extra: Extra = .{}, | ||
| 230 | |||
| 231 | const Extra = packed struct { | ||
| 232 | is_objc_methname: bool = false, | ||
| 233 | is_objc_selref: bool = false, | ||
| 234 | }; | ||
| 235 | }; | ||
| 236 | |||
| 237 | const assert = std.debug.assert; | ||
| 238 | const macho = std.macho; | ||
| 239 | const mem = std.mem; | ||
| 240 | const std = @import("std"); | ||
| 241 | |||
| 242 | const Allocator = std.mem.Allocator; | ||
| 243 | const Atom = @import("Atom.zig"); | ||
| 244 | const File = @import("file.zig").File; | ||
| 245 | const InternalObject = @This(); | ||
| 246 | const MachO = @import("../MachO.zig"); | ||
| 247 | const Object = @import("Object.zig"); | ||
| 248 | const Relocation = @import("Relocation.zig"); | ||
| 249 | const Symbol = @import("Symbol.zig"); | ||
src/link/MachO/Object.zig+1892-929| ... | @@ -1,1130 +1,2093 @@ | ... | @@ -1,1130 +1,2093 @@ |
| 1 | //! Represents an input relocatable Object file. | 1 | archive: ?[]const u8 = null, |
| 2 | //! Each Object is fully loaded into memory for easier | 2 | path: []const u8, |
| 3 | //! access into different data within. | ||
| 4 | |||
| 5 | name: []const u8, | ||
| 6 | mtime: u64, | 3 | mtime: u64, |
| 7 | contents: []align(@alignOf(u64)) const u8, | 4 | data: []const u8, |
| 8 | 5 | index: File.Index, | |
| 9 | header: macho.mach_header_64 = undefined, | ||
| 10 | |||
| 11 | /// Symtab and strtab might not exist for empty object files so we use an optional | ||
| 12 | /// to signal this. | ||
| 13 | in_symtab: ?[]align(1) const macho.nlist_64 = null, | ||
| 14 | in_strtab: ?[]const u8 = null, | ||
| 15 | |||
| 16 | /// Output symtab is sorted so that we can easily reference symbols following each | ||
| 17 | /// other in address space. | ||
| 18 | /// The length of the symtab is at least of the input symtab length however there | ||
| 19 | /// can be trailing section symbols. | ||
| 20 | symtab: []macho.nlist_64 = undefined, | ||
| 21 | /// Can be undefined as set together with in_symtab. | ||
| 22 | source_symtab_lookup: []u32 = undefined, | ||
| 23 | /// Can be undefined as set together with in_symtab. | ||
| 24 | reverse_symtab_lookup: []u32 = undefined, | ||
| 25 | /// Can be undefined as set together with in_symtab. | ||
| 26 | source_address_lookup: []i64 = undefined, | ||
| 27 | /// Can be undefined as set together with in_symtab. | ||
| 28 | source_section_index_lookup: []Entry = undefined, | ||
| 29 | /// Can be undefined as set together with in_symtab. | ||
| 30 | strtab_lookup: []u32 = undefined, | ||
| 31 | /// Can be undefined as set together with in_symtab. | ||
| 32 | atom_by_index_table: []?Atom.Index = undefined, | ||
| 33 | /// Can be undefined as set together with in_symtab. | ||
| 34 | globals_lookup: []i64 = undefined, | ||
| 35 | /// Can be undefined as set together with in_symtab. | ||
| 36 | relocs_lookup: []Entry = undefined, | ||
| 37 | |||
| 38 | /// All relocations sorted and flatened, sorted by address descending | ||
| 39 | /// per section. | ||
| 40 | relocations: std.ArrayListUnmanaged(macho.relocation_info) = .{}, | ||
| 41 | /// Beginning index to the relocations array for each input section | ||
| 42 | /// defined within this Object file. | ||
| 43 | section_relocs_lookup: std.ArrayListUnmanaged(u32) = .{}, | ||
| 44 | |||
| 45 | /// Data-in-code records sorted by address. | ||
| 46 | data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, | ||
| 47 | |||
| 48 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | ||
| 49 | exec_atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | ||
| 50 | |||
| 51 | eh_frame_sect_id: ?u8 = null, | ||
| 52 | eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{}, | ||
| 53 | eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, | ||
| 54 | 6 | ||
| 55 | unwind_info_sect_id: ?u8 = null, | 7 | header: ?macho.mach_header_64 = null, |
| 56 | unwind_relocs_lookup: []Record = undefined, | 8 | sections: std.MultiArrayList(Section) = .{}, |
| 57 | unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, | 9 | symtab: std.MultiArrayList(Nlist) = .{}, |
| 10 | strtab: []const u8 = &[0]u8{}, | ||
| 58 | 11 | ||
| 59 | const Entry = struct { | 12 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, |
| 60 | start: u32 = 0, | 13 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, |
| 61 | len: u32 = 0, | ||
| 62 | }; | ||
| 63 | |||
| 64 | const Record = struct { | ||
| 65 | dead: bool, | ||
| 66 | reloc: Entry, | ||
| 67 | }; | ||
| 68 | |||
| 69 | pub fn isObject(file: std.fs.File) bool { | ||
| 70 | const reader = file.reader(); | ||
| 71 | const hdr = reader.readStruct(macho.mach_header_64) catch return false; | ||
| 72 | defer file.seekTo(0) catch {}; | ||
| 73 | return hdr.filetype == macho.MH_OBJECT; | ||
| 74 | } | ||
| 75 | 14 | ||
| 76 | pub fn deinit(self: *Object, gpa: Allocator) void { | 15 | platform: ?MachO.Options.Platform = null, |
| 77 | self.atoms.deinit(gpa); | 16 | dwarf_info: ?DwarfInfo = null, |
| 78 | self.exec_atoms.deinit(gpa); | 17 | stab_files: std.ArrayListUnmanaged(StabFile) = .{}, |
| 79 | gpa.free(self.name); | 18 | |
| 80 | gpa.free(self.contents); | 19 | eh_frame_sect_index: ?u8 = null, |
| 81 | if (self.in_symtab) |_| { | 20 | compact_unwind_sect_index: ?u8 = null, |
| 82 | gpa.free(self.source_symtab_lookup); | 21 | cies: std.ArrayListUnmanaged(Cie) = .{}, |
| 83 | gpa.free(self.reverse_symtab_lookup); | 22 | fdes: std.ArrayListUnmanaged(Fde) = .{}, |
| 84 | gpa.free(self.source_address_lookup); | 23 | eh_frame_data: std.ArrayListUnmanaged(u8) = .{}, |
| 85 | gpa.free(self.source_section_index_lookup); | 24 | unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record.Index) = .{}, |
| 86 | gpa.free(self.strtab_lookup); | 25 | |
| 87 | gpa.free(self.symtab); | 26 | alive: bool = true, |
| 88 | gpa.free(self.atom_by_index_table); | 27 | hidden: bool = false, |
| 89 | gpa.free(self.globals_lookup); | 28 | num_rebase_relocs: u32 = 0, |
| 90 | gpa.free(self.relocs_lookup); | 29 | num_bind_relocs: u32 = 0, |
| 30 | num_weak_bind_relocs: u32 = 0, | ||
| 31 | |||
| 32 | output_symtab_ctx: MachO.SymtabCtx = .{}, | ||
| 33 | |||
| 34 | pub fn deinit(self: *Object, allocator: Allocator) void { | ||
| 35 | for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| { | ||
| 36 | relocs.deinit(allocator); | ||
| 37 | sub.deinit(allocator); | ||
| 91 | } | 38 | } |
| 92 | self.eh_frame_relocs_lookup.deinit(gpa); | 39 | self.sections.deinit(allocator); |
| 93 | self.eh_frame_records_lookup.deinit(gpa); | 40 | self.symtab.deinit(allocator); |
| 94 | if (self.hasUnwindRecords()) { | 41 | self.symbols.deinit(allocator); |
| 95 | gpa.free(self.unwind_relocs_lookup); | 42 | self.atoms.deinit(allocator); |
| 43 | self.cies.deinit(allocator); | ||
| 44 | self.fdes.deinit(allocator); | ||
| 45 | self.eh_frame_data.deinit(allocator); | ||
| 46 | self.unwind_records.deinit(allocator); | ||
| 47 | if (self.dwarf_info) |*dw| dw.deinit(allocator); | ||
| 48 | for (self.stab_files.items) |*sf| { | ||
| 49 | sf.stabs.deinit(allocator); | ||
| 96 | } | 50 | } |
| 97 | self.unwind_records_lookup.deinit(gpa); | 51 | self.stab_files.deinit(allocator); |
| 98 | self.relocations.deinit(gpa); | ||
| 99 | self.section_relocs_lookup.deinit(gpa); | ||
| 100 | self.data_in_code.deinit(gpa); | ||
| 101 | } | 52 | } |
| 102 | 53 | ||
| 103 | pub fn parse(self: *Object, allocator: Allocator) !void { | 54 | pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 104 | var stream = std.io.fixedBufferStream(self.contents); | 55 | const tracy = trace(@src()); |
| 56 | defer tracy.end(); | ||
| 57 | |||
| 58 | const gpa = macho_file.base.allocator; | ||
| 59 | var stream = std.io.fixedBufferStream(self.data); | ||
| 105 | const reader = stream.reader(); | 60 | const reader = stream.reader(); |
| 106 | 61 | ||
| 107 | self.header = try reader.readStruct(macho.mach_header_64); | 62 | self.header = try reader.readStruct(macho.mach_header_64); |
| 108 | 63 | ||
| 109 | var it = LoadCommandIterator{ | 64 | if (self.getLoadCommand(.SEGMENT_64)) |lc| { |
| 110 | .ncmds = self.header.ncmds, | 65 | const sections = lc.getSections(); |
| 111 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 66 | try self.sections.ensureUnusedCapacity(gpa, sections.len); |
| 112 | }; | 67 | for (sections) |sect| { |
| 113 | const nsects = self.getSourceSections().len; | 68 | const index = try self.sections.addOne(gpa); |
| 114 | 69 | self.sections.set(index, .{ .header = sect }); | |
| 115 | // Prepopulate relocations per section lookup table. | 70 | |
| 116 | try self.section_relocs_lookup.resize(allocator, nsects); | 71 | if (mem.eql(u8, sect.sectName(), "__eh_frame")) { |
| 117 | @memset(self.section_relocs_lookup.items, 0); | 72 | self.eh_frame_sect_index = @intCast(index); |
| 118 | 73 | } else if (mem.eql(u8, sect.sectName(), "__compact_unwind")) { | |
| 119 | // Parse symtab. | 74 | self.compact_unwind_sect_index = @intCast(index); |
| 120 | const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { | 75 | } |
| 121 | .SYMTAB => break cmd.cast(macho.symtab_command).?, | 76 | } |
| 122 | else => {}, | ||
| 123 | } else return; | ||
| 124 | |||
| 125 | self.in_symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.contents.ptr + symtab.symoff))[0..symtab.nsyms]; | ||
| 126 | self.in_strtab = self.contents[symtab.stroff..][0..symtab.strsize]; | ||
| 127 | |||
| 128 | self.symtab = try allocator.alloc(macho.nlist_64, self.in_symtab.?.len + nsects); | ||
| 129 | self.source_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len); | ||
| 130 | self.reverse_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len); | ||
| 131 | self.strtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len); | ||
| 132 | self.globals_lookup = try allocator.alloc(i64, self.in_symtab.?.len); | ||
| 133 | self.atom_by_index_table = try allocator.alloc(?Atom.Index, self.in_symtab.?.len + nsects); | ||
| 134 | self.relocs_lookup = try allocator.alloc(Entry, self.in_symtab.?.len + nsects); | ||
| 135 | // This is wasteful but we need to be able to lookup source symbol address after stripping and | ||
| 136 | // allocating of sections. | ||
| 137 | self.source_address_lookup = try allocator.alloc(i64, self.in_symtab.?.len); | ||
| 138 | self.source_section_index_lookup = try allocator.alloc(Entry, nsects); | ||
| 139 | |||
| 140 | for (self.symtab) |*sym| { | ||
| 141 | sym.* = .{ | ||
| 142 | .n_value = 0, | ||
| 143 | .n_sect = 0, | ||
| 144 | .n_desc = 0, | ||
| 145 | .n_strx = 0, | ||
| 146 | .n_type = 0, | ||
| 147 | }; | ||
| 148 | } | 77 | } |
| 149 | 78 | if (self.getLoadCommand(.SYMTAB)) |lc| { | |
| 150 | @memset(self.globals_lookup, -1); | 79 | const cmd = lc.cast(macho.symtab_command).?; |
| 151 | @memset(self.atom_by_index_table, null); | 80 | self.strtab = self.data[cmd.stroff..][0..cmd.strsize]; |
| 152 | @memset(self.source_section_index_lookup, .{}); | 81 | |
| 153 | @memset(self.relocs_lookup, .{}); | 82 | const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.data.ptr + cmd.symoff))[0..cmd.nsyms]; |
| 154 | 83 | try self.symtab.ensureUnusedCapacity(gpa, symtab.len); | |
| 155 | // You would expect that the symbol table is at least pre-sorted based on symbol's type: | 84 | for (symtab) |nlist| { |
| 156 | // local < extern defined < undefined. Unfortunately, this is not guaranteed! For instance, | 85 | self.symtab.appendAssumeCapacity(.{ |
| 157 | // the GO compiler does not necessarily respect that therefore we sort immediately by type | 86 | .nlist = nlist, |
| 158 | // and address within. | 87 | .atom = 0, |
| 159 | var sorted_all_syms = try std.ArrayList(SymbolAtIndex).initCapacity(allocator, self.in_symtab.?.len); | 88 | .size = 0, |
| 160 | defer sorted_all_syms.deinit(); | 89 | }); |
| 161 | 90 | } | |
| 162 | for (self.in_symtab.?, 0..) |_, index| { | ||
| 163 | sorted_all_syms.appendAssumeCapacity(.{ .index = @as(u32, @intCast(index)) }); | ||
| 164 | } | 91 | } |
| 165 | 92 | ||
| 166 | // We sort by type: defined < undefined, and | 93 | const NlistIdx = struct { |
| 167 | // afterwards by address in each group. Normally, dysymtab should | 94 | nlist: macho.nlist_64, |
| 168 | // be enough to guarantee the sort, but turns out not every compiler | 95 | idx: usize, |
| 169 | // is kind enough to specify the symbols in the correct order. | 96 | |
| 170 | mem.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan); | 97 | fn rank(ctx: *const Object, nl: macho.nlist_64) u8 { |
| 171 | 98 | if (!nl.ext()) { | |
| 172 | var prev_sect_id: u8 = 0; | 99 | const name = ctx.getString(nl.n_strx); |
| 173 | var section_index_lookup: ?Entry = null; | 100 | if (name.len == 0) return 5; |
| 174 | for (sorted_all_syms.items, 0..) |sym_id, i| { | 101 | if (name[0] == 'l' or name[0] == 'L') return 4; |
| 175 | const sym = sym_id.getSymbol(self); | 102 | return 3; |
| 176 | |||
| 177 | if (section_index_lookup) |*lookup| { | ||
| 178 | if (sym.n_sect != prev_sect_id or sym.undf()) { | ||
| 179 | self.source_section_index_lookup[prev_sect_id - 1] = lookup.*; | ||
| 180 | section_index_lookup = null; | ||
| 181 | } else { | ||
| 182 | lookup.len += 1; | ||
| 183 | } | 103 | } |
| 104 | return if (nl.weakDef()) 2 else 1; | ||
| 184 | } | 105 | } |
| 185 | if (sym.sect() and section_index_lookup == null) { | 106 | |
| 186 | section_index_lookup = .{ .start = @as(u32, @intCast(i)), .len = 1 }; | 107 | fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool { |
| 108 | if (lhs.nlist.n_sect == rhs.nlist.n_sect) { | ||
| 109 | if (lhs.nlist.n_value == rhs.nlist.n_value) { | ||
| 110 | return rank(ctx, lhs.nlist) < rank(ctx, rhs.nlist); | ||
| 111 | } | ||
| 112 | return lhs.nlist.n_value < rhs.nlist.n_value; | ||
| 113 | } | ||
| 114 | return lhs.nlist.n_sect < rhs.nlist.n_sect; | ||
| 187 | } | 115 | } |
| 116 | }; | ||
| 188 | 117 | ||
| 189 | prev_sect_id = sym.n_sect; | 118 | var nlists = try std.ArrayList(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len); |
| 119 | defer nlists.deinit(); | ||
| 120 | for (self.symtab.items(.nlist), 0..) |nlist, i| { | ||
| 121 | if (nlist.stab() or !nlist.sect()) continue; | ||
| 122 | nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i }); | ||
| 123 | } | ||
| 124 | mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan); | ||
| 190 | 125 | ||
| 191 | self.symtab[i] = sym; | 126 | if (self.hasSubsections()) { |
| 192 | self.source_symtab_lookup[i] = sym_id.index; | 127 | try self.initSubsections(nlists.items, macho_file); |
| 193 | self.reverse_symtab_lookup[sym_id.index] = @as(u32, @intCast(i)); | 128 | } else { |
| 194 | self.source_address_lookup[i] = if (sym.undf()) -1 else @as(i64, @intCast(sym.n_value)); | 129 | try self.initSections(nlists.items, macho_file); |
| 130 | } | ||
| 195 | 131 | ||
| 196 | const sym_name_len = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.in_strtab.?.ptr + sym.n_strx)), 0).len + 1; | 132 | try self.initLiteralSections(macho_file); |
| 197 | self.strtab_lookup[i] = @as(u32, @intCast(sym_name_len)); | 133 | try self.linkNlistToAtom(macho_file); |
| 134 | |||
| 135 | try self.sortAtoms(macho_file); | ||
| 136 | try self.initSymbols(macho_file); | ||
| 137 | try self.initSymbolStabs(nlists.items, macho_file); | ||
| 138 | try self.initRelocs(macho_file); | ||
| 139 | |||
| 140 | if (self.eh_frame_sect_index) |index| { | ||
| 141 | try self.initEhFrameRecords(index, macho_file); | ||
| 198 | } | 142 | } |
| 199 | 143 | ||
| 200 | // If there were no undefined symbols, make sure we populate the | 144 | if (self.compact_unwind_sect_index) |index| { |
| 201 | // source section index lookup for the last scanned section. | 145 | try self.initUnwindRecords(index, macho_file); |
| 202 | if (section_index_lookup) |lookup| { | ||
| 203 | self.source_section_index_lookup[prev_sect_id - 1] = lookup; | ||
| 204 | } | 146 | } |
| 205 | 147 | ||
| 206 | // Parse __TEXT,__eh_frame header if one exists | 148 | self.initPlatform(); |
| 207 | self.eh_frame_sect_id = self.getSourceSectionIndexByName("__TEXT", "__eh_frame"); | 149 | try self.initDwarfInfo(macho_file); |
| 150 | |||
| 151 | for (self.atoms.items) |atom_index| { | ||
| 152 | const atom = macho_file.getAtom(atom_index).?; | ||
| 153 | const isec = atom.getInputSection(macho_file); | ||
| 154 | if (mem.eql(u8, isec.sectName(), "__eh_frame") or | ||
| 155 | mem.eql(u8, isec.sectName(), "__compact_unwind") or | ||
| 156 | isec.attrs() & macho.S_ATTR_DEBUG != 0) | ||
| 157 | { | ||
| 158 | atom.flags.alive = false; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 208 | 162 | ||
| 209 | // Parse __LD,__compact_unwind header if one exists | 163 | inline fn isLiteral(sect: macho.section_64) bool { |
| 210 | self.unwind_info_sect_id = self.getSourceSectionIndexByName("__LD", "__compact_unwind"); | 164 | return switch (sect.type()) { |
| 211 | if (self.hasUnwindRecords()) { | 165 | macho.S_CSTRING_LITERALS, |
| 212 | self.unwind_relocs_lookup = try allocator.alloc(Record, self.getUnwindRecords().len); | 166 | macho.S_4BYTE_LITERALS, |
| 213 | @memset(self.unwind_relocs_lookup, .{ .dead = true, .reloc = .{} }); | 167 | macho.S_8BYTE_LITERALS, |
| 168 | macho.S_16BYTE_LITERALS, | ||
| 169 | macho.S_LITERAL_POINTERS, | ||
| 170 | => true, | ||
| 171 | else => false, | ||
| 172 | }; | ||
| 173 | } | ||
| 174 | |||
| 175 | fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { | ||
| 176 | const tracy = trace(@src()); | ||
| 177 | defer tracy.end(); | ||
| 178 | const gpa = macho_file.base.allocator; | ||
| 179 | const slice = self.sections.slice(); | ||
| 180 | for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| { | ||
| 181 | if (isLiteral(sect)) continue; | ||
| 182 | |||
| 183 | const nlist_start = for (nlists, 0..) |nlist, i| { | ||
| 184 | if (nlist.nlist.n_sect - 1 == n_sect) break i; | ||
| 185 | } else nlists.len; | ||
| 186 | const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| { | ||
| 187 | if (nlist.nlist.n_sect - 1 != n_sect) break i; | ||
| 188 | } else nlists.len; | ||
| 189 | |||
| 190 | if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) { | ||
| 191 | const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); | ||
| 192 | defer gpa.free(name); | ||
| 193 | const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr; | ||
| 194 | const atom_index = try self.addAtom(.{ | ||
| 195 | .name = name, | ||
| 196 | .n_sect = @intCast(n_sect), | ||
| 197 | .off = 0, | ||
| 198 | .size = size, | ||
| 199 | .alignment = sect.@"align", | ||
| 200 | }, macho_file); | ||
| 201 | try subsections.append(gpa, .{ | ||
| 202 | .atom = atom_index, | ||
| 203 | .off = 0, | ||
| 204 | }); | ||
| 205 | } | ||
| 206 | |||
| 207 | var idx: usize = nlist_start; | ||
| 208 | while (idx < nlist_end) { | ||
| 209 | const alias_start = idx; | ||
| 210 | const nlist = nlists[alias_start]; | ||
| 211 | |||
| 212 | while (idx < nlist_end and | ||
| 213 | nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1) | ||
| 214 | {} | ||
| 215 | |||
| 216 | const size = if (idx < nlist_end) | ||
| 217 | nlists[idx].nlist.n_value - nlist.nlist.n_value | ||
| 218 | else | ||
| 219 | sect.addr + sect.size - nlist.nlist.n_value; | ||
| 220 | const alignment = if (nlist.nlist.n_value > 0) | ||
| 221 | @min(@ctz(nlist.nlist.n_value), sect.@"align") | ||
| 222 | else | ||
| 223 | sect.@"align"; | ||
| 224 | const atom_index = try self.addAtom(.{ | ||
| 225 | .name = self.getString(nlist.nlist.n_strx), | ||
| 226 | .n_sect = @intCast(n_sect), | ||
| 227 | .off = nlist.nlist.n_value - sect.addr, | ||
| 228 | .size = size, | ||
| 229 | .alignment = alignment, | ||
| 230 | }, macho_file); | ||
| 231 | try subsections.append(gpa, .{ | ||
| 232 | .atom = atom_index, | ||
| 233 | .off = nlist.nlist.n_value - sect.addr, | ||
| 234 | }); | ||
| 235 | |||
| 236 | for (alias_start..idx) |i| { | ||
| 237 | self.symtab.items(.size)[nlists[i].idx] = size; | ||
| 238 | } | ||
| 239 | } | ||
| 214 | } | 240 | } |
| 215 | } | 241 | } |
| 216 | 242 | ||
| 217 | const SymbolAtIndex = struct { | 243 | fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void { |
| 218 | index: u32, | 244 | const tracy = trace(@src()); |
| 245 | defer tracy.end(); | ||
| 246 | const gpa = macho_file.base.allocator; | ||
| 247 | const slice = self.sections.slice(); | ||
| 248 | |||
| 249 | try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len); | ||
| 250 | |||
| 251 | for (slice.items(.header), 0..) |sect, n_sect| { | ||
| 252 | if (isLiteral(sect)) continue; | ||
| 253 | |||
| 254 | const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); | ||
| 255 | defer gpa.free(name); | ||
| 256 | |||
| 257 | const atom_index = try self.addAtom(.{ | ||
| 258 | .name = name, | ||
| 259 | .n_sect = @intCast(n_sect), | ||
| 260 | .off = 0, | ||
| 261 | .size = sect.size, | ||
| 262 | .alignment = sect.@"align", | ||
| 263 | }, macho_file); | ||
| 264 | try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 }); | ||
| 265 | |||
| 266 | const nlist_start = for (nlists, 0..) |nlist, i| { | ||
| 267 | if (nlist.nlist.n_sect - 1 == n_sect) break i; | ||
| 268 | } else nlists.len; | ||
| 269 | const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| { | ||
| 270 | if (nlist.nlist.n_sect - 1 != n_sect) break i; | ||
| 271 | } else nlists.len; | ||
| 272 | |||
| 273 | var idx: usize = nlist_start; | ||
| 274 | while (idx < nlist_end) { | ||
| 275 | const nlist = nlists[idx]; | ||
| 276 | |||
| 277 | while (idx < nlist_end and | ||
| 278 | nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1) | ||
| 279 | {} | ||
| 280 | |||
| 281 | const size = if (idx < nlist_end) | ||
| 282 | nlists[idx].nlist.n_value - nlist.nlist.n_value | ||
| 283 | else | ||
| 284 | sect.addr + sect.size - nlist.nlist.n_value; | ||
| 285 | |||
| 286 | for (nlist_start..idx) |i| { | ||
| 287 | self.symtab.items(.size)[nlists[i].idx] = size; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | } | ||
| 219 | 292 | ||
| 220 | const Context = *const Object; | 293 | const AddAtomArgs = struct { |
| 294 | name: [:0]const u8, | ||
| 295 | n_sect: u8, | ||
| 296 | off: u64, | ||
| 297 | size: u64, | ||
| 298 | alignment: u32, | ||
| 299 | }; | ||
| 221 | 300 | ||
| 222 | fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 { | 301 | fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index { |
| 223 | return ctx.in_symtab.?[self.index]; | 302 | const gpa = macho_file.base.allocator; |
| 303 | const atom_index = try macho_file.addAtom(); | ||
| 304 | const atom = macho_file.getAtom(atom_index).?; | ||
| 305 | atom.file = self.index; | ||
| 306 | atom.atom_index = atom_index; | ||
| 307 | atom.name = try macho_file.string_intern.insert(gpa, args.name); | ||
| 308 | atom.n_sect = args.n_sect; | ||
| 309 | atom.size = args.size; | ||
| 310 | atom.alignment = args.alignment; | ||
| 311 | atom.off = args.off; | ||
| 312 | try self.atoms.append(gpa, atom_index); | ||
| 313 | return atom_index; | ||
| 314 | } | ||
| 315 | |||
| 316 | fn initLiteralSections(self: *Object, macho_file: *MachO) !void { | ||
| 317 | const tracy = trace(@src()); | ||
| 318 | defer tracy.end(); | ||
| 319 | // TODO here we should split into equal-sized records, hash the contents, and then | ||
| 320 | // deduplicate - ICF. | ||
| 321 | // For now, we simply cover each literal section with one large atom. | ||
| 322 | const gpa = macho_file.base.allocator; | ||
| 323 | const slice = self.sections.slice(); | ||
| 324 | |||
| 325 | try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len); | ||
| 326 | |||
| 327 | for (slice.items(.header), 0..) |sect, n_sect| { | ||
| 328 | if (!isLiteral(sect)) continue; | ||
| 329 | |||
| 330 | const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); | ||
| 331 | defer gpa.free(name); | ||
| 332 | |||
| 333 | const atom_index = try self.addAtom(.{ | ||
| 334 | .name = name, | ||
| 335 | .n_sect = @intCast(n_sect), | ||
| 336 | .off = 0, | ||
| 337 | .size = sect.size, | ||
| 338 | .alignment = sect.@"align", | ||
| 339 | }, macho_file); | ||
| 340 | try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 }); | ||
| 224 | } | 341 | } |
| 342 | } | ||
| 225 | 343 | ||
| 226 | fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 { | 344 | pub fn findAtom(self: Object, addr: u64) ?Atom.Index { |
| 227 | const off = self.getSymbol(ctx).n_strx; | 345 | const tracy = trace(@src()); |
| 228 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.in_strtab.?.ptr + off)), 0); | 346 | defer tracy.end(); |
| 347 | const slice = self.sections.slice(); | ||
| 348 | for (slice.items(.header), slice.items(.subsections), 0..) |sect, subs, n_sect| { | ||
| 349 | if (subs.items.len == 0) continue; | ||
| 350 | if (sect.addr == addr) return subs.items[0].atom; | ||
| 351 | if (sect.addr < addr and addr < sect.addr + sect.size) { | ||
| 352 | return self.findAtomInSection(addr, @intCast(n_sect)); | ||
| 353 | } | ||
| 229 | } | 354 | } |
| 355 | return null; | ||
| 356 | } | ||
| 230 | 357 | ||
| 231 | fn getSymbolSeniority(self: SymbolAtIndex, ctx: Context) u2 { | 358 | fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index { |
| 232 | const sym = self.getSymbol(ctx); | 359 | const tracy = trace(@src()); |
| 233 | if (!sym.ext()) { | 360 | defer tracy.end(); |
| 234 | const sym_name = self.getSymbolName(ctx); | 361 | const slice = self.sections.slice(); |
| 235 | if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3; | 362 | const sect = slice.items(.header)[n_sect]; |
| 236 | return 2; | 363 | const subsections = slice.items(.subsections)[n_sect]; |
| 364 | |||
| 365 | var min: usize = 0; | ||
| 366 | var max: usize = subsections.items.len; | ||
| 367 | while (min < max) { | ||
| 368 | const idx = (min + max) / 2; | ||
| 369 | const sub = subsections.items[idx]; | ||
| 370 | const sub_addr = sect.addr + sub.off; | ||
| 371 | const sub_size = if (idx + 1 < subsections.items.len) | ||
| 372 | subsections.items[idx + 1].off - sub.off | ||
| 373 | else | ||
| 374 | sect.size - sub.off; | ||
| 375 | if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom; | ||
| 376 | if (sub_addr < addr) { | ||
| 377 | min = idx + 1; | ||
| 378 | } else { | ||
| 379 | max = idx; | ||
| 237 | } | 380 | } |
| 238 | if (sym.weakDef() or sym.pext()) return 1; | ||
| 239 | return 0; | ||
| 240 | } | 381 | } |
| 241 | 382 | ||
| 242 | /// Performs lexicographic-like check. | 383 | if (min < subsections.items.len) { |
| 243 | /// * lhs and rhs defined | 384 | const sub = subsections.items[min]; |
| 244 | /// * if lhs == rhs | 385 | const sub_addr = sect.addr + sub.off; |
| 245 | /// * if lhs.n_sect == rhs.n_sect | 386 | const sub_size = if (min + 1 < subsections.items.len) |
| 246 | /// * ext < weak < local < temp | 387 | subsections.items[min + 1].off - sub.off |
| 247 | /// * lhs.n_sect < rhs.n_sect | 388 | else |
| 248 | /// * lhs < rhs | 389 | sect.size - sub.off; |
| 249 | /// * !rhs is undefined | 390 | if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom; |
| 250 | fn lessThan(ctx: Context, lhs_index: SymbolAtIndex, rhs_index: SymbolAtIndex) bool { | ||
| 251 | const lhs = lhs_index.getSymbol(ctx); | ||
| 252 | const rhs = rhs_index.getSymbol(ctx); | ||
| 253 | if (lhs.sect() and rhs.sect()) { | ||
| 254 | if (lhs.n_value == rhs.n_value) { | ||
| 255 | if (lhs.n_sect == rhs.n_sect) { | ||
| 256 | const lhs_senior = lhs_index.getSymbolSeniority(ctx); | ||
| 257 | const rhs_senior = rhs_index.getSymbolSeniority(ctx); | ||
| 258 | if (lhs_senior == rhs_senior) { | ||
| 259 | return lessThanByNStrx(ctx, lhs_index, rhs_index); | ||
| 260 | } else return lhs_senior < rhs_senior; | ||
| 261 | } else return lhs.n_sect < rhs.n_sect; | ||
| 262 | } else return lhs.n_value < rhs.n_value; | ||
| 263 | } else if (lhs.undf() and rhs.undf()) { | ||
| 264 | return lessThanByNStrx(ctx, lhs_index, rhs_index); | ||
| 265 | } else return rhs.undf(); | ||
| 266 | } | 391 | } |
| 267 | 392 | ||
| 268 | fn lessThanByNStrx(ctx: Context, lhs: SymbolAtIndex, rhs: SymbolAtIndex) bool { | 393 | return null; |
| 269 | return lhs.getSymbol(ctx).n_strx < rhs.getSymbol(ctx).n_strx; | 394 | } |
| 395 | |||
| 396 | fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void { | ||
| 397 | const tracy = trace(@src()); | ||
| 398 | defer tracy.end(); | ||
| 399 | for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| { | ||
| 400 | if (!nlist.stab() and nlist.sect()) { | ||
| 401 | if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| { | ||
| 402 | atom.* = atom_index; | ||
| 403 | } else { | ||
| 404 | macho_file.base.fatal("{}: symbol {s} not attached to any (sub)section", .{ | ||
| 405 | self.fmtPath(), self.getString(nlist.n_strx), | ||
| 406 | }); | ||
| 407 | return error.ParseFailed; | ||
| 408 | } | ||
| 409 | } | ||
| 270 | } | 410 | } |
| 271 | }; | 411 | } |
| 272 | 412 | ||
| 273 | fn filterSymbolsBySection(symbols: []macho.nlist_64, n_sect: u8) struct { | 413 | fn initSymbols(self: *Object, macho_file: *MachO) !void { |
| 274 | index: u32, | 414 | const tracy = trace(@src()); |
| 275 | len: u32, | 415 | defer tracy.end(); |
| 276 | } { | 416 | const gpa = macho_file.base.allocator; |
| 277 | const FirstMatch = struct { | 417 | const slice = self.symtab.slice(); |
| 278 | n_sect: u8, | 418 | |
| 419 | try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len); | ||
| 279 | 420 | ||
| 280 | pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool { | 421 | for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| { |
| 281 | return symbol.n_sect == pred.n_sect; | 422 | if (nlist.ext()) { |
| 423 | const name = self.getString(nlist.n_strx); | ||
| 424 | const off = try macho_file.string_intern.insert(gpa, name); | ||
| 425 | const gop = try macho_file.getOrCreateGlobal(off); | ||
| 426 | self.symbols.addOneAssumeCapacity().* = gop.index; | ||
| 427 | continue; | ||
| 282 | } | 428 | } |
| 283 | }; | ||
| 284 | const FirstNonMatch = struct { | ||
| 285 | n_sect: u8, | ||
| 286 | 429 | ||
| 287 | pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool { | 430 | const index = try macho_file.addSymbol(); |
| 288 | return symbol.n_sect != pred.n_sect; | 431 | self.symbols.appendAssumeCapacity(index); |
| 432 | const symbol = macho_file.getSymbol(index); | ||
| 433 | const name = self.getString(nlist.n_strx); | ||
| 434 | symbol.* = .{ | ||
| 435 | .value = nlist.n_value, | ||
| 436 | .name = try macho_file.string_intern.insert(gpa, name), | ||
| 437 | .nlist_idx = @intCast(i), | ||
| 438 | .atom = 0, | ||
| 439 | .file = self.index, | ||
| 440 | }; | ||
| 441 | |||
| 442 | if (macho_file.getAtom(atom_index)) |atom| { | ||
| 443 | assert(!nlist.abs()); | ||
| 444 | symbol.value -= atom.getInputAddress(macho_file); | ||
| 445 | symbol.atom = atom_index; | ||
| 289 | } | 446 | } |
| 290 | }; | ||
| 291 | 447 | ||
| 292 | const index = MachO.lsearch(macho.nlist_64, symbols, FirstMatch{ | 448 | symbol.flags.abs = nlist.abs(); |
| 293 | .n_sect = n_sect, | 449 | symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip(); |
| 294 | }); | ||
| 295 | const len = MachO.lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{ | ||
| 296 | .n_sect = n_sect, | ||
| 297 | }); | ||
| 298 | 450 | ||
| 299 | return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) }; | 451 | if (nlist.sect() and |
| 452 | self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES) | ||
| 453 | { | ||
| 454 | symbol.flags.tlv = true; | ||
| 455 | } | ||
| 456 | } | ||
| 300 | } | 457 | } |
| 301 | 458 | ||
| 302 | fn filterSymbolsByAddress(symbols: []macho.nlist_64, start_addr: u64, end_addr: u64) struct { | 459 | fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void { |
| 303 | index: u32, | 460 | const tracy = trace(@src()); |
| 304 | len: u32, | 461 | defer tracy.end(); |
| 305 | } { | ||
| 306 | const Predicate = struct { | ||
| 307 | addr: u64, | ||
| 308 | 462 | ||
| 309 | pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool { | 463 | const SymbolLookup = struct { |
| 310 | return symbol.n_value >= pred.addr; | 464 | ctx: *const Object, |
| 465 | entries: @TypeOf(nlists), | ||
| 466 | |||
| 467 | fn find(fs: @This(), addr: u64) ?Symbol.Index { | ||
| 468 | // TODO binary search since we have the list sorted | ||
| 469 | for (fs.entries) |nlist| { | ||
| 470 | if (nlist.nlist.n_value == addr) return fs.ctx.symbols.items[nlist.idx]; | ||
| 471 | } | ||
| 472 | return null; | ||
| 311 | } | 473 | } |
| 312 | }; | 474 | }; |
| 313 | 475 | ||
| 314 | const index = MachO.lsearch(macho.nlist_64, symbols, Predicate{ | 476 | const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| { |
| 315 | .addr = start_addr, | 477 | if (nlist.stab()) break @intCast(i); |
| 316 | }); | 478 | } else @intCast(self.symtab.items(.nlist).len); |
| 317 | const len = MachO.lsearch(macho.nlist_64, symbols[index..], Predicate{ | 479 | const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| { |
| 318 | .addr = end_addr, | 480 | if (!nlist.stab()) break @intCast(i); |
| 319 | }); | 481 | } else @intCast(self.symtab.items(.nlist).len); |
| 482 | |||
| 483 | if (start == end) return; | ||
| 484 | |||
| 485 | const gpa = macho_file.base.allocator; | ||
| 486 | const syms = self.symtab.items(.nlist); | ||
| 487 | const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists }; | ||
| 488 | |||
| 489 | var i: u32 = start; | ||
| 490 | while (i < end) : (i += 1) { | ||
| 491 | const open = syms[i]; | ||
| 492 | if (open.n_type != macho.N_SO) { | ||
| 493 | macho_file.base.fatal("{}: unexpected symbol stab type 0x{x} as the first entry", .{ | ||
| 494 | self.fmtPath(), | ||
| 495 | open.n_type, | ||
| 496 | }); | ||
| 497 | return error.ParseFailed; | ||
| 498 | } | ||
| 499 | |||
| 500 | while (i < end and syms[i].n_type == macho.N_SO and syms[i].n_sect != 0) : (i += 1) {} | ||
| 501 | |||
| 502 | var sf: StabFile = .{ .comp_dir = i }; | ||
| 503 | // TODO validate | ||
| 504 | i += 3; | ||
| 505 | |||
| 506 | while (i < end and syms[i].n_type != macho.N_SO) : (i += 1) { | ||
| 507 | const nlist = syms[i]; | ||
| 508 | var stab: StabFile.Stab = .{}; | ||
| 509 | switch (nlist.n_type) { | ||
| 510 | macho.N_BNSYM => { | ||
| 511 | stab.tag = .func; | ||
| 512 | stab.symbol = sym_lookup.find(nlist.n_value); | ||
| 513 | // TODO validate | ||
| 514 | i += 3; | ||
| 515 | }, | ||
| 516 | macho.N_GSYM => { | ||
| 517 | stab.tag = .global; | ||
| 518 | stab.symbol = macho_file.getGlobalByName(self.getString(nlist.n_strx)); | ||
| 519 | }, | ||
| 520 | macho.N_STSYM => { | ||
| 521 | stab.tag = .static; | ||
| 522 | stab.symbol = sym_lookup.find(nlist.n_value); | ||
| 523 | }, | ||
| 524 | else => { | ||
| 525 | macho_file.base.fatal("{}: unhandled symbol stab type 0x{x}", .{ | ||
| 526 | self.fmtPath(), | ||
| 527 | nlist.n_type, | ||
| 528 | }); | ||
| 529 | return error.ParseFailed; | ||
| 530 | }, | ||
| 531 | } | ||
| 532 | try sf.stabs.append(gpa, stab); | ||
| 533 | } | ||
| 320 | 534 | ||
| 321 | return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) }; | 535 | try self.stab_files.append(gpa, sf); |
| 536 | } | ||
| 322 | } | 537 | } |
| 323 | 538 | ||
| 324 | const SortedSection = struct { | 539 | fn sortAtoms(self: *Object, macho_file: *MachO) !void { |
| 325 | header: macho.section_64, | 540 | const lessThanAtom = struct { |
| 326 | id: u8, | 541 | fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool { |
| 327 | }; | 542 | return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx); |
| 543 | } | ||
| 544 | }.lessThanAtom; | ||
| 545 | mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom); | ||
| 546 | } | ||
| 547 | |||
| 548 | fn initRelocs(self: *Object, macho_file: *MachO) !void { | ||
| 549 | const tracy = trace(@src()); | ||
| 550 | defer tracy.end(); | ||
| 551 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 552 | const slice = self.sections.slice(); | ||
| 553 | |||
| 554 | for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| { | ||
| 555 | if (sect.nreloc == 0) continue; | ||
| 556 | // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit | ||
| 557 | // debug symbol stabs in the relocatable. This made me curious why that is. For now, | ||
| 558 | // I shall comply, but I wanna compare with dsymutil. | ||
| 559 | if (sect.attrs() & macho.S_ATTR_DEBUG != 0 and | ||
| 560 | !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; | ||
| 561 | |||
| 562 | switch (cpu_arch) { | ||
| 563 | .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file), | ||
| 564 | .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file), | ||
| 565 | else => unreachable, | ||
| 566 | } | ||
| 328 | 567 | ||
| 329 | fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) bool { | 568 | mem.sort(Relocation, out.items, {}, Relocation.lessThan); |
| 330 | _ = ctx; | ||
| 331 | if (lhs.header.addr == rhs.header.addr) { | ||
| 332 | return lhs.id < rhs.id; | ||
| 333 | } | 569 | } |
| 334 | return lhs.header.addr < rhs.header.addr; | ||
| 335 | } | ||
| 336 | 570 | ||
| 337 | pub const SplitIntoAtomsError = error{ | 571 | for (slice.items(.header), slice.items(.relocs), slice.items(.subsections)) |sect, relocs, subsections| { |
| 338 | OutOfMemory, | 572 | if (sect.isZerofill()) continue; |
| 339 | EndOfStream, | 573 | |
| 340 | MissingEhFrameSection, | 574 | var next_reloc: usize = 0; |
| 341 | BadDwarfCfi, | 575 | for (subsections.items) |subsection| { |
| 342 | }; | 576 | const atom = macho_file.getAtom(subsection.atom).?; |
| 577 | if (!atom.flags.alive) continue; | ||
| 578 | if (next_reloc >= relocs.items.len) break; | ||
| 579 | const end_addr = atom.off + atom.size; | ||
| 580 | atom.relocs.pos = next_reloc; | ||
| 343 | 581 | ||
| 344 | pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) SplitIntoAtomsError!void { | 582 | while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {} |
| 345 | const comp = macho_file.base.comp; | ||
| 346 | const gpa = comp.gpa; | ||
| 347 | log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name }); | ||
| 348 | 583 | ||
| 349 | try self.splitRegularSections(macho_file, object_id); | 584 | atom.relocs.len = next_reloc - atom.relocs.pos; |
| 350 | try self.parseEhFrameSection(macho_file, object_id); | 585 | } |
| 351 | try self.parseUnwindInfo(macho_file, object_id); | 586 | } |
| 352 | try self.parseDataInCode(gpa); | ||
| 353 | } | 587 | } |
| 354 | 588 | ||
| 355 | /// Splits input regular sections into Atoms. | 589 | fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { |
| 356 | /// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section | 590 | const tracy = trace(@src()); |
| 357 | /// into subsections where each subsection then represents an Atom. | 591 | defer tracy.end(); |
| 358 | pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void { | 592 | const gpa = macho_file.base.allocator; |
| 359 | const comp = macho_file.base.comp; | 593 | const nlists = self.symtab.items(.nlist); |
| 360 | const gpa = comp.gpa; | 594 | const slice = self.sections.slice(); |
| 361 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 595 | const sect = slice.items(.header)[sect_id]; |
| 362 | 596 | const relocs = slice.items(.relocs)[sect_id]; | |
| 363 | const sections = self.getSourceSections(); | 597 | |
| 364 | for (sections, 0..) |sect, id| { | 598 | const data = self.getSectionData(sect_id); |
| 365 | if (sect.isDebug()) continue; | 599 | try self.eh_frame_data.ensureTotalCapacityPrecise(gpa, data.len); |
| 366 | const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse { | 600 | self.eh_frame_data.appendSliceAssumeCapacity(data); |
| 367 | log.debug(" unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() }); | 601 | |
| 368 | continue; | 602 | // Check for non-personality relocs in FDEs and apply them |
| 369 | }; | 603 | for (relocs.items, 0..) |rel, i| { |
| 370 | if (sect.size == 0) continue; | 604 | switch (rel.type) { |
| 605 | .unsigned => { | ||
| 606 | assert((rel.meta.length == 2 or rel.meta.length == 3) and rel.meta.has_subtractor); // TODO error | ||
| 607 | const S: i64 = switch (rel.tag) { | ||
| 608 | .local => rel.meta.symbolnum, | ||
| 609 | .@"extern" => @intCast(nlists[rel.meta.symbolnum].n_value), | ||
| 610 | }; | ||
| 611 | const A = rel.addend; | ||
| 612 | const SUB: i64 = blk: { | ||
| 613 | const sub_rel = relocs.items[i - 1]; | ||
| 614 | break :blk switch (sub_rel.tag) { | ||
| 615 | .local => sub_rel.meta.symbolnum, | ||
| 616 | .@"extern" => @intCast(nlists[sub_rel.meta.symbolnum].n_value), | ||
| 617 | }; | ||
| 618 | }; | ||
| 619 | switch (rel.meta.length) { | ||
| 620 | 0, 1 => unreachable, | ||
| 621 | 2 => mem.writeInt(u32, self.eh_frame_data.items[rel.offset..][0..4], @bitCast(@as(i32, @truncate(S + A - SUB))), .little), | ||
| 622 | 3 => mem.writeInt(u64, self.eh_frame_data.items[rel.offset..][0..8], @bitCast(S + A - SUB), .little), | ||
| 623 | } | ||
| 624 | }, | ||
| 625 | else => {}, | ||
| 626 | } | ||
| 627 | } | ||
| 371 | 628 | ||
| 372 | const sect_id = @as(u8, @intCast(id)); | 629 | var it = eh_frame.Iterator{ .data = self.eh_frame_data.items }; |
| 373 | const sym = self.getSectionAliasSymbolPtr(sect_id); | 630 | while (try it.next()) |rec| { |
| 374 | sym.* = .{ | 631 | switch (rec.tag) { |
| 375 | .n_strx = 0, | 632 | .cie => try self.cies.append(gpa, .{ |
| 376 | .n_type = macho.N_SECT, | 633 | .offset = rec.offset, |
| 377 | .n_sect = out_sect_id + 1, | 634 | .size = rec.size, |
| 378 | .n_desc = 0, | 635 | .file = self.index, |
| 379 | .n_value = sect.addr, | 636 | }), |
| 380 | }; | 637 | .fde => try self.fdes.append(gpa, .{ |
| 638 | .offset = rec.offset, | ||
| 639 | .size = rec.size, | ||
| 640 | .cie = undefined, | ||
| 641 | .file = self.index, | ||
| 642 | }), | ||
| 643 | } | ||
| 644 | } | ||
| 645 | |||
| 646 | for (self.cies.items) |*cie| { | ||
| 647 | try cie.parse(macho_file); | ||
| 381 | } | 648 | } |
| 382 | 649 | ||
| 383 | if (self.in_symtab == null) { | 650 | for (self.fdes.items) |*fde| { |
| 384 | for (sections, 0..) |sect, id| { | 651 | try fde.parse(macho_file); |
| 385 | if (sect.isDebug()) continue; | 652 | } |
| 386 | const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue; | 653 | |
| 387 | if (sect.size == 0) continue; | 654 | const sortFn = struct { |
| 388 | 655 | fn sortFn(ctx: *MachO, lhs: Fde, rhs: Fde) bool { | |
| 389 | const sect_id: u8 = @intCast(id); | 656 | return lhs.getAtom(ctx).getInputAddress(ctx) < rhs.getAtom(ctx).getInputAddress(ctx); |
| 390 | const sym_index = self.getSectionAliasSymbolIndex(sect_id); | 657 | } |
| 391 | const atom_index = try self.createAtomFromSubsection( | 658 | }.sortFn; |
| 392 | macho_file, | 659 | |
| 393 | object_id, | 660 | mem.sort(Fde, self.fdes.items, macho_file, sortFn); |
| 394 | sym_index, | 661 | |
| 395 | sym_index, | 662 | // Parse and attach personality pointers to CIEs if any |
| 396 | 1, | 663 | for (relocs.items) |rel| { |
| 397 | sect.size, | 664 | switch (rel.type) { |
| 398 | Alignment.fromLog2Units(sect.@"align"), | 665 | .got => { |
| 399 | out_sect_id, | 666 | assert(rel.meta.length == 2 and rel.tag == .@"extern"); |
| 400 | ); | 667 | const cie = for (self.cies.items) |*cie| { |
| 401 | macho_file.addAtomToSection(atom_index); | 668 | if (cie.offset <= rel.offset and rel.offset < cie.offset + cie.getSize()) break cie; |
| 669 | } else { | ||
| 670 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 671 | self.fmtPath(), sect.segName(), sect.sectName(), rel.offset, | ||
| 672 | }); | ||
| 673 | return error.ParseFailed; | ||
| 674 | }; | ||
| 675 | cie.personality = .{ .index = @intCast(rel.target), .offset = rel.offset - cie.offset }; | ||
| 676 | }, | ||
| 677 | else => {}, | ||
| 402 | } | 678 | } |
| 403 | return; | ||
| 404 | } | 679 | } |
| 680 | } | ||
| 681 | |||
| 682 | fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { | ||
| 683 | const tracy = trace(@src()); | ||
| 684 | defer tracy.end(); | ||
| 685 | |||
| 686 | const SymbolLookup = struct { | ||
| 687 | ctx: *const Object, | ||
| 405 | 688 | ||
| 406 | // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we | 689 | fn find(fs: @This(), addr: u64) ?Symbol.Index { |
| 407 | // have to infer the start of undef section in the symtab ourselves. | 690 | for (fs.ctx.symbols.items, 0..) |sym_index, i| { |
| 408 | const iundefsym = blk: { | 691 | const nlist = fs.ctx.symtab.items(.nlist)[i]; |
| 409 | const dysymtab = self.getDysymtab() orelse { | 692 | if (nlist.ext() and nlist.n_value == addr) return sym_index; |
| 410 | var iundefsym: usize = self.in_symtab.?.len; | ||
| 411 | while (iundefsym > 0) : (iundefsym -= 1) { | ||
| 412 | const sym = self.symtab[iundefsym - 1]; | ||
| 413 | if (sym.sect()) break; | ||
| 414 | } | 693 | } |
| 415 | break :blk iundefsym; | 694 | return null; |
| 416 | }; | 695 | } |
| 417 | break :blk dysymtab.iundefsym; | ||
| 418 | }; | 696 | }; |
| 419 | 697 | ||
| 420 | // We only care about defined symbols, so filter every other out. | 698 | const gpa = macho_file.base.allocator; |
| 421 | const symtab = try gpa.dupe(macho.nlist_64, self.symtab[0..iundefsym]); | 699 | const data = self.getSectionData(sect_id); |
| 422 | defer gpa.free(symtab); | 700 | const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); |
| 423 | 701 | const recs = @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data.ptr))[0..nrecs]; | |
| 424 | const subsections_via_symbols = self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; | 702 | const sym_lookup = SymbolLookup{ .ctx = self }; |
| 703 | |||
| 704 | try self.unwind_records.resize(gpa, nrecs); | ||
| 705 | |||
| 706 | const header = self.sections.items(.header)[sect_id]; | ||
| 707 | const relocs = self.sections.items(.relocs)[sect_id].items; | ||
| 708 | var reloc_idx: usize = 0; | ||
| 709 | for (recs, self.unwind_records.items, 0..) |rec, *out_index, rec_idx| { | ||
| 710 | const rec_start = rec_idx * @sizeOf(macho.compact_unwind_entry); | ||
| 711 | const rec_end = rec_start + @sizeOf(macho.compact_unwind_entry); | ||
| 712 | const reloc_start = reloc_idx; | ||
| 713 | while (reloc_idx < relocs.len and | ||
| 714 | relocs[reloc_idx].offset < rec_end) : (reloc_idx += 1) | ||
| 715 | {} | ||
| 716 | |||
| 717 | out_index.* = try macho_file.addUnwindRecord(); | ||
| 718 | const out = macho_file.getUnwindRecord(out_index.*); | ||
| 719 | out.length = rec.rangeLength; | ||
| 720 | out.enc = .{ .enc = rec.compactUnwindEncoding }; | ||
| 721 | out.file = self.index; | ||
| 722 | |||
| 723 | for (relocs[reloc_start..reloc_idx]) |rel| { | ||
| 724 | if (rel.type != .unsigned or rel.meta.length != 3) { | ||
| 725 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 726 | self.fmtPath(), header.segName(), header.sectName(), rel.offset, | ||
| 727 | }); | ||
| 728 | return error.ParseFailed; | ||
| 729 | } | ||
| 730 | assert(rel.type == .unsigned and rel.meta.length == 3); // TODO error | ||
| 731 | const offset = rel.offset - rec_start; | ||
| 732 | switch (offset) { | ||
| 733 | 0 => switch (rel.tag) { // target symbol | ||
| 734 | .@"extern" => { | ||
| 735 | out.atom = self.symtab.items(.atom)[rel.meta.symbolnum]; | ||
| 736 | out.atom_offset = @intCast(rec.rangeStart); | ||
| 737 | }, | ||
| 738 | .local => if (self.findAtom(rec.rangeStart)) |atom_index| { | ||
| 739 | out.atom = atom_index; | ||
| 740 | const atom = out.getAtom(macho_file); | ||
| 741 | out.atom_offset = @intCast(rec.rangeStart - atom.getInputAddress(macho_file)); | ||
| 742 | } else { | ||
| 743 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 744 | self.fmtPath(), header.segName(), header.sectName(), rel.offset, | ||
| 745 | }); | ||
| 746 | return error.ParseFailed; | ||
| 747 | }, | ||
| 748 | }, | ||
| 749 | 16 => switch (rel.tag) { // personality function | ||
| 750 | .@"extern" => { | ||
| 751 | out.personality = rel.target; | ||
| 752 | }, | ||
| 753 | .local => if (sym_lookup.find(rec.personalityFunction)) |sym_index| { | ||
| 754 | out.personality = sym_index; | ||
| 755 | } else { | ||
| 756 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 757 | self.fmtPath(), header.segName(), header.sectName(), rel.offset, | ||
| 758 | }); | ||
| 759 | return error.ParseFailed; | ||
| 760 | }, | ||
| 761 | }, | ||
| 762 | 24 => switch (rel.tag) { // lsda | ||
| 763 | .@"extern" => { | ||
| 764 | out.lsda = self.symtab.items(.atom)[rel.meta.symbolnum]; | ||
| 765 | out.lsda_offset = @intCast(rec.lsda); | ||
| 766 | }, | ||
| 767 | .local => if (self.findAtom(rec.lsda)) |atom_index| { | ||
| 768 | out.lsda = atom_index; | ||
| 769 | const atom = out.getLsdaAtom(macho_file).?; | ||
| 770 | out.lsda_offset = @intCast(rec.lsda - atom.getInputAddress(macho_file)); | ||
| 771 | } else { | ||
| 772 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 773 | self.fmtPath(), header.segName(), header.sectName(), rel.offset, | ||
| 774 | }); | ||
| 775 | return error.ParseFailed; | ||
| 776 | }, | ||
| 777 | }, | ||
| 778 | else => {}, | ||
| 779 | } | ||
| 780 | } | ||
| 781 | } | ||
| 425 | 782 | ||
| 426 | // Sort section headers by address. | 783 | if (!macho_file.options.relocatable) try self.synthesiseNullUnwindRecords(macho_file); |
| 427 | var sorted_sections = try gpa.alloc(SortedSection, sections.len); | ||
| 428 | defer gpa.free(sorted_sections); | ||
| 429 | 784 | ||
| 430 | for (sections, 0..) |sect, id| { | 785 | const sortFn = struct { |
| 431 | sorted_sections[id] = .{ .header = sect, .id = @as(u8, @intCast(id)) }; | 786 | fn sortFn(ctx: *MachO, lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool { |
| 787 | const lhs = ctx.getUnwindRecord(lhs_index); | ||
| 788 | const rhs = ctx.getUnwindRecord(rhs_index); | ||
| 789 | const lhsa = lhs.getAtom(ctx); | ||
| 790 | const rhsa = rhs.getAtom(ctx); | ||
| 791 | return lhsa.getInputAddress(ctx) + lhs.atom_offset < rhsa.getInputAddress(ctx) + rhs.atom_offset; | ||
| 792 | } | ||
| 793 | }.sortFn; | ||
| 794 | mem.sort(UnwindInfo.Record.Index, self.unwind_records.items, macho_file, sortFn); | ||
| 795 | |||
| 796 | // Associate unwind records to atoms | ||
| 797 | var next_cu: u32 = 0; | ||
| 798 | while (next_cu < self.unwind_records.items.len) { | ||
| 799 | const start = next_cu; | ||
| 800 | const rec_index = self.unwind_records.items[start]; | ||
| 801 | const rec = macho_file.getUnwindRecord(rec_index); | ||
| 802 | while (next_cu < self.unwind_records.items.len and | ||
| 803 | macho_file.getUnwindRecord(self.unwind_records.items[next_cu]).atom == rec.atom) : (next_cu += 1) | ||
| 804 | {} | ||
| 805 | |||
| 806 | const atom = rec.getAtom(macho_file); | ||
| 807 | atom.unwind_records = .{ .pos = start, .len = next_cu - start }; | ||
| 432 | } | 808 | } |
| 809 | } | ||
| 433 | 810 | ||
| 434 | mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress); | 811 | fn synthesiseNullUnwindRecords(self: *Object, macho_file: *MachO) !void { |
| 435 | 812 | // Synthesise missing unwind records. | |
| 436 | var sect_sym_index: u32 = 0; | 813 | // The logic here is as follows: |
| 437 | for (sorted_sections) |section| { | 814 | // 1. if an atom has unwind info record that is not DWARF, FDE is marked dead |
| 438 | const sect = section.header; | 815 | // 2. if an atom has unwind info record that is DWARF, FDE is tied to this unwind record |
| 439 | if (sect.isDebug()) continue; | 816 | // 3. if an atom doesn't have unwind info record but FDE is available, synthesise and tie |
| 440 | 817 | // 4. if an atom doesn't have either, synthesise a null unwind info record | |
| 441 | const sect_id = section.id; | 818 | |
| 442 | log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() }); | 819 | const Superposition = struct { atom: Atom.Index, size: u64, cu: ?UnwindInfo.Record.Index = null, fde: ?Fde.Index = null }; |
| 443 | 820 | ||
| 444 | // Get output segment/section in the final artifact. | 821 | const gpa = macho_file.base.allocator; |
| 445 | const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue; | 822 | var superposition = std.AutoArrayHashMap(u64, Superposition).init(gpa); |
| 446 | 823 | defer superposition.deinit(); | |
| 447 | log.debug(" output sect({d}, '{s},{s}')", .{ | 824 | |
| 448 | out_sect_id + 1, | 825 | const slice = self.symtab.slice(); |
| 449 | macho_file.sections.items(.header)[out_sect_id].segName(), | 826 | for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| { |
| 450 | macho_file.sections.items(.header)[out_sect_id].sectName(), | 827 | if (nlist.stab()) continue; |
| 451 | }); | 828 | if (!nlist.sect()) continue; |
| 452 | 829 | const sect = self.sections.items(.header)[nlist.n_sect - 1]; | |
| 453 | try self.parseRelocs(gpa, section.id); | 830 | if (sect.isCode()) { |
| 454 | 831 | try superposition.ensureUnusedCapacity(1); | |
| 455 | const cpu_arch = target.cpu.arch; | 832 | const gop = superposition.getOrPutAssumeCapacity(nlist.n_value); |
| 456 | const sect_loc = filterSymbolsBySection(symtab[sect_sym_index..], sect_id + 1); | 833 | if (gop.found_existing) { |
| 457 | const sect_start_index = sect_sym_index + sect_loc.index; | 834 | assert(gop.value_ptr.atom == atom and gop.value_ptr.size == size); |
| 458 | |||
| 459 | sect_sym_index += sect_loc.len; | ||
| 460 | |||
| 461 | if (sect.size == 0) continue; | ||
| 462 | if (subsections_via_symbols and sect_loc.len > 0) { | ||
| 463 | // If the first nlist does not match the start of the section, | ||
| 464 | // then we need to encapsulate the memory range [section start, first symbol) | ||
| 465 | // as a temporary symbol and insert the matching Atom. | ||
| 466 | const first_sym = symtab[sect_start_index]; | ||
| 467 | if (first_sym.n_value > sect.addr) { | ||
| 468 | const sym_index = self.getSectionAliasSymbolIndex(sect_id); | ||
| 469 | const atom_size = first_sym.n_value - sect.addr; | ||
| 470 | const atom_index = try self.createAtomFromSubsection( | ||
| 471 | macho_file, | ||
| 472 | object_id, | ||
| 473 | sym_index, | ||
| 474 | sym_index, | ||
| 475 | 1, | ||
| 476 | atom_size, | ||
| 477 | Alignment.fromLog2Units(sect.@"align"), | ||
| 478 | out_sect_id, | ||
| 479 | ); | ||
| 480 | if (!sect.isZerofill()) { | ||
| 481 | try self.cacheRelocs(macho_file, atom_index); | ||
| 482 | } | ||
| 483 | macho_file.addAtomToSection(atom_index); | ||
| 484 | } | 835 | } |
| 836 | gop.value_ptr.* = .{ .atom = atom, .size = size }; | ||
| 837 | } | ||
| 838 | } | ||
| 485 | 839 | ||
| 486 | var next_sym_index = sect_start_index; | 840 | for (self.unwind_records.items) |rec_index| { |
| 487 | while (next_sym_index < sect_start_index + sect_loc.len) { | 841 | const rec = macho_file.getUnwindRecord(rec_index); |
| 488 | const next_sym = symtab[next_sym_index]; | 842 | const atom = rec.getAtom(macho_file); |
| 489 | const addr = next_sym.n_value; | 843 | const addr = atom.getInputAddress(macho_file) + rec.atom_offset; |
| 490 | const atom_loc = filterSymbolsByAddress(symtab[next_sym_index..], addr, addr + 1); | 844 | superposition.getPtr(addr).?.cu = rec_index; |
| 491 | assert(atom_loc.len > 0); | 845 | } |
| 492 | const atom_sym_index = atom_loc.index + next_sym_index; | ||
| 493 | const nsyms_trailing = atom_loc.len; | ||
| 494 | next_sym_index += atom_loc.len; | ||
| 495 | |||
| 496 | const atom_size = if (next_sym_index < sect_start_index + sect_loc.len) | ||
| 497 | symtab[next_sym_index].n_value - addr | ||
| 498 | else | ||
| 499 | sect.addr + sect.size - addr; | ||
| 500 | 846 | ||
| 501 | const atom_align = Alignment.fromLog2Units(if (addr > 0) | 847 | for (self.fdes.items, 0..) |fde, fde_index| { |
| 502 | @min(@ctz(addr), sect.@"align") | 848 | const atom = fde.getAtom(macho_file); |
| 503 | else | 849 | const addr = atom.getInputAddress(macho_file) + fde.atom_offset; |
| 504 | sect.@"align"); | 850 | superposition.getPtr(addr).?.fde = @intCast(fde_index); |
| 505 | 851 | } | |
| 506 | const atom_index = try self.createAtomFromSubsection( | 852 | |
| 507 | macho_file, | 853 | for (superposition.keys(), superposition.values()) |addr, meta| { |
| 508 | object_id, | 854 | if (meta.fde) |fde_index| { |
| 509 | atom_sym_index, | 855 | const fde = &self.fdes.items[fde_index]; |
| 510 | atom_sym_index, | 856 | |
| 511 | nsyms_trailing, | 857 | if (meta.cu) |rec_index| { |
| 512 | atom_size, | 858 | const rec = macho_file.getUnwindRecord(rec_index); |
| 513 | atom_align, | 859 | if (!rec.enc.isDwarf(macho_file)) { |
| 514 | out_sect_id, | 860 | // Mark FDE dead |
| 515 | ); | 861 | fde.alive = false; |
| 516 | 862 | } else { | |
| 517 | // TODO rework this at the relocation level | 863 | // Tie FDE to unwind record |
| 518 | if (cpu_arch == .x86_64 and addr == sect.addr) { | 864 | rec.fde = fde_index; |
| 519 | // In x86_64 relocs, it can so happen that the compiler refers to the same | ||
| 520 | // atom by both the actual assigned symbol and the start of the section. In this | ||
| 521 | // case, we need to link the two together so add an alias. | ||
| 522 | const alias_index = self.getSectionAliasSymbolIndex(sect_id); | ||
| 523 | self.atom_by_index_table[alias_index] = atom_index; | ||
| 524 | } | 865 | } |
| 525 | if (!sect.isZerofill()) { | 866 | } else { |
| 526 | try self.cacheRelocs(macho_file, atom_index); | 867 | // Synthesise new unwind info record |
| 868 | const fde_data = fde.getData(macho_file); | ||
| 869 | const atom_size = mem.readInt(u64, fde_data[16..][0..8], .little); | ||
| 870 | const rec_index = try macho_file.addUnwindRecord(); | ||
| 871 | const rec = macho_file.getUnwindRecord(rec_index); | ||
| 872 | try self.unwind_records.append(gpa, rec_index); | ||
| 873 | rec.length = @intCast(atom_size); | ||
| 874 | rec.atom = fde.atom; | ||
| 875 | rec.atom_offset = fde.atom_offset; | ||
| 876 | rec.fde = fde_index; | ||
| 877 | rec.file = fde.file; | ||
| 878 | switch (macho_file.options.cpu_arch.?) { | ||
| 879 | .x86_64 => rec.enc.setMode(macho.UNWIND_X86_64_MODE.DWARF), | ||
| 880 | .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF), | ||
| 881 | else => unreachable, | ||
| 527 | } | 882 | } |
| 528 | macho_file.addAtomToSection(atom_index); | ||
| 529 | } | 883 | } |
| 530 | } else { | 884 | } else if (meta.cu == null and meta.fde == null) { |
| 531 | const alias_index = self.getSectionAliasSymbolIndex(sect_id); | 885 | // Create a null record |
| 532 | const atom_index = try self.createAtomFromSubsection( | 886 | const rec_index = try macho_file.addUnwindRecord(); |
| 533 | macho_file, | 887 | const rec = macho_file.getUnwindRecord(rec_index); |
| 534 | object_id, | 888 | const atom = macho_file.getAtom(meta.atom).?; |
| 535 | alias_index, | 889 | try self.unwind_records.append(gpa, rec_index); |
| 536 | sect_start_index, | 890 | rec.length = @intCast(meta.size); |
| 537 | sect_loc.len, | 891 | rec.atom = meta.atom; |
| 538 | sect.size, | 892 | rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file)); |
| 539 | Alignment.fromLog2Units(sect.@"align"), | 893 | rec.file = self.index; |
| 540 | out_sect_id, | ||
| 541 | ); | ||
| 542 | if (!sect.isZerofill()) { | ||
| 543 | try self.cacheRelocs(macho_file, atom_index); | ||
| 544 | } | ||
| 545 | macho_file.addAtomToSection(atom_index); | ||
| 546 | } | 894 | } |
| 547 | } | 895 | } |
| 548 | } | 896 | } |
| 549 | 897 | ||
| 550 | fn createAtomFromSubsection( | 898 | fn initPlatform(self: *Object) void { |
| 551 | self: *Object, | 899 | var it = LoadCommandIterator{ |
| 552 | macho_file: *MachO, | 900 | .ncmds = self.header.?.ncmds, |
| 553 | object_id: u32, | 901 | .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], |
| 554 | sym_index: u32, | 902 | }; |
| 555 | inner_sym_index: u32, | 903 | self.platform = while (it.next()) |cmd| { |
| 556 | inner_nsyms_trailing: u32, | 904 | switch (cmd.cmd()) { |
| 557 | size: u64, | 905 | .BUILD_VERSION, |
| 558 | alignment: Alignment, | 906 | .VERSION_MIN_MACOSX, |
| 559 | out_sect_id: u8, | 907 | .VERSION_MIN_IPHONEOS, |
| 560 | ) !Atom.Index { | 908 | .VERSION_MIN_TVOS, |
| 561 | const comp = macho_file.base.comp; | 909 | .VERSION_MIN_WATCHOS, |
| 562 | const gpa = comp.gpa; | 910 | => break MachO.Options.Platform.fromLoadCommand(cmd), |
| 563 | const atom_index = try macho_file.createAtom(sym_index, .{ | 911 | else => {}, |
| 564 | .size = size, | 912 | } |
| 565 | .alignment = alignment, | 913 | } else null; |
| 566 | }); | 914 | } |
| 567 | const atom = macho_file.getAtomPtr(atom_index); | ||
| 568 | atom.inner_sym_index = inner_sym_index; | ||
| 569 | atom.inner_nsyms_trailing = inner_nsyms_trailing; | ||
| 570 | atom.file = object_id + 1; | ||
| 571 | self.symtab[sym_index].n_sect = out_sect_id + 1; | ||
| 572 | |||
| 573 | log.debug("creating ATOM(%{d}, '{s}') in sect({d}, '{s},{s}') in object({d})", .{ | ||
| 574 | sym_index, | ||
| 575 | self.getSymbolName(sym_index), | ||
| 576 | out_sect_id + 1, | ||
| 577 | macho_file.sections.items(.header)[out_sect_id].segName(), | ||
| 578 | macho_file.sections.items(.header)[out_sect_id].sectName(), | ||
| 579 | object_id, | ||
| 580 | }); | ||
| 581 | |||
| 582 | try self.atoms.append(gpa, atom_index); | ||
| 583 | self.atom_by_index_table[sym_index] = atom_index; | ||
| 584 | 915 | ||
| 585 | var it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 916 | /// Currently, we only check if a compile unit for this input object file exists |
| 586 | while (it.next()) |sym_loc| { | 917 | /// and record that so that we can emit symbol stabs. |
| 587 | const inner = macho_file.getSymbolPtr(sym_loc); | 918 | /// TODO in the future, we want parse debug info and debug line sections so that |
| 588 | inner.n_sect = out_sect_id + 1; | 919 | /// we can provide nice error locations to the user. |
| 589 | self.atom_by_index_table[sym_loc.sym_index] = atom_index; | 920 | fn initDwarfInfo(self: *Object, macho_file: *MachO) !void { |
| 921 | const tracy = trace(@src()); | ||
| 922 | defer tracy.end(); | ||
| 923 | |||
| 924 | const gpa = macho_file.base.allocator; | ||
| 925 | |||
| 926 | var debug_info_index: ?usize = null; | ||
| 927 | var debug_abbrev_index: ?usize = null; | ||
| 928 | var debug_str_index: ?usize = null; | ||
| 929 | |||
| 930 | for (self.sections.items(.header), 0..) |sect, index| { | ||
| 931 | if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue; | ||
| 932 | if (mem.eql(u8, sect.sectName(), "__debug_info")) debug_info_index = index; | ||
| 933 | if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) debug_abbrev_index = index; | ||
| 934 | if (mem.eql(u8, sect.sectName(), "__debug_str")) debug_str_index = index; | ||
| 590 | } | 935 | } |
| 591 | 936 | ||
| 592 | const out_sect = macho_file.sections.items(.header)[out_sect_id]; | 937 | if (debug_info_index == null or debug_abbrev_index == null) return; |
| 593 | if (out_sect.isCode() and | ||
| 594 | mem.eql(u8, "__TEXT", out_sect.segName()) and | ||
| 595 | mem.eql(u8, "__text", out_sect.sectName())) | ||
| 596 | { | ||
| 597 | // TODO currently assuming a single section for executable machine code | ||
| 598 | try self.exec_atoms.append(gpa, atom_index); | ||
| 599 | } | ||
| 600 | 938 | ||
| 601 | return atom_index; | 939 | var dwarf_info = DwarfInfo{ |
| 940 | .debug_info = self.getSectionData(@intCast(debug_info_index.?)), | ||
| 941 | .debug_abbrev = self.getSectionData(@intCast(debug_abbrev_index.?)), | ||
| 942 | .debug_str = if (debug_str_index) |index| self.getSectionData(@intCast(index)) else "", | ||
| 943 | }; | ||
| 944 | dwarf_info.init(gpa) catch { | ||
| 945 | macho_file.base.fatal("{}: invalid __DWARF info found", .{self.fmtPath()}); | ||
| 946 | return error.ParseFailed; | ||
| 947 | }; | ||
| 948 | self.dwarf_info = dwarf_info; | ||
| 602 | } | 949 | } |
| 603 | 950 | ||
| 604 | fn filterRelocs( | 951 | pub fn resolveSymbols(self: *Object, macho_file: *MachO) void { |
| 605 | relocs: []align(1) const macho.relocation_info, | 952 | const tracy = trace(@src()); |
| 606 | start_addr: u64, | 953 | defer tracy.end(); |
| 607 | end_addr: u64, | ||
| 608 | ) Entry { | ||
| 609 | const Predicate = struct { | ||
| 610 | addr: u64, | ||
| 611 | 954 | ||
| 612 | pub fn predicate(self: @This(), rel: macho.relocation_info) bool { | 955 | for (self.symbols.items, 0..) |index, i| { |
| 613 | return rel.r_address >= self.addr; | 956 | const nlist_idx = @as(Symbol.Index, @intCast(i)); |
| 614 | } | 957 | const nlist = self.symtab.items(.nlist)[nlist_idx]; |
| 615 | }; | 958 | const atom_index = self.symtab.items(.atom)[nlist_idx]; |
| 616 | const LPredicate = struct { | ||
| 617 | addr: u64, | ||
| 618 | 959 | ||
| 619 | pub fn predicate(self: @This(), rel: macho.relocation_info) bool { | 960 | if (!nlist.ext()) continue; |
| 620 | return rel.r_address < self.addr; | 961 | if (nlist.undf() and !nlist.tentative()) continue; |
| 962 | if (nlist.sect()) { | ||
| 963 | const atom = macho_file.getAtom(atom_index).?; | ||
| 964 | if (!atom.flags.alive) continue; | ||
| 621 | } | 965 | } |
| 622 | }; | ||
| 623 | 966 | ||
| 624 | const start = MachO.bsearch(macho.relocation_info, relocs, Predicate{ .addr = end_addr }); | 967 | const symbol = macho_file.getSymbol(index); |
| 625 | const len = MachO.lsearch(macho.relocation_info, relocs[start..], LPredicate{ .addr = start_addr }); | 968 | if (self.asFile().getSymbolRank(.{ |
| 969 | .archive = !self.alive, | ||
| 970 | .weak = nlist.weakDef(), | ||
| 971 | .tentative = nlist.tentative(), | ||
| 972 | }) < symbol.getSymbolRank(macho_file)) { | ||
| 973 | const value = if (nlist.sect()) blk: { | ||
| 974 | const atom = macho_file.getAtom(atom_index).?; | ||
| 975 | break :blk nlist.n_value - atom.getInputAddress(macho_file); | ||
| 976 | } else nlist.n_value; | ||
| 977 | symbol.value = value; | ||
| 978 | symbol.atom = atom_index; | ||
| 979 | symbol.nlist_idx = nlist_idx; | ||
| 980 | symbol.file = self.index; | ||
| 981 | symbol.flags.weak = nlist.weakDef(); | ||
| 982 | symbol.flags.abs = nlist.abs(); | ||
| 983 | symbol.flags.tentative = nlist.tentative(); | ||
| 984 | symbol.flags.weak_ref = false; | ||
| 985 | symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0; | ||
| 986 | symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip(); | ||
| 987 | symbol.flags.interposable = macho_file.options.dylib and macho_file.options.namespace == .flat and !nlist.pext(); | ||
| 988 | |||
| 989 | if (nlist.sect() and | ||
| 990 | self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES) | ||
| 991 | { | ||
| 992 | symbol.flags.tlv = true; | ||
| 993 | } | ||
| 994 | } | ||
| 626 | 995 | ||
| 627 | return .{ .start = @as(u32, @intCast(start)), .len = @as(u32, @intCast(len)) }; | 996 | // Regardless of who the winner is, we still merge symbol visibility here. |
| 997 | if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) { | ||
| 998 | if (symbol.visibility != .global) { | ||
| 999 | symbol.visibility = .hidden; | ||
| 1000 | } | ||
| 1001 | } else { | ||
| 1002 | symbol.visibility = .global; | ||
| 1003 | } | ||
| 1004 | } | ||
| 628 | } | 1005 | } |
| 629 | 1006 | ||
| 630 | /// Parse all relocs for the input section, and sort in descending order. | 1007 | pub fn resetGlobals(self: *Object, macho_file: *MachO) void { |
| 631 | /// Previously, I have wrongly assumed the compilers output relocations for each | 1008 | for (self.symbols.items, 0..) |sym_index, nlist_idx| { |
| 632 | /// section in a sorted manner which is simply not true. | 1009 | if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue; |
| 633 | fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void { | 1010 | const sym = macho_file.getSymbol(sym_index); |
| 634 | const section = self.getSourceSection(sect_id); | 1011 | const name = sym.name; |
| 635 | const start = @as(u32, @intCast(self.relocations.items.len)); | 1012 | sym.* = .{}; |
| 636 | if (self.getSourceRelocs(section)) |relocs| { | 1013 | sym.name = name; |
| 637 | try self.relocations.ensureUnusedCapacity(gpa, relocs.len); | ||
| 638 | self.relocations.appendUnalignedSliceAssumeCapacity(relocs); | ||
| 639 | mem.sort(macho.relocation_info, self.relocations.items[start..], {}, relocGreaterThan); | ||
| 640 | } | 1014 | } |
| 641 | self.section_relocs_lookup.items[sect_id] = start; | ||
| 642 | } | 1015 | } |
| 643 | 1016 | ||
| 644 | fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void { | 1017 | pub fn markLive(self: *Object, macho_file: *MachO) void { |
| 645 | const atom = macho_file.getAtom(atom_index); | 1018 | const tracy = trace(@src()); |
| 646 | 1019 | defer tracy.end(); | |
| 647 | const source_sect_id = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: { | 1020 | |
| 648 | break :blk source_sym.n_sect - 1; | 1021 | for (self.symbols.items, 0..) |index, nlist_idx| { |
| 649 | } else blk: { | 1022 | const nlist = self.symtab.items(.nlist)[nlist_idx]; |
| 650 | // If there was no matching symbol present in the source symtab, this means | 1023 | if (!nlist.ext()) continue; |
| 651 | // we are dealing with either an entire section, or part of it, but also | ||
| 652 | // starting at the beginning. | ||
| 653 | const nbase = @as(u32, @intCast(self.in_symtab.?.len)); | ||
| 654 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | ||
| 655 | break :blk sect_id; | ||
| 656 | }; | ||
| 657 | const source_sect = self.getSourceSection(source_sect_id); | ||
| 658 | assert(!source_sect.isZerofill()); | ||
| 659 | const relocs = self.getRelocs(source_sect_id); | ||
| 660 | |||
| 661 | self.relocs_lookup[atom.sym_index] = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: { | ||
| 662 | const offset = source_sym.n_value - source_sect.addr; | ||
| 663 | break :blk filterRelocs(relocs, offset, offset + atom.size); | ||
| 664 | } else filterRelocs(relocs, 0, atom.size); | ||
| 665 | } | ||
| 666 | 1024 | ||
| 667 | fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { | 1025 | const sym = macho_file.getSymbol(index); |
| 668 | _ = ctx; | 1026 | const file = sym.getFile(macho_file) orelse continue; |
| 669 | return lhs.r_address > rhs.r_address; | 1027 | const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative); |
| 1028 | if (should_keep and file == .object and !file.object.alive) { | ||
| 1029 | file.object.alive = true; | ||
| 1030 | file.object.markLive(macho_file); | ||
| 1031 | } | ||
| 1032 | } | ||
| 670 | } | 1033 | } |
| 671 | 1034 | ||
| 672 | fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void { | 1035 | pub fn scanRelocs(self: Object, macho_file: *MachO) !void { |
| 673 | const sect_id = self.eh_frame_sect_id orelse return; | 1036 | const tracy = trace(@src()); |
| 674 | const sect = self.getSourceSection(sect_id); | 1037 | defer tracy.end(); |
| 675 | 1038 | ||
| 676 | log.debug("parsing __TEXT,__eh_frame section", .{}); | 1039 | for (self.atoms.items) |atom_index| { |
| 1040 | const atom = macho_file.getAtom(atom_index).?; | ||
| 1041 | if (!atom.flags.alive) continue; | ||
| 1042 | const sect = atom.getInputSection(macho_file); | ||
| 1043 | if (sect.isZerofill()) continue; | ||
| 1044 | try atom.scanRelocs(macho_file); | ||
| 1045 | } | ||
| 677 | 1046 | ||
| 678 | const comp = macho_file.base.comp; | 1047 | for (self.unwind_records.items) |rec_index| { |
| 679 | const gpa = comp.gpa; | 1048 | const rec = macho_file.getUnwindRecord(rec_index); |
| 1049 | if (!rec.alive) continue; | ||
| 1050 | if (rec.getFde(macho_file)) |fde| { | ||
| 1051 | if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| { | ||
| 1052 | sym.flags.got = true; | ||
| 1053 | } | ||
| 1054 | } else if (rec.getPersonality(macho_file)) |sym| { | ||
| 1055 | sym.flags.got = true; | ||
| 1056 | } | ||
| 1057 | } | ||
| 1058 | } | ||
| 680 | 1059 | ||
| 681 | if (macho_file.eh_frame_section_index == null) { | 1060 | pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void { |
| 682 | macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{}); | 1061 | const tracy = trace(@src()); |
| 1062 | defer tracy.end(); | ||
| 1063 | const gpa = macho_file.base.allocator; | ||
| 1064 | |||
| 1065 | for (self.symbols.items, 0..) |index, i| { | ||
| 1066 | const sym = macho_file.getSymbol(index); | ||
| 1067 | if (!sym.flags.tentative) continue; | ||
| 1068 | const sym_file = sym.getFile(macho_file).?; | ||
| 1069 | if (sym_file.getIndex() != self.index) continue; | ||
| 1070 | |||
| 1071 | const nlist_idx = @as(Symbol.Index, @intCast(i)); | ||
| 1072 | const nlist = &self.symtab.items(.nlist)[nlist_idx]; | ||
| 1073 | const nlist_atom = &self.symtab.items(.atom)[nlist_idx]; | ||
| 1074 | |||
| 1075 | const atom_index = try macho_file.addAtom(); | ||
| 1076 | try self.atoms.append(gpa, atom_index); | ||
| 1077 | |||
| 1078 | const name = try std.fmt.allocPrintZ(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}); | ||
| 1079 | defer gpa.free(name); | ||
| 1080 | const atom = macho_file.getAtom(atom_index).?; | ||
| 1081 | atom.atom_index = atom_index; | ||
| 1082 | atom.name = try macho_file.string_intern.insert(gpa, name); | ||
| 1083 | atom.file = self.index; | ||
| 1084 | atom.size = nlist.n_value; | ||
| 1085 | atom.alignment = (nlist.n_desc >> 8) & 0x0f; | ||
| 1086 | |||
| 1087 | const n_sect = try self.addSection(gpa, "__DATA", "__common"); | ||
| 1088 | const sect = &self.sections.items(.header)[n_sect]; | ||
| 1089 | sect.flags = macho.S_ZEROFILL; | ||
| 1090 | sect.size = atom.size; | ||
| 1091 | sect.@"align" = atom.alignment; | ||
| 1092 | atom.n_sect = n_sect; | ||
| 1093 | |||
| 1094 | sym.value = 0; | ||
| 1095 | sym.atom = atom_index; | ||
| 1096 | sym.flags.weak = false; | ||
| 1097 | sym.flags.weak_ref = false; | ||
| 1098 | sym.flags.tentative = false; | ||
| 1099 | sym.visibility = .global; | ||
| 1100 | |||
| 1101 | nlist.n_value = 0; | ||
| 1102 | nlist.n_type = macho.N_EXT | macho.N_SECT; | ||
| 1103 | nlist.n_sect = 0; | ||
| 1104 | nlist.n_desc = 0; | ||
| 1105 | nlist_atom.* = atom_index; | ||
| 683 | } | 1106 | } |
| 1107 | } | ||
| 684 | 1108 | ||
| 685 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 1109 | fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 { |
| 686 | const cpu_arch = target.cpu.arch; | 1110 | const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator))); |
| 687 | try self.parseRelocs(gpa, sect_id); | 1111 | self.sections.set(n_sect, .{ |
| 688 | const relocs = self.getRelocs(sect_id); | 1112 | .header = .{ |
| 1113 | .sectname = MachO.makeStaticString(sectname), | ||
| 1114 | .segname = MachO.makeStaticString(segname), | ||
| 1115 | }, | ||
| 1116 | }); | ||
| 1117 | return n_sect; | ||
| 1118 | } | ||
| 689 | 1119 | ||
| 690 | var it = self.getEhFrameRecordsIterator(); | 1120 | pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { |
| 691 | var record_count: u32 = 0; | 1121 | const tracy = trace(@src()); |
| 692 | while (try it.next()) |_| { | 1122 | defer tracy.end(); |
| 693 | record_count += 1; | 1123 | |
| 1124 | for (self.symbols.items) |sym_index| { | ||
| 1125 | const sym = macho_file.getSymbol(sym_index); | ||
| 1126 | const file = sym.getFile(macho_file) orelse continue; | ||
| 1127 | if (file.getIndex() != self.index) continue; | ||
| 1128 | if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue; | ||
| 1129 | if (sym.isSymbolStab(macho_file)) continue; | ||
| 1130 | const name = sym.getName(macho_file); | ||
| 1131 | // TODO in -r mode, we actually want to merge symbol names and emit only one | ||
| 1132 | // work it out when emitting relocs | ||
| 1133 | if (name.len > 0 and (name[0] == 'L' or name[0] == 'l') and !macho_file.options.relocatable) continue; | ||
| 1134 | sym.flags.output_symtab = true; | ||
| 1135 | if (sym.isLocal()) { | ||
| 1136 | try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file); | ||
| 1137 | self.output_symtab_ctx.nlocals += 1; | ||
| 1138 | } else if (sym.flags.@"export") { | ||
| 1139 | try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file); | ||
| 1140 | self.output_symtab_ctx.nexports += 1; | ||
| 1141 | } else { | ||
| 1142 | assert(sym.flags.import); | ||
| 1143 | try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file); | ||
| 1144 | self.output_symtab_ctx.nimports += 1; | ||
| 1145 | } | ||
| 1146 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); | ||
| 694 | } | 1147 | } |
| 695 | 1148 | ||
| 696 | try self.eh_frame_relocs_lookup.ensureTotalCapacity(gpa, record_count); | 1149 | if (!macho_file.options.strip and self.hasDebugInfo()) self.calcStabsSize(macho_file); |
| 697 | try self.eh_frame_records_lookup.ensureUnusedCapacity(gpa, record_count); | 1150 | } |
| 698 | 1151 | ||
| 699 | it.reset(); | 1152 | pub fn calcStabsSize(self: *Object, macho_file: *MachO) void { |
| 1153 | if (self.dwarf_info) |dw| { | ||
| 1154 | // TODO handle multiple CUs | ||
| 1155 | const cu = dw.compile_units.items[0]; | ||
| 1156 | const comp_dir = cu.getCompileDir(dw) orelse return; | ||
| 1157 | const tu_name = cu.getSourceFile(dw) orelse return; | ||
| 700 | 1158 | ||
| 701 | while (try it.next()) |record| { | 1159 | self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO |
| 702 | const offset = it.pos - record.getSize(); | 1160 | self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir |
| 703 | const rel_pos: Entry = switch (cpu_arch) { | 1161 | self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name |
| 704 | .aarch64 => filterRelocs(relocs, offset, offset + record.getSize()), | 1162 | |
| 705 | .x86_64 => .{}, | 1163 | if (self.archive) |path| { |
| 706 | else => unreachable, | 1164 | self.output_symtab_ctx.strsize += @as(u32, @intCast(path.len + 1 + self.path.len + 1 + 1)); |
| 707 | }; | 1165 | } else { |
| 708 | self.eh_frame_relocs_lookup.putAssumeCapacityNoClobber(offset, .{ | 1166 | self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1)); |
| 709 | .dead = false, | 1167 | } |
| 710 | .reloc = rel_pos, | 1168 | |
| 711 | }); | 1169 | for (self.symbols.items) |sym_index| { |
| 712 | 1170 | const sym = macho_file.getSymbol(sym_index); | |
| 713 | if (record.tag == .fde) { | 1171 | const file = sym.getFile(macho_file) orelse continue; |
| 714 | const reloc_target = blk: { | 1172 | if (file.getIndex() != self.index) continue; |
| 715 | switch (cpu_arch) { | 1173 | if (!sym.flags.output_symtab) continue; |
| 716 | .aarch64 => { | 1174 | if (macho_file.options.relocatable) { |
| 717 | assert(rel_pos.len > 0); // TODO convert to an error as the FDE eh frame is malformed | 1175 | const name = sym.getName(macho_file); |
| 718 | // Find function symbol that this record describes | 1176 | if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue; |
| 719 | const rel = for (relocs[rel_pos.start..][0..rel_pos.len]) |rel| { | 1177 | } |
| 720 | if (rel.r_address - @as(i32, @intCast(offset)) == 8 and | 1178 | const sect = macho_file.sections.items(.header)[sym.out_n_sect]; |
| 721 | @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_UNSIGNED) | 1179 | if (sect.isCode()) { |
| 722 | break rel; | 1180 | self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM |
| 723 | } else unreachable; | 1181 | } else if (sym.visibility == .global) { |
| 724 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 1182 | self.output_symtab_ctx.nstabs += 1; // N_GSYM |
| 725 | .object_id = object_id, | ||
| 726 | .rel = rel, | ||
| 727 | .code = it.data[offset..], | ||
| 728 | .base_offset = @as(i32, @intCast(offset)), | ||
| 729 | }); | ||
| 730 | break :blk reloc_target; | ||
| 731 | }, | ||
| 732 | .x86_64 => { | ||
| 733 | const target_address = record.getTargetSymbolAddress(.{ | ||
| 734 | .base_addr = sect.addr, | ||
| 735 | .base_offset = offset, | ||
| 736 | }); | ||
| 737 | const target_sym_index = self.getSymbolByAddress(target_address, null); | ||
| 738 | const reloc_target = if (self.getGlobal(target_sym_index)) |global_index| | ||
| 739 | macho_file.globals.items[global_index] | ||
| 740 | else | ||
| 741 | SymbolWithLoc{ .sym_index = target_sym_index, .file = object_id + 1 }; | ||
| 742 | break :blk reloc_target; | ||
| 743 | }, | ||
| 744 | else => unreachable, | ||
| 745 | } | ||
| 746 | }; | ||
| 747 | if (reloc_target.getFile() != object_id) { | ||
| 748 | log.debug("FDE at offset {x} marked DEAD", .{offset}); | ||
| 749 | self.eh_frame_relocs_lookup.getPtr(offset).?.dead = true; | ||
| 750 | } else { | 1183 | } else { |
| 751 | // You would think that we are done but turns out that the compilers may use | 1184 | self.output_symtab_ctx.nstabs += 1; // N_STSYM |
| 752 | // whichever symbol alias they want for a target symbol. This in particular | 1185 | } |
| 753 | // very problematic when using Zig's @export feature to re-export symbols under | 1186 | } |
| 754 | // additional names. For that reason, we need to ensure we record aliases here | 1187 | } else { |
| 755 | // too so that we can tie them with their matching unwind records and vice versa. | 1188 | assert(self.hasSymbolStabs()); |
| 756 | const aliases = self.getSymbolAliases(reloc_target.sym_index); | 1189 | |
| 757 | var i: u32 = 0; | 1190 | for (self.stab_files.items) |sf| { |
| 758 | while (i < aliases.len) : (i += 1) { | 1191 | self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO |
| 759 | const actual_target = SymbolWithLoc{ | 1192 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getCompDir(self).len + 1)); // comp_dir |
| 760 | .sym_index = i + aliases.start, | 1193 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getTuName(self).len + 1)); // tu_name |
| 761 | .file = reloc_target.file, | 1194 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getOsoPath(self).len + 1)); // path |
| 762 | }; | 1195 | |
| 763 | log.debug("FDE at offset {x} tracks {s}", .{ | 1196 | for (sf.stabs.items) |stab| { |
| 764 | offset, | 1197 | const sym = stab.getSymbol(macho_file) orelse continue; |
| 765 | macho_file.getSymbolName(actual_target), | 1198 | const file = sym.getFile(macho_file).?; |
| 766 | }); | 1199 | if (file.getIndex() != self.index) continue; |
| 767 | try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset); | 1200 | if (!sym.flags.output_symtab) continue; |
| 768 | } | 1201 | const nstabs: u32 = switch (stab.tag) { |
| 1202 | .func => 4, // N_BNSYM, N_FUN, N_FUN, N_ENSYM | ||
| 1203 | .global => 1, // N_GSYM | ||
| 1204 | .static => 1, // N_STSYM | ||
| 1205 | }; | ||
| 1206 | self.output_symtab_ctx.nstabs += nstabs; | ||
| 769 | } | 1207 | } |
| 770 | } | 1208 | } |
| 771 | } | 1209 | } |
| 772 | } | 1210 | } |
| 773 | 1211 | ||
| 774 | fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void { | 1212 | pub fn writeSymtab(self: Object, macho_file: *MachO) void { |
| 775 | const comp = macho_file.base.comp; | 1213 | const tracy = trace(@src()); |
| 776 | const gpa = comp.gpa; | 1214 | defer tracy.end(); |
| 777 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 1215 | |
| 778 | const cpu_arch = target.cpu.arch; | 1216 | for (self.symbols.items) |sym_index| { |
| 779 | const sect_id = self.unwind_info_sect_id orelse { | 1217 | const sym = macho_file.getSymbol(sym_index); |
| 780 | // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`, | 1218 | const file = sym.getFile(macho_file) orelse continue; |
| 781 | // we will try fully synthesising unwind info records to somewhat match Apple ld's | 1219 | if (file.getIndex() != self.index) continue; |
| 782 | // approach. However, we will only synthesise DWARF records and nothing more. For this reason, | 1220 | const idx = sym.getOutputSymtabIndex(macho_file) orelse continue; |
| 783 | // we still create the output `__TEXT,__unwind_info` section. | 1221 | const n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); |
| 784 | if (self.hasEhFrameRecords()) { | 1222 | macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file)); |
| 785 | if (macho_file.unwind_info_section_index == null) { | 1223 | macho_file.strtab.appendAssumeCapacity(0); |
| 786 | macho_file.unwind_info_section_index = try macho_file.initSection( | 1224 | const out_sym = &macho_file.symtab.items[idx]; |
| 787 | "__TEXT", | 1225 | out_sym.n_strx = n_strx; |
| 788 | "__unwind_info", | 1226 | sym.setOutputSym(macho_file, out_sym); |
| 789 | .{}, | 1227 | } |
| 790 | ); | 1228 | |
| 1229 | if (!macho_file.options.strip and self.hasDebugInfo()) self.writeStabs(macho_file); | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | pub fn writeStabs(self: *const Object, macho_file: *MachO) void { | ||
| 1233 | const writeFuncStab = struct { | ||
| 1234 | inline fn writeFuncStab( | ||
| 1235 | n_strx: u32, | ||
| 1236 | n_sect: u8, | ||
| 1237 | n_value: u64, | ||
| 1238 | size: u64, | ||
| 1239 | index: u32, | ||
| 1240 | ctx: *MachO, | ||
| 1241 | ) void { | ||
| 1242 | ctx.symtab.items[index] = .{ | ||
| 1243 | .n_strx = 0, | ||
| 1244 | .n_type = macho.N_BNSYM, | ||
| 1245 | .n_sect = n_sect, | ||
| 1246 | .n_desc = 0, | ||
| 1247 | .n_value = n_value, | ||
| 1248 | }; | ||
| 1249 | ctx.symtab.items[index + 1] = .{ | ||
| 1250 | .n_strx = n_strx, | ||
| 1251 | .n_type = macho.N_FUN, | ||
| 1252 | .n_sect = n_sect, | ||
| 1253 | .n_desc = 0, | ||
| 1254 | .n_value = n_value, | ||
| 1255 | }; | ||
| 1256 | ctx.symtab.items[index + 2] = .{ | ||
| 1257 | .n_strx = 0, | ||
| 1258 | .n_type = macho.N_FUN, | ||
| 1259 | .n_sect = 0, | ||
| 1260 | .n_desc = 0, | ||
| 1261 | .n_value = size, | ||
| 1262 | }; | ||
| 1263 | ctx.symtab.items[index + 3] = .{ | ||
| 1264 | .n_strx = 0, | ||
| 1265 | .n_type = macho.N_ENSYM, | ||
| 1266 | .n_sect = n_sect, | ||
| 1267 | .n_desc = 0, | ||
| 1268 | .n_value = size, | ||
| 1269 | }; | ||
| 1270 | } | ||
| 1271 | }.writeFuncStab; | ||
| 1272 | |||
| 1273 | var index = self.output_symtab_ctx.istab; | ||
| 1274 | |||
| 1275 | if (self.dwarf_info) |dw| { | ||
| 1276 | // TODO handle multiple CUs | ||
| 1277 | const cu = dw.compile_units.items[0]; | ||
| 1278 | const comp_dir = cu.getCompileDir(dw) orelse return; | ||
| 1279 | const tu_name = cu.getSourceFile(dw) orelse return; | ||
| 1280 | |||
| 1281 | // Open scope | ||
| 1282 | // N_SO comp_dir | ||
| 1283 | var n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 1284 | macho_file.strtab.appendSliceAssumeCapacity(comp_dir); | ||
| 1285 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1286 | macho_file.symtab.items[index] = .{ | ||
| 1287 | .n_strx = n_strx, | ||
| 1288 | .n_type = macho.N_SO, | ||
| 1289 | .n_sect = 0, | ||
| 1290 | .n_desc = 0, | ||
| 1291 | .n_value = 0, | ||
| 1292 | }; | ||
| 1293 | index += 1; | ||
| 1294 | // N_SO tu_name | ||
| 1295 | n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 1296 | macho_file.strtab.appendSliceAssumeCapacity(tu_name); | ||
| 1297 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1298 | macho_file.symtab.items[index] = .{ | ||
| 1299 | .n_strx = n_strx, | ||
| 1300 | .n_type = macho.N_SO, | ||
| 1301 | .n_sect = 0, | ||
| 1302 | .n_desc = 0, | ||
| 1303 | .n_value = 0, | ||
| 1304 | }; | ||
| 1305 | index += 1; | ||
| 1306 | // N_OSO path | ||
| 1307 | n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 1308 | if (self.archive) |path| { | ||
| 1309 | macho_file.strtab.appendSliceAssumeCapacity(path); | ||
| 1310 | macho_file.strtab.appendAssumeCapacity('('); | ||
| 1311 | macho_file.strtab.appendSliceAssumeCapacity(self.path); | ||
| 1312 | macho_file.strtab.appendAssumeCapacity(')'); | ||
| 1313 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1314 | } else { | ||
| 1315 | macho_file.strtab.appendSliceAssumeCapacity(self.path); | ||
| 1316 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1317 | } | ||
| 1318 | macho_file.symtab.items[index] = .{ | ||
| 1319 | .n_strx = n_strx, | ||
| 1320 | .n_type = macho.N_OSO, | ||
| 1321 | .n_sect = 0, | ||
| 1322 | .n_desc = 1, | ||
| 1323 | .n_value = self.mtime, | ||
| 1324 | }; | ||
| 1325 | index += 1; | ||
| 1326 | |||
| 1327 | for (self.symbols.items) |sym_index| { | ||
| 1328 | const sym = macho_file.getSymbol(sym_index); | ||
| 1329 | const file = sym.getFile(macho_file) orelse continue; | ||
| 1330 | if (file.getIndex() != self.index) continue; | ||
| 1331 | if (!sym.flags.output_symtab) continue; | ||
| 1332 | if (macho_file.options.relocatable) { | ||
| 1333 | const name = sym.getName(macho_file); | ||
| 1334 | if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue; | ||
| 1335 | } | ||
| 1336 | const sect = macho_file.sections.items(.header)[sym.out_n_sect]; | ||
| 1337 | const sym_n_strx = n_strx: { | ||
| 1338 | const symtab_index = sym.getOutputSymtabIndex(macho_file).?; | ||
| 1339 | const osym = macho_file.symtab.items[symtab_index]; | ||
| 1340 | break :n_strx osym.n_strx; | ||
| 1341 | }; | ||
| 1342 | const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; | ||
| 1343 | const sym_n_value = sym.getAddress(.{}, macho_file); | ||
| 1344 | const sym_size = sym.getSize(macho_file); | ||
| 1345 | if (sect.isCode()) { | ||
| 1346 | writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); | ||
| 1347 | index += 4; | ||
| 1348 | } else if (sym.visibility == .global) { | ||
| 1349 | macho_file.symtab.items[index] = .{ | ||
| 1350 | .n_strx = sym_n_strx, | ||
| 1351 | .n_type = macho.N_GSYM, | ||
| 1352 | .n_sect = sym_n_sect, | ||
| 1353 | .n_desc = 0, | ||
| 1354 | .n_value = 0, | ||
| 1355 | }; | ||
| 1356 | index += 1; | ||
| 1357 | } else { | ||
| 1358 | macho_file.symtab.items[index] = .{ | ||
| 1359 | .n_strx = sym_n_strx, | ||
| 1360 | .n_type = macho.N_STSYM, | ||
| 1361 | .n_sect = sym_n_sect, | ||
| 1362 | .n_desc = 0, | ||
| 1363 | .n_value = sym_n_value, | ||
| 1364 | }; | ||
| 1365 | index += 1; | ||
| 791 | } | 1366 | } |
| 792 | } | 1367 | } |
| 793 | return; | ||
| 794 | }; | ||
| 795 | 1368 | ||
| 796 | log.debug("parsing unwind info in {s}", .{self.name}); | 1369 | // Close scope |
| 1370 | // N_SO | ||
| 1371 | macho_file.symtab.items[index] = .{ | ||
| 1372 | .n_strx = 0, | ||
| 1373 | .n_type = macho.N_SO, | ||
| 1374 | .n_sect = 0, | ||
| 1375 | .n_desc = 0, | ||
| 1376 | .n_value = 0, | ||
| 1377 | }; | ||
| 1378 | } else { | ||
| 1379 | assert(self.hasSymbolStabs()); | ||
| 1380 | |||
| 1381 | for (self.stab_files.items) |sf| { | ||
| 1382 | // Open scope | ||
| 1383 | // N_SO comp_dir | ||
| 1384 | var n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 1385 | macho_file.strtab.appendSliceAssumeCapacity(sf.getCompDir(self)); | ||
| 1386 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1387 | macho_file.symtab.items[index] = .{ | ||
| 1388 | .n_strx = n_strx, | ||
| 1389 | .n_type = macho.N_SO, | ||
| 1390 | .n_sect = 0, | ||
| 1391 | .n_desc = 0, | ||
| 1392 | .n_value = 0, | ||
| 1393 | }; | ||
| 1394 | index += 1; | ||
| 1395 | // N_SO tu_name | ||
| 1396 | n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 1397 | macho_file.strtab.appendSliceAssumeCapacity(sf.getTuName(self)); | ||
| 1398 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1399 | macho_file.symtab.items[index] = .{ | ||
| 1400 | .n_strx = n_strx, | ||
| 1401 | .n_type = macho.N_SO, | ||
| 1402 | .n_sect = 0, | ||
| 1403 | .n_desc = 0, | ||
| 1404 | .n_value = 0, | ||
| 1405 | }; | ||
| 1406 | index += 1; | ||
| 1407 | // N_OSO path | ||
| 1408 | n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); | ||
| 1409 | macho_file.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self)); | ||
| 1410 | macho_file.strtab.appendAssumeCapacity(0); | ||
| 1411 | macho_file.symtab.items[index] = .{ | ||
| 1412 | .n_strx = n_strx, | ||
| 1413 | .n_type = macho.N_OSO, | ||
| 1414 | .n_sect = 0, | ||
| 1415 | .n_desc = 1, | ||
| 1416 | .n_value = sf.getOsoModTime(self), | ||
| 1417 | }; | ||
| 1418 | index += 1; | ||
| 1419 | |||
| 1420 | for (sf.stabs.items) |stab| { | ||
| 1421 | const sym = stab.getSymbol(macho_file) orelse continue; | ||
| 1422 | const file = sym.getFile(macho_file).?; | ||
| 1423 | if (file.getIndex() != self.index) continue; | ||
| 1424 | if (!sym.flags.output_symtab) continue; | ||
| 1425 | const sym_n_strx = n_strx: { | ||
| 1426 | const symtab_index = sym.getOutputSymtabIndex(macho_file).?; | ||
| 1427 | const osym = macho_file.symtab.items[symtab_index]; | ||
| 1428 | break :n_strx osym.n_strx; | ||
| 1429 | }; | ||
| 1430 | const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; | ||
| 1431 | const sym_n_value = sym.getAddress(.{}, macho_file); | ||
| 1432 | const sym_size = sym.getSize(macho_file); | ||
| 1433 | switch (stab.tag) { | ||
| 1434 | .func => { | ||
| 1435 | writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); | ||
| 1436 | index += 4; | ||
| 1437 | }, | ||
| 1438 | .global => { | ||
| 1439 | macho_file.symtab.items[index] = .{ | ||
| 1440 | .n_strx = sym_n_strx, | ||
| 1441 | .n_type = macho.N_GSYM, | ||
| 1442 | .n_sect = sym_n_sect, | ||
| 1443 | .n_desc = 0, | ||
| 1444 | .n_value = 0, | ||
| 1445 | }; | ||
| 1446 | index += 1; | ||
| 1447 | }, | ||
| 1448 | .static => { | ||
| 1449 | macho_file.symtab.items[index] = .{ | ||
| 1450 | .n_strx = sym_n_strx, | ||
| 1451 | .n_type = macho.N_STSYM, | ||
| 1452 | .n_sect = sym_n_sect, | ||
| 1453 | .n_desc = 0, | ||
| 1454 | .n_value = sym_n_value, | ||
| 1455 | }; | ||
| 1456 | index += 1; | ||
| 1457 | }, | ||
| 1458 | } | ||
| 1459 | } | ||
| 797 | 1460 | ||
| 798 | if (macho_file.unwind_info_section_index == null) { | 1461 | // Close scope |
| 799 | macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{}); | 1462 | // N_SO |
| 1463 | macho_file.symtab.items[index] = .{ | ||
| 1464 | .n_strx = 0, | ||
| 1465 | .n_type = macho.N_SO, | ||
| 1466 | .n_sect = 0, | ||
| 1467 | .n_desc = 0, | ||
| 1468 | .n_value = 0, | ||
| 1469 | }; | ||
| 1470 | index += 1; | ||
| 1471 | } | ||
| 800 | } | 1472 | } |
| 1473 | } | ||
| 801 | 1474 | ||
| 802 | const unwind_records = self.getUnwindRecords(); | 1475 | fn getLoadCommand(self: Object, lc: macho.LC) ?LoadCommandIterator.LoadCommand { |
| 803 | 1476 | var it = LoadCommandIterator{ | |
| 804 | try self.unwind_records_lookup.ensureUnusedCapacity(gpa, @as(u32, @intCast(unwind_records.len))); | 1477 | .ncmds = self.header.?.ncmds, |
| 1478 | .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | ||
| 1479 | }; | ||
| 1480 | while (it.next()) |cmd| { | ||
| 1481 | if (cmd.cmd() == lc) return cmd; | ||
| 1482 | } else return null; | ||
| 1483 | } | ||
| 805 | 1484 | ||
| 806 | const needs_eh_frame = for (unwind_records) |record| { | 1485 | pub fn getSectionData(self: *const Object, index: u32) []const u8 { |
| 807 | if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true; | 1486 | const slice = self.sections.slice(); |
| 808 | } else false; | 1487 | assert(index < slice.items(.header).len); |
| 1488 | const sect = slice.items(.header)[index]; | ||
| 1489 | return self.data[sect.offset..][0..sect.size]; | ||
| 1490 | } | ||
| 809 | 1491 | ||
| 810 | if (needs_eh_frame and !self.hasEhFrameRecords()) return error.MissingEhFrameSection; | 1492 | fn getString(self: Object, off: u32) [:0]const u8 { |
| 1493 | assert(off < self.strtab.len); | ||
| 1494 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0); | ||
| 1495 | } | ||
| 811 | 1496 | ||
| 812 | try self.parseRelocs(gpa, sect_id); | 1497 | /// TODO handle multiple CUs |
| 813 | const relocs = self.getRelocs(sect_id); | 1498 | pub fn hasDebugInfo(self: Object) bool { |
| 1499 | if (self.dwarf_info) |dw| { | ||
| 1500 | return dw.compile_units.items.len > 0; | ||
| 1501 | } | ||
| 1502 | return self.hasSymbolStabs(); | ||
| 1503 | } | ||
| 814 | 1504 | ||
| 815 | for (unwind_records, 0..) |record, record_id| { | 1505 | fn hasSymbolStabs(self: Object) bool { |
| 816 | const offset = record_id * @sizeOf(macho.compact_unwind_entry); | 1506 | return self.stab_files.items.len > 0; |
| 817 | const rel_pos = filterRelocs( | 1507 | } |
| 818 | relocs, | ||
| 819 | offset, | ||
| 820 | offset + @sizeOf(macho.compact_unwind_entry), | ||
| 821 | ); | ||
| 822 | assert(rel_pos.len > 0); // TODO convert to an error as the unwind info is malformed | ||
| 823 | self.unwind_relocs_lookup[record_id] = .{ | ||
| 824 | .dead = false, | ||
| 825 | .reloc = rel_pos, | ||
| 826 | }; | ||
| 827 | 1508 | ||
| 828 | // Find function symbol that this record describes | 1509 | pub fn hasObjc(self: Object) bool { |
| 829 | const rel = relocs[rel_pos.start..][rel_pos.len - 1]; | 1510 | for (self.symtab.items(.nlist)) |nlist| { |
| 830 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 1511 | const name = self.getString(nlist.n_strx); |
| 831 | .object_id = object_id, | 1512 | if (mem.startsWith(u8, name, "_OBJC_CLASS_$_")) return true; |
| 832 | .rel = rel, | 1513 | } |
| 833 | .code = mem.asBytes(&record), | 1514 | for (self.sections.items(.header)) |sect| { |
| 834 | .base_offset = @as(i32, @intCast(offset)), | 1515 | if (mem.eql(u8, sect.segName(), "__DATA") and mem.eql(u8, sect.sectName(), "__objc_catlist")) return true; |
| 835 | }); | 1516 | if (mem.eql(u8, sect.segName(), "__TEXT") and mem.eql(u8, sect.sectName(), "__swift")) return true; |
| 836 | if (reloc_target.getFile() != object_id) { | ||
| 837 | log.debug("unwind record {d} marked DEAD", .{record_id}); | ||
| 838 | self.unwind_relocs_lookup[record_id].dead = true; | ||
| 839 | } else { | ||
| 840 | // You would think that we are done but turns out that the compilers may use | ||
| 841 | // whichever symbol alias they want for a target symbol. This in particular | ||
| 842 | // very problematic when using Zig's @export feature to re-export symbols under | ||
| 843 | // additional names. For that reason, we need to ensure we record aliases here | ||
| 844 | // too so that we can tie them with their matching unwind records and vice versa. | ||
| 845 | const aliases = self.getSymbolAliases(reloc_target.sym_index); | ||
| 846 | var i: u32 = 0; | ||
| 847 | while (i < aliases.len) : (i += 1) { | ||
| 848 | const actual_target = SymbolWithLoc{ | ||
| 849 | .sym_index = i + aliases.start, | ||
| 850 | .file = reloc_target.file, | ||
| 851 | }; | ||
| 852 | log.debug("unwind record {d} tracks {s}", .{ | ||
| 853 | record_id, | ||
| 854 | macho_file.getSymbolName(actual_target), | ||
| 855 | }); | ||
| 856 | try self.unwind_records_lookup.putNoClobber(gpa, actual_target, @intCast(record_id)); | ||
| 857 | } | ||
| 858 | } | ||
| 859 | } | 1517 | } |
| 1518 | return false; | ||
| 860 | } | 1519 | } |
| 861 | 1520 | ||
| 862 | pub fn getSourceSymbol(self: Object, index: u32) ?macho.nlist_64 { | 1521 | pub fn getDataInCode(self: Object) []align(1) const macho.data_in_code_entry { |
| 863 | const symtab = self.in_symtab.?; | 1522 | const lc = self.getLoadCommand(.DATA_IN_CODE) orelse return &[0]macho.data_in_code_entry{}; |
| 864 | if (index >= symtab.len) return null; | 1523 | const cmd = lc.cast(macho.linkedit_data_command).?; |
| 865 | const mapped_index = self.source_symtab_lookup[index]; | 1524 | const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry)); |
| 866 | return symtab[mapped_index]; | 1525 | const dice = @as( |
| 1526 | [*]align(1) const macho.data_in_code_entry, | ||
| 1527 | @ptrCast(self.data.ptr + cmd.dataoff), | ||
| 1528 | )[0..ndice]; | ||
| 1529 | return dice; | ||
| 867 | } | 1530 | } |
| 868 | 1531 | ||
| 869 | pub fn getSourceSection(self: Object, index: u8) macho.section_64 { | 1532 | pub inline fn hasSubsections(self: Object) bool { |
| 870 | const sections = self.getSourceSections(); | 1533 | return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; |
| 871 | assert(index < sections.len); | ||
| 872 | return sections[index]; | ||
| 873 | } | 1534 | } |
| 874 | 1535 | ||
| 875 | pub fn getSourceSectionByName(self: Object, segname: []const u8, sectname: []const u8) ?macho.section_64 { | 1536 | pub fn asFile(self: *Object) File { |
| 876 | const index = self.getSourceSectionIndexByName(segname, sectname) orelse return null; | 1537 | return .{ .object = self }; |
| 877 | const sections = self.getSourceSections(); | ||
| 878 | return sections[index]; | ||
| 879 | } | 1538 | } |
| 880 | 1539 | ||
| 881 | pub fn getSourceSectionIndexByName(self: Object, segname: []const u8, sectname: []const u8) ?u8 { | 1540 | pub fn format( |
| 882 | const sections = self.getSourceSections(); | 1541 | self: *Object, |
| 883 | for (sections, 0..) |sect, i| { | 1542 | comptime unused_fmt_string: []const u8, |
| 884 | if (mem.eql(u8, segname, sect.segName()) and mem.eql(u8, sectname, sect.sectName())) | 1543 | options: std.fmt.FormatOptions, |
| 885 | return @as(u8, @intCast(i)); | 1544 | writer: anytype, |
| 886 | } else return null; | 1545 | ) !void { |
| 1546 | _ = self; | ||
| 1547 | _ = unused_fmt_string; | ||
| 1548 | _ = options; | ||
| 1549 | _ = writer; | ||
| 1550 | @compileError("do not format objects directly"); | ||
| 887 | } | 1551 | } |
| 888 | 1552 | ||
| 889 | pub fn getSourceSections(self: Object) []align(1) const macho.section_64 { | 1553 | const FormatContext = struct { |
| 890 | var it = LoadCommandIterator{ | 1554 | object: *Object, |
| 891 | .ncmds = self.header.ncmds, | 1555 | macho_file: *MachO, |
| 892 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1556 | }; |
| 893 | }; | ||
| 894 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 895 | .SEGMENT_64 => { | ||
| 896 | return cmd.getSections(); | ||
| 897 | }, | ||
| 898 | else => {}, | ||
| 899 | } else unreachable; | ||
| 900 | } | ||
| 901 | 1557 | ||
| 902 | pub fn parseDataInCode(self: *Object, gpa: Allocator) !void { | 1558 | pub fn fmtAtoms(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatAtoms) { |
| 903 | var it = LoadCommandIterator{ | 1559 | return .{ .data = .{ |
| 904 | .ncmds = self.header.ncmds, | 1560 | .object = self, |
| 905 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1561 | .macho_file = macho_file, |
| 906 | }; | 1562 | } }; |
| 907 | const cmd = while (it.next()) |cmd| { | ||
| 908 | switch (cmd.cmd()) { | ||
| 909 | .DATA_IN_CODE => break cmd.cast(macho.linkedit_data_command).?, | ||
| 910 | else => {}, | ||
| 911 | } | ||
| 912 | } else return; | ||
| 913 | const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry)); | ||
| 914 | const dice = @as([*]align(1) const macho.data_in_code_entry, @ptrCast(self.contents.ptr + cmd.dataoff))[0..ndice]; | ||
| 915 | try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len); | ||
| 916 | self.data_in_code.appendUnalignedSliceAssumeCapacity(dice); | ||
| 917 | mem.sort(macho.data_in_code_entry, self.data_in_code.items, {}, diceLessThan); | ||
| 918 | } | 1563 | } |
| 919 | 1564 | ||
| 920 | fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool { | 1565 | fn formatAtoms( |
| 921 | _ = ctx; | 1566 | ctx: FormatContext, |
| 922 | return lhs.offset < rhs.offset; | 1567 | comptime unused_fmt_string: []const u8, |
| 1568 | options: std.fmt.FormatOptions, | ||
| 1569 | writer: anytype, | ||
| 1570 | ) !void { | ||
| 1571 | _ = unused_fmt_string; | ||
| 1572 | _ = options; | ||
| 1573 | const object = ctx.object; | ||
| 1574 | try writer.writeAll(" atoms\n"); | ||
| 1575 | for (object.atoms.items) |atom_index| { | ||
| 1576 | const atom = ctx.macho_file.getAtom(atom_index).?; | ||
| 1577 | try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)}); | ||
| 1578 | } | ||
| 923 | } | 1579 | } |
| 924 | 1580 | ||
| 925 | fn getDysymtab(self: Object) ?macho.dysymtab_command { | 1581 | pub fn fmtCies(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatCies) { |
| 926 | var it = LoadCommandIterator{ | 1582 | return .{ .data = .{ |
| 927 | .ncmds = self.header.ncmds, | 1583 | .object = self, |
| 928 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1584 | .macho_file = macho_file, |
| 929 | }; | 1585 | } }; |
| 930 | while (it.next()) |cmd| { | ||
| 931 | switch (cmd.cmd()) { | ||
| 932 | .DYSYMTAB => return cmd.cast(macho.dysymtab_command).?, | ||
| 933 | else => {}, | ||
| 934 | } | ||
| 935 | } else return null; | ||
| 936 | } | 1586 | } |
| 937 | 1587 | ||
| 938 | pub fn parseDwarfInfo(self: Object) DwarfInfo { | 1588 | fn formatCies( |
| 939 | var di = DwarfInfo{ | 1589 | ctx: FormatContext, |
| 940 | .debug_info = &[0]u8{}, | 1590 | comptime unused_fmt_string: []const u8, |
| 941 | .debug_abbrev = &[0]u8{}, | 1591 | options: std.fmt.FormatOptions, |
| 942 | .debug_str = &[0]u8{}, | 1592 | writer: anytype, |
| 943 | }; | 1593 | ) !void { |
| 944 | for (self.getSourceSections()) |sect| { | 1594 | _ = unused_fmt_string; |
| 945 | if (!sect.isDebug()) continue; | 1595 | _ = options; |
| 946 | const sectname = sect.sectName(); | 1596 | const object = ctx.object; |
| 947 | if (mem.eql(u8, sectname, "__debug_info")) { | 1597 | try writer.writeAll(" cies\n"); |
| 948 | di.debug_info = self.getSectionContents(sect); | 1598 | for (object.cies.items, 0..) |cie, i| { |
| 949 | } else if (mem.eql(u8, sectname, "__debug_abbrev")) { | 1599 | try writer.print(" cie({d}) : {}\n", .{ i, cie.fmt(ctx.macho_file) }); |
| 950 | di.debug_abbrev = self.getSectionContents(sect); | ||
| 951 | } else if (mem.eql(u8, sectname, "__debug_str")) { | ||
| 952 | di.debug_str = self.getSectionContents(sect); | ||
| 953 | } | ||
| 954 | } | 1600 | } |
| 955 | return di; | ||
| 956 | } | 1601 | } |
| 957 | 1602 | ||
| 958 | /// Returns Platform composed from the first encountered build version type load command: | 1603 | pub fn fmtFdes(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatFdes) { |
| 959 | /// either LC_BUILD_VERSION or LC_VERSION_MIN_*. | 1604 | return .{ .data = .{ |
| 960 | pub fn getPlatform(self: Object) ?Platform { | 1605 | .object = self, |
| 961 | var it = LoadCommandIterator{ | 1606 | .macho_file = macho_file, |
| 962 | .ncmds = self.header.ncmds, | 1607 | } }; |
| 963 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | ||
| 964 | }; | ||
| 965 | while (it.next()) |cmd| { | ||
| 966 | switch (cmd.cmd()) { | ||
| 967 | .BUILD_VERSION, | ||
| 968 | .VERSION_MIN_MACOSX, | ||
| 969 | .VERSION_MIN_IPHONEOS, | ||
| 970 | .VERSION_MIN_TVOS, | ||
| 971 | .VERSION_MIN_WATCHOS, | ||
| 972 | => return Platform.fromLoadCommand(cmd), | ||
| 973 | else => {}, | ||
| 974 | } | ||
| 975 | } else return null; | ||
| 976 | } | 1608 | } |
| 977 | 1609 | ||
| 978 | pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 { | 1610 | fn formatFdes( |
| 979 | const size = @as(usize, @intCast(sect.size)); | 1611 | ctx: FormatContext, |
| 980 | return self.contents[sect.offset..][0..size]; | 1612 | comptime unused_fmt_string: []const u8, |
| 1613 | options: std.fmt.FormatOptions, | ||
| 1614 | writer: anytype, | ||
| 1615 | ) !void { | ||
| 1616 | _ = unused_fmt_string; | ||
| 1617 | _ = options; | ||
| 1618 | const object = ctx.object; | ||
| 1619 | try writer.writeAll(" fdes\n"); | ||
| 1620 | for (object.fdes.items, 0..) |fde, i| { | ||
| 1621 | try writer.print(" fde({d}) : {}\n", .{ i, fde.fmt(ctx.macho_file) }); | ||
| 1622 | } | ||
| 981 | } | 1623 | } |
| 982 | 1624 | ||
| 983 | pub fn getSectionAliasSymbolIndex(self: Object, sect_id: u8) u32 { | 1625 | pub fn fmtUnwindRecords(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatUnwindRecords) { |
| 984 | const start = @as(u32, @intCast(self.in_symtab.?.len)); | 1626 | return .{ .data = .{ |
| 985 | return start + sect_id; | 1627 | .object = self, |
| 1628 | .macho_file = macho_file, | ||
| 1629 | } }; | ||
| 986 | } | 1630 | } |
| 987 | 1631 | ||
| 988 | pub fn getSectionAliasSymbol(self: *Object, sect_id: u8) macho.nlist_64 { | 1632 | fn formatUnwindRecords( |
| 989 | return self.symtab[self.getSectionAliasSymbolIndex(sect_id)]; | 1633 | ctx: FormatContext, |
| 1634 | comptime unused_fmt_string: []const u8, | ||
| 1635 | options: std.fmt.FormatOptions, | ||
| 1636 | writer: anytype, | ||
| 1637 | ) !void { | ||
| 1638 | _ = unused_fmt_string; | ||
| 1639 | _ = options; | ||
| 1640 | const object = ctx.object; | ||
| 1641 | const macho_file = ctx.macho_file; | ||
| 1642 | try writer.writeAll(" unwind records\n"); | ||
| 1643 | for (object.unwind_records.items) |rec| { | ||
| 1644 | try writer.print(" rec({d}) : {}\n", .{ rec, macho_file.getUnwindRecord(rec).fmt(macho_file) }); | ||
| 1645 | } | ||
| 990 | } | 1646 | } |
| 991 | 1647 | ||
| 992 | pub fn getSectionAliasSymbolPtr(self: *Object, sect_id: u8) *macho.nlist_64 { | 1648 | pub fn fmtSymtab(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatSymtab) { |
| 993 | return &self.symtab[self.getSectionAliasSymbolIndex(sect_id)]; | 1649 | return .{ .data = .{ |
| 1650 | .object = self, | ||
| 1651 | .macho_file = macho_file, | ||
| 1652 | } }; | ||
| 994 | } | 1653 | } |
| 995 | 1654 | ||
| 996 | fn getSourceRelocs(self: Object, sect: macho.section_64) ?[]align(1) const macho.relocation_info { | 1655 | fn formatSymtab( |
| 997 | if (sect.nreloc == 0) return null; | 1656 | ctx: FormatContext, |
| 998 | return @as([*]align(1) const macho.relocation_info, @ptrCast(self.contents.ptr + sect.reloff))[0..sect.nreloc]; | 1657 | comptime unused_fmt_string: []const u8, |
| 1658 | options: std.fmt.FormatOptions, | ||
| 1659 | writer: anytype, | ||
| 1660 | ) !void { | ||
| 1661 | _ = unused_fmt_string; | ||
| 1662 | _ = options; | ||
| 1663 | const object = ctx.object; | ||
| 1664 | try writer.writeAll(" symbols\n"); | ||
| 1665 | for (object.symbols.items) |index| { | ||
| 1666 | const sym = ctx.macho_file.getSymbol(index); | ||
| 1667 | try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)}); | ||
| 1668 | } | ||
| 999 | } | 1669 | } |
| 1000 | 1670 | ||
| 1001 | pub fn getRelocs(self: Object, sect_id: u8) []const macho.relocation_info { | 1671 | pub fn fmtPath(self: Object) std.fmt.Formatter(formatPath) { |
| 1002 | const sect = self.getSourceSection(sect_id); | 1672 | return .{ .data = self }; |
| 1003 | const start = self.section_relocs_lookup.items[sect_id]; | ||
| 1004 | const len = sect.nreloc; | ||
| 1005 | return self.relocations.items[start..][0..len]; | ||
| 1006 | } | 1673 | } |
| 1007 | 1674 | ||
| 1008 | pub fn getSymbolName(self: Object, index: u32) []const u8 { | 1675 | fn formatPath( |
| 1009 | const strtab = self.in_strtab.?; | 1676 | object: Object, |
| 1010 | const sym = self.symtab[index]; | 1677 | comptime unused_fmt_string: []const u8, |
| 1678 | options: std.fmt.FormatOptions, | ||
| 1679 | writer: anytype, | ||
| 1680 | ) !void { | ||
| 1681 | _ = unused_fmt_string; | ||
| 1682 | _ = options; | ||
| 1683 | if (object.archive) |path| { | ||
| 1684 | try writer.writeAll(path); | ||
| 1685 | try writer.writeByte('('); | ||
| 1686 | try writer.writeAll(object.path); | ||
| 1687 | try writer.writeByte(')'); | ||
| 1688 | } else try writer.writeAll(object.path); | ||
| 1689 | } | ||
| 1690 | |||
| 1691 | const Section = struct { | ||
| 1692 | header: macho.section_64, | ||
| 1693 | subsections: std.ArrayListUnmanaged(Subsection) = .{}, | ||
| 1694 | relocs: std.ArrayListUnmanaged(Relocation) = .{}, | ||
| 1695 | }; | ||
| 1696 | |||
| 1697 | const Subsection = struct { | ||
| 1698 | atom: Atom.Index, | ||
| 1699 | off: u64, | ||
| 1700 | }; | ||
| 1701 | |||
| 1702 | const Nlist = struct { | ||
| 1703 | nlist: macho.nlist_64, | ||
| 1704 | size: u64, | ||
| 1705 | atom: Atom.Index, | ||
| 1706 | }; | ||
| 1707 | |||
| 1708 | const StabFile = struct { | ||
| 1709 | comp_dir: u32, | ||
| 1710 | stabs: std.ArrayListUnmanaged(Stab) = .{}, | ||
| 1011 | 1711 | ||
| 1012 | if (self.getSourceSymbol(index) == null) { | 1712 | fn getCompDir(sf: StabFile, object: *const Object) [:0]const u8 { |
| 1013 | assert(sym.n_strx == 0); | 1713 | const nlist = object.symtab.items(.nlist)[sf.comp_dir]; |
| 1014 | return ""; | 1714 | return object.getString(nlist.n_strx); |
| 1015 | } | 1715 | } |
| 1016 | 1716 | ||
| 1017 | const start = sym.n_strx; | 1717 | fn getTuName(sf: StabFile, object: *const Object) [:0]const u8 { |
| 1018 | const len = self.strtab_lookup[index]; | 1718 | const nlist = object.symtab.items(.nlist)[sf.comp_dir + 1]; |
| 1719 | return object.getString(nlist.n_strx); | ||
| 1720 | } | ||
| 1019 | 1721 | ||
| 1020 | return strtab[start..][0 .. len - 1 :0]; | 1722 | fn getOsoPath(sf: StabFile, object: *const Object) [:0]const u8 { |
| 1021 | } | 1723 | const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2]; |
| 1724 | return object.getString(nlist.n_strx); | ||
| 1725 | } | ||
| 1022 | 1726 | ||
| 1023 | fn getSymbolAliases(self: Object, index: u32) Entry { | 1727 | fn getOsoModTime(sf: StabFile, object: *const Object) u64 { |
| 1024 | const addr = self.source_address_lookup[index]; | 1728 | const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2]; |
| 1025 | var start = index; | 1729 | return nlist.n_value; |
| 1026 | while (start > 0 and | 1730 | } |
| 1027 | self.source_address_lookup[start - 1] == addr) : (start -= 1) | ||
| 1028 | {} | ||
| 1029 | const end: u32 = for (self.source_address_lookup[start..], start..) |saddr, i| { | ||
| 1030 | if (saddr != addr) break @as(u32, @intCast(i)); | ||
| 1031 | } else @as(u32, @intCast(self.source_address_lookup.len)); | ||
| 1032 | return .{ .start = start, .len = end - start }; | ||
| 1033 | } | ||
| 1034 | 1731 | ||
| 1035 | pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 { | 1732 | const Stab = struct { |
| 1036 | // Find containing atom | 1733 | tag: enum { func, global, static } = .func, |
| 1037 | const Predicate = struct { | 1734 | symbol: ?Symbol.Index = null, |
| 1038 | addr: i64, | ||
| 1039 | 1735 | ||
| 1040 | pub fn predicate(pred: @This(), other: i64) bool { | 1736 | fn getSymbol(stab: Stab, macho_file: *MachO) ?*Symbol { |
| 1041 | return if (other == -1) true else other > pred.addr; | 1737 | return if (stab.symbol) |s| macho_file.getSymbol(s) else null; |
| 1042 | } | 1738 | } |
| 1043 | }; | 1739 | }; |
| 1740 | }; | ||
| 1044 | 1741 | ||
| 1045 | if (sect_hint) |sect_id| { | 1742 | const x86_64 = struct { |
| 1046 | if (self.source_section_index_lookup[sect_id].len > 0) { | 1743 | fn parseRelocs( |
| 1047 | const lookup = self.source_section_index_lookup[sect_id]; | 1744 | self: *const Object, |
| 1048 | const target_sym_index = MachO.lsearch( | 1745 | n_sect: u8, |
| 1049 | i64, | 1746 | sect: macho.section_64, |
| 1050 | self.source_address_lookup[lookup.start..][0..lookup.len], | 1747 | out: *std.ArrayListUnmanaged(Relocation), |
| 1051 | Predicate{ .addr = @as(i64, @intCast(addr)) }, | 1748 | macho_file: *MachO, |
| 1052 | ); | 1749 | ) !void { |
| 1053 | if (target_sym_index > 0) { | 1750 | const gpa = macho_file.base.allocator; |
| 1054 | // Hone in on the most senior alias of the target symbol. | 1751 | |
| 1055 | // See SymbolAtIndex.lessThan for more context. | 1752 | const relocs = @as( |
| 1056 | const aliases = self.getSymbolAliases(@intCast(lookup.start + target_sym_index - 1)); | 1753 | [*]align(1) const macho.relocation_info, |
| 1057 | return aliases.start; | 1754 | @ptrCast(self.data.ptr + sect.reloff), |
| 1058 | } | 1755 | )[0..sect.nreloc]; |
| 1059 | } | 1756 | const code = self.getSectionData(@intCast(n_sect)); |
| 1060 | return self.getSectionAliasSymbolIndex(sect_id); | 1757 | |
| 1061 | } | 1758 | try out.ensureTotalCapacityPrecise(gpa, relocs.len); |
| 1759 | |||
| 1760 | var i: usize = 0; | ||
| 1761 | while (i < relocs.len) : (i += 1) { | ||
| 1762 | const rel = relocs[i]; | ||
| 1763 | const rel_type: macho.reloc_type_x86_64 = @enumFromInt(rel.r_type); | ||
| 1764 | const rel_offset = @as(u32, @intCast(rel.r_address)); | ||
| 1765 | |||
| 1766 | var addend = switch (rel.r_length) { | ||
| 1767 | 0 => code[rel_offset], | ||
| 1768 | 1 => mem.readInt(i16, code[rel_offset..][0..2], .little), | ||
| 1769 | 2 => mem.readInt(i32, code[rel_offset..][0..4], .little), | ||
| 1770 | 3 => mem.readInt(i64, code[rel_offset..][0..8], .little), | ||
| 1771 | }; | ||
| 1772 | addend += switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) { | ||
| 1773 | .X86_64_RELOC_SIGNED_1 => 1, | ||
| 1774 | .X86_64_RELOC_SIGNED_2 => 2, | ||
| 1775 | .X86_64_RELOC_SIGNED_4 => 4, | ||
| 1776 | else => 0, | ||
| 1777 | }; | ||
| 1062 | 1778 | ||
| 1063 | const target_sym_index = MachO.lsearch(i64, self.source_address_lookup, Predicate{ | 1779 | const target = if (rel.r_extern == 0) blk: { |
| 1064 | .addr = @as(i64, @intCast(addr)), | 1780 | const nsect = rel.r_symbolnum - 1; |
| 1065 | }); | 1781 | const taddr: i64 = if (rel.r_pcrel == 1) |
| 1066 | assert(target_sym_index > 0); | 1782 | @as(i64, @intCast(sect.addr)) + rel.r_address + addend + 4 |
| 1067 | return @as(u32, @intCast(target_sym_index - 1)); | 1783 | else |
| 1068 | } | 1784 | addend; |
| 1785 | const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse { | ||
| 1786 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 1787 | self.fmtPath(), sect.segName(), sect.sectName(), rel.r_address, | ||
| 1788 | }); | ||
| 1789 | return error.ParseFailed; | ||
| 1790 | }; | ||
| 1791 | addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file))); | ||
| 1792 | break :blk target; | ||
| 1793 | } else self.symbols.items[rel.r_symbolnum]; | ||
| 1794 | |||
| 1795 | const has_subtractor = if (i > 0 and | ||
| 1796 | @as(macho.reloc_type_x86_64, @enumFromInt(relocs[i - 1].r_type)) == .X86_64_RELOC_SUBTRACTOR) | ||
| 1797 | blk: { | ||
| 1798 | if (rel_type != .X86_64_RELOC_UNSIGNED) { | ||
| 1799 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: X86_64_RELOC_SUBTRACTOR followed by {s}", .{ | ||
| 1800 | self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type), | ||
| 1801 | }); | ||
| 1802 | return error.ParseFailed; | ||
| 1803 | } | ||
| 1804 | break :blk true; | ||
| 1805 | } else false; | ||
| 1806 | |||
| 1807 | const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| { | ||
| 1808 | switch (err) { | ||
| 1809 | error.Pcrel => macho_file.base.fatal( | ||
| 1810 | "{}: {s},{s}: 0x{x}: PC-relative {s} relocation", | ||
| 1811 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) }, | ||
| 1812 | ), | ||
| 1813 | error.NonPcrel => macho_file.base.fatal( | ||
| 1814 | "{}: {s},{s}: 0x{x}: non-PC-relative {s} relocation", | ||
| 1815 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) }, | ||
| 1816 | ), | ||
| 1817 | error.InvalidLength => macho_file.base.fatal( | ||
| 1818 | "{}: {s},{s}: 0x{x}: invalid length of {d} in {s} relocation", | ||
| 1819 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) }, | ||
| 1820 | ), | ||
| 1821 | error.NonExtern => macho_file.base.fatal( | ||
| 1822 | "{}: {s},{s}: 0x{x}: non-extern target in {s} relocation", | ||
| 1823 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) }, | ||
| 1824 | ), | ||
| 1825 | } | ||
| 1826 | return error.ParseFailed; | ||
| 1827 | }; | ||
| 1069 | 1828 | ||
| 1070 | pub fn getGlobal(self: Object, sym_index: u32) ?u32 { | 1829 | out.appendAssumeCapacity(.{ |
| 1071 | if (self.globals_lookup[sym_index] == -1) return null; | 1830 | .tag = if (rel.r_extern == 1) .@"extern" else .local, |
| 1072 | return @as(u32, @intCast(self.globals_lookup[sym_index])); | 1831 | .offset = @as(u32, @intCast(rel.r_address)), |
| 1073 | } | 1832 | .target = target, |
| 1833 | .addend = addend, | ||
| 1834 | .type = @"type", | ||
| 1835 | .meta = .{ | ||
| 1836 | .pcrel = rel.r_pcrel == 1, | ||
| 1837 | .has_subtractor = has_subtractor, | ||
| 1838 | .length = rel.r_length, | ||
| 1839 | .symbolnum = rel.r_symbolnum, | ||
| 1840 | }, | ||
| 1841 | }); | ||
| 1842 | } | ||
| 1843 | } | ||
| 1074 | 1844 | ||
| 1075 | pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?Atom.Index { | 1845 | fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_x86_64) !Relocation.Type { |
| 1076 | return self.atom_by_index_table[sym_index]; | 1846 | switch (rel_type) { |
| 1077 | } | 1847 | .X86_64_RELOC_UNSIGNED => { |
| 1848 | if (rel.r_pcrel == 1) return error.Pcrel; | ||
| 1849 | if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength; | ||
| 1850 | return .unsigned; | ||
| 1851 | }, | ||
| 1852 | |||
| 1853 | .X86_64_RELOC_SUBTRACTOR => { | ||
| 1854 | if (rel.r_pcrel == 1) return error.Pcrel; | ||
| 1855 | return .subtractor; | ||
| 1856 | }, | ||
| 1857 | |||
| 1858 | .X86_64_RELOC_BRANCH, | ||
| 1859 | .X86_64_RELOC_GOT_LOAD, | ||
| 1860 | .X86_64_RELOC_GOT, | ||
| 1861 | .X86_64_RELOC_TLV, | ||
| 1862 | => { | ||
| 1863 | if (rel.r_pcrel == 0) return error.NonPcrel; | ||
| 1864 | if (rel.r_length != 2) return error.InvalidLength; | ||
| 1865 | if (rel.r_extern == 0) return error.NonExtern; | ||
| 1866 | return switch (rel_type) { | ||
| 1867 | .X86_64_RELOC_BRANCH => .branch, | ||
| 1868 | .X86_64_RELOC_GOT_LOAD => .got_load, | ||
| 1869 | .X86_64_RELOC_GOT => .got, | ||
| 1870 | .X86_64_RELOC_TLV => .tlv, | ||
| 1871 | else => unreachable, | ||
| 1872 | }; | ||
| 1873 | }, | ||
| 1874 | |||
| 1875 | .X86_64_RELOC_SIGNED, | ||
| 1876 | .X86_64_RELOC_SIGNED_1, | ||
| 1877 | .X86_64_RELOC_SIGNED_2, | ||
| 1878 | .X86_64_RELOC_SIGNED_4, | ||
| 1879 | => { | ||
| 1880 | if (rel.r_pcrel == 0) return error.NonPcrel; | ||
| 1881 | if (rel.r_length != 2) return error.InvalidLength; | ||
| 1882 | return switch (rel_type) { | ||
| 1883 | .X86_64_RELOC_SIGNED => .signed, | ||
| 1884 | .X86_64_RELOC_SIGNED_1 => .signed1, | ||
| 1885 | .X86_64_RELOC_SIGNED_2 => .signed2, | ||
| 1886 | .X86_64_RELOC_SIGNED_4 => .signed4, | ||
| 1887 | else => unreachable, | ||
| 1888 | }; | ||
| 1889 | }, | ||
| 1890 | } | ||
| 1891 | } | ||
| 1892 | }; | ||
| 1078 | 1893 | ||
| 1079 | pub fn hasUnwindRecords(self: Object) bool { | 1894 | const aarch64 = struct { |
| 1080 | return self.unwind_info_sect_id != null; | 1895 | fn parseRelocs( |
| 1081 | } | 1896 | self: *const Object, |
| 1897 | n_sect: u8, | ||
| 1898 | sect: macho.section_64, | ||
| 1899 | out: *std.ArrayListUnmanaged(Relocation), | ||
| 1900 | macho_file: *MachO, | ||
| 1901 | ) !void { | ||
| 1902 | const gpa = macho_file.base.allocator; | ||
| 1903 | |||
| 1904 | const relocs = @as( | ||
| 1905 | [*]align(1) const macho.relocation_info, | ||
| 1906 | @ptrCast(self.data.ptr + sect.reloff), | ||
| 1907 | )[0..sect.nreloc]; | ||
| 1908 | const code = self.getSectionData(@intCast(n_sect)); | ||
| 1909 | |||
| 1910 | try out.ensureTotalCapacityPrecise(gpa, relocs.len); | ||
| 1911 | |||
| 1912 | var i: usize = 0; | ||
| 1913 | while (i < relocs.len) : (i += 1) { | ||
| 1914 | var rel = relocs[i]; | ||
| 1915 | const rel_offset = @as(u32, @intCast(rel.r_address)); | ||
| 1916 | |||
| 1917 | var addend: i64 = 0; | ||
| 1918 | |||
| 1919 | switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | ||
| 1920 | .ARM64_RELOC_ADDEND => { | ||
| 1921 | addend = rel.r_symbolnum; | ||
| 1922 | i += 1; | ||
| 1923 | if (i >= relocs.len) { | ||
| 1924 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: unterminated ARM64_RELOC_ADDEND", .{ | ||
| 1925 | self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, | ||
| 1926 | }); | ||
| 1927 | return error.ParseFailed; | ||
| 1928 | } | ||
| 1929 | rel = relocs[i]; | ||
| 1930 | switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | ||
| 1931 | .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {}, | ||
| 1932 | else => |x| { | ||
| 1933 | macho_file.base.fatal( | ||
| 1934 | "{}: {s},{s}: 0x{x}: ARM64_RELOC_ADDEND followed by {s}", | ||
| 1935 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(x) }, | ||
| 1936 | ); | ||
| 1937 | return error.ParseFailed; | ||
| 1938 | }, | ||
| 1939 | } | ||
| 1940 | }, | ||
| 1941 | .ARM64_RELOC_UNSIGNED => { | ||
| 1942 | addend = switch (rel.r_length) { | ||
| 1943 | 0 => code[rel_offset], | ||
| 1944 | 1 => mem.readInt(i16, code[rel_offset..][0..2], .little), | ||
| 1945 | 2 => mem.readInt(i32, code[rel_offset..][0..4], .little), | ||
| 1946 | 3 => mem.readInt(i64, code[rel_offset..][0..8], .little), | ||
| 1947 | }; | ||
| 1948 | }, | ||
| 1949 | else => {}, | ||
| 1950 | } | ||
| 1082 | 1951 | ||
| 1083 | pub fn getUnwindRecords(self: Object) []align(1) const macho.compact_unwind_entry { | 1952 | const rel_type: macho.reloc_type_arm64 = @enumFromInt(rel.r_type); |
| 1084 | const sect_id = self.unwind_info_sect_id orelse return &[0]macho.compact_unwind_entry{}; | ||
| 1085 | const sect = self.getSourceSection(sect_id); | ||
| 1086 | const data = self.getSectionContents(sect); | ||
| 1087 | const num_entries = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); | ||
| 1088 | return @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data))[0..num_entries]; | ||
| 1089 | } | ||
| 1090 | 1953 | ||
| 1091 | pub fn hasEhFrameRecords(self: Object) bool { | 1954 | const target = if (rel.r_extern == 0) blk: { |
| 1092 | return self.eh_frame_sect_id != null; | 1955 | const nsect = rel.r_symbolnum - 1; |
| 1093 | } | 1956 | const taddr: i64 = if (rel.r_pcrel == 1) |
| 1957 | @as(i64, @intCast(sect.addr)) + rel.r_address + addend | ||
| 1958 | else | ||
| 1959 | addend; | ||
| 1960 | const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse { | ||
| 1961 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ | ||
| 1962 | self.fmtPath(), sect.segName(), sect.sectName(), rel.r_address, | ||
| 1963 | }); | ||
| 1964 | return error.ParseFailed; | ||
| 1965 | }; | ||
| 1966 | addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file))); | ||
| 1967 | break :blk target; | ||
| 1968 | } else self.symbols.items[rel.r_symbolnum]; | ||
| 1969 | |||
| 1970 | const has_subtractor = if (i > 0 and | ||
| 1971 | @as(macho.reloc_type_arm64, @enumFromInt(relocs[i - 1].r_type)) == .ARM64_RELOC_SUBTRACTOR) | ||
| 1972 | blk: { | ||
| 1973 | if (rel_type != .ARM64_RELOC_UNSIGNED) { | ||
| 1974 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: ARM64_RELOC_SUBTRACTOR followed by {s}", .{ | ||
| 1975 | self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type), | ||
| 1976 | }); | ||
| 1977 | return error.ParseFailed; | ||
| 1978 | } | ||
| 1979 | break :blk true; | ||
| 1980 | } else false; | ||
| 1981 | |||
| 1982 | const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| { | ||
| 1983 | switch (err) { | ||
| 1984 | error.Pcrel => macho_file.base.fatal( | ||
| 1985 | "{}: {s},{s}: 0x{x}: PC-relative {s} relocation", | ||
| 1986 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) }, | ||
| 1987 | ), | ||
| 1988 | error.NonPcrel => macho_file.base.fatal( | ||
| 1989 | "{}: {s},{s}: 0x{x}: non-PC-relative {s} relocation", | ||
| 1990 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) }, | ||
| 1991 | ), | ||
| 1992 | error.InvalidLength => macho_file.base.fatal( | ||
| 1993 | "{}: {s},{s}: 0x{x}: invalid length of {d} in {s} relocation", | ||
| 1994 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) }, | ||
| 1995 | ), | ||
| 1996 | error.NonExtern => macho_file.base.fatal( | ||
| 1997 | "{}: {s},{s}: 0x{x}: non-extern target in {s} relocation", | ||
| 1998 | .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) }, | ||
| 1999 | ), | ||
| 2000 | } | ||
| 2001 | return error.ParseFailed; | ||
| 2002 | }; | ||
| 1094 | 2003 | ||
| 1095 | pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator { | 2004 | out.appendAssumeCapacity(.{ |
| 1096 | const sect_id = self.eh_frame_sect_id orelse return .{ .data = &[0]u8{} }; | 2005 | .tag = if (rel.r_extern == 1) .@"extern" else .local, |
| 1097 | const sect = self.getSourceSection(sect_id); | 2006 | .offset = @as(u32, @intCast(rel.r_address)), |
| 1098 | const data = self.getSectionContents(sect); | 2007 | .target = target, |
| 1099 | return .{ .data = data }; | 2008 | .addend = addend, |
| 1100 | } | 2009 | .type = @"type", |
| 2010 | .meta = .{ | ||
| 2011 | .pcrel = rel.r_pcrel == 1, | ||
| 2012 | .has_subtractor = has_subtractor, | ||
| 2013 | .length = rel.r_length, | ||
| 2014 | .symbolnum = rel.r_symbolnum, | ||
| 2015 | }, | ||
| 2016 | }); | ||
| 2017 | } | ||
| 2018 | } | ||
| 1101 | 2019 | ||
| 1102 | pub fn hasDataInCode(self: Object) bool { | 2020 | fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_arm64) !Relocation.Type { |
| 1103 | return self.data_in_code.items.len > 0; | 2021 | switch (rel_type) { |
| 1104 | } | 2022 | .ARM64_RELOC_UNSIGNED => { |
| 2023 | if (rel.r_pcrel == 1) return error.Pcrel; | ||
| 2024 | if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength; | ||
| 2025 | return .unsigned; | ||
| 2026 | }, | ||
| 2027 | |||
| 2028 | .ARM64_RELOC_SUBTRACTOR => { | ||
| 2029 | if (rel.r_pcrel == 1) return error.Pcrel; | ||
| 2030 | return .subtractor; | ||
| 2031 | }, | ||
| 2032 | |||
| 2033 | .ARM64_RELOC_BRANCH26, | ||
| 2034 | .ARM64_RELOC_PAGE21, | ||
| 2035 | .ARM64_RELOC_GOT_LOAD_PAGE21, | ||
| 2036 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | ||
| 2037 | .ARM64_RELOC_POINTER_TO_GOT, | ||
| 2038 | => { | ||
| 2039 | if (rel.r_pcrel == 0) return error.NonPcrel; | ||
| 2040 | if (rel.r_length != 2) return error.InvalidLength; | ||
| 2041 | if (rel.r_extern == 0) return error.NonExtern; | ||
| 2042 | return switch (rel_type) { | ||
| 2043 | .ARM64_RELOC_BRANCH26 => .branch, | ||
| 2044 | .ARM64_RELOC_PAGE21 => .page, | ||
| 2045 | .ARM64_RELOC_GOT_LOAD_PAGE21 => .got_load_page, | ||
| 2046 | .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp_page, | ||
| 2047 | .ARM64_RELOC_POINTER_TO_GOT => .got, | ||
| 2048 | else => unreachable, | ||
| 2049 | }; | ||
| 2050 | }, | ||
| 2051 | |||
| 2052 | .ARM64_RELOC_PAGEOFF12, | ||
| 2053 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | ||
| 2054 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | ||
| 2055 | => { | ||
| 2056 | if (rel.r_pcrel == 1) return error.Pcrel; | ||
| 2057 | if (rel.r_length != 2) return error.InvalidLength; | ||
| 2058 | if (rel.r_extern == 0) return error.NonExtern; | ||
| 2059 | return switch (rel_type) { | ||
| 2060 | .ARM64_RELOC_PAGEOFF12 => .pageoff, | ||
| 2061 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got_load_pageoff, | ||
| 2062 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp_pageoff, | ||
| 2063 | else => unreachable, | ||
| 2064 | }; | ||
| 2065 | }, | ||
| 1105 | 2066 | ||
| 1106 | const Object = @This(); | 2067 | .ARM64_RELOC_ADDEND => unreachable, // We make it part of the addend field |
| 2068 | } | ||
| 2069 | } | ||
| 2070 | }; | ||
| 1107 | 2071 | ||
| 1108 | const std = @import("std"); | ||
| 1109 | const build_options = @import("build_options"); | ||
| 1110 | const assert = std.debug.assert; | 2072 | const assert = std.debug.assert; |
| 1111 | const dwarf = std.dwarf; | ||
| 1112 | const eh_frame = @import("eh_frame.zig"); | 2073 | const eh_frame = @import("eh_frame.zig"); |
| 1113 | const fs = std.fs; | ||
| 1114 | const io = std.io; | ||
| 1115 | const log = std.log.scoped(.link); | 2074 | const log = std.log.scoped(.link); |
| 1116 | const macho = std.macho; | 2075 | const macho = std.macho; |
| 1117 | const math = std.math; | 2076 | const math = std.math; |
| 1118 | const mem = std.mem; | 2077 | const mem = std.mem; |
| 1119 | const sort = std.sort; | 2078 | const trace = @import("../tracy.zig").trace; |
| 1120 | const trace = @import("../../tracy.zig").trace; | 2079 | const std = @import("std"); |
| 1121 | 2080 | ||
| 1122 | const Allocator = mem.Allocator; | 2081 | const Allocator = mem.Allocator; |
| 1123 | const Atom = @import("Atom.zig"); | 2082 | const Atom = @import("Atom.zig"); |
| 2083 | const Cie = eh_frame.Cie; | ||
| 1124 | const DwarfInfo = @import("DwarfInfo.zig"); | 2084 | const DwarfInfo = @import("DwarfInfo.zig"); |
| 2085 | const Fde = eh_frame.Fde; | ||
| 2086 | const File = @import("file.zig").File; | ||
| 1125 | const LoadCommandIterator = macho.LoadCommandIterator; | 2087 | const LoadCommandIterator = macho.LoadCommandIterator; |
| 1126 | const MachO = @import("../MachO.zig"); | 2088 | const MachO = @import("../MachO.zig"); |
| 1127 | const Platform = @import("load_commands.zig").Platform; | 2089 | const Object = @This(); |
| 1128 | const SymbolWithLoc = MachO.SymbolWithLoc; | 2090 | const Relocation = @import("Relocation.zig"); |
| 2091 | const StringTable = @import("../strtab.zig").StringTable; | ||
| 2092 | const Symbol = @import("Symbol.zig"); | ||
| 1129 | const UnwindInfo = @import("UnwindInfo.zig"); | 2093 | const UnwindInfo = @import("UnwindInfo.zig"); |
| 1130 | const Alignment = Atom.Alignment; |
src/link/MachO/Relocation.zig+92-225| ... | @@ -1,235 +1,62 @@ | ... | @@ -1,235 +1,62 @@ |
| 1 | //! Relocation used by the self-hosted backends to instruct the linker where and how to | 1 | tag: enum { @"extern", local }, |
| 2 | //! fixup the values when flushing the contents to file and/or memory. | ||
| 3 | |||
| 4 | type: Type, | ||
| 5 | target: SymbolWithLoc, | ||
| 6 | offset: u32, | 2 | offset: u32, |
| 3 | target: u32, | ||
| 7 | addend: i64, | 4 | addend: i64, |
| 8 | pcrel: bool, | 5 | type: Type, |
| 9 | length: u2, | 6 | meta: packed struct { |
| 10 | dirty: bool = true, | 7 | pcrel: bool, |
| 11 | 8 | has_subtractor: bool, | |
| 12 | pub const Type = enum { | 9 | length: u2, |
| 13 | // x86, x86_64 | 10 | symbolnum: u24, |
| 14 | /// RIP-relative displacement to a GOT pointer | 11 | }, |
| 15 | got, | 12 | |
| 16 | /// RIP-relative displacement | 13 | pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol { |
| 17 | signed, | 14 | assert(rel.tag == .@"extern"); |
| 18 | /// RIP-relative displacement to a TLV thunk | 15 | return macho_file.getSymbol(rel.target); |
| 19 | tlv, | ||
| 20 | |||
| 21 | // aarch64 | ||
| 22 | /// PC-relative distance to target page in GOT section | ||
| 23 | got_page, | ||
| 24 | /// Offset to a GOT pointer relative to the start of a page in GOT section | ||
| 25 | got_pageoff, | ||
| 26 | /// PC-relative distance to target page in a section | ||
| 27 | page, | ||
| 28 | /// Offset to a pointer relative to the start of a page in a section | ||
| 29 | pageoff, | ||
| 30 | |||
| 31 | // common | ||
| 32 | /// PC/RIP-relative displacement B/BL/CALL | ||
| 33 | branch, | ||
| 34 | /// Absolute pointer value | ||
| 35 | unsigned, | ||
| 36 | /// Relative offset to TLV initializer | ||
| 37 | tlv_initializer, | ||
| 38 | }; | ||
| 39 | |||
| 40 | /// Returns true if and only if the reloc can be resolved. | ||
| 41 | pub fn isResolvable(self: Relocation, macho_file: *MachO) bool { | ||
| 42 | _ = self.getTargetBaseAddress(macho_file) orelse return false; | ||
| 43 | return true; | ||
| 44 | } | 16 | } |
| 45 | 17 | ||
| 46 | pub fn isGotIndirection(self: Relocation) bool { | 18 | pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom { |
| 47 | return switch (self.type) { | 19 | assert(rel.tag == .local); |
| 48 | .got, .got_page, .got_pageoff => true, | 20 | return macho_file.getAtom(rel.target).?; |
| 49 | else => false, | ||
| 50 | }; | ||
| 51 | } | 21 | } |
| 52 | 22 | ||
| 53 | pub fn isStubTrampoline(self: Relocation, macho_file: *MachO) bool { | 23 | pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 { |
| 54 | return switch (self.type) { | 24 | return switch (rel.tag) { |
| 55 | .branch => macho_file.getSymbol(self.target).undf(), | 25 | .local => rel.getTargetAtom(macho_file).value, |
| 56 | else => false, | 26 | .@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file), |
| 57 | }; | 27 | }; |
| 58 | } | 28 | } |
| 59 | 29 | ||
| 60 | pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 { | 30 | pub fn getGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 { |
| 61 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 31 | return switch (rel.tag) { |
| 62 | if (self.isStubTrampoline(macho_file)) { | 32 | .local => 0, |
| 63 | const index = macho_file.stub_table.lookup.get(self.target) orelse return null; | 33 | .@"extern" => rel.getTargetSymbol(macho_file).getGotAddress(macho_file), |
| 64 | const header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?]; | ||
| 65 | return header.addr + | ||
| 66 | index * @import("stubs.zig").stubSize(target.cpu.arch); | ||
| 67 | } | ||
| 68 | switch (self.type) { | ||
| 69 | .got, .got_page, .got_pageoff => { | ||
| 70 | const got_index = macho_file.got_table.lookup.get(self.target) orelse return null; | ||
| 71 | const header = macho_file.sections.items(.header)[macho_file.got_section_index.?]; | ||
| 72 | return header.addr + got_index * @sizeOf(u64); | ||
| 73 | }, | ||
| 74 | .tlv => { | ||
| 75 | const atom_index = macho_file.tlv_table.get(self.target) orelse return null; | ||
| 76 | const atom = macho_file.getAtom(atom_index); | ||
| 77 | return atom.getSymbol(macho_file).n_value; | ||
| 78 | }, | ||
| 79 | else => { | ||
| 80 | const target_atom_index = macho_file.getAtomIndexForSymbol(self.target) orelse return null; | ||
| 81 | const target_atom = macho_file.getAtom(target_atom_index); | ||
| 82 | return target_atom.getSymbol(macho_file).n_value; | ||
| 83 | }, | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, code: []u8) void { | ||
| 88 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 89 | const arch = target.cpu.arch; | ||
| 90 | const atom = macho_file.getAtom(atom_index); | ||
| 91 | const source_sym = atom.getSymbol(macho_file); | ||
| 92 | const source_addr = source_sym.n_value + self.offset; | ||
| 93 | |||
| 94 | const target_base_addr = self.getTargetBaseAddress(macho_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable(). | ||
| 95 | const target_addr: i64 = switch (self.type) { | ||
| 96 | .tlv_initializer => blk: { | ||
| 97 | assert(self.addend == 0); // Addend here makes no sense. | ||
| 98 | const header = macho_file.sections.items(.header)[macho_file.thread_data_section_index.?]; | ||
| 99 | break :blk @as(i64, @intCast(target_base_addr - header.addr)); | ||
| 100 | }, | ||
| 101 | else => @as(i64, @intCast(target_base_addr)) + self.addend, | ||
| 102 | }; | 34 | }; |
| 103 | |||
| 104 | relocs_log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{ | ||
| 105 | source_addr, | ||
| 106 | target_addr, | ||
| 107 | macho_file.getSymbolName(self.target), | ||
| 108 | @tagName(self.type), | ||
| 109 | }); | ||
| 110 | |||
| 111 | switch (arch) { | ||
| 112 | .aarch64 => self.resolveAarch64(source_addr, target_addr, code), | ||
| 113 | .x86_64 => self.resolveX8664(source_addr, target_addr, code), | ||
| 114 | else => unreachable, | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void { | ||
| 119 | var buffer = code[self.offset..]; | ||
| 120 | switch (self.type) { | ||
| 121 | .branch => { | ||
| 122 | const displacement = math.cast( | ||
| 123 | i28, | ||
| 124 | @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)), | ||
| 125 | ) orelse unreachable; // TODO codegen should never allow for jump larger than i28 displacement | ||
| 126 | var inst = aarch64.Instruction{ | ||
| 127 | .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( | ||
| 128 | aarch64.Instruction, | ||
| 129 | aarch64.Instruction.unconditional_branch_immediate, | ||
| 130 | ), buffer[0..4]), | ||
| 131 | }; | ||
| 132 | inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2)))); | ||
| 133 | mem.writeInt(u32, buffer[0..4], inst.toU32(), .little); | ||
| 134 | }, | ||
| 135 | .page, .got_page => { | ||
| 136 | const source_page = @as(i32, @intCast(source_addr >> 12)); | ||
| 137 | const target_page = @as(i32, @intCast(target_addr >> 12)); | ||
| 138 | const pages = @as(u21, @bitCast(@as(i21, @intCast(target_page - source_page)))); | ||
| 139 | var inst = aarch64.Instruction{ | ||
| 140 | .pc_relative_address = mem.bytesToValue(meta.TagPayload( | ||
| 141 | aarch64.Instruction, | ||
| 142 | aarch64.Instruction.pc_relative_address, | ||
| 143 | ), buffer[0..4]), | ||
| 144 | }; | ||
| 145 | inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2)); | ||
| 146 | inst.pc_relative_address.immlo = @as(u2, @truncate(pages)); | ||
| 147 | mem.writeInt(u32, buffer[0..4], inst.toU32(), .little); | ||
| 148 | }, | ||
| 149 | .pageoff, .got_pageoff => { | ||
| 150 | const narrowed = @as(u12, @truncate(@as(u64, @intCast(target_addr)))); | ||
| 151 | if (isArithmeticOp(buffer[0..4])) { | ||
| 152 | var inst = aarch64.Instruction{ | ||
| 153 | .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( | ||
| 154 | aarch64.Instruction, | ||
| 155 | aarch64.Instruction.add_subtract_immediate, | ||
| 156 | ), buffer[0..4]), | ||
| 157 | }; | ||
| 158 | inst.add_subtract_immediate.imm12 = narrowed; | ||
| 159 | mem.writeInt(u32, buffer[0..4], inst.toU32(), .little); | ||
| 160 | } else { | ||
| 161 | var inst = aarch64.Instruction{ | ||
| 162 | .load_store_register = mem.bytesToValue(meta.TagPayload( | ||
| 163 | aarch64.Instruction, | ||
| 164 | aarch64.Instruction.load_store_register, | ||
| 165 | ), buffer[0..4]), | ||
| 166 | }; | ||
| 167 | const offset: u12 = blk: { | ||
| 168 | if (inst.load_store_register.size == 0) { | ||
| 169 | if (inst.load_store_register.v == 1) { | ||
| 170 | // 128-bit SIMD is scaled by 16. | ||
| 171 | break :blk @divExact(narrowed, 16); | ||
| 172 | } | ||
| 173 | // Otherwise, 8-bit SIMD or ldrb. | ||
| 174 | break :blk narrowed; | ||
| 175 | } else { | ||
| 176 | const denom: u4 = math.powi(u4, 2, inst.load_store_register.size) catch unreachable; | ||
| 177 | break :blk @divExact(narrowed, denom); | ||
| 178 | } | ||
| 179 | }; | ||
| 180 | inst.load_store_register.offset = offset; | ||
| 181 | mem.writeInt(u32, buffer[0..4], inst.toU32(), .little); | ||
| 182 | } | ||
| 183 | }, | ||
| 184 | .tlv_initializer, .unsigned => switch (self.length) { | ||
| 185 | 2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little), | ||
| 186 | 3 => mem.writeInt(u64, buffer[0..8], @as(u64, @bitCast(target_addr)), .little), | ||
| 187 | else => unreachable, | ||
| 188 | }, | ||
| 189 | .got, .signed, .tlv => unreachable, // Invalid target architecture. | ||
| 190 | } | ||
| 191 | } | 35 | } |
| 192 | 36 | ||
| 193 | fn resolveX8664(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void { | 37 | pub fn getRelocAddend(rel: Relocation, cpu_arch: std.Target.Cpu.Arch) i64 { |
| 194 | switch (self.type) { | 38 | const addend: i64 = switch (rel.type) { |
| 195 | .branch, .got, .tlv, .signed => { | 39 | .signed => 0, |
| 196 | const displacement = @as(i32, @intCast(@as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)) - 4)); | 40 | .signed1 => -1, |
| 197 | mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @bitCast(displacement)), .little); | 41 | .signed2 => -2, |
| 198 | }, | 42 | .signed4 => -4, |
| 199 | .tlv_initializer, .unsigned => { | 43 | else => 0, |
| 200 | switch (self.length) { | 44 | }; |
| 201 | 2 => { | 45 | return switch (cpu_arch) { |
| 202 | mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little); | 46 | .x86_64 => if (rel.meta.pcrel) addend - 4 else addend, |
| 203 | }, | 47 | else => addend, |
| 204 | 3 => { | 48 | }; |
| 205 | mem.writeInt(u64, code[self.offset..][0..8], @as(u64, @bitCast(target_addr)), .little); | ||
| 206 | }, | ||
| 207 | else => unreachable, | ||
| 208 | } | ||
| 209 | }, | ||
| 210 | .got_page, .got_pageoff, .page, .pageoff => unreachable, // Invalid target architecture. | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | pub inline fn isArithmeticOp(inst: *const [4]u8) bool { | ||
| 215 | const group_decode = @as(u5, @truncate(inst[3])); | ||
| 216 | return ((group_decode >> 2) == 4); | ||
| 217 | } | ||
| 218 | |||
| 219 | pub fn calcPcRelativeDisplacementX86(source_addr: u64, target_addr: u64, correction: u3) error{Overflow}!i32 { | ||
| 220 | const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 4 + correction)); | ||
| 221 | return math.cast(i32, disp) orelse error.Overflow; | ||
| 222 | } | 49 | } |
| 223 | 50 | ||
| 224 | pub fn calcPcRelativeDisplacementArm64(source_addr: u64, target_addr: u64) error{Overflow}!i28 { | 51 | pub fn lessThan(ctx: void, lhs: Relocation, rhs: Relocation) bool { |
| 225 | const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)); | 52 | _ = ctx; |
| 226 | return math.cast(i28, disp) orelse error.Overflow; | 53 | return lhs.offset < rhs.offset; |
| 227 | } | 54 | } |
| 228 | 55 | ||
| 229 | pub fn calcNumberOfPages(source_addr: u64, target_addr: u64) i21 { | 56 | pub fn calcNumberOfPages(saddr: u64, taddr: u64) error{Overflow}!i21 { |
| 230 | const source_page = @as(i32, @intCast(source_addr >> 12)); | 57 | const spage = math.cast(i32, saddr >> 12) orelse return error.Overflow; |
| 231 | const target_page = @as(i32, @intCast(target_addr >> 12)); | 58 | const tpage = math.cast(i32, taddr >> 12) orelse return error.Overflow; |
| 232 | const pages = @as(i21, @intCast(target_page - source_page)); | 59 | const pages = math.cast(i21, tpage - spage) orelse return error.Overflow; |
| 233 | return pages; | 60 | return pages; |
| 234 | } | 61 | } |
| 235 | 62 | ||
| ... | @@ -242,8 +69,8 @@ pub const PageOffsetInstKind = enum { | ... | @@ -242,8 +69,8 @@ pub const PageOffsetInstKind = enum { |
| 242 | load_store_128, | 69 | load_store_128, |
| 243 | }; | 70 | }; |
| 244 | 71 | ||
| 245 | pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { | 72 | pub fn calcPageOffset(taddr: u64, kind: PageOffsetInstKind) !u12 { |
| 246 | const narrowed = @as(u12, @truncate(target_addr)); | 73 | const narrowed = @as(u12, @truncate(taddr)); |
| 247 | return switch (kind) { | 74 | return switch (kind) { |
| 248 | .arithmetic, .load_store_8 => narrowed, | 75 | .arithmetic, .load_store_8 => narrowed, |
| 249 | .load_store_16 => try math.divExact(u12, narrowed, 2), | 76 | .load_store_16 => try math.divExact(u12, narrowed, 2), |
| ... | @@ -253,17 +80,57 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { | ... | @@ -253,17 +80,57 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { |
| 253 | }; | 80 | }; |
| 254 | } | 81 | } |
| 255 | 82 | ||
| 256 | const Relocation = @This(); | 83 | pub inline fn isArithmeticOp(inst: *const [4]u8) bool { |
| 84 | const group_decode = @as(u5, @truncate(inst[3])); | ||
| 85 | return ((group_decode >> 2) == 4); | ||
| 86 | } | ||
| 87 | |||
| 88 | pub const Type = enum { | ||
| 89 | // x86_64 | ||
| 90 | /// RIP-relative displacement (X86_64_RELOC_SIGNED) | ||
| 91 | signed, | ||
| 92 | /// RIP-relative displacement (X86_64_RELOC_SIGNED_1) | ||
| 93 | signed1, | ||
| 94 | /// RIP-relative displacement (X86_64_RELOC_SIGNED_2) | ||
| 95 | signed2, | ||
| 96 | /// RIP-relative displacement (X86_64_RELOC_SIGNED_4) | ||
| 97 | signed4, | ||
| 98 | /// RIP-relative GOT load (X86_64_RELOC_GOT_LOAD) | ||
| 99 | got_load, | ||
| 100 | /// RIP-relative TLV load (X86_64_RELOC_TLV) | ||
| 101 | tlv, | ||
| 102 | |||
| 103 | // arm64 | ||
| 104 | /// PC-relative load (distance to page, ARM64_RELOC_PAGE21) | ||
| 105 | page, | ||
| 106 | /// Non-PC-relative offset to symbol (ARM64_RELOC_PAGEOFF12) | ||
| 107 | pageoff, | ||
| 108 | /// PC-relative GOT load (distance to page, ARM64_RELOC_GOT_LOAD_PAGE21) | ||
| 109 | got_load_page, | ||
| 110 | /// Non-PC-relative offset to GOT slot (ARM64_RELOC_GOT_LOAD_PAGEOFF12) | ||
| 111 | got_load_pageoff, | ||
| 112 | /// PC-relative TLV load (distance to page, ARM64_RELOC_TLVP_LOAD_PAGE21) | ||
| 113 | tlvp_page, | ||
| 114 | /// Non-PC-relative offset to TLV slot (ARM64_RELOC_TLVP_LOAD_PAGEOFF12) | ||
| 115 | tlvp_pageoff, | ||
| 116 | |||
| 117 | // common | ||
| 118 | /// PC-relative call/bl/b (X86_64_RELOC_BRANCH or ARM64_RELOC_BRANCH26) | ||
| 119 | branch, | ||
| 120 | /// PC-relative displacement to GOT pointer (X86_64_RELOC_GOT or ARM64_RELOC_POINTER_TO_GOT) | ||
| 121 | got, | ||
| 122 | /// Absolute subtractor value (X86_64_RELOC_SUBTRACTOR or ARM64_RELOC_SUBTRACTOR) | ||
| 123 | subtractor, | ||
| 124 | /// Absolute relocation (X86_64_RELOC_UNSIGNED or ARM64_RELOC_UNSIGNED) | ||
| 125 | unsigned, | ||
| 126 | }; | ||
| 257 | 127 | ||
| 258 | const std = @import("std"); | ||
| 259 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 260 | const assert = std.debug.assert; | 128 | const assert = std.debug.assert; |
| 261 | const relocs_log = std.log.scoped(.link_relocs); | ||
| 262 | const macho = std.macho; | 129 | const macho = std.macho; |
| 263 | const math = std.math; | 130 | const math = std.math; |
| 264 | const mem = std.mem; | 131 | const std = @import("std"); |
| 265 | const meta = std.meta; | ||
| 266 | 132 | ||
| 267 | const Atom = @import("Atom.zig"); | 133 | const Atom = @import("Atom.zig"); |
| 268 | const MachO = @import("../MachO.zig"); | 134 | const MachO = @import("../MachO.zig"); |
| 269 | const SymbolWithLoc = MachO.SymbolWithLoc; | 135 | const Relocation = @This(); |
| 136 | const Symbol = @import("Symbol.zig"); |
src/link/MachO/Symbol.zig created+383| ... | @@ -0,0 +1,383 @@ | ||
| 1 | //! Represents a defined symbol. | ||
| 2 | |||
| 3 | /// Allocated address value of this symbol. | ||
| 4 | value: u64 = 0, | ||
| 5 | |||
| 6 | /// Offset into the linker's intern table. | ||
| 7 | name: u32 = 0, | ||
| 8 | |||
| 9 | /// File where this symbol is defined. | ||
| 10 | file: File.Index = 0, | ||
| 11 | |||
| 12 | /// Atom containing this symbol if any. | ||
| 13 | /// Index of 0 means there is no associated atom with this symbol. | ||
| 14 | /// Use `getAtom` to get the pointer to the atom. | ||
| 15 | atom: Atom.Index = 0, | ||
| 16 | |||
| 17 | /// Assigned output section index for this atom. | ||
| 18 | out_n_sect: u16 = 0, | ||
| 19 | |||
| 20 | /// Index of the source nlist this symbol references. | ||
| 21 | /// Use `getNlist` to pull the nlist from the relevant file. | ||
| 22 | nlist_idx: u32 = 0, | ||
| 23 | |||
| 24 | /// Misc flags for the symbol packaged as packed struct for compression. | ||
| 25 | flags: Flags = .{}, | ||
| 26 | |||
| 27 | visibility: Visibility = .local, | ||
| 28 | |||
| 29 | extra: u32 = 0, | ||
| 30 | |||
| 31 | pub fn isLocal(symbol: Symbol) bool { | ||
| 32 | return !(symbol.flags.import or symbol.flags.@"export"); | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool { | ||
| 36 | const file = symbol.getFile(macho_file) orelse return false; | ||
| 37 | return switch (file) { | ||
| 38 | .object => symbol.getNlist(macho_file).stab(), | ||
| 39 | else => false, | ||
| 40 | }; | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool { | ||
| 44 | const name = symbol.getName(macho_file); | ||
| 45 | return std.mem.indexOf(u8, name, "$tlv$init") != null; | ||
| 46 | } | ||
| 47 | |||
| 48 | pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool { | ||
| 49 | const file = symbol.getFile(macho_file).?; | ||
| 50 | const is_dylib_weak = switch (file) { | ||
| 51 | .dylib => |x| x.weak, | ||
| 52 | else => false, | ||
| 53 | }; | ||
| 54 | return is_dylib_weak or symbol.flags.weak_ref; | ||
| 55 | } | ||
| 56 | |||
| 57 | pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 { | ||
| 58 | return macho_file.string_intern.getAssumeExists(symbol.name); | ||
| 59 | } | ||
| 60 | |||
| 61 | pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom { | ||
| 62 | return macho_file.getAtom(symbol.atom); | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File { | ||
| 66 | return macho_file.getFile(symbol.file); | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Asserts file is an object. | ||
| 70 | pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 { | ||
| 71 | const file = symbol.getFile(macho_file).?; | ||
| 72 | return switch (file) { | ||
| 73 | .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx], | ||
| 74 | else => unreachable, | ||
| 75 | }; | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 { | ||
| 79 | const file = symbol.getFile(macho_file).?; | ||
| 80 | assert(file == .object); | ||
| 81 | return file.object.symtab.items(.size)[symbol.nlist_idx]; | ||
| 82 | } | ||
| 83 | |||
| 84 | pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 { | ||
| 85 | assert(symbol.flags.import); | ||
| 86 | const file = symbol.getFile(macho_file) orelse return null; | ||
| 87 | return switch (file) { | ||
| 88 | .dylib => |x| x.ordinal, | ||
| 89 | else => null, | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 { | ||
| 94 | const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32); | ||
| 95 | const in_archive = switch (file) { | ||
| 96 | .object => |x| !x.alive, | ||
| 97 | else => false, | ||
| 98 | }; | ||
| 99 | return file.getSymbolRank(.{ | ||
| 100 | .archive = in_archive, | ||
| 101 | .weak = symbol.flags.weak, | ||
| 102 | .tentative = symbol.flags.tentative, | ||
| 103 | }); | ||
| 104 | } | ||
| 105 | |||
| 106 | pub fn getAddress(symbol: Symbol, opts: struct { | ||
| 107 | stubs: bool = true, | ||
| 108 | }, macho_file: *MachO) u64 { | ||
| 109 | if (opts.stubs) { | ||
| 110 | if (symbol.flags.stubs) { | ||
| 111 | return symbol.getStubsAddress(macho_file); | ||
| 112 | } else if (symbol.flags.objc_stubs) { | ||
| 113 | return symbol.getObjcStubsAddress(macho_file); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value; | ||
| 117 | return symbol.value; | ||
| 118 | } | ||
| 119 | |||
| 120 | pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 { | ||
| 121 | if (!symbol.flags.got) return 0; | ||
| 122 | const extra = symbol.getExtra(macho_file).?; | ||
| 123 | return macho_file.got.getAddress(extra.got, macho_file); | ||
| 124 | } | ||
| 125 | |||
| 126 | pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { | ||
| 127 | if (!symbol.flags.stubs) return 0; | ||
| 128 | const extra = symbol.getExtra(macho_file).?; | ||
| 129 | return macho_file.stubs.getAddress(extra.stubs, macho_file); | ||
| 130 | } | ||
| 131 | |||
| 132 | pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { | ||
| 133 | if (!symbol.flags.objc_stubs) return 0; | ||
| 134 | const extra = symbol.getExtra(macho_file).?; | ||
| 135 | return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file); | ||
| 136 | } | ||
| 137 | |||
| 138 | pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 { | ||
| 139 | if (!symbol.flags.objc_stubs) return 0; | ||
| 140 | const extra = symbol.getExtra(macho_file).?; | ||
| 141 | const atom = macho_file.getAtom(extra.objc_selrefs).?; | ||
| 142 | assert(atom.flags.alive); | ||
| 143 | return atom.value; | ||
| 144 | } | ||
| 145 | |||
| 146 | pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { | ||
| 147 | if (!symbol.flags.tlv_ptr) return 0; | ||
| 148 | const extra = symbol.getExtra(macho_file).?; | ||
| 149 | return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file); | ||
| 150 | } | ||
| 151 | |||
| 152 | pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 { | ||
| 153 | if (!symbol.flags.output_symtab) return null; | ||
| 154 | assert(!symbol.isSymbolStab(macho_file)); | ||
| 155 | const file = symbol.getFile(macho_file).?; | ||
| 156 | const symtab_ctx = switch (file) { | ||
| 157 | inline else => |x| x.output_symtab_ctx, | ||
| 158 | }; | ||
| 159 | var idx = symbol.getExtra(macho_file).?.symtab; | ||
| 160 | if (symbol.isLocal()) { | ||
| 161 | idx += symtab_ctx.ilocal; | ||
| 162 | } else if (symbol.flags.@"export") { | ||
| 163 | idx += symtab_ctx.iexport; | ||
| 164 | } else { | ||
| 165 | assert(symbol.flags.import); | ||
| 166 | idx += symtab_ctx.iimport; | ||
| 167 | } | ||
| 168 | return idx; | ||
| 169 | } | ||
| 170 | |||
| 171 | const AddExtraOpts = struct { | ||
| 172 | got: ?u32 = null, | ||
| 173 | stubs: ?u32 = null, | ||
| 174 | objc_stubs: ?u32 = null, | ||
| 175 | objc_selrefs: ?u32 = null, | ||
| 176 | tlv_ptr: ?u32 = null, | ||
| 177 | symtab: ?u32 = null, | ||
| 178 | }; | ||
| 179 | |||
| 180 | pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void { | ||
| 181 | if (symbol.getExtra(macho_file) == null) { | ||
| 182 | symbol.extra = try macho_file.addSymbolExtra(.{}); | ||
| 183 | } | ||
| 184 | var extra = symbol.getExtra(macho_file).?; | ||
| 185 | inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { | ||
| 186 | if (@field(opts, field.name)) |x| { | ||
| 187 | @field(extra, field.name) = x; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | symbol.setExtra(extra, macho_file); | ||
| 191 | } | ||
| 192 | |||
| 193 | pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra { | ||
| 194 | return macho_file.getSymbolExtra(symbol.extra); | ||
| 195 | } | ||
| 196 | |||
| 197 | pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void { | ||
| 198 | macho_file.setSymbolExtra(symbol.extra, extra); | ||
| 199 | } | ||
| 200 | |||
| 201 | pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void { | ||
| 202 | if (symbol.isLocal()) { | ||
| 203 | out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; | ||
| 204 | out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1); | ||
| 205 | out.n_desc = 0; | ||
| 206 | out.n_value = symbol.getAddress(.{}, macho_file); | ||
| 207 | |||
| 208 | switch (symbol.visibility) { | ||
| 209 | .hidden => out.n_type |= macho.N_PEXT, | ||
| 210 | else => {}, | ||
| 211 | } | ||
| 212 | } else if (symbol.flags.@"export") { | ||
| 213 | assert(symbol.visibility == .global); | ||
| 214 | out.n_type = macho.N_EXT; | ||
| 215 | out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; | ||
| 216 | out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1); | ||
| 217 | out.n_value = symbol.getAddress(.{}, macho_file); | ||
| 218 | out.n_desc = 0; | ||
| 219 | |||
| 220 | if (symbol.flags.weak) { | ||
| 221 | out.n_desc |= macho.N_WEAK_DEF; | ||
| 222 | } | ||
| 223 | if (symbol.flags.dyn_ref) { | ||
| 224 | out.n_desc |= macho.REFERENCED_DYNAMICALLY; | ||
| 225 | } | ||
| 226 | } else { | ||
| 227 | assert(symbol.visibility == .global); | ||
| 228 | out.n_type = macho.N_EXT; | ||
| 229 | out.n_sect = 0; | ||
| 230 | out.n_value = 0; | ||
| 231 | out.n_desc = 0; | ||
| 232 | |||
| 233 | const ord: u16 = if (macho_file.options.namespace == .flat) | ||
| 234 | @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)) | ||
| 235 | else if (symbol.getDylibOrdinal(macho_file)) |ord| | ||
| 236 | ord | ||
| 237 | else | ||
| 238 | macho.BIND_SPECIAL_DYLIB_SELF; | ||
| 239 | out.n_desc = macho.N_SYMBOL_RESOLVER * ord; | ||
| 240 | |||
| 241 | if (symbol.flags.weak) { | ||
| 242 | out.n_desc |= macho.N_WEAK_DEF; | ||
| 243 | } | ||
| 244 | |||
| 245 | if (symbol.weakRef(macho_file)) { | ||
| 246 | out.n_desc |= macho.N_WEAK_REF; | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | pub fn format( | ||
| 252 | symbol: Symbol, | ||
| 253 | comptime unused_fmt_string: []const u8, | ||
| 254 | options: std.fmt.FormatOptions, | ||
| 255 | writer: anytype, | ||
| 256 | ) !void { | ||
| 257 | _ = symbol; | ||
| 258 | _ = unused_fmt_string; | ||
| 259 | _ = options; | ||
| 260 | _ = writer; | ||
| 261 | @compileError("do not format symbols directly"); | ||
| 262 | } | ||
| 263 | |||
| 264 | const FormatContext = struct { | ||
| 265 | symbol: Symbol, | ||
| 266 | macho_file: *MachO, | ||
| 267 | }; | ||
| 268 | |||
| 269 | pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Formatter(format2) { | ||
| 270 | return .{ .data = .{ | ||
| 271 | .symbol = symbol, | ||
| 272 | .macho_file = macho_file, | ||
| 273 | } }; | ||
| 274 | } | ||
| 275 | |||
| 276 | fn format2( | ||
| 277 | ctx: FormatContext, | ||
| 278 | comptime unused_fmt_string: []const u8, | ||
| 279 | options: std.fmt.FormatOptions, | ||
| 280 | writer: anytype, | ||
| 281 | ) !void { | ||
| 282 | _ = options; | ||
| 283 | _ = unused_fmt_string; | ||
| 284 | const symbol = ctx.symbol; | ||
| 285 | try writer.print("%{d} : {s} : @{x}", .{ | ||
| 286 | symbol.nlist_idx, | ||
| 287 | symbol.getName(ctx.macho_file), | ||
| 288 | symbol.getAddress(.{}, ctx.macho_file), | ||
| 289 | }); | ||
| 290 | if (symbol.getFile(ctx.macho_file)) |file| { | ||
| 291 | if (symbol.out_n_sect != 0) { | ||
| 292 | try writer.print(" : sect({d})", .{symbol.out_n_sect}); | ||
| 293 | } | ||
| 294 | if (symbol.getAtom(ctx.macho_file)) |atom| { | ||
| 295 | try writer.print(" : atom({d})", .{atom.atom_index}); | ||
| 296 | } | ||
| 297 | var buf: [2]u8 = .{'_'} ** 2; | ||
| 298 | if (symbol.flags.@"export") buf[0] = 'E'; | ||
| 299 | if (symbol.flags.import) buf[1] = 'I'; | ||
| 300 | try writer.print(" : {s}", .{&buf}); | ||
| 301 | if (symbol.flags.weak) try writer.writeAll(" : weak"); | ||
| 302 | if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab"); | ||
| 303 | switch (file) { | ||
| 304 | .internal => |x| try writer.print(" : internal({d})", .{x.index}), | ||
| 305 | .object => |x| try writer.print(" : object({d})", .{x.index}), | ||
| 306 | .dylib => |x| try writer.print(" : dylib({d})", .{x.index}), | ||
| 307 | } | ||
| 308 | } else try writer.writeAll(" : unresolved"); | ||
| 309 | } | ||
| 310 | |||
| 311 | pub const Flags = packed struct { | ||
| 312 | /// Whether the symbol is imported at runtime. | ||
| 313 | import: bool = false, | ||
| 314 | |||
| 315 | /// Whether the symbol is exported at runtime. | ||
| 316 | @"export": bool = false, | ||
| 317 | |||
| 318 | /// Whether this symbol is weak. | ||
| 319 | weak: bool = false, | ||
| 320 | |||
| 321 | /// Whether this symbol is weakly referenced. | ||
| 322 | weak_ref: bool = false, | ||
| 323 | |||
| 324 | /// Whether this symbol is dynamically referenced. | ||
| 325 | dyn_ref: bool = false, | ||
| 326 | |||
| 327 | /// Whether this symbol was marked as N_NO_DEAD_STRIP. | ||
| 328 | no_dead_strip: bool = false, | ||
| 329 | |||
| 330 | /// Whether this symbol can be interposed at runtime. | ||
| 331 | interposable: bool = false, | ||
| 332 | |||
| 333 | /// Whether this symbol is absolute. | ||
| 334 | abs: bool = false, | ||
| 335 | |||
| 336 | /// Whether this symbol is a tentative definition. | ||
| 337 | tentative: bool = false, | ||
| 338 | |||
| 339 | /// Whether this symbol is a thread-local variable. | ||
| 340 | tlv: bool = false, | ||
| 341 | |||
| 342 | /// Whether the symbol makes into the output symtab or not. | ||
| 343 | output_symtab: bool = false, | ||
| 344 | |||
| 345 | /// Whether the symbol contains __got indirection. | ||
| 346 | got: bool = false, | ||
| 347 | |||
| 348 | /// Whether the symbols contains __stubs indirection. | ||
| 349 | stubs: bool = false, | ||
| 350 | |||
| 351 | /// Whether the symbol has a TLV pointer. | ||
| 352 | tlv_ptr: bool = false, | ||
| 353 | |||
| 354 | /// Whether the symbol contains __objc_stubs indirection. | ||
| 355 | objc_stubs: bool = false, | ||
| 356 | }; | ||
| 357 | |||
| 358 | pub const Visibility = enum { | ||
| 359 | global, | ||
| 360 | hidden, | ||
| 361 | local, | ||
| 362 | }; | ||
| 363 | |||
| 364 | pub const Extra = struct { | ||
| 365 | got: u32 = 0, | ||
| 366 | stubs: u32 = 0, | ||
| 367 | objc_stubs: u32 = 0, | ||
| 368 | objc_selrefs: u32 = 0, | ||
| 369 | tlv_ptr: u32 = 0, | ||
| 370 | symtab: u32 = 0, | ||
| 371 | }; | ||
| 372 | |||
| 373 | pub const Index = u32; | ||
| 374 | |||
| 375 | const assert = std.debug.assert; | ||
| 376 | const macho = std.macho; | ||
| 377 | const std = @import("std"); | ||
| 378 | |||
| 379 | const Atom = @import("Atom.zig"); | ||
| 380 | const File = @import("file.zig").File; | ||
| 381 | const MachO = @import("../MachO.zig"); | ||
| 382 | const Object = @import("Object.zig"); | ||
| 383 | const Symbol = @This(); | ||
src/link/MachO/Trie.zig deleted-613| ... | @@ -1,613 +0,0 @@ | ||
| 1 | //! Represents export trie used in MachO executables and dynamic libraries. | ||
| 2 | //! The purpose of an export trie is to encode as compactly as possible all | ||
| 3 | //! export symbols for the loader `dyld`. | ||
| 4 | //! The export trie encodes offset and other information using ULEB128 | ||
| 5 | //! encoding, and is part of the __LINKEDIT segment. | ||
| 6 | //! | ||
| 7 | //! Description from loader.h: | ||
| 8 | //! | ||
| 9 | //! The symbols exported by a dylib are encoded in a trie. This is a compact | ||
| 10 | //! representation that factors out common prefixes. It also reduces LINKEDIT pages | ||
| 11 | //! in RAM because it encodes all information (name, address, flags) in one small, | ||
| 12 | //! contiguous range. The export area is a stream of nodes. The first node sequentially | ||
| 13 | //! is the start node for the trie. | ||
| 14 | //! | ||
| 15 | //! Nodes for a symbol start with a uleb128 that is the length of the exported symbol | ||
| 16 | //! information for the string so far. If there is no exported symbol, the node starts | ||
| 17 | //! with a zero byte. If there is exported info, it follows the length. | ||
| 18 | //! | ||
| 19 | //! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded | ||
| 20 | //! offset which is location of the content named by the symbol from the mach_header | ||
| 21 | //! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags | ||
| 22 | //! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string | ||
| 23 | //! is zero length, then the symbol is re-export from the specified dylib with the same name. | ||
| 24 | //! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two | ||
| 25 | //! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers. | ||
| 26 | //! The resolver is used by lazy pointers and must be called to get the actual address to use. | ||
| 27 | //! | ||
| 28 | //! After the optional exported symbol information is a byte of how many edges (0-255) that | ||
| 29 | //! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of | ||
| 30 | //! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to. | ||
| 31 | /// The root node of the trie. | ||
| 32 | root: ?*Node = null, | ||
| 33 | |||
| 34 | /// If you want to access nodes ordered in DFS fashion, | ||
| 35 | /// you should call `finalize` first since the nodes | ||
| 36 | /// in this container are not guaranteed to not be stale | ||
| 37 | /// if more insertions took place after the last `finalize` | ||
| 38 | /// call. | ||
| 39 | ordered_nodes: std.ArrayListUnmanaged(*Node) = .{}, | ||
| 40 | |||
| 41 | /// The size of the trie in bytes. | ||
| 42 | /// This value may be outdated if there were additional | ||
| 43 | /// insertions performed after `finalize` was called. | ||
| 44 | /// Call `finalize` before accessing this value to ensure | ||
| 45 | /// it is up-to-date. | ||
| 46 | size: u64 = 0, | ||
| 47 | |||
| 48 | /// Number of nodes currently in the trie. | ||
| 49 | node_count: usize = 0, | ||
| 50 | |||
| 51 | trie_dirty: bool = true, | ||
| 52 | |||
| 53 | /// Export symbol that is to be placed in the trie. | ||
| 54 | pub const ExportSymbol = struct { | ||
| 55 | /// Name of the symbol. | ||
| 56 | name: []const u8, | ||
| 57 | |||
| 58 | /// Offset of this symbol's virtual memory address from the beginning | ||
| 59 | /// of the __TEXT segment. | ||
| 60 | vmaddr_offset: u64, | ||
| 61 | |||
| 62 | /// Export flags of this exported symbol. | ||
| 63 | export_flags: u64, | ||
| 64 | }; | ||
| 65 | |||
| 66 | /// Insert a symbol into the trie, updating the prefixes in the process. | ||
| 67 | /// This operation may change the layout of the trie by splicing edges in | ||
| 68 | /// certain circumstances. | ||
| 69 | pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void { | ||
| 70 | const node = try self.root.?.put(allocator, symbol.name); | ||
| 71 | node.terminal_info = .{ | ||
| 72 | .vmaddr_offset = symbol.vmaddr_offset, | ||
| 73 | .export_flags = symbol.export_flags, | ||
| 74 | }; | ||
| 75 | self.trie_dirty = true; | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Finalizes this trie for writing to a byte stream. | ||
| 79 | /// This step performs multiple passes through the trie ensuring | ||
| 80 | /// there are no gaps after every `Node` is ULEB128 encoded. | ||
| 81 | /// Call this method before trying to `write` the trie to a byte stream. | ||
| 82 | pub fn finalize(self: *Trie, allocator: Allocator) !void { | ||
| 83 | if (!self.trie_dirty) return; | ||
| 84 | |||
| 85 | self.ordered_nodes.shrinkRetainingCapacity(0); | ||
| 86 | try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count); | ||
| 87 | |||
| 88 | var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator); | ||
| 89 | defer fifo.deinit(); | ||
| 90 | |||
| 91 | try fifo.writeItem(self.root.?); | ||
| 92 | |||
| 93 | while (fifo.readItem()) |next| { | ||
| 94 | for (next.edges.items) |*edge| { | ||
| 95 | try fifo.writeItem(edge.to); | ||
| 96 | } | ||
| 97 | self.ordered_nodes.appendAssumeCapacity(next); | ||
| 98 | } | ||
| 99 | |||
| 100 | var more: bool = true; | ||
| 101 | while (more) { | ||
| 102 | self.size = 0; | ||
| 103 | more = false; | ||
| 104 | for (self.ordered_nodes.items) |node| { | ||
| 105 | const res = try node.finalize(self.size); | ||
| 106 | self.size += res.node_size; | ||
| 107 | if (res.updated) more = true; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | self.trie_dirty = false; | ||
| 112 | } | ||
| 113 | |||
| 114 | const ReadError = error{ | ||
| 115 | OutOfMemory, | ||
| 116 | EndOfStream, | ||
| 117 | Overflow, | ||
| 118 | }; | ||
| 119 | |||
| 120 | /// Parse the trie from a byte stream. | ||
| 121 | pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize { | ||
| 122 | return self.root.?.read(allocator, reader); | ||
| 123 | } | ||
| 124 | |||
| 125 | /// Write the trie to a byte stream. | ||
| 126 | /// Panics if the trie was not finalized using `finalize` before calling this method. | ||
| 127 | pub fn write(self: Trie, writer: anytype) !u64 { | ||
| 128 | assert(!self.trie_dirty); | ||
| 129 | var counting_writer = std.io.countingWriter(writer); | ||
| 130 | for (self.ordered_nodes.items) |node| { | ||
| 131 | try node.write(counting_writer.writer()); | ||
| 132 | } | ||
| 133 | return counting_writer.bytes_written; | ||
| 134 | } | ||
| 135 | |||
| 136 | pub fn init(self: *Trie, allocator: Allocator) !void { | ||
| 137 | assert(self.root == null); | ||
| 138 | const root = try allocator.create(Node); | ||
| 139 | root.* = .{ .base = self }; | ||
| 140 | self.root = root; | ||
| 141 | self.node_count += 1; | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn deinit(self: *Trie, allocator: Allocator) void { | ||
| 145 | if (self.root) |root| { | ||
| 146 | root.deinit(allocator); | ||
| 147 | allocator.destroy(root); | ||
| 148 | } | ||
| 149 | self.ordered_nodes.deinit(allocator); | ||
| 150 | } | ||
| 151 | |||
| 152 | test "Trie node count" { | ||
| 153 | const gpa = testing.allocator; | ||
| 154 | var trie: Trie = .{}; | ||
| 155 | defer trie.deinit(gpa); | ||
| 156 | try trie.init(gpa); | ||
| 157 | |||
| 158 | try testing.expectEqual(trie.node_count, 0); | ||
| 159 | try testing.expect(trie.root == null); | ||
| 160 | |||
| 161 | try trie.put(gpa, .{ | ||
| 162 | .name = "_main", | ||
| 163 | .vmaddr_offset = 0, | ||
| 164 | .export_flags = 0, | ||
| 165 | }); | ||
| 166 | try testing.expectEqual(trie.node_count, 2); | ||
| 167 | |||
| 168 | // Inserting the same node shouldn't update the trie. | ||
| 169 | try trie.put(gpa, .{ | ||
| 170 | .name = "_main", | ||
| 171 | .vmaddr_offset = 0, | ||
| 172 | .export_flags = 0, | ||
| 173 | }); | ||
| 174 | try testing.expectEqual(trie.node_count, 2); | ||
| 175 | |||
| 176 | try trie.put(gpa, .{ | ||
| 177 | .name = "__mh_execute_header", | ||
| 178 | .vmaddr_offset = 0x1000, | ||
| 179 | .export_flags = 0, | ||
| 180 | }); | ||
| 181 | try testing.expectEqual(trie.node_count, 4); | ||
| 182 | |||
| 183 | // Inserting the same node shouldn't update the trie. | ||
| 184 | try trie.put(gpa, .{ | ||
| 185 | .name = "__mh_execute_header", | ||
| 186 | .vmaddr_offset = 0x1000, | ||
| 187 | .export_flags = 0, | ||
| 188 | }); | ||
| 189 | try testing.expectEqual(trie.node_count, 4); | ||
| 190 | try trie.put(gpa, .{ | ||
| 191 | .name = "_main", | ||
| 192 | .vmaddr_offset = 0, | ||
| 193 | .export_flags = 0, | ||
| 194 | }); | ||
| 195 | try testing.expectEqual(trie.node_count, 4); | ||
| 196 | } | ||
| 197 | |||
| 198 | test "Trie basic" { | ||
| 199 | const gpa = testing.allocator; | ||
| 200 | var trie: Trie = .{}; | ||
| 201 | defer trie.deinit(gpa); | ||
| 202 | try trie.init(gpa); | ||
| 203 | |||
| 204 | // root --- _st ---> node | ||
| 205 | try trie.put(gpa, .{ | ||
| 206 | .name = "_st", | ||
| 207 | .vmaddr_offset = 0, | ||
| 208 | .export_flags = 0, | ||
| 209 | }); | ||
| 210 | try testing.expect(trie.root.?.edges.items.len == 1); | ||
| 211 | try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st")); | ||
| 212 | |||
| 213 | { | ||
| 214 | // root --- _st ---> node --- art ---> node | ||
| 215 | try trie.put(gpa, .{ | ||
| 216 | .name = "_start", | ||
| 217 | .vmaddr_offset = 0, | ||
| 218 | .export_flags = 0, | ||
| 219 | }); | ||
| 220 | try testing.expect(trie.root.?.edges.items.len == 1); | ||
| 221 | |||
| 222 | const nextEdge = &trie.root.?.edges.items[0]; | ||
| 223 | try testing.expect(mem.eql(u8, nextEdge.label, "_st")); | ||
| 224 | try testing.expect(nextEdge.to.edges.items.len == 1); | ||
| 225 | try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art")); | ||
| 226 | } | ||
| 227 | { | ||
| 228 | // root --- _ ---> node --- st ---> node --- art ---> node | ||
| 229 | // | | ||
| 230 | // | --- main ---> node | ||
| 231 | try trie.put(gpa, .{ | ||
| 232 | .name = "_main", | ||
| 233 | .vmaddr_offset = 0, | ||
| 234 | .export_flags = 0, | ||
| 235 | }); | ||
| 236 | try testing.expect(trie.root.?.edges.items.len == 1); | ||
| 237 | |||
| 238 | const nextEdge = &trie.root.?.edges.items[0]; | ||
| 239 | try testing.expect(mem.eql(u8, nextEdge.label, "_")); | ||
| 240 | try testing.expect(nextEdge.to.edges.items.len == 2); | ||
| 241 | try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st")); | ||
| 242 | try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main")); | ||
| 243 | |||
| 244 | const nextNextEdge = &nextEdge.to.edges.items[0]; | ||
| 245 | try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art")); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void { | ||
| 250 | assert(expected.len > 0); | ||
| 251 | if (mem.eql(u8, expected, given)) return; | ||
| 252 | const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); | ||
| 253 | defer testing.allocator.free(expected_fmt); | ||
| 254 | const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); | ||
| 255 | defer testing.allocator.free(given_fmt); | ||
| 256 | const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?; | ||
| 257 | const padding = try testing.allocator.alloc(u8, idx + 5); | ||
| 258 | defer testing.allocator.free(padding); | ||
| 259 | @memset(padding, ' '); | ||
| 260 | std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding }); | ||
| 261 | return error.TestFailed; | ||
| 262 | } | ||
| 263 | |||
| 264 | test "write Trie to a byte stream" { | ||
| 265 | var gpa = testing.allocator; | ||
| 266 | var trie: Trie = .{}; | ||
| 267 | defer trie.deinit(gpa); | ||
| 268 | try trie.init(gpa); | ||
| 269 | |||
| 270 | try trie.put(gpa, .{ | ||
| 271 | .name = "__mh_execute_header", | ||
| 272 | .vmaddr_offset = 0, | ||
| 273 | .export_flags = 0, | ||
| 274 | }); | ||
| 275 | try trie.put(gpa, .{ | ||
| 276 | .name = "_main", | ||
| 277 | .vmaddr_offset = 0x1000, | ||
| 278 | .export_flags = 0, | ||
| 279 | }); | ||
| 280 | |||
| 281 | try trie.finalize(gpa); | ||
| 282 | try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes. | ||
| 283 | |||
| 284 | const exp_buffer = [_]u8{ | ||
| 285 | 0x0, 0x1, // node root | ||
| 286 | 0x5f, 0x0, 0x5, // edge '_' | ||
| 287 | 0x0, 0x2, // non-terminal node | ||
| 288 | 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header' | ||
| 289 | 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header' | ||
| 290 | 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main' | ||
| 291 | 0x2, 0x0, 0x0, 0x0, // terminal node | ||
| 292 | 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node | ||
| 293 | }; | ||
| 294 | |||
| 295 | const buffer = try gpa.alloc(u8, trie.size); | ||
| 296 | defer gpa.free(buffer); | ||
| 297 | var stream = std.io.fixedBufferStream(buffer); | ||
| 298 | { | ||
| 299 | _ = try trie.write(stream.writer()); | ||
| 300 | try expectEqualHexStrings(&exp_buffer, buffer); | ||
| 301 | } | ||
| 302 | { | ||
| 303 | // Writing finalized trie again should yield the same result. | ||
| 304 | try stream.seekTo(0); | ||
| 305 | _ = try trie.write(stream.writer()); | ||
| 306 | try expectEqualHexStrings(&exp_buffer, buffer); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | test "parse Trie from byte stream" { | ||
| 311 | var gpa = testing.allocator; | ||
| 312 | |||
| 313 | const in_buffer = [_]u8{ | ||
| 314 | 0x0, 0x1, // node root | ||
| 315 | 0x5f, 0x0, 0x5, // edge '_' | ||
| 316 | 0x0, 0x2, // non-terminal node | ||
| 317 | 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header' | ||
| 318 | 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header' | ||
| 319 | 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main' | ||
| 320 | 0x2, 0x0, 0x0, 0x0, // terminal node | ||
| 321 | 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node | ||
| 322 | }; | ||
| 323 | |||
| 324 | var in_stream = std.io.fixedBufferStream(&in_buffer); | ||
| 325 | var trie: Trie = .{}; | ||
| 326 | defer trie.deinit(gpa); | ||
| 327 | try trie.init(gpa); | ||
| 328 | const nread = try trie.read(gpa, in_stream.reader()); | ||
| 329 | |||
| 330 | try testing.expect(nread == in_buffer.len); | ||
| 331 | |||
| 332 | try trie.finalize(gpa); | ||
| 333 | |||
| 334 | const out_buffer = try gpa.alloc(u8, trie.size); | ||
| 335 | defer gpa.free(out_buffer); | ||
| 336 | var out_stream = std.io.fixedBufferStream(out_buffer); | ||
| 337 | _ = try trie.write(out_stream.writer()); | ||
| 338 | try expectEqualHexStrings(&in_buffer, out_buffer); | ||
| 339 | } | ||
| 340 | |||
| 341 | test "ordering bug" { | ||
| 342 | var gpa = testing.allocator; | ||
| 343 | var trie: Trie = .{}; | ||
| 344 | defer trie.deinit(gpa); | ||
| 345 | try trie.init(gpa); | ||
| 346 | |||
| 347 | try trie.put(gpa, .{ | ||
| 348 | .name = "_asStr", | ||
| 349 | .vmaddr_offset = 0x558, | ||
| 350 | .export_flags = 0, | ||
| 351 | }); | ||
| 352 | try trie.put(gpa, .{ | ||
| 353 | .name = "_a", | ||
| 354 | .vmaddr_offset = 0x8008, | ||
| 355 | .export_flags = 0, | ||
| 356 | }); | ||
| 357 | try trie.finalize(gpa); | ||
| 358 | |||
| 359 | const exp_buffer = [_]u8{ | ||
| 360 | 0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00, | ||
| 361 | 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72, | ||
| 362 | 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00, | ||
| 363 | }; | ||
| 364 | |||
| 365 | const buffer = try gpa.alloc(u8, trie.size); | ||
| 366 | defer gpa.free(buffer); | ||
| 367 | var stream = std.io.fixedBufferStream(buffer); | ||
| 368 | // Writing finalized trie again should yield the same result. | ||
| 369 | _ = try trie.write(stream.writer()); | ||
| 370 | try expectEqualHexStrings(&exp_buffer, buffer); | ||
| 371 | } | ||
| 372 | |||
| 373 | pub const Node = struct { | ||
| 374 | base: *Trie, | ||
| 375 | |||
| 376 | /// Terminal info associated with this node. | ||
| 377 | /// If this node is not a terminal node, info is null. | ||
| 378 | terminal_info: ?struct { | ||
| 379 | /// Export flags associated with this exported symbol. | ||
| 380 | export_flags: u64, | ||
| 381 | /// VM address offset wrt to the section this symbol is defined against. | ||
| 382 | vmaddr_offset: u64, | ||
| 383 | } = null, | ||
| 384 | |||
| 385 | /// Offset of this node in the trie output byte stream. | ||
| 386 | trie_offset: ?u64 = null, | ||
| 387 | |||
| 388 | /// List of all edges originating from this node. | ||
| 389 | edges: std.ArrayListUnmanaged(Edge) = .{}, | ||
| 390 | |||
| 391 | node_dirty: bool = true, | ||
| 392 | |||
| 393 | /// Edge connecting to nodes in the trie. | ||
| 394 | pub const Edge = struct { | ||
| 395 | from: *Node, | ||
| 396 | to: *Node, | ||
| 397 | label: []u8, | ||
| 398 | |||
| 399 | fn deinit(self: *Edge, allocator: Allocator) void { | ||
| 400 | self.to.deinit(allocator); | ||
| 401 | allocator.destroy(self.to); | ||
| 402 | allocator.free(self.label); | ||
| 403 | self.from = undefined; | ||
| 404 | self.to = undefined; | ||
| 405 | self.label = undefined; | ||
| 406 | } | ||
| 407 | }; | ||
| 408 | |||
| 409 | fn deinit(self: *Node, allocator: Allocator) void { | ||
| 410 | for (self.edges.items) |*edge| { | ||
| 411 | edge.deinit(allocator); | ||
| 412 | } | ||
| 413 | self.edges.deinit(allocator); | ||
| 414 | } | ||
| 415 | |||
| 416 | /// Inserts a new node starting from `self`. | ||
| 417 | fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node { | ||
| 418 | // Check for match with edges from this node. | ||
| 419 | for (self.edges.items) |*edge| { | ||
| 420 | const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to; | ||
| 421 | if (match == 0) continue; | ||
| 422 | if (match == edge.label.len) return edge.to.put(allocator, label[match..]); | ||
| 423 | |||
| 424 | // Found a match, need to splice up nodes. | ||
| 425 | // From: A -> B | ||
| 426 | // To: A -> C -> B | ||
| 427 | const mid = try allocator.create(Node); | ||
| 428 | mid.* = .{ .base = self.base }; | ||
| 429 | const to_label = try allocator.dupe(u8, edge.label[match..]); | ||
| 430 | allocator.free(edge.label); | ||
| 431 | const to_node = edge.to; | ||
| 432 | edge.to = mid; | ||
| 433 | edge.label = try allocator.dupe(u8, label[0..match]); | ||
| 434 | self.base.node_count += 1; | ||
| 435 | |||
| 436 | try mid.edges.append(allocator, .{ | ||
| 437 | .from = mid, | ||
| 438 | .to = to_node, | ||
| 439 | .label = to_label, | ||
| 440 | }); | ||
| 441 | |||
| 442 | return if (match == label.len) mid else mid.put(allocator, label[match..]); | ||
| 443 | } | ||
| 444 | |||
| 445 | // Add a new node. | ||
| 446 | const node = try allocator.create(Node); | ||
| 447 | node.* = .{ .base = self.base }; | ||
| 448 | self.base.node_count += 1; | ||
| 449 | |||
| 450 | try self.edges.append(allocator, .{ | ||
| 451 | .from = self, | ||
| 452 | .to = node, | ||
| 453 | .label = try allocator.dupe(u8, label), | ||
| 454 | }); | ||
| 455 | |||
| 456 | return node; | ||
| 457 | } | ||
| 458 | |||
| 459 | /// Recursively parses the node from the input byte stream. | ||
| 460 | fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize { | ||
| 461 | self.node_dirty = true; | ||
| 462 | const trie_offset = try reader.context.getPos(); | ||
| 463 | self.trie_offset = trie_offset; | ||
| 464 | |||
| 465 | var nread: usize = 0; | ||
| 466 | |||
| 467 | const node_size = try leb.readULEB128(u64, reader); | ||
| 468 | if (node_size > 0) { | ||
| 469 | const export_flags = try leb.readULEB128(u64, reader); | ||
| 470 | // TODO Parse special flags. | ||
| 471 | assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and | ||
| 472 | export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0); | ||
| 473 | |||
| 474 | const vmaddr_offset = try leb.readULEB128(u64, reader); | ||
| 475 | |||
| 476 | self.terminal_info = .{ | ||
| 477 | .export_flags = export_flags, | ||
| 478 | .vmaddr_offset = vmaddr_offset, | ||
| 479 | }; | ||
| 480 | } | ||
| 481 | |||
| 482 | const nedges = try reader.readByte(); | ||
| 483 | self.base.node_count += nedges; | ||
| 484 | |||
| 485 | nread += (try reader.context.getPos()) - trie_offset; | ||
| 486 | |||
| 487 | var i: usize = 0; | ||
| 488 | while (i < nedges) : (i += 1) { | ||
| 489 | const edge_start_pos = try reader.context.getPos(); | ||
| 490 | |||
| 491 | const label = blk: { | ||
| 492 | var label_buf = std.ArrayList(u8).init(allocator); | ||
| 493 | while (true) { | ||
| 494 | const next = try reader.readByte(); | ||
| 495 | if (next == @as(u8, 0)) | ||
| 496 | break; | ||
| 497 | try label_buf.append(next); | ||
| 498 | } | ||
| 499 | break :blk try label_buf.toOwnedSlice(); | ||
| 500 | }; | ||
| 501 | |||
| 502 | const seek_to = try leb.readULEB128(u64, reader); | ||
| 503 | const return_pos = try reader.context.getPos(); | ||
| 504 | |||
| 505 | nread += return_pos - edge_start_pos; | ||
| 506 | try reader.context.seekTo(seek_to); | ||
| 507 | |||
| 508 | const node = try allocator.create(Node); | ||
| 509 | node.* = .{ .base = self.base }; | ||
| 510 | |||
| 511 | nread += try node.read(allocator, reader); | ||
| 512 | try self.edges.append(allocator, .{ | ||
| 513 | .from = self, | ||
| 514 | .to = node, | ||
| 515 | .label = label, | ||
| 516 | }); | ||
| 517 | try reader.context.seekTo(return_pos); | ||
| 518 | } | ||
| 519 | |||
| 520 | return nread; | ||
| 521 | } | ||
| 522 | |||
| 523 | /// Writes this node to a byte stream. | ||
| 524 | /// The children of this node *are* not written to the byte stream | ||
| 525 | /// recursively. To write all nodes to a byte stream in sequence, | ||
| 526 | /// iterate over `Trie.ordered_nodes` and call this method on each node. | ||
| 527 | /// This is one of the requirements of the MachO. | ||
| 528 | /// Panics if `finalize` was not called before calling this method. | ||
| 529 | fn write(self: Node, writer: anytype) !void { | ||
| 530 | assert(!self.node_dirty); | ||
| 531 | if (self.terminal_info) |info| { | ||
| 532 | // Terminal node info: encode export flags and vmaddr offset of this symbol. | ||
| 533 | var info_buf: [@sizeOf(u64) * 2]u8 = undefined; | ||
| 534 | var info_stream = std.io.fixedBufferStream(&info_buf); | ||
| 535 | // TODO Implement for special flags. | ||
| 536 | assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and | ||
| 537 | info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0); | ||
| 538 | try leb.writeULEB128(info_stream.writer(), info.export_flags); | ||
| 539 | try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset); | ||
| 540 | |||
| 541 | // Encode the size of the terminal node info. | ||
| 542 | var size_buf: [@sizeOf(u64)]u8 = undefined; | ||
| 543 | var size_stream = std.io.fixedBufferStream(&size_buf); | ||
| 544 | try leb.writeULEB128(size_stream.writer(), info_stream.pos); | ||
| 545 | |||
| 546 | // Now, write them to the output stream. | ||
| 547 | try writer.writeAll(size_buf[0..size_stream.pos]); | ||
| 548 | try writer.writeAll(info_buf[0..info_stream.pos]); | ||
| 549 | } else { | ||
| 550 | // Non-terminal node is delimited by 0 byte. | ||
| 551 | try writer.writeByte(0); | ||
| 552 | } | ||
| 553 | // Write number of edges (max legal number of edges is 256). | ||
| 554 | try writer.writeByte(@as(u8, @intCast(self.edges.items.len))); | ||
| 555 | |||
| 556 | for (self.edges.items) |edge| { | ||
| 557 | // Write edge label and offset to next node in trie. | ||
| 558 | try writer.writeAll(edge.label); | ||
| 559 | try writer.writeByte(0); | ||
| 560 | try leb.writeULEB128(writer, edge.to.trie_offset.?); | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | const FinalizeResult = struct { | ||
| 565 | /// Current size of this node in bytes. | ||
| 566 | node_size: u64, | ||
| 567 | |||
| 568 | /// True if the trie offset of this node in the output byte stream | ||
| 569 | /// would need updating; false otherwise. | ||
| 570 | updated: bool, | ||
| 571 | }; | ||
| 572 | |||
| 573 | /// Updates offset of this node in the output byte stream. | ||
| 574 | fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult { | ||
| 575 | var stream = std.io.countingWriter(std.io.null_writer); | ||
| 576 | const writer = stream.writer(); | ||
| 577 | |||
| 578 | var node_size: u64 = 0; | ||
| 579 | if (self.terminal_info) |info| { | ||
| 580 | try leb.writeULEB128(writer, info.export_flags); | ||
| 581 | try leb.writeULEB128(writer, info.vmaddr_offset); | ||
| 582 | try leb.writeULEB128(writer, stream.bytes_written); | ||
| 583 | } else { | ||
| 584 | node_size += 1; // 0x0 for non-terminal nodes | ||
| 585 | } | ||
| 586 | node_size += 1; // 1 byte for edge count | ||
| 587 | |||
| 588 | for (self.edges.items) |edge| { | ||
| 589 | const next_node_offset = edge.to.trie_offset orelse 0; | ||
| 590 | node_size += edge.label.len + 1; | ||
| 591 | try leb.writeULEB128(writer, next_node_offset); | ||
| 592 | } | ||
| 593 | |||
| 594 | const trie_offset = self.trie_offset orelse 0; | ||
| 595 | const updated = offset_in_trie != trie_offset; | ||
| 596 | self.trie_offset = offset_in_trie; | ||
| 597 | self.node_dirty = false; | ||
| 598 | node_size += stream.bytes_written; | ||
| 599 | |||
| 600 | return FinalizeResult{ .node_size = node_size, .updated = updated }; | ||
| 601 | } | ||
| 602 | }; | ||
| 603 | |||
| 604 | const Trie = @This(); | ||
| 605 | |||
| 606 | const std = @import("std"); | ||
| 607 | const mem = std.mem; | ||
| 608 | const leb = std.leb; | ||
| 609 | const log = std.log.scoped(.link); | ||
| 610 | const macho = std.macho; | ||
| 611 | const testing = std.testing; | ||
| 612 | const assert = std.debug.assert; | ||
| 613 | const Allocator = mem.Allocator; | ||
src/link/MachO/UnwindInfo.zig+429-578| ... | @@ -1,376 +1,122 @@ | ... | @@ -1,376 +1,122 @@ |
| 1 | gpa: Allocator, | ||
| 2 | |||
| 3 | /// List of all unwind records gathered from all objects and sorted | 1 | /// List of all unwind records gathered from all objects and sorted |
| 4 | /// by source function address. | 2 | /// by allocated relative function address within the section. |
| 5 | records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{}, | 3 | records: std.ArrayListUnmanaged(Record.Index) = .{}, |
| 6 | records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{}, | ||
| 7 | 4 | ||
| 8 | /// List of all personalities referenced by either unwind info entries | 5 | /// List of all personalities referenced by either unwind info entries |
| 9 | /// or __eh_frame entries. | 6 | /// or __eh_frame entries. |
| 10 | personalities: [max_personalities]SymbolWithLoc = undefined, | 7 | personalities: [max_personalities]Symbol.Index = undefined, |
| 11 | personalities_count: u2 = 0, | 8 | personalities_count: u2 = 0, |
| 12 | 9 | ||
| 13 | /// List of common encodings sorted in descending order with the most common first. | 10 | /// List of common encodings sorted in descending order with the most common first. |
| 14 | common_encodings: [max_common_encodings]macho.compact_unwind_encoding_t = undefined, | 11 | common_encodings: [max_common_encodings]Encoding = undefined, |
| 15 | common_encodings_count: u7 = 0, | 12 | common_encodings_count: u7 = 0, |
| 16 | 13 | ||
| 17 | /// List of record indexes containing an LSDA pointer. | 14 | /// List of record indexes containing an LSDA pointer. |
| 18 | lsdas: std.ArrayListUnmanaged(RecordIndex) = .{}, | 15 | lsdas: std.ArrayListUnmanaged(u32) = .{}, |
| 19 | lsdas_lookup: std.AutoHashMapUnmanaged(RecordIndex, u32) = .{}, | 16 | lsdas_lookup: std.ArrayListUnmanaged(u32) = .{}, |
| 20 | 17 | ||
| 21 | /// List of second level pages. | 18 | /// List of second level pages. |
| 22 | pages: std.ArrayListUnmanaged(Page) = .{}, | 19 | pages: std.ArrayListUnmanaged(Page) = .{}, |
| 23 | 20 | ||
| 24 | /// Upper bound (exclusive) of all the record ranges | 21 | pub fn deinit(info: *UnwindInfo, allocator: Allocator) void { |
| 25 | end_boundary: u64 = 0, | 22 | info.records.deinit(allocator); |
| 26 | 23 | info.pages.deinit(allocator); | |
| 27 | const RecordIndex = u32; | 24 | info.lsdas.deinit(allocator); |
| 28 | 25 | info.lsdas_lookup.deinit(allocator); | |
| 29 | const max_personalities = 3; | 26 | } |
| 30 | const max_common_encodings = 127; | ||
| 31 | const max_compact_encodings = 256; | ||
| 32 | |||
| 33 | const second_level_page_bytes = 0x1000; | ||
| 34 | const second_level_page_words = second_level_page_bytes / @sizeOf(u32); | ||
| 35 | |||
| 36 | const max_regular_second_level_entries = | ||
| 37 | (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) / | ||
| 38 | @sizeOf(macho.unwind_info_regular_second_level_entry); | ||
| 39 | |||
| 40 | const max_compressed_second_level_entries = | ||
| 41 | (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) / | ||
| 42 | @sizeOf(u32); | ||
| 43 | |||
| 44 | const compressed_entry_func_offset_mask = ~@as(u24, 0); | ||
| 45 | |||
| 46 | const Page = struct { | ||
| 47 | kind: enum { regular, compressed }, | ||
| 48 | start: RecordIndex, | ||
| 49 | count: u16, | ||
| 50 | page_encodings: [max_compact_encodings]RecordIndex = undefined, | ||
| 51 | page_encodings_count: u9 = 0, | ||
| 52 | |||
| 53 | fn appendPageEncoding(page: *Page, record_id: RecordIndex) void { | ||
| 54 | assert(page.page_encodings_count <= max_compact_encodings); | ||
| 55 | page.page_encodings[page.page_encodings_count] = record_id; | ||
| 56 | page.page_encodings_count += 1; | ||
| 57 | } | ||
| 58 | |||
| 59 | fn getPageEncoding( | ||
| 60 | page: *const Page, | ||
| 61 | info: *const UnwindInfo, | ||
| 62 | enc: macho.compact_unwind_encoding_t, | ||
| 63 | ) ?u8 { | ||
| 64 | comptime var index: u9 = 0; | ||
| 65 | inline while (index < max_compact_encodings) : (index += 1) { | ||
| 66 | if (index >= page.page_encodings_count) return null; | ||
| 67 | const record_id = page.page_encodings[index]; | ||
| 68 | const record = info.records.items[record_id]; | ||
| 69 | if (record.compactUnwindEncoding == enc) { | ||
| 70 | return @as(u8, @intCast(index)); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | return null; | ||
| 74 | } | ||
| 75 | |||
| 76 | fn format( | ||
| 77 | page: *const Page, | ||
| 78 | comptime unused_format_string: []const u8, | ||
| 79 | options: std.fmt.FormatOptions, | ||
| 80 | writer: anytype, | ||
| 81 | ) !void { | ||
| 82 | _ = page; | ||
| 83 | _ = unused_format_string; | ||
| 84 | _ = options; | ||
| 85 | _ = writer; | ||
| 86 | @compileError("do not format Page directly; use page.fmtDebug()"); | ||
| 87 | } | ||
| 88 | |||
| 89 | const DumpCtx = struct { | ||
| 90 | page: *const Page, | ||
| 91 | info: *const UnwindInfo, | ||
| 92 | }; | ||
| 93 | |||
| 94 | fn dump( | ||
| 95 | ctx: DumpCtx, | ||
| 96 | comptime unused_format_string: []const u8, | ||
| 97 | options: std.fmt.FormatOptions, | ||
| 98 | writer: anytype, | ||
| 99 | ) @TypeOf(writer).Error!void { | ||
| 100 | _ = options; | ||
| 101 | comptime assert(unused_format_string.len == 0); | ||
| 102 | try writer.writeAll("Page:\n"); | ||
| 103 | try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)}); | ||
| 104 | try writer.print(" entries: {d} - {d}\n", .{ | ||
| 105 | ctx.page.start, | ||
| 106 | ctx.page.start + ctx.page.count, | ||
| 107 | }); | ||
| 108 | try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count}); | ||
| 109 | for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |record_id, i| { | ||
| 110 | const record = ctx.info.records.items[record_id]; | ||
| 111 | const enc = record.compactUnwindEncoding; | ||
| 112 | try writer.print(" {d}: 0x{x:0>8}\n", .{ ctx.info.common_encodings_count + i, enc }); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | fn fmtDebug(page: *const Page, info: *const UnwindInfo) std.fmt.Formatter(dump) { | ||
| 117 | return .{ .data = .{ | ||
| 118 | .page = page, | ||
| 119 | .info = info, | ||
| 120 | } }; | ||
| 121 | } | ||
| 122 | |||
| 123 | fn write(page: *const Page, info: *const UnwindInfo, writer: anytype) !void { | ||
| 124 | switch (page.kind) { | ||
| 125 | .regular => { | ||
| 126 | try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{ | ||
| 127 | .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header), | ||
| 128 | .entryCount = page.count, | ||
| 129 | }); | ||
| 130 | |||
| 131 | for (info.records.items[page.start..][0..page.count]) |record| { | ||
| 132 | try writer.writeStruct(macho.unwind_info_regular_second_level_entry{ | ||
| 133 | .functionOffset = @as(u32, @intCast(record.rangeStart)), | ||
| 134 | .encoding = record.compactUnwindEncoding, | ||
| 135 | }); | ||
| 136 | } | ||
| 137 | }, | ||
| 138 | .compressed => { | ||
| 139 | const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) + | ||
| 140 | @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32); | ||
| 141 | try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{ | ||
| 142 | .entryPageOffset = entry_offset, | ||
| 143 | .entryCount = page.count, | ||
| 144 | .encodingsPageOffset = @sizeOf( | ||
| 145 | macho.unwind_info_compressed_second_level_page_header, | ||
| 146 | ), | ||
| 147 | .encodingsCount = page.page_encodings_count, | ||
| 148 | }); | ||
| 149 | |||
| 150 | for (page.page_encodings[0..page.page_encodings_count]) |record_id| { | ||
| 151 | const enc = info.records.items[record_id].compactUnwindEncoding; | ||
| 152 | try writer.writeInt(u32, enc, .little); | ||
| 153 | } | ||
| 154 | |||
| 155 | assert(page.count > 0); | ||
| 156 | const first_entry = info.records.items[page.start]; | ||
| 157 | for (info.records.items[page.start..][0..page.count]) |record| { | ||
| 158 | const enc_index = blk: { | ||
| 159 | if (info.getCommonEncoding(record.compactUnwindEncoding)) |id| { | ||
| 160 | break :blk id; | ||
| 161 | } | ||
| 162 | const ncommon = info.common_encodings_count; | ||
| 163 | break :blk ncommon + page.getPageEncoding(info, record.compactUnwindEncoding).?; | ||
| 164 | }; | ||
| 165 | const compressed = macho.UnwindInfoCompressedEntry{ | ||
| 166 | .funcOffset = @as(u24, @intCast(record.rangeStart - first_entry.rangeStart)), | ||
| 167 | .encodingIndex = @as(u8, @intCast(enc_index)), | ||
| 168 | }; | ||
| 169 | try writer.writeStruct(compressed); | ||
| 170 | } | ||
| 171 | }, | ||
| 172 | } | ||
| 173 | } | ||
| 174 | }; | ||
| 175 | 27 | ||
| 176 | pub fn deinit(info: *UnwindInfo) void { | 28 | fn canFold(macho_file: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool { |
| 177 | info.records.deinit(info.gpa); | 29 | const cpu_arch = macho_file.options.cpu_arch.?; |
| 178 | info.records_lookup.deinit(info.gpa); | 30 | const lhs = macho_file.getUnwindRecord(lhs_index); |
| 179 | info.pages.deinit(info.gpa); | 31 | const rhs = macho_file.getUnwindRecord(rhs_index); |
| 180 | info.lsdas.deinit(info.gpa); | 32 | if (cpu_arch == .x86_64) { |
| 181 | info.lsdas_lookup.deinit(info.gpa); | 33 | if (lhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND) or |
| 34 | rhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND)) return false; | ||
| 35 | } | ||
| 36 | const lhs_per = lhs.personality orelse 0; | ||
| 37 | const rhs_per = rhs.personality orelse 0; | ||
| 38 | return lhs.enc.eql(rhs.enc) and | ||
| 39 | lhs_per == rhs_per and | ||
| 40 | lhs.fde == rhs.fde and | ||
| 41 | lhs.getLsdaAtom(macho_file) == null and rhs.getLsdaAtom(macho_file) == null; | ||
| 182 | } | 42 | } |
| 183 | 43 | ||
| 184 | pub fn scanRelocs(macho_file: *MachO) !void { | 44 | pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void { |
| 185 | if (macho_file.unwind_info_section_index == null) return; | 45 | const gpa = macho_file.base.allocator; |
| 186 | 46 | ||
| 187 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 47 | log.debug("generating unwind info", .{}); |
| 188 | const cpu_arch = target.cpu.arch; | 48 | |
| 189 | for (macho_file.objects.items, 0..) |*object, object_id| { | 49 | // Collect all unwind records |
| 190 | const unwind_records = object.getUnwindRecords(); | 50 | for (macho_file.sections.items(.atoms)) |atoms| { |
| 191 | for (object.exec_atoms.items) |atom_index| { | 51 | for (atoms.items) |atom_index| { |
| 192 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 52 | const atom = macho_file.getAtom(atom_index) orelse continue; |
| 193 | while (inner_syms_it.next()) |sym| { | 53 | if (!atom.flags.alive) continue; |
| 194 | const record_id = object.unwind_records_lookup.get(sym) orelse continue; | 54 | const recs = atom.getUnwindRecords(macho_file); |
| 195 | if (object.unwind_relocs_lookup[record_id].dead) continue; | 55 | try info.records.ensureUnusedCapacity(gpa, recs.len); |
| 196 | const record = unwind_records[record_id]; | 56 | for (recs) |rec| { |
| 197 | if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { | 57 | if (!macho_file.getUnwindRecord(rec).alive) continue; |
| 198 | if (getPersonalityFunctionReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| { | 58 | info.records.appendAssumeCapacity(rec); |
| 199 | // Personality function; add GOT pointer. | ||
| 200 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | ||
| 201 | .object_id = @as(u32, @intCast(object_id)), | ||
| 202 | .rel = rel, | ||
| 203 | .code = mem.asBytes(&record), | ||
| 204 | .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))), | ||
| 205 | }); | ||
| 206 | try macho_file.addGotEntry(reloc_target); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | 59 | } |
| 210 | } | 60 | } |
| 211 | } | 61 | } |
| 212 | } | ||
| 213 | |||
| 214 | pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ||
| 215 | if (macho_file.unwind_info_section_index == null) return; | ||
| 216 | |||
| 217 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 218 | const cpu_arch = target.cpu.arch; | ||
| 219 | |||
| 220 | var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa); | ||
| 221 | defer records.deinit(); | ||
| 222 | |||
| 223 | var sym_indexes = std.ArrayList(SymbolWithLoc).init(info.gpa); | ||
| 224 | defer sym_indexes.deinit(); | ||
| 225 | |||
| 226 | // TODO handle dead stripping | ||
| 227 | for (macho_file.objects.items, 0..) |*object, object_id| { | ||
| 228 | log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id }); | ||
| 229 | const unwind_records = object.getUnwindRecords(); | ||
| 230 | |||
| 231 | // Contents of unwind records does not have to cover all symbol in executable section | ||
| 232 | // so we need insert them ourselves. | ||
| 233 | try records.ensureUnusedCapacity(object.exec_atoms.items.len); | ||
| 234 | try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len); | ||
| 235 | |||
| 236 | for (object.exec_atoms.items) |atom_index| { | ||
| 237 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | ||
| 238 | var prev_symbol: ?SymbolWithLoc = null; | ||
| 239 | while (inner_syms_it.next()) |symbol| { | ||
| 240 | var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: { | ||
| 241 | if (object.unwind_relocs_lookup[record_id].dead) continue; | ||
| 242 | var record = unwind_records[record_id]; | ||
| 243 | |||
| 244 | if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { | ||
| 245 | info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record); | ||
| 246 | } else { | ||
| 247 | if (getPersonalityFunctionReloc( | ||
| 248 | macho_file, | ||
| 249 | @as(u32, @intCast(object_id)), | ||
| 250 | record_id, | ||
| 251 | )) |rel| { | ||
| 252 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | ||
| 253 | .object_id = @as(u32, @intCast(object_id)), | ||
| 254 | .rel = rel, | ||
| 255 | .code = mem.asBytes(&record), | ||
| 256 | .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))), | ||
| 257 | }); | ||
| 258 | const personality_index = info.getPersonalityFunction(reloc_target) orelse inner: { | ||
| 259 | const personality_index = info.personalities_count; | ||
| 260 | info.personalities[personality_index] = reloc_target; | ||
| 261 | info.personalities_count += 1; | ||
| 262 | break :inner personality_index; | ||
| 263 | }; | ||
| 264 | |||
| 265 | record.personalityFunction = personality_index + 1; | ||
| 266 | UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1); | ||
| 267 | } | ||
| 268 | |||
| 269 | if (getLsdaReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| { | ||
| 270 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | ||
| 271 | .object_id = @as(u32, @intCast(object_id)), | ||
| 272 | .rel = rel, | ||
| 273 | .code = mem.asBytes(&record), | ||
| 274 | .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))), | ||
| 275 | }); | ||
| 276 | record.lsda = @as(u64, @bitCast(reloc_target)); | ||
| 277 | } | ||
| 278 | } | ||
| 279 | break :blk record; | ||
| 280 | } else blk: { | ||
| 281 | const sym = macho_file.getSymbol(symbol); | ||
| 282 | if (sym.n_desc == MachO.N_DEAD) continue; | ||
| 283 | if (prev_symbol) |prev_sym| { | ||
| 284 | const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value; | ||
| 285 | const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value; | ||
| 286 | if (prev_addr == curr_addr) continue; | ||
| 287 | } | ||
| 288 | |||
| 289 | if (!object.hasUnwindRecords()) { | ||
| 290 | if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| { | ||
| 291 | if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue; | ||
| 292 | var record = nullRecord(); | ||
| 293 | info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record); | ||
| 294 | switch (cpu_arch) { | ||
| 295 | .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF), | ||
| 296 | .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF), | ||
| 297 | else => unreachable, | ||
| 298 | } | ||
| 299 | break :blk record; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | break :blk nullRecord(); | ||
| 304 | }; | ||
| 305 | 62 | ||
| 306 | const atom = macho_file.getAtom(atom_index); | 63 | // Encode records |
| 307 | const sym = macho_file.getSymbol(symbol); | 64 | for (info.records.items) |index| { |
| 308 | assert(sym.n_desc != MachO.N_DEAD); | 65 | const rec = macho_file.getUnwindRecord(index); |
| 309 | const size = if (inner_syms_it.next()) |next_sym| blk: { | 66 | if (rec.getFde(macho_file)) |fde| { |
| 310 | // All this trouble to account for symbol aliases. | 67 | rec.enc.setDwarfSectionOffset(@intCast(fde.out_offset)); |
| 311 | // TODO I think that remodelling the linker so that a Symbol references an Atom | 68 | } else if (rec.getPersonality(macho_file)) |_| { |
| 312 | // is the way to go, kinda like we do for ELF. We might also want to perhaps tag | 69 | const personality_index = try info.getOrPutPersonalityFunction(rec.personality.?); // TODO handle error |
| 313 | // symbol aliases somehow so that they are excluded from everything except relocation | 70 | rec.enc.setPersonalityIndex(personality_index + 1); |
| 314 | // resolution. | ||
| 315 | defer inner_syms_it.pos -= 1; | ||
| 316 | const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value; | ||
| 317 | const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value; | ||
| 318 | if (next_addr > curr_addr) break :blk next_addr - curr_addr; | ||
| 319 | break :blk macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value; | ||
| 320 | } else macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value; | ||
| 321 | record.rangeStart = sym.n_value; | ||
| 322 | record.rangeLength = @as(u32, @intCast(size)); | ||
| 323 | |||
| 324 | try records.append(record); | ||
| 325 | try sym_indexes.append(symbol); | ||
| 326 | |||
| 327 | prev_symbol = symbol; | ||
| 328 | } | ||
| 329 | } | 71 | } |
| 330 | } | 72 | } |
| 331 | 73 | ||
| 332 | // Record the ending boundary before folding. | 74 | // Sort by assigned relative address within each output section |
| 333 | assert(records.items.len > 0); | 75 | const sortFn = struct { |
| 334 | info.end_boundary = blk: { | 76 | fn sortFn(ctx: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool { |
| 335 | const last_record = records.items[records.items.len - 1]; | 77 | const lhs = ctx.getUnwindRecord(lhs_index); |
| 336 | break :blk last_record.rangeStart + last_record.rangeLength; | 78 | const rhs = ctx.getUnwindRecord(rhs_index); |
| 337 | }; | 79 | const lhsa = lhs.getAtom(ctx); |
| 80 | const rhsa = rhs.getAtom(ctx); | ||
| 81 | if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx); | ||
| 82 | return lhsa.out_n_sect < rhsa.out_n_sect; | ||
| 83 | } | ||
| 84 | }.sortFn; | ||
| 85 | mem.sort(Record.Index, info.records.items, macho_file, sortFn); | ||
| 338 | 86 | ||
| 339 | // Fold records | 87 | // Fold the records |
| 340 | try info.records.ensureTotalCapacity(info.gpa, records.items.len); | 88 | // Any adjacent two records that share encoding can be folded into one. |
| 341 | try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(sym_indexes.items.len))); | 89 | { |
| 342 | 90 | var i: usize = 0; | |
| 343 | var maybe_prev: ?macho.compact_unwind_entry = null; | 91 | var j: usize = 1; |
| 344 | for (records.items, 0..) |record, i| { | 92 | while (j < info.records.items.len) : (j += 1) { |
| 345 | const record_id = blk: { | 93 | if (canFold(macho_file, info.records.items[i], info.records.items[j])) { |
| 346 | if (maybe_prev) |prev| { | 94 | const rec = macho_file.getUnwindRecord(info.records.items[i]); |
| 347 | const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | 95 | rec.length += macho_file.getUnwindRecord(info.records.items[j]).length + 1; |
| 348 | if (is_dwarf or | ||
| 349 | (prev.compactUnwindEncoding != record.compactUnwindEncoding) or | ||
| 350 | (prev.personalityFunction != record.personalityFunction) or | ||
| 351 | record.lsda > 0) | ||
| 352 | { | ||
| 353 | const record_id = @as(RecordIndex, @intCast(info.records.items.len)); | ||
| 354 | info.records.appendAssumeCapacity(record); | ||
| 355 | maybe_prev = record; | ||
| 356 | break :blk record_id; | ||
| 357 | } else { | ||
| 358 | break :blk @as(RecordIndex, @intCast(info.records.items.len - 1)); | ||
| 359 | } | ||
| 360 | } else { | 96 | } else { |
| 361 | const record_id = @as(RecordIndex, @intCast(info.records.items.len)); | 97 | i += 1; |
| 362 | info.records.appendAssumeCapacity(record); | 98 | info.records.items[i] = info.records.items[j]; |
| 363 | maybe_prev = record; | ||
| 364 | break :blk record_id; | ||
| 365 | } | 99 | } |
| 366 | }; | 100 | } |
| 367 | info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id); | 101 | info.records.shrinkAndFree(gpa, i + 1); |
| 102 | } | ||
| 103 | |||
| 104 | for (info.records.items) |rec_index| { | ||
| 105 | const rec = macho_file.getUnwindRecord(rec_index); | ||
| 106 | const atom = rec.getAtom(macho_file); | ||
| 107 | log.debug("@{x}-{x} : {s} : rec({d}) : {}", .{ | ||
| 108 | rec.getAtomAddress(macho_file), | ||
| 109 | rec.getAtomAddress(macho_file) + rec.length, | ||
| 110 | atom.getName(macho_file), | ||
| 111 | rec_index, | ||
| 112 | rec.enc, | ||
| 113 | }); | ||
| 368 | } | 114 | } |
| 369 | 115 | ||
| 370 | // Calculate common encodings | 116 | // Calculate common encodings |
| 371 | { | 117 | { |
| 372 | const CommonEncWithCount = struct { | 118 | const CommonEncWithCount = struct { |
| 373 | enc: macho.compact_unwind_encoding_t, | 119 | enc: Encoding, |
| 374 | count: u32, | 120 | count: u32, |
| 375 | 121 | ||
| 376 | fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool { | 122 | fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool { |
| ... | @@ -380,39 +126,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -380,39 +126,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 380 | }; | 126 | }; |
| 381 | 127 | ||
| 382 | const Context = struct { | 128 | const Context = struct { |
| 383 | pub fn hash(ctx: @This(), key: macho.compact_unwind_encoding_t) u32 { | 129 | pub fn hash(ctx: @This(), key: Encoding) u32 { |
| 384 | _ = ctx; | 130 | _ = ctx; |
| 385 | return key; | 131 | return key.enc; |
| 386 | } | 132 | } |
| 387 | 133 | ||
| 388 | pub fn eql( | 134 | pub fn eql( |
| 389 | ctx: @This(), | 135 | ctx: @This(), |
| 390 | key1: macho.compact_unwind_encoding_t, | 136 | key1: Encoding, |
| 391 | key2: macho.compact_unwind_encoding_t, | 137 | key2: Encoding, |
| 392 | b_index: usize, | 138 | b_index: usize, |
| 393 | ) bool { | 139 | ) bool { |
| 394 | _ = ctx; | 140 | _ = ctx; |
| 395 | _ = b_index; | 141 | _ = b_index; |
| 396 | return key1 == key2; | 142 | return key1.eql(key2); |
| 397 | } | 143 | } |
| 398 | }; | 144 | }; |
| 399 | 145 | ||
| 400 | var common_encodings_counts = std.ArrayHashMap( | 146 | var common_encodings_counts = std.ArrayHashMap( |
| 401 | macho.compact_unwind_encoding_t, | 147 | Encoding, |
| 402 | CommonEncWithCount, | 148 | CommonEncWithCount, |
| 403 | Context, | 149 | Context, |
| 404 | false, | 150 | false, |
| 405 | ).init(info.gpa); | 151 | ).init(gpa); |
| 406 | defer common_encodings_counts.deinit(); | 152 | defer common_encodings_counts.deinit(); |
| 407 | 153 | ||
| 408 | for (info.records.items) |record| { | 154 | for (info.records.items) |rec_index| { |
| 409 | assert(!isNull(record)); | 155 | const rec = macho_file.getUnwindRecord(rec_index); |
| 410 | if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) continue; | 156 | if (rec.enc.isDwarf(macho_file)) continue; |
| 411 | const enc = record.compactUnwindEncoding; | 157 | const gop = try common_encodings_counts.getOrPut(rec.enc); |
| 412 | const gop = try common_encodings_counts.getOrPut(enc); | ||
| 413 | if (!gop.found_existing) { | 158 | if (!gop.found_existing) { |
| 414 | gop.value_ptr.* = .{ | 159 | gop.value_ptr.* = .{ |
| 415 | .enc = enc, | 160 | .enc = rec.enc, |
| 416 | .count = 0, | 161 | .count = 0, |
| 417 | }; | 162 | }; |
| 418 | } | 163 | } |
| ... | @@ -427,7 +172,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -427,7 +172,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 427 | if (i >= max_common_encodings) break; | 172 | if (i >= max_common_encodings) break; |
| 428 | if (slice[i].count < 2) continue; | 173 | if (slice[i].count < 2) continue; |
| 429 | info.appendCommonEncoding(slice[i].enc); | 174 | info.appendCommonEncoding(slice[i].enc); |
| 430 | log.debug("adding common encoding: {d} => 0x{x:0>8}", .{ i, slice[i].enc }); | 175 | log.debug("adding common encoding: {d} => {}", .{ i, slice[i].enc }); |
| 431 | } | 176 | } |
| 432 | } | 177 | } |
| 433 | 178 | ||
| ... | @@ -435,8 +180,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -435,8 +180,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 435 | { | 180 | { |
| 436 | var i: u32 = 0; | 181 | var i: u32 = 0; |
| 437 | while (i < info.records.items.len) { | 182 | while (i < info.records.items.len) { |
| 438 | const range_start_max: u64 = | 183 | const rec = macho_file.getUnwindRecord(info.records.items[i]); |
| 439 | info.records.items[i].rangeStart + compressed_entry_func_offset_mask; | 184 | const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask; |
| 440 | var encoding_count: u9 = info.common_encodings_count; | 185 | var encoding_count: u9 = info.common_encodings_count; |
| 441 | var space_left: u32 = second_level_page_words - | 186 | var space_left: u32 = second_level_page_words - |
| 442 | @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32); | 187 | @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32); |
| ... | @@ -447,19 +192,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -447,19 +192,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 447 | }; | 192 | }; |
| 448 | 193 | ||
| 449 | while (space_left >= 1 and i < info.records.items.len) { | 194 | while (space_left >= 1 and i < info.records.items.len) { |
| 450 | const record = info.records.items[i]; | 195 | const next = macho_file.getUnwindRecord(info.records.items[i]); |
| 451 | const enc = record.compactUnwindEncoding; | 196 | const is_dwarf = next.enc.isDwarf(macho_file); |
| 452 | const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | ||
| 453 | 197 | ||
| 454 | if (record.rangeStart >= range_start_max) { | 198 | if (next.getAtomAddress(macho_file) >= range_start_max) { |
| 455 | break; | 199 | break; |
| 456 | } else if (info.getCommonEncoding(enc) != null or | 200 | } else if (info.getCommonEncoding(next.enc) != null or |
| 457 | page.getPageEncoding(info, enc) != null and !is_dwarf) | 201 | page.getPageEncoding(next.enc) != null and !is_dwarf) |
| 458 | { | 202 | { |
| 459 | i += 1; | 203 | i += 1; |
| 460 | space_left -= 1; | 204 | space_left -= 1; |
| 461 | } else if (space_left >= 2 and encoding_count < max_compact_encodings) { | 205 | } else if (space_left >= 2 and encoding_count < max_compact_encodings) { |
| 462 | page.appendPageEncoding(i); | 206 | page.appendPageEncoding(next.enc); |
| 463 | i += 1; | 207 | i += 1; |
| 464 | space_left -= 2; | 208 | space_left -= 2; |
| 465 | encoding_count += 1; | 209 | encoding_count += 1; |
| ... | @@ -481,63 +225,24 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -481,63 +225,24 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 481 | page.kind = .compressed; | 225 | page.kind = .compressed; |
| 482 | } | 226 | } |
| 483 | 227 | ||
| 484 | log.debug("{}", .{page.fmtDebug(info)}); | 228 | log.debug("{}", .{page.fmt(info.*)}); |
| 485 | 229 | ||
| 486 | try info.pages.append(info.gpa, page); | 230 | try info.pages.append(gpa, page); |
| 487 | } | 231 | } |
| 488 | } | 232 | } |
| 489 | 233 | ||
| 490 | // Save indices of records requiring LSDA relocation | 234 | // Save records having an LSDA pointer |
| 491 | try info.lsdas_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(info.records.items.len))); | 235 | try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len); |
| 492 | for (info.records.items, 0..) |rec, i| { | 236 | for (info.records.items, 0..) |index, i| { |
| 493 | info.lsdas_lookup.putAssumeCapacityNoClobber(@as(RecordIndex, @intCast(i)), @as(u32, @intCast(info.lsdas.items.len))); | 237 | const rec = macho_file.getUnwindRecord(index); |
| 494 | if (rec.lsda == 0) continue; | 238 | info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len)); |
| 495 | try info.lsdas.append(info.gpa, @as(RecordIndex, @intCast(i))); | 239 | if (rec.getLsdaAtom(macho_file)) |_| { |
| 496 | } | 240 | try info.lsdas.append(gpa, @intCast(i)); |
| 497 | } | 241 | } |
| 498 | |||
| 499 | fn collectPersonalityFromDwarf( | ||
| 500 | info: *UnwindInfo, | ||
| 501 | macho_file: *MachO, | ||
| 502 | object_id: u32, | ||
| 503 | sym_loc: SymbolWithLoc, | ||
| 504 | record: *macho.compact_unwind_entry, | ||
| 505 | ) void { | ||
| 506 | const object = &macho_file.objects.items[object_id]; | ||
| 507 | var it = object.getEhFrameRecordsIterator(); | ||
| 508 | const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?; | ||
| 509 | it.seekTo(fde_offset); | ||
| 510 | const fde = (it.next() catch return).?; // We don't care about the error since we already handled it | ||
| 511 | const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset); | ||
| 512 | const cie_offset = fde_offset + 4 - cie_ptr; | ||
| 513 | it.seekTo(cie_offset); | ||
| 514 | const cie = (it.next() catch return).?; // We don't care about the error since we already handled it | ||
| 515 | |||
| 516 | if (cie.getPersonalityPointerReloc( | ||
| 517 | macho_file, | ||
| 518 | @as(u32, @intCast(object_id)), | ||
| 519 | cie_offset, | ||
| 520 | )) |target| { | ||
| 521 | const personality_index = info.getPersonalityFunction(target) orelse inner: { | ||
| 522 | const personality_index = info.personalities_count; | ||
| 523 | info.personalities[personality_index] = target; | ||
| 524 | info.personalities_count += 1; | ||
| 525 | break :inner personality_index; | ||
| 526 | }; | ||
| 527 | |||
| 528 | record.personalityFunction = personality_index + 1; | ||
| 529 | UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1); | ||
| 530 | } | 242 | } |
| 531 | } | 243 | } |
| 532 | 244 | ||
| 533 | pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) void { | 245 | pub fn calcSize(info: UnwindInfo) usize { |
| 534 | const sect_id = macho_file.unwind_info_section_index orelse return; | ||
| 535 | const sect = &macho_file.sections.items(.header)[sect_id]; | ||
| 536 | sect.@"align" = 2; | ||
| 537 | sect.size = info.calcRequiredSize(); | ||
| 538 | } | ||
| 539 | |||
| 540 | fn calcRequiredSize(info: UnwindInfo) usize { | ||
| 541 | var total_size: usize = 0; | 246 | var total_size: usize = 0; |
| 542 | total_size += @sizeOf(macho.unwind_info_section_header); | 247 | total_size += @sizeOf(macho.unwind_info_section_header); |
| 543 | total_size += | 248 | total_size += |
| ... | @@ -549,59 +254,12 @@ fn calcRequiredSize(info: UnwindInfo) usize { | ... | @@ -549,59 +254,12 @@ fn calcRequiredSize(info: UnwindInfo) usize { |
| 549 | return total_size; | 254 | return total_size; |
| 550 | } | 255 | } |
| 551 | 256 | ||
| 552 | pub fn write(info: *UnwindInfo, macho_file: *MachO) !void { | 257 | pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void { |
| 553 | const sect_id = macho_file.unwind_info_section_index orelse return; | 258 | const seg = macho_file.getTextSegment(); |
| 554 | const sect = &macho_file.sections.items(.header)[sect_id]; | 259 | const header = macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?]; |
| 555 | const seg_id = macho_file.sections.items(.segment_index)[sect_id]; | ||
| 556 | const seg = macho_file.segments.items[seg_id]; | ||
| 557 | |||
| 558 | const text_sect_id = macho_file.text_section_index.?; | ||
| 559 | const text_sect = macho_file.sections.items(.header)[text_sect_id]; | ||
| 560 | 260 | ||
| 561 | var personalities: [max_personalities]u32 = undefined; | 261 | var stream = std.io.fixedBufferStream(buffer); |
| 562 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 262 | var cwriter = std.io.countingWriter(stream.writer()); |
| 563 | const cpu_arch = target.cpu.arch; | ||
| 564 | |||
| 565 | log.debug("Personalities:", .{}); | ||
| 566 | for (info.personalities[0..info.personalities_count], 0..) |reloc_target, i| { | ||
| 567 | const addr = macho_file.getGotEntryAddress(reloc_target).?; | ||
| 568 | personalities[i] = @as(u32, @intCast(addr - seg.vmaddr)); | ||
| 569 | log.debug(" {d}: 0x{x} ({s})", .{ i, personalities[i], macho_file.getSymbolName(reloc_target) }); | ||
| 570 | } | ||
| 571 | |||
| 572 | for (info.records.items) |*rec| { | ||
| 573 | // Finalize missing address values | ||
| 574 | rec.rangeStart += text_sect.addr - seg.vmaddr; | ||
| 575 | if (rec.personalityFunction > 0) { | ||
| 576 | const index = math.cast(usize, rec.personalityFunction - 1) orelse return error.Overflow; | ||
| 577 | rec.personalityFunction = personalities[index]; | ||
| 578 | } | ||
| 579 | |||
| 580 | if (rec.compactUnwindEncoding > 0 and !UnwindEncoding.isDwarf(rec.compactUnwindEncoding, cpu_arch)) { | ||
| 581 | const lsda_target = @as(SymbolWithLoc, @bitCast(rec.lsda)); | ||
| 582 | if (lsda_target.getFile()) |_| { | ||
| 583 | const sym = macho_file.getSymbol(lsda_target); | ||
| 584 | rec.lsda = sym.n_value - seg.vmaddr; | ||
| 585 | } | ||
| 586 | } | ||
| 587 | } | ||
| 588 | |||
| 589 | for (info.records.items, 0..) |record, i| { | ||
| 590 | log.debug("Unwind record at offset 0x{x}", .{i * @sizeOf(macho.compact_unwind_entry)}); | ||
| 591 | log.debug(" start: 0x{x}", .{record.rangeStart}); | ||
| 592 | log.debug(" length: 0x{x}", .{record.rangeLength}); | ||
| 593 | log.debug(" compact encoding: 0x{x:0>8}", .{record.compactUnwindEncoding}); | ||
| 594 | log.debug(" personality: 0x{x}", .{record.personalityFunction}); | ||
| 595 | log.debug(" LSDA: 0x{x}", .{record.lsda}); | ||
| 596 | } | ||
| 597 | |||
| 598 | var buffer = std.ArrayList(u8).init(info.gpa); | ||
| 599 | defer buffer.deinit(); | ||
| 600 | |||
| 601 | const size = info.calcRequiredSize(); | ||
| 602 | try buffer.ensureTotalCapacityPrecise(size); | ||
| 603 | |||
| 604 | var cwriter = std.io.countingWriter(buffer.writer()); | ||
| 605 | const writer = cwriter.writer(); | 263 | const writer = cwriter.writer(); |
| 606 | 264 | ||
| 607 | const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header); | 265 | const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header); |
| ... | @@ -621,211 +279,404 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -621,211 +279,404 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void { |
| 621 | }); | 279 | }); |
| 622 | 280 | ||
| 623 | try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count])); | 281 | try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count])); |
| 624 | try writer.writeAll(mem.sliceAsBytes(personalities[0..info.personalities_count])); | ||
| 625 | 282 | ||
| 626 | const pages_base_offset = @as(u32, @intCast(size - (info.pages.items.len * second_level_page_bytes))); | 283 | for (info.personalities[0..info.personalities_count]) |sym_index| { |
| 284 | const sym = macho_file.getSymbol(sym_index); | ||
| 285 | try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little); | ||
| 286 | } | ||
| 287 | |||
| 288 | const pages_base_offset = @as(u32, @intCast(header.size - (info.pages.items.len * second_level_page_bytes))); | ||
| 627 | const lsda_base_offset = @as(u32, @intCast(pages_base_offset - | 289 | const lsda_base_offset = @as(u32, @intCast(pages_base_offset - |
| 628 | (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry)))); | 290 | (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry)))); |
| 629 | for (info.pages.items, 0..) |page, i| { | 291 | for (info.pages.items, 0..) |page, i| { |
| 630 | assert(page.count > 0); | 292 | assert(page.count > 0); |
| 631 | const first_entry = info.records.items[page.start]; | 293 | const rec = macho_file.getUnwindRecord(info.records.items[page.start]); |
| 632 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ | 294 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ |
| 633 | .functionOffset = @as(u32, @intCast(first_entry.rangeStart)), | 295 | .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), |
| 634 | .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)), | 296 | .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)), |
| 635 | .lsdaIndexArraySectionOffset = lsda_base_offset + | 297 | .lsdaIndexArraySectionOffset = lsda_base_offset + |
| 636 | info.lsdas_lookup.get(page.start).? * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), | 298 | info.lsdas_lookup.items[page.start] * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), |
| 637 | }); | 299 | }); |
| 638 | } | 300 | } |
| 639 | 301 | ||
| 640 | // Relocate end boundary address | 302 | const last_rec = macho_file.getUnwindRecord(info.records.items[info.records.items.len - 1]); |
| 641 | const end_boundary = @as(u32, @intCast(info.end_boundary + text_sect.addr - seg.vmaddr)); | 303 | const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr)); |
| 642 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ | 304 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ |
| 643 | .functionOffset = end_boundary, | 305 | .functionOffset = sentinel_address, |
| 644 | .secondLevelPagesSectionOffset = 0, | 306 | .secondLevelPagesSectionOffset = 0, |
| 645 | .lsdaIndexArraySectionOffset = lsda_base_offset + | 307 | .lsdaIndexArraySectionOffset = lsda_base_offset + |
| 646 | @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), | 308 | @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), |
| 647 | }); | 309 | }); |
| 648 | 310 | ||
| 649 | for (info.lsdas.items) |record_id| { | 311 | for (info.lsdas.items) |index| { |
| 650 | const record = info.records.items[record_id]; | 312 | const rec = macho_file.getUnwindRecord(info.records.items[index]); |
| 651 | try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{ | 313 | try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{ |
| 652 | .functionOffset = @as(u32, @intCast(record.rangeStart)), | 314 | .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), |
| 653 | .lsdaOffset = @as(u32, @intCast(record.lsda)), | 315 | .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)), |
| 654 | }); | 316 | }); |
| 655 | } | 317 | } |
| 656 | 318 | ||
| 657 | for (info.pages.items) |page| { | 319 | for (info.pages.items) |page| { |
| 658 | const start = cwriter.bytes_written; | 320 | const start = cwriter.bytes_written; |
| 659 | try page.write(info, writer); | 321 | try page.write(info, macho_file, writer); |
| 660 | const nwritten = cwriter.bytes_written - start; | 322 | const nwritten = cwriter.bytes_written - start; |
| 661 | if (nwritten < second_level_page_bytes) { | 323 | if (nwritten < second_level_page_bytes) { |
| 662 | const offset = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow; | 324 | try writer.writeByteNTimes(0, second_level_page_bytes - nwritten); |
| 663 | try writer.writeByteNTimes(0, offset); | ||
| 664 | } | 325 | } |
| 665 | } | 326 | } |
| 666 | 327 | ||
| 667 | const padding = buffer.items.len - cwriter.bytes_written; | 328 | const padding = buffer.len - cwriter.bytes_written; |
| 668 | if (padding > 0) { | 329 | if (padding > 0) { |
| 669 | const offset = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow; | 330 | @memset(buffer[cwriter.bytes_written..], 0); |
| 670 | @memset(buffer.items[offset..], 0); | ||
| 671 | } | ||
| 672 | |||
| 673 | try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset); | ||
| 674 | } | ||
| 675 | |||
| 676 | fn getRelocs(macho_file: *MachO, object_id: u32, record_id: usize) []const macho.relocation_info { | ||
| 677 | const object = &macho_file.objects.items[object_id]; | ||
| 678 | assert(object.hasUnwindRecords()); | ||
| 679 | const rel_pos = object.unwind_relocs_lookup[record_id].reloc; | ||
| 680 | const relocs = object.getRelocs(object.unwind_info_sect_id.?); | ||
| 681 | return relocs[rel_pos.start..][0..rel_pos.len]; | ||
| 682 | } | ||
| 683 | |||
| 684 | fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool { | ||
| 685 | const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))); | ||
| 686 | const rel_offset = rel.r_address - base_offset; | ||
| 687 | return rel_offset == 16; | ||
| 688 | } | ||
| 689 | |||
| 690 | pub fn getPersonalityFunctionReloc( | ||
| 691 | macho_file: *MachO, | ||
| 692 | object_id: u32, | ||
| 693 | record_id: usize, | ||
| 694 | ) ?macho.relocation_info { | ||
| 695 | const relocs = getRelocs(macho_file, object_id, record_id); | ||
| 696 | for (relocs) |rel| { | ||
| 697 | if (isPersonalityFunction(record_id, rel)) return rel; | ||
| 698 | } | 331 | } |
| 699 | return null; | ||
| 700 | } | 332 | } |
| 701 | 333 | ||
| 702 | fn getPersonalityFunction(info: UnwindInfo, global_index: SymbolWithLoc) ?u2 { | 334 | fn getOrPutPersonalityFunction(info: *UnwindInfo, sym_index: Symbol.Index) error{TooManyPersonalities}!u2 { |
| 703 | comptime var index: u2 = 0; | 335 | comptime var index: u2 = 0; |
| 704 | inline while (index < max_personalities) : (index += 1) { | 336 | inline while (index < max_personalities) : (index += 1) { |
| 705 | if (index >= info.personalities_count) return null; | 337 | if (info.personalities[index] == sym_index) { |
| 706 | if (info.personalities[index].eql(global_index)) { | 338 | return index; |
| 339 | } else if (index == info.personalities_count) { | ||
| 340 | info.personalities[index] = sym_index; | ||
| 341 | info.personalities_count += 1; | ||
| 707 | return index; | 342 | return index; |
| 708 | } | 343 | } |
| 709 | } | 344 | } |
| 710 | return null; | 345 | return error.TooManyPersonalities; |
| 711 | } | ||
| 712 | |||
| 713 | fn isLsda(record_id: usize, rel: macho.relocation_info) bool { | ||
| 714 | const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))); | ||
| 715 | const rel_offset = rel.r_address - base_offset; | ||
| 716 | return rel_offset == 24; | ||
| 717 | } | ||
| 718 | |||
| 719 | pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info { | ||
| 720 | const relocs = getRelocs(macho_file, object_id, record_id); | ||
| 721 | for (relocs) |rel| { | ||
| 722 | if (isLsda(record_id, rel)) return rel; | ||
| 723 | } | ||
| 724 | return null; | ||
| 725 | } | 346 | } |
| 726 | 347 | ||
| 727 | pub fn isNull(rec: macho.compact_unwind_entry) bool { | 348 | fn appendCommonEncoding(info: *UnwindInfo, enc: Encoding) void { |
| 728 | return rec.rangeStart == 0 and | ||
| 729 | rec.rangeLength == 0 and | ||
| 730 | rec.compactUnwindEncoding == 0 and | ||
| 731 | rec.lsda == 0 and | ||
| 732 | rec.personalityFunction == 0; | ||
| 733 | } | ||
| 734 | |||
| 735 | inline fn nullRecord() macho.compact_unwind_entry { | ||
| 736 | return .{ | ||
| 737 | .rangeStart = 0, | ||
| 738 | .rangeLength = 0, | ||
| 739 | .compactUnwindEncoding = 0, | ||
| 740 | .personalityFunction = 0, | ||
| 741 | .lsda = 0, | ||
| 742 | }; | ||
| 743 | } | ||
| 744 | |||
| 745 | fn appendCommonEncoding(info: *UnwindInfo, enc: macho.compact_unwind_encoding_t) void { | ||
| 746 | assert(info.common_encodings_count <= max_common_encodings); | 349 | assert(info.common_encodings_count <= max_common_encodings); |
| 747 | info.common_encodings[info.common_encodings_count] = enc; | 350 | info.common_encodings[info.common_encodings_count] = enc; |
| 748 | info.common_encodings_count += 1; | 351 | info.common_encodings_count += 1; |
| 749 | } | 352 | } |
| 750 | 353 | ||
| 751 | fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7 { | 354 | fn getCommonEncoding(info: UnwindInfo, enc: Encoding) ?u7 { |
| 752 | comptime var index: u7 = 0; | 355 | comptime var index: u7 = 0; |
| 753 | inline while (index < max_common_encodings) : (index += 1) { | 356 | inline while (index < max_common_encodings) : (index += 1) { |
| 754 | if (index >= info.common_encodings_count) return null; | 357 | if (index >= info.common_encodings_count) return null; |
| 755 | if (info.common_encodings[index] == enc) { | 358 | if (info.common_encodings[index].eql(enc)) { |
| 756 | return index; | 359 | return index; |
| 757 | } | 360 | } |
| 758 | } | 361 | } |
| 759 | return null; | 362 | return null; |
| 760 | } | 363 | } |
| 761 | 364 | ||
| 762 | pub const UnwindEncoding = struct { | 365 | pub const Encoding = extern struct { |
| 763 | pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 { | 366 | enc: macho.compact_unwind_encoding_t, |
| 367 | |||
| 368 | pub fn getMode(enc: Encoding) u4 { | ||
| 764 | comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK); | 369 | comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK); |
| 765 | return @as(u4, @truncate((enc & macho.UNWIND_ARM64_MODE_MASK) >> 24)); | 370 | return @as(u4, @truncate((enc.enc & macho.UNWIND_ARM64_MODE_MASK) >> 24)); |
| 766 | } | 371 | } |
| 767 | 372 | ||
| 768 | pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool { | 373 | pub fn isDwarf(enc: Encoding, macho_file: *MachO) bool { |
| 769 | const mode = getMode(enc); | 374 | const mode = enc.getMode(); |
| 770 | return switch (cpu_arch) { | 375 | return switch (macho_file.options.cpu_arch.?) { |
| 771 | .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF, | 376 | .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF, |
| 772 | .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF, | 377 | .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF, |
| 773 | else => unreachable, | 378 | else => unreachable, |
| 774 | }; | 379 | }; |
| 775 | } | 380 | } |
| 776 | 381 | ||
| 777 | pub fn setMode(enc: *macho.compact_unwind_encoding_t, mode: anytype) void { | 382 | pub fn setMode(enc: *Encoding, mode: anytype) void { |
| 778 | enc.* |= @as(u32, @intCast(@intFromEnum(mode))) << 24; | 383 | enc.enc |= @as(u32, @intCast(@intFromEnum(mode))) << 24; |
| 779 | } | 384 | } |
| 780 | 385 | ||
| 781 | pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool { | 386 | pub fn hasLsda(enc: Encoding) bool { |
| 782 | const has_lsda = @as(u1, @truncate((enc & macho.UNWIND_HAS_LSDA) >> 31)); | 387 | const has_lsda = @as(u1, @truncate((enc.enc & macho.UNWIND_HAS_LSDA) >> 31)); |
| 783 | return has_lsda == 1; | 388 | return has_lsda == 1; |
| 784 | } | 389 | } |
| 785 | 390 | ||
| 786 | pub fn setHasLsda(enc: *macho.compact_unwind_encoding_t, has_lsda: bool) void { | 391 | pub fn setHasLsda(enc: *Encoding, has_lsda: bool) void { |
| 787 | const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31; | 392 | const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31; |
| 788 | enc.* |= mask; | 393 | enc.enc |= mask; |
| 789 | } | 394 | } |
| 790 | 395 | ||
| 791 | pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 { | 396 | pub fn getPersonalityIndex(enc: Encoding) u2 { |
| 792 | const index = @as(u2, @truncate((enc & macho.UNWIND_PERSONALITY_MASK) >> 28)); | 397 | const index = @as(u2, @truncate((enc.enc & macho.UNWIND_PERSONALITY_MASK) >> 28)); |
| 793 | return index; | 398 | return index; |
| 794 | } | 399 | } |
| 795 | 400 | ||
| 796 | pub fn setPersonalityIndex(enc: *macho.compact_unwind_encoding_t, index: u2) void { | 401 | pub fn setPersonalityIndex(enc: *Encoding, index: u2) void { |
| 797 | const mask = @as(u32, @intCast(index)) << 28; | 402 | const mask = @as(u32, @intCast(index)) << 28; |
| 798 | enc.* |= mask; | 403 | enc.enc |= mask; |
| 799 | } | 404 | } |
| 800 | 405 | ||
| 801 | pub fn getDwarfSectionOffset(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) u24 { | 406 | pub fn getDwarfSectionOffset(enc: Encoding) u24 { |
| 802 | assert(isDwarf(enc, cpu_arch)); | 407 | const offset = @as(u24, @truncate(enc.enc)); |
| 803 | const offset = @as(u24, @truncate(enc)); | ||
| 804 | return offset; | 408 | return offset; |
| 805 | } | 409 | } |
| 806 | 410 | ||
| 807 | pub fn setDwarfSectionOffset(enc: *macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch, offset: u24) void { | 411 | pub fn setDwarfSectionOffset(enc: *Encoding, offset: u24) void { |
| 808 | assert(isDwarf(enc.*, cpu_arch)); | 412 | enc.enc |= offset; |
| 809 | enc.* |= offset; | 413 | } |
| 414 | |||
| 415 | pub fn eql(enc: Encoding, other: Encoding) bool { | ||
| 416 | return enc.enc == other.enc; | ||
| 417 | } | ||
| 418 | |||
| 419 | pub fn format( | ||
| 420 | enc: Encoding, | ||
| 421 | comptime unused_fmt_string: []const u8, | ||
| 422 | options: std.fmt.FormatOptions, | ||
| 423 | writer: anytype, | ||
| 424 | ) !void { | ||
| 425 | _ = unused_fmt_string; | ||
| 426 | _ = options; | ||
| 427 | try writer.print("0x{x:0>8}", .{enc.enc}); | ||
| 810 | } | 428 | } |
| 811 | }; | 429 | }; |
| 812 | 430 | ||
| 813 | const UnwindInfo = @This(); | 431 | pub const Record = struct { |
| 432 | length: u32 = 0, | ||
| 433 | enc: Encoding = .{ .enc = 0 }, | ||
| 434 | atom: Atom.Index = 0, | ||
| 435 | atom_offset: u32 = 0, | ||
| 436 | lsda: Atom.Index = 0, | ||
| 437 | lsda_offset: u32 = 0, | ||
| 438 | personality: ?Symbol.Index = null, // TODO make this zero-is-null | ||
| 439 | fde: Fde.Index = 0, // TODO actually make FDE at 0 an invalid FDE | ||
| 440 | file: File.Index = 0, | ||
| 441 | alive: bool = true, | ||
| 442 | |||
| 443 | pub fn getObject(rec: Record, macho_file: *MachO) *Object { | ||
| 444 | return macho_file.getFile(rec.file).?.object; | ||
| 445 | } | ||
| 446 | |||
| 447 | pub fn getAtom(rec: Record, macho_file: *MachO) *Atom { | ||
| 448 | return macho_file.getAtom(rec.atom).?; | ||
| 449 | } | ||
| 450 | |||
| 451 | pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom { | ||
| 452 | return macho_file.getAtom(rec.lsda); | ||
| 453 | } | ||
| 454 | |||
| 455 | pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol { | ||
| 456 | const personality = rec.personality orelse return null; | ||
| 457 | return macho_file.getSymbol(personality); | ||
| 458 | } | ||
| 459 | |||
| 460 | pub fn getFde(rec: Record, macho_file: *MachO) ?Fde { | ||
| 461 | if (!rec.enc.isDwarf(macho_file)) return null; | ||
| 462 | return rec.getObject(macho_file).fdes.items[rec.fde]; | ||
| 463 | } | ||
| 464 | |||
| 465 | pub fn getFdePtr(rec: Record, macho_file: *MachO) ?*Fde { | ||
| 466 | if (!rec.enc.isDwarf(macho_file)) return null; | ||
| 467 | return &rec.getObject(macho_file).fdes.items[rec.fde]; | ||
| 468 | } | ||
| 469 | |||
| 470 | pub fn getAtomAddress(rec: Record, macho_file: *MachO) u64 { | ||
| 471 | const atom = rec.getAtom(macho_file); | ||
| 472 | return atom.value + rec.atom_offset; | ||
| 473 | } | ||
| 474 | |||
| 475 | pub fn getLsdaAddress(rec: Record, macho_file: *MachO) u64 { | ||
| 476 | const lsda = rec.getLsdaAtom(macho_file) orelse return 0; | ||
| 477 | return lsda.value + rec.lsda_offset; | ||
| 478 | } | ||
| 479 | |||
| 480 | pub fn format( | ||
| 481 | rec: Record, | ||
| 482 | comptime unused_fmt_string: []const u8, | ||
| 483 | options: std.fmt.FormatOptions, | ||
| 484 | writer: anytype, | ||
| 485 | ) !void { | ||
| 486 | _ = rec; | ||
| 487 | _ = unused_fmt_string; | ||
| 488 | _ = options; | ||
| 489 | _ = writer; | ||
| 490 | @compileError("do not format UnwindInfo.Records directly"); | ||
| 491 | } | ||
| 492 | |||
| 493 | pub fn fmt(rec: Record, macho_file: *MachO) std.fmt.Formatter(format2) { | ||
| 494 | return .{ .data = .{ | ||
| 495 | .rec = rec, | ||
| 496 | .macho_file = macho_file, | ||
| 497 | } }; | ||
| 498 | } | ||
| 499 | |||
| 500 | const FormatContext = struct { | ||
| 501 | rec: Record, | ||
| 502 | macho_file: *MachO, | ||
| 503 | }; | ||
| 504 | |||
| 505 | fn format2( | ||
| 506 | ctx: FormatContext, | ||
| 507 | comptime unused_fmt_string: []const u8, | ||
| 508 | options: std.fmt.FormatOptions, | ||
| 509 | writer: anytype, | ||
| 510 | ) !void { | ||
| 511 | _ = unused_fmt_string; | ||
| 512 | _ = options; | ||
| 513 | const rec = ctx.rec; | ||
| 514 | const macho_file = ctx.macho_file; | ||
| 515 | try writer.print("{x} : len({x})", .{ | ||
| 516 | rec.enc.enc, rec.length, | ||
| 517 | }); | ||
| 518 | if (rec.enc.isDwarf(macho_file)) try writer.print(" : fde({d})", .{rec.fde}); | ||
| 519 | try writer.print(" : {s}", .{rec.getAtom(macho_file).getName(macho_file)}); | ||
| 520 | if (!rec.alive) try writer.writeAll(" : [*]"); | ||
| 521 | } | ||
| 522 | |||
| 523 | pub const Index = u32; | ||
| 524 | }; | ||
| 525 | |||
| 526 | const max_personalities = 3; | ||
| 527 | const max_common_encodings = 127; | ||
| 528 | const max_compact_encodings = 256; | ||
| 529 | |||
| 530 | const second_level_page_bytes = 0x1000; | ||
| 531 | const second_level_page_words = second_level_page_bytes / @sizeOf(u32); | ||
| 532 | |||
| 533 | const max_regular_second_level_entries = | ||
| 534 | (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) / | ||
| 535 | @sizeOf(macho.unwind_info_regular_second_level_entry); | ||
| 536 | |||
| 537 | const max_compressed_second_level_entries = | ||
| 538 | (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) / | ||
| 539 | @sizeOf(u32); | ||
| 540 | |||
| 541 | const compressed_entry_func_offset_mask = ~@as(u24, 0); | ||
| 542 | |||
| 543 | const Page = struct { | ||
| 544 | kind: enum { regular, compressed }, | ||
| 545 | start: u32, | ||
| 546 | count: u16, | ||
| 547 | page_encodings: [max_compact_encodings]Encoding = undefined, | ||
| 548 | page_encodings_count: u9 = 0, | ||
| 549 | |||
| 550 | fn appendPageEncoding(page: *Page, enc: Encoding) void { | ||
| 551 | assert(page.page_encodings_count <= max_compact_encodings); | ||
| 552 | page.page_encodings[page.page_encodings_count] = enc; | ||
| 553 | page.page_encodings_count += 1; | ||
| 554 | } | ||
| 555 | |||
| 556 | fn getPageEncoding(page: Page, enc: Encoding) ?u8 { | ||
| 557 | comptime var index: u9 = 0; | ||
| 558 | inline while (index < max_compact_encodings) : (index += 1) { | ||
| 559 | if (index >= page.page_encodings_count) return null; | ||
| 560 | if (page.page_encodings[index].eql(enc)) { | ||
| 561 | return @as(u8, @intCast(index)); | ||
| 562 | } | ||
| 563 | } | ||
| 564 | return null; | ||
| 565 | } | ||
| 566 | |||
| 567 | fn format( | ||
| 568 | page: *const Page, | ||
| 569 | comptime unused_format_string: []const u8, | ||
| 570 | options: std.fmt.FormatOptions, | ||
| 571 | writer: anytype, | ||
| 572 | ) !void { | ||
| 573 | _ = page; | ||
| 574 | _ = unused_format_string; | ||
| 575 | _ = options; | ||
| 576 | _ = writer; | ||
| 577 | @compileError("do not format Page directly; use page.fmt()"); | ||
| 578 | } | ||
| 579 | |||
| 580 | const FormatPageContext = struct { | ||
| 581 | page: Page, | ||
| 582 | info: UnwindInfo, | ||
| 583 | }; | ||
| 584 | |||
| 585 | fn format2( | ||
| 586 | ctx: FormatPageContext, | ||
| 587 | comptime unused_format_string: []const u8, | ||
| 588 | options: std.fmt.FormatOptions, | ||
| 589 | writer: anytype, | ||
| 590 | ) @TypeOf(writer).Error!void { | ||
| 591 | _ = options; | ||
| 592 | _ = unused_format_string; | ||
| 593 | try writer.writeAll("Page:\n"); | ||
| 594 | try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)}); | ||
| 595 | try writer.print(" entries: {d} - {d}\n", .{ | ||
| 596 | ctx.page.start, | ||
| 597 | ctx.page.start + ctx.page.count, | ||
| 598 | }); | ||
| 599 | try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count}); | ||
| 600 | for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |enc, i| { | ||
| 601 | try writer.print(" {d}: {}\n", .{ ctx.info.common_encodings_count + i, enc }); | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | fn fmt(page: Page, info: UnwindInfo) std.fmt.Formatter(format2) { | ||
| 606 | return .{ .data = .{ | ||
| 607 | .page = page, | ||
| 608 | .info = info, | ||
| 609 | } }; | ||
| 610 | } | ||
| 611 | |||
| 612 | fn write(page: Page, info: UnwindInfo, macho_file: *MachO, writer: anytype) !void { | ||
| 613 | const seg = macho_file.getTextSegment(); | ||
| 614 | |||
| 615 | switch (page.kind) { | ||
| 616 | .regular => { | ||
| 617 | try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{ | ||
| 618 | .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header), | ||
| 619 | .entryCount = page.count, | ||
| 620 | }); | ||
| 621 | |||
| 622 | for (info.records.items[page.start..][0..page.count]) |index| { | ||
| 623 | const rec = macho_file.getUnwindRecord(index); | ||
| 624 | try writer.writeStruct(macho.unwind_info_regular_second_level_entry{ | ||
| 625 | .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), | ||
| 626 | .encoding = rec.enc.enc, | ||
| 627 | }); | ||
| 628 | } | ||
| 629 | }, | ||
| 630 | .compressed => { | ||
| 631 | const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) + | ||
| 632 | @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32); | ||
| 633 | try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{ | ||
| 634 | .entryPageOffset = entry_offset, | ||
| 635 | .entryCount = page.count, | ||
| 636 | .encodingsPageOffset = @sizeOf(macho.unwind_info_compressed_second_level_page_header), | ||
| 637 | .encodingsCount = page.page_encodings_count, | ||
| 638 | }); | ||
| 639 | |||
| 640 | for (page.page_encodings[0..page.page_encodings_count]) |enc| { | ||
| 641 | try writer.writeInt(u32, enc.enc, .little); | ||
| 642 | } | ||
| 643 | |||
| 644 | assert(page.count > 0); | ||
| 645 | const first_rec = macho_file.getUnwindRecord(info.records.items[page.start]); | ||
| 646 | for (info.records.items[page.start..][0..page.count]) |index| { | ||
| 647 | const rec = macho_file.getUnwindRecord(index); | ||
| 648 | const enc_index = blk: { | ||
| 649 | if (info.getCommonEncoding(rec.enc)) |id| break :blk id; | ||
| 650 | const ncommon = info.common_encodings_count; | ||
| 651 | break :blk ncommon + page.getPageEncoding(rec.enc).?; | ||
| 652 | }; | ||
| 653 | const compressed = macho.UnwindInfoCompressedEntry{ | ||
| 654 | .funcOffset = @as(u24, @intCast(rec.getAtomAddress(macho_file) - first_rec.getAtomAddress(macho_file))), | ||
| 655 | .encodingIndex = @as(u8, @intCast(enc_index)), | ||
| 656 | }; | ||
| 657 | try writer.writeStruct(compressed); | ||
| 658 | } | ||
| 659 | }, | ||
| 660 | } | ||
| 661 | } | ||
| 662 | }; | ||
| 814 | 663 | ||
| 815 | const std = @import("std"); | 664 | const std = @import("std"); |
| 816 | const assert = std.debug.assert; | 665 | const assert = std.debug.assert; |
| 817 | const eh_frame = @import("eh_frame.zig"); | 666 | const eh_frame = @import("eh_frame.zig"); |
| 818 | const fs = std.fs; | 667 | const fs = std.fs; |
| 819 | const leb = std.leb; | 668 | const leb = std.leb; |
| 820 | const log = std.log.scoped(.unwind_info); | 669 | const log = std.log.scoped(.link); |
| 821 | const macho = std.macho; | 670 | const macho = std.macho; |
| 822 | const math = std.math; | 671 | const math = std.math; |
| 823 | const mem = std.mem; | 672 | const mem = std.mem; |
| 824 | const trace = @import("../../tracy.zig").trace; | 673 | const trace = @import("../tracy.zig").trace; |
| 825 | 674 | ||
| 826 | const Allocator = mem.Allocator; | 675 | const Allocator = mem.Allocator; |
| 827 | const Atom = @import("Atom.zig"); | 676 | const Atom = @import("Atom.zig"); |
| 828 | const EhFrameRecord = eh_frame.EhFrameRecord; | 677 | const Fde = eh_frame.Fde; |
| 678 | const File = @import("file.zig").File; | ||
| 829 | const MachO = @import("../MachO.zig"); | 679 | const MachO = @import("../MachO.zig"); |
| 830 | const Object = @import("Object.zig"); | 680 | const Object = @import("Object.zig"); |
| 831 | const SymbolWithLoc = MachO.SymbolWithLoc; | 681 | const Symbol = @import("Symbol.zig"); |
| 682 | const UnwindInfo = @This(); |
src/link/MachO/dead_strip.zig+140-431| ... | @@ -1,495 +1,204 @@ | ... | @@ -1,495 +1,204 @@ |
| 1 | //! An algorithm for dead stripping of unreferenced Atoms. | ||
| 2 | |||
| 3 | pub fn gcAtoms(macho_file: *MachO) !void { | 1 | pub fn gcAtoms(macho_file: *MachO) !void { |
| 4 | const comp = macho_file.base.comp; | 2 | const gpa = macho_file.base.allocator; |
| 5 | const gpa = comp.gpa; | ||
| 6 | |||
| 7 | var arena = std.heap.ArenaAllocator.init(gpa); | ||
| 8 | defer arena.deinit(); | ||
| 9 | |||
| 10 | var roots = AtomTable.init(arena.allocator()); | ||
| 11 | try roots.ensureUnusedCapacity(@as(u32, @intCast(macho_file.globals.items.len))); | ||
| 12 | 3 | ||
| 13 | var alive = AtomTable.init(arena.allocator()); | 4 | var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1); |
| 14 | try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len))); | 5 | defer objects.deinit(); |
| 6 | for (macho_file.objects.items) |index| objects.appendAssumeCapacity(index); | ||
| 7 | if (macho_file.internal_object_index) |index| objects.appendAssumeCapacity(index); | ||
| 15 | 8 | ||
| 16 | try collectRoots(macho_file, &roots); | 9 | var roots = std.ArrayList(*Atom).init(gpa); |
| 17 | mark(macho_file, roots, &alive); | 10 | defer roots.deinit(); |
| 18 | prune(macho_file, alive); | ||
| 19 | } | ||
| 20 | 11 | ||
| 21 | fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void { | 12 | try collectRoots(&roots, objects.items, macho_file); |
| 22 | const sym = macho_file.getSymbol(sym_loc); | 13 | mark(roots.items, objects.items, macho_file); |
| 23 | assert(!sym.undf()); | 14 | prune(objects.items, macho_file); |
| 24 | const object = &macho_file.objects.items[file]; | ||
| 25 | const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error | ||
| 26 | log.debug("root(ATOM({d}, %{d}, {d}))", .{ | ||
| 27 | atom_index, | ||
| 28 | macho_file.getAtom(atom_index).sym_index, | ||
| 29 | file, | ||
| 30 | }); | ||
| 31 | _ = try roots.getOrPut(atom_index); | ||
| 32 | } | 15 | } |
| 33 | 16 | ||
| 34 | fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void { | 17 | fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void { |
| 35 | log.debug("collecting roots", .{}); | 18 | for (objects) |index| { |
| 36 | 19 | const object = macho_file.getFile(index).?; | |
| 37 | const comp = macho_file.base.comp; | 20 | for (object.getSymbols()) |sym_index| { |
| 38 | 21 | const sym = macho_file.getSymbol(sym_index); | |
| 39 | switch (comp.config.output_mode) { | 22 | const file = sym.getFile(macho_file) orelse continue; |
| 40 | .Exe => { | 23 | if (file.getIndex() != index) continue; |
| 41 | // Add entrypoint as GC root | 24 | if (sym.flags.no_dead_strip or (macho_file.options.dylib and sym.visibility == .global)) |
| 42 | if (macho_file.getEntryPoint()) |global| { | 25 | try markSymbol(sym, roots, macho_file); |
| 43 | if (global.getFile()) |file| { | 26 | } |
| 44 | try addRoot(macho_file, roots, file, global); | ||
| 45 | } else { | ||
| 46 | assert(macho_file.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib. | ||
| 47 | } | ||
| 48 | } | ||
| 49 | }, | ||
| 50 | else => |other| { | ||
| 51 | assert(other == .Lib); | ||
| 52 | // Add exports as GC roots | ||
| 53 | for (macho_file.globals.items) |global| { | ||
| 54 | const sym = macho_file.getSymbol(global); | ||
| 55 | if (sym.undf()) continue; | ||
| 56 | if (sym.n_desc == MachO.N_BOUNDARY) continue; | ||
| 57 | 27 | ||
| 58 | if (global.getFile()) |file| { | 28 | for (object.getAtoms()) |atom_index| { |
| 59 | try addRoot(macho_file, roots, file, global); | 29 | const atom = macho_file.getAtom(atom_index).?; |
| 60 | } | 30 | const isec = atom.getInputSection(macho_file); |
| 31 | switch (isec.type()) { | ||
| 32 | macho.S_MOD_INIT_FUNC_POINTERS, | ||
| 33 | macho.S_MOD_TERM_FUNC_POINTERS, | ||
| 34 | => if (markAtom(atom)) try roots.append(atom), | ||
| 35 | |||
| 36 | else => if (isec.isDontDeadStrip() and markAtom(atom)) { | ||
| 37 | try roots.append(atom); | ||
| 38 | }, | ||
| 61 | } | 39 | } |
| 62 | }, | 40 | } |
| 63 | } | ||
| 64 | |||
| 65 | // Add all symbols force-defined by the user. | ||
| 66 | for (comp.force_undefined_symbols.keys()) |sym_name| { | ||
| 67 | const global_index = macho_file.resolver.get(sym_name).?; | ||
| 68 | const global = macho_file.globals.items[global_index]; | ||
| 69 | const sym = macho_file.getSymbol(global); | ||
| 70 | assert(!sym.undf()); | ||
| 71 | try addRoot(macho_file, roots, global.getFile().?, global); | ||
| 72 | } | 41 | } |
| 73 | 42 | ||
| 74 | for (macho_file.objects.items) |object| { | 43 | for (macho_file.objects.items) |index| { |
| 75 | const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; | 44 | for (macho_file.getFile(index).?.object.unwind_records.items) |cu_index| { |
| 76 | 45 | const cu = macho_file.getUnwindRecord(cu_index); | |
| 77 | for (object.atoms.items) |atom_index| { | 46 | if (!cu.alive) continue; |
| 78 | const is_gc_root = blk: { | 47 | if (cu.getFde(macho_file)) |fde| { |
| 79 | // Modelled after ld64 which treats each object file compiled without MH_SUBSECTIONS_VIA_SYMBOLS | 48 | if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file); |
| 80 | // as a root. | 49 | } else if (cu.getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file); |
| 81 | if (!has_subsections) break :blk true; | ||
| 82 | |||
| 83 | const atom = macho_file.getAtom(atom_index); | ||
| 84 | const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| | ||
| 85 | source_sym.n_sect - 1 | ||
| 86 | else sect_id: { | ||
| 87 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | ||
| 88 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | ||
| 89 | break :sect_id sect_id; | ||
| 90 | }; | ||
| 91 | const source_sect = object.getSourceSection(sect_id); | ||
| 92 | if (source_sect.isDontDeadStrip()) break :blk true; | ||
| 93 | switch (source_sect.type()) { | ||
| 94 | macho.S_MOD_INIT_FUNC_POINTERS, | ||
| 95 | macho.S_MOD_TERM_FUNC_POINTERS, | ||
| 96 | => break :blk true, | ||
| 97 | else => break :blk false, | ||
| 98 | } | ||
| 99 | }; | ||
| 100 | |||
| 101 | if (is_gc_root) { | ||
| 102 | _ = try roots.getOrPut(atom_index); | ||
| 103 | |||
| 104 | log.debug("root(ATOM({d}, %{d}, {?d}))", .{ | ||
| 105 | atom_index, | ||
| 106 | macho_file.getAtom(atom_index).sym_index, | ||
| 107 | macho_file.getAtom(atom_index).getFile(), | ||
| 108 | }); | ||
| 109 | } | ||
| 110 | } | 50 | } |
| 111 | } | 51 | } |
| 112 | } | ||
| 113 | |||
| 114 | fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void { | ||
| 115 | if (alive.contains(atom_index)) return; | ||
| 116 | |||
| 117 | const atom = macho_file.getAtom(atom_index); | ||
| 118 | const sym_loc = atom.getSymbolWithLoc(); | ||
| 119 | 52 | ||
| 120 | log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() }); | 53 | for (macho_file.undefined_symbols.items) |sym_index| { |
| 121 | 54 | const sym = macho_file.getSymbol(sym_index); | |
| 122 | alive.putAssumeCapacityNoClobber(atom_index, {}); | 55 | try markSymbol(sym, roots, macho_file); |
| 123 | 56 | } | |
| 124 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 125 | const cpu_arch = target.cpu.arch; | ||
| 126 | |||
| 127 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 128 | const header = macho_file.sections.items(.header)[sym.n_sect - 1]; | ||
| 129 | if (header.isZerofill()) return; | ||
| 130 | |||
| 131 | const code = Atom.getAtomCode(macho_file, atom_index); | ||
| 132 | const relocs = Atom.getAtomRelocs(macho_file, atom_index); | ||
| 133 | const ctx = Atom.getRelocContext(macho_file, atom_index); | ||
| 134 | |||
| 135 | for (relocs) |rel| { | ||
| 136 | const reloc_target = switch (cpu_arch) { | ||
| 137 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | ||
| 138 | .ARM64_RELOC_ADDEND => continue, | ||
| 139 | else => Atom.parseRelocTarget(macho_file, .{ | ||
| 140 | .object_id = atom.getFile().?, | ||
| 141 | .rel = rel, | ||
| 142 | .code = code, | ||
| 143 | .base_offset = ctx.base_offset, | ||
| 144 | .base_addr = ctx.base_addr, | ||
| 145 | }), | ||
| 146 | }, | ||
| 147 | .x86_64 => Atom.parseRelocTarget(macho_file, .{ | ||
| 148 | .object_id = atom.getFile().?, | ||
| 149 | .rel = rel, | ||
| 150 | .code = code, | ||
| 151 | .base_offset = ctx.base_offset, | ||
| 152 | .base_addr = ctx.base_addr, | ||
| 153 | }), | ||
| 154 | else => unreachable, | ||
| 155 | }; | ||
| 156 | const target_sym = macho_file.getSymbol(reloc_target); | ||
| 157 | |||
| 158 | if (target_sym.undf()) continue; | ||
| 159 | if (reloc_target.getFile() == null) { | ||
| 160 | const target_sym_name = macho_file.getSymbolName(reloc_target); | ||
| 161 | if (mem.eql(u8, "__mh_execute_header", target_sym_name)) continue; | ||
| 162 | if (mem.eql(u8, "___dso_handle", target_sym_name)) continue; | ||
| 163 | 57 | ||
| 164 | unreachable; // referenced symbol not found | 58 | for (&[_]?Symbol.Index{ |
| 59 | macho_file.entry_index, | ||
| 60 | macho_file.dyld_stub_binder_index, | ||
| 61 | macho_file.objc_msg_send_index, | ||
| 62 | }) |index| { | ||
| 63 | if (index) |idx| { | ||
| 64 | const sym = macho_file.getSymbol(idx); | ||
| 65 | try markSymbol(sym, roots, macho_file); | ||
| 165 | } | 66 | } |
| 166 | |||
| 167 | const object = macho_file.objects.items[reloc_target.getFile().?]; | ||
| 168 | const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index).?; | ||
| 169 | log.debug(" following ATOM({d}, %{d}, {?d})", .{ | ||
| 170 | target_atom_index, | ||
| 171 | macho_file.getAtom(target_atom_index).sym_index, | ||
| 172 | macho_file.getAtom(target_atom_index).getFile(), | ||
| 173 | }); | ||
| 174 | |||
| 175 | markLive(macho_file, target_atom_index, alive); | ||
| 176 | } | 67 | } |
| 177 | } | 68 | } |
| 178 | 69 | ||
| 179 | fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool { | 70 | fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void { |
| 180 | const atom = macho_file.getAtom(atom_index); | 71 | const atom = sym.getAtom(macho_file) orelse return; |
| 181 | const sym_loc = atom.getSymbolWithLoc(); | 72 | if (markAtom(atom)) try roots.append(atom); |
| 182 | 73 | } | |
| 183 | log.debug("refersLive(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() }); | ||
| 184 | |||
| 185 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 186 | const cpu_arch = target.cpu.arch; | ||
| 187 | |||
| 188 | const sym = macho_file.getSymbol(sym_loc); | ||
| 189 | const header = macho_file.sections.items(.header)[sym.n_sect - 1]; | ||
| 190 | assert(!header.isZerofill()); | ||
| 191 | |||
| 192 | const code = Atom.getAtomCode(macho_file, atom_index); | ||
| 193 | const relocs = Atom.getAtomRelocs(macho_file, atom_index); | ||
| 194 | const ctx = Atom.getRelocContext(macho_file, atom_index); | ||
| 195 | |||
| 196 | for (relocs) |rel| { | ||
| 197 | const reloc_target = switch (cpu_arch) { | ||
| 198 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | ||
| 199 | .ARM64_RELOC_ADDEND => continue, | ||
| 200 | else => Atom.parseRelocTarget(macho_file, .{ | ||
| 201 | .object_id = atom.getFile().?, | ||
| 202 | .rel = rel, | ||
| 203 | .code = code, | ||
| 204 | .base_offset = ctx.base_offset, | ||
| 205 | .base_addr = ctx.base_addr, | ||
| 206 | }), | ||
| 207 | }, | ||
| 208 | .x86_64 => Atom.parseRelocTarget(macho_file, .{ | ||
| 209 | .object_id = atom.getFile().?, | ||
| 210 | .rel = rel, | ||
| 211 | .code = code, | ||
| 212 | .base_offset = ctx.base_offset, | ||
| 213 | .base_addr = ctx.base_addr, | ||
| 214 | }), | ||
| 215 | else => unreachable, | ||
| 216 | }; | ||
| 217 | |||
| 218 | const object = macho_file.objects.items[reloc_target.getFile().?]; | ||
| 219 | const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index) orelse { | ||
| 220 | log.debug("atom for symbol '{s}' not found; skipping...", .{macho_file.getSymbolName(reloc_target)}); | ||
| 221 | continue; | ||
| 222 | }; | ||
| 223 | if (alive.contains(target_atom_index)) { | ||
| 224 | log.debug(" refers live ATOM({d}, %{d}, {?d})", .{ | ||
| 225 | target_atom_index, | ||
| 226 | macho_file.getAtom(target_atom_index).sym_index, | ||
| 227 | macho_file.getAtom(target_atom_index).getFile(), | ||
| 228 | }); | ||
| 229 | return true; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | 74 | ||
| 233 | return false; | 75 | fn markAtom(atom: *Atom) bool { |
| 76 | const already_visited = atom.flags.visited; | ||
| 77 | atom.flags.visited = true; | ||
| 78 | return atom.flags.alive and !already_visited; | ||
| 234 | } | 79 | } |
| 235 | 80 | ||
| 236 | fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void { | 81 | fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void { |
| 237 | var it = roots.keyIterator(); | 82 | for (roots) |root| { |
| 238 | while (it.next()) |root| { | 83 | markLive(root, macho_file); |
| 239 | markLive(macho_file, root.*, alive); | ||
| 240 | } | 84 | } |
| 241 | 85 | ||
| 242 | var loop: bool = true; | 86 | var loop: bool = true; |
| 243 | while (loop) { | 87 | while (loop) { |
| 244 | loop = false; | 88 | loop = false; |
| 245 | 89 | ||
| 246 | for (macho_file.objects.items) |object| { | 90 | for (objects) |index| { |
| 247 | for (object.atoms.items) |atom_index| { | 91 | for (macho_file.getFile(index).?.getAtoms()) |atom_index| { |
| 248 | if (alive.contains(atom_index)) continue; | 92 | const atom = macho_file.getAtom(atom_index).?; |
| 249 | 93 | const isec = atom.getInputSection(macho_file); | |
| 250 | const atom = macho_file.getAtom(atom_index); | 94 | if (isec.isDontDeadStripIfReferencesLive() and !atom.flags.alive and refersLive(atom, macho_file)) { |
| 251 | const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| | 95 | markLive(atom, macho_file); |
| 252 | source_sym.n_sect - 1 | 96 | loop = true; |
| 253 | else blk: { | ||
| 254 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | ||
| 255 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | ||
| 256 | break :blk sect_id; | ||
| 257 | }; | ||
| 258 | const source_sect = object.getSourceSection(sect_id); | ||
| 259 | |||
| 260 | if (source_sect.isDontDeadStripIfReferencesLive()) { | ||
| 261 | if (refersLive(macho_file, atom_index, alive.*)) { | ||
| 262 | markLive(macho_file, atom_index, alive); | ||
| 263 | loop = true; | ||
| 264 | } | ||
| 265 | } | 97 | } |
| 266 | } | 98 | } |
| 267 | } | 99 | } |
| 268 | } | 100 | } |
| 269 | |||
| 270 | for (macho_file.objects.items, 0..) |_, object_id| { | ||
| 271 | // Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so, | ||
| 272 | // marking all references as live. | ||
| 273 | markUnwindRecords(macho_file, @as(u32, @intCast(object_id)), alive); | ||
| 274 | } | ||
| 275 | } | 101 | } |
| 276 | 102 | ||
| 277 | fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void { | 103 | fn markLive(atom: *Atom, macho_file: *MachO) void { |
| 278 | const object = &macho_file.objects.items[object_id]; | 104 | assert(atom.flags.visited); |
| 279 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 105 | atom.flags.alive = true; |
| 280 | const cpu_arch = target.cpu.arch; | 106 | track_live_log.debug("{}marking live atom({d},{s})", .{ |
| 281 | 107 | track_live_level, | |
| 282 | const unwind_records = object.getUnwindRecords(); | 108 | atom.atom_index, |
| 109 | atom.getName(macho_file), | ||
| 110 | }); | ||
| 283 | 111 | ||
| 284 | for (object.exec_atoms.items) |atom_index| { | 112 | if (build_options.enable_logging) |
| 285 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 113 | track_live_level.incr(); |
| 286 | 114 | ||
| 287 | if (!object.hasUnwindRecords()) { | 115 | for (atom.getRelocs(macho_file)) |rel| { |
| 288 | if (alive.contains(atom_index)) { | 116 | const target_atom = switch (rel.tag) { |
| 289 | // Mark references live and continue. | 117 | .local => rel.getTargetAtom(macho_file), |
| 290 | markEhFrameRecords(macho_file, object_id, atom_index, alive); | 118 | .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file), |
| 291 | } else { | 119 | }; |
| 292 | while (inner_syms_it.next()) |sym| { | 120 | if (target_atom) |ta| { |
| 293 | if (object.eh_frame_records_lookup.get(sym)) |fde_offset| { | 121 | if (markAtom(ta)) markLive(ta, macho_file); |
| 294 | // Mark dead and continue. | ||
| 295 | object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true; | ||
| 296 | } | ||
| 297 | } | ||
| 298 | } | ||
| 299 | continue; | ||
| 300 | } | 122 | } |
| 123 | } | ||
| 301 | 124 | ||
| 302 | while (inner_syms_it.next()) |sym| { | 125 | for (atom.getUnwindRecords(macho_file)) |cu_index| { |
| 303 | const record_id = object.unwind_records_lookup.get(sym) orelse continue; | 126 | const cu = macho_file.getUnwindRecord(cu_index); |
| 304 | if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do | 127 | const cu_atom = cu.getAtom(macho_file); |
| 305 | if (!alive.contains(atom_index)) { | 128 | if (markAtom(cu_atom)) markLive(cu_atom, macho_file); |
| 306 | // Mark the record dead and continue. | ||
| 307 | object.unwind_relocs_lookup[record_id].dead = true; | ||
| 308 | if (object.eh_frame_records_lookup.get(sym)) |fde_offset| { | ||
| 309 | object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true; | ||
| 310 | } | ||
| 311 | continue; | ||
| 312 | } | ||
| 313 | 129 | ||
| 314 | const record = unwind_records[record_id]; | 130 | if (cu.getLsdaAtom(macho_file)) |lsda| { |
| 315 | if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { | 131 | if (markAtom(lsda)) markLive(lsda, macho_file); |
| 316 | markEhFrameRecords(macho_file, object_id, atom_index, alive); | 132 | } |
| 317 | } else { | 133 | if (cu.getFde(macho_file)) |fde| { |
| 318 | if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| { | 134 | const fde_atom = fde.getAtom(macho_file); |
| 319 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 135 | if (markAtom(fde_atom)) markLive(fde_atom, macho_file); |
| 320 | .object_id = object_id, | ||
| 321 | .rel = rel, | ||
| 322 | .code = mem.asBytes(&record), | ||
| 323 | .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))), | ||
| 324 | }); | ||
| 325 | const target_sym = macho_file.getSymbol(reloc_target); | ||
| 326 | if (!target_sym.undf()) { | ||
| 327 | const target_object = macho_file.objects.items[reloc_target.getFile().?]; | ||
| 328 | const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?; | ||
| 329 | markLive(macho_file, target_atom_index, alive); | ||
| 330 | } | ||
| 331 | } | ||
| 332 | 136 | ||
| 333 | if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| { | 137 | if (fde.getLsdaAtom(macho_file)) |lsda| { |
| 334 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 138 | if (markAtom(lsda)) markLive(lsda, macho_file); |
| 335 | .object_id = object_id, | ||
| 336 | .rel = rel, | ||
| 337 | .code = mem.asBytes(&record), | ||
| 338 | .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))), | ||
| 339 | }); | ||
| 340 | const target_object = macho_file.objects.items[reloc_target.getFile().?]; | ||
| 341 | const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?; | ||
| 342 | markLive(macho_file, target_atom_index, alive); | ||
| 343 | } | ||
| 344 | } | 139 | } |
| 345 | } | 140 | } |
| 346 | } | 141 | } |
| 347 | } | 142 | } |
| 348 | 143 | ||
| 349 | fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void { | 144 | fn refersLive(atom: *Atom, macho_file: *MachO) bool { |
| 350 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 145 | for (atom.getRelocs(macho_file)) |rel| { |
| 351 | const cpu_arch = target.cpu.arch; | 146 | const target_atom = switch (rel.tag) { |
| 352 | const object = &macho_file.objects.items[object_id]; | 147 | .local => rel.getTargetAtom(macho_file), |
| 353 | var it = object.getEhFrameRecordsIterator(); | 148 | .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file), |
| 354 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 149 | }; |
| 355 | 150 | if (target_atom) |ta| { | |
| 356 | while (inner_syms_it.next()) |sym| { | 151 | if (ta.flags.alive) return true; |
| 357 | const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias | ||
| 358 | it.seekTo(fde_offset); | ||
| 359 | const fde = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled | ||
| 360 | |||
| 361 | const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset); | ||
| 362 | const cie_offset = fde_offset + 4 - cie_ptr; | ||
| 363 | it.seekTo(cie_offset); | ||
| 364 | const cie = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled | ||
| 365 | |||
| 366 | switch (cpu_arch) { | ||
| 367 | .aarch64 => { | ||
| 368 | // Mark FDE references which should include any referenced LSDA record | ||
| 369 | const relocs = eh_frame.getRelocs(macho_file, object_id, fde_offset); | ||
| 370 | for (relocs) |rel| { | ||
| 371 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | ||
| 372 | .object_id = object_id, | ||
| 373 | .rel = rel, | ||
| 374 | .code = fde.data, | ||
| 375 | .base_offset = @as(i32, @intCast(fde_offset)) + 4, | ||
| 376 | }); | ||
| 377 | const target_sym = macho_file.getSymbol(reloc_target); | ||
| 378 | if (!target_sym.undf()) blk: { | ||
| 379 | const target_object = macho_file.objects.items[reloc_target.getFile().?]; | ||
| 380 | const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index) orelse | ||
| 381 | break :blk; | ||
| 382 | markLive(macho_file, target_atom_index, alive); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | }, | ||
| 386 | .x86_64 => { | ||
| 387 | const sect = object.getSourceSection(object.eh_frame_sect_id.?); | ||
| 388 | const lsda_ptr = fde.getLsdaPointer(cie, .{ | ||
| 389 | .base_addr = sect.addr, | ||
| 390 | .base_offset = fde_offset, | ||
| 391 | }) catch continue; // We don't care about the error at this point since it was already handled | ||
| 392 | if (lsda_ptr) |lsda_address| { | ||
| 393 | // Mark LSDA record as live | ||
| 394 | const sym_index = object.getSymbolByAddress(lsda_address, null); | ||
| 395 | const target_atom_index = object.getAtomIndexForSymbol(sym_index).?; | ||
| 396 | markLive(macho_file, target_atom_index, alive); | ||
| 397 | } | ||
| 398 | }, | ||
| 399 | else => unreachable, | ||
| 400 | } | 152 | } |
| 153 | } | ||
| 154 | return false; | ||
| 155 | } | ||
| 401 | 156 | ||
| 402 | // Mark CIE references which should include any referenced personalities | 157 | fn prune(objects: []const File.Index, macho_file: *MachO) void { |
| 403 | // that are defined locally. | 158 | for (objects) |index| { |
| 404 | if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |reloc_target| { | 159 | for (macho_file.getFile(index).?.getAtoms()) |atom_index| { |
| 405 | const target_sym = macho_file.getSymbol(reloc_target); | 160 | const atom = macho_file.getAtom(atom_index).?; |
| 406 | if (!target_sym.undf()) { | 161 | if (atom.flags.alive and !atom.flags.visited) { |
| 407 | const target_object = macho_file.objects.items[reloc_target.getFile().?]; | 162 | atom.flags.alive = false; |
| 408 | const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?; | 163 | atom.markUnwindRecordsDead(macho_file); |
| 409 | markLive(macho_file, target_atom_index, alive); | ||
| 410 | } | 164 | } |
| 411 | } | 165 | } |
| 412 | } | 166 | } |
| 413 | } | 167 | } |
| 414 | 168 | ||
| 415 | fn prune(macho_file: *MachO, alive: AtomTable) void { | 169 | const Level = struct { |
| 416 | log.debug("pruning dead atoms", .{}); | 170 | value: usize = 0, |
| 417 | for (macho_file.objects.items) |*object| { | ||
| 418 | var i: usize = 0; | ||
| 419 | while (i < object.atoms.items.len) { | ||
| 420 | const atom_index = object.atoms.items[i]; | ||
| 421 | if (alive.contains(atom_index)) { | ||
| 422 | i += 1; | ||
| 423 | continue; | ||
| 424 | } | ||
| 425 | |||
| 426 | const atom = macho_file.getAtom(atom_index); | ||
| 427 | const sym_loc = atom.getSymbolWithLoc(); | ||
| 428 | |||
| 429 | log.debug("prune(ATOM({d}, %{d}, {?d}))", .{ | ||
| 430 | atom_index, | ||
| 431 | sym_loc.sym_index, | ||
| 432 | sym_loc.getFile(), | ||
| 433 | }); | ||
| 434 | log.debug(" {s} in {s}", .{ macho_file.getSymbolName(sym_loc), object.name }); | ||
| 435 | |||
| 436 | const sym = macho_file.getSymbolPtr(sym_loc); | ||
| 437 | const sect_id = sym.n_sect - 1; | ||
| 438 | var section = macho_file.sections.get(sect_id); | ||
| 439 | section.header.size -= atom.size; | ||
| 440 | |||
| 441 | if (atom.prev_index) |prev_index| { | ||
| 442 | const prev = macho_file.getAtomPtr(prev_index); | ||
| 443 | prev.next_index = atom.next_index; | ||
| 444 | } else { | ||
| 445 | if (atom.next_index) |next_index| { | ||
| 446 | section.first_atom_index = next_index; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | if (atom.next_index) |next_index| { | ||
| 450 | const next = macho_file.getAtomPtr(next_index); | ||
| 451 | next.prev_index = atom.prev_index; | ||
| 452 | } else { | ||
| 453 | if (atom.prev_index) |prev_index| { | ||
| 454 | section.last_atom_index = prev_index; | ||
| 455 | } else { | ||
| 456 | assert(section.header.size == 0); | ||
| 457 | section.first_atom_index = null; | ||
| 458 | section.last_atom_index = null; | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | macho_file.sections.set(sect_id, section); | ||
| 463 | _ = object.atoms.swapRemove(i); | ||
| 464 | |||
| 465 | sym.n_desc = MachO.N_DEAD; | ||
| 466 | 171 | ||
| 467 | var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 172 | fn incr(self: *@This()) void { |
| 468 | while (inner_sym_it.next()) |inner| { | 173 | self.value += 1; |
| 469 | const inner_sym = macho_file.getSymbolPtr(inner); | 174 | } |
| 470 | inner_sym.n_desc = MachO.N_DEAD; | ||
| 471 | } | ||
| 472 | 175 | ||
| 473 | if (Atom.getSectionAlias(macho_file, atom_index)) |alias| { | 176 | pub fn format( |
| 474 | const alias_sym = macho_file.getSymbolPtr(alias); | 177 | self: *const @This(), |
| 475 | alias_sym.n_desc = MachO.N_DEAD; | 178 | comptime unused_fmt_string: []const u8, |
| 476 | } | 179 | options: std.fmt.FormatOptions, |
| 477 | } | 180 | writer: anytype, |
| 181 | ) !void { | ||
| 182 | _ = unused_fmt_string; | ||
| 183 | _ = options; | ||
| 184 | try writer.writeByteNTimes(' ', self.value); | ||
| 478 | } | 185 | } |
| 479 | } | 186 | }; |
| 187 | |||
| 188 | var track_live_level: Level = .{}; | ||
| 480 | 189 | ||
| 481 | const std = @import("std"); | ||
| 482 | const assert = std.debug.assert; | 190 | const assert = std.debug.assert; |
| 483 | const eh_frame = @import("eh_frame.zig"); | 191 | const build_options = @import("build_options"); |
| 484 | const log = std.log.scoped(.dead_strip); | 192 | const log = std.log.scoped(.dead_strip); |
| 485 | const macho = std.macho; | 193 | const macho = std.macho; |
| 486 | const math = std.math; | 194 | const math = std.math; |
| 487 | const mem = std.mem; | 195 | const mem = std.mem; |
| 196 | const trace = @import("../tracy.zig").trace; | ||
| 197 | const track_live_log = std.log.scoped(.dead_strip_track_live); | ||
| 198 | const std = @import("std"); | ||
| 488 | 199 | ||
| 489 | const Allocator = mem.Allocator; | 200 | const Allocator = mem.Allocator; |
| 490 | const Atom = @import("Atom.zig"); | 201 | const Atom = @import("Atom.zig"); |
| 202 | const File = @import("file.zig").File; | ||
| 491 | const MachO = @import("../MachO.zig"); | 203 | const MachO = @import("../MachO.zig"); |
| 492 | const SymbolWithLoc = MachO.SymbolWithLoc; | 204 | const Symbol = @import("Symbol.zig"); |
| 493 | const UnwindInfo = @import("UnwindInfo.zig"); | ||
| 494 | |||
| 495 | const AtomTable = std.AutoHashMap(Atom.Index, void); |
src/link/MachO/dyld_info/Rebase.zig+12-12| ... | @@ -1,3 +1,14 @@ | ... | @@ -1,3 +1,14 @@ |
| 1 | const Rebase = @This(); | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const leb = std.leb; | ||
| 6 | const log = std.log.scoped(.dyld_info); | ||
| 7 | const macho = std.macho; | ||
| 8 | const testing = std.testing; | ||
| 9 | |||
| 10 | const Allocator = std.mem.Allocator; | ||
| 11 | |||
| 1 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 12 | entries: std.ArrayListUnmanaged(Entry) = .{}, |
| 2 | buffer: std.ArrayListUnmanaged(u8) = .{}, | 13 | buffer: std.ArrayListUnmanaged(u8) = .{}, |
| 3 | 14 | ||
| ... | @@ -168,7 +179,7 @@ fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void { | ... | @@ -168,7 +179,7 @@ fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void { |
| 168 | 179 | ||
| 169 | fn addAddr(addr: u64, writer: anytype) !void { | 180 | fn addAddr(addr: u64, writer: anytype) !void { |
| 170 | log.debug(">>> add: {x}", .{addr}); | 181 | log.debug(">>> add: {x}", .{addr}); |
| 171 | if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) { | 182 | if (std.mem.isAligned(addr, @sizeOf(u64))) { |
| 172 | const imm = @divExact(addr, @sizeOf(u64)); | 183 | const imm = @divExact(addr, @sizeOf(u64)); |
| 173 | if (imm <= 0xf) { | 184 | if (imm <= 0xf) { |
| 174 | try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm))); | 185 | try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm))); |
| ... | @@ -561,14 +572,3 @@ test "rebase - composite" { | ... | @@ -561,14 +572,3 @@ test "rebase - composite" { |
| 561 | macho.REBASE_OPCODE_DONE, | 572 | macho.REBASE_OPCODE_DONE, |
| 562 | }, rebase.buffer.items); | 573 | }, rebase.buffer.items); |
| 563 | } | 574 | } |
| 564 | |||
| 565 | const Rebase = @This(); | ||
| 566 | |||
| 567 | const std = @import("std"); | ||
| 568 | const assert = std.debug.assert; | ||
| 569 | const leb = std.leb; | ||
| 570 | const log = std.log.scoped(.dyld_info); | ||
| 571 | const macho = std.macho; | ||
| 572 | const testing = std.testing; | ||
| 573 | |||
| 574 | const Allocator = std.mem.Allocator; |
src/link/MachO/dyld_info/Trie.zig created+612| ... | @@ -0,0 +1,612 @@ | ||
| 1 | //! Represents export trie used in MachO executables and dynamic libraries. | ||
| 2 | //! The purpose of an export trie is to encode as compactly as possible all | ||
| 3 | //! export symbols for the loader `dyld`. | ||
| 4 | //! The export trie encodes offset and other information using ULEB128 | ||
| 5 | //! encoding, and is part of the __LINKEDIT segment. | ||
| 6 | //! | ||
| 7 | //! Description from loader.h: | ||
| 8 | //! | ||
| 9 | //! The symbols exported by a dylib are encoded in a trie. This is a compact | ||
| 10 | //! representation that factors out common prefixes. It also reduces LINKEDIT pages | ||
| 11 | //! in RAM because it encodes all information (name, address, flags) in one small, | ||
| 12 | //! contiguous range. The export area is a stream of nodes. The first node sequentially | ||
| 13 | //! is the start node for the trie. | ||
| 14 | //! | ||
| 15 | //! Nodes for a symbol start with a uleb128 that is the length of the exported symbol | ||
| 16 | //! information for the string so far. If there is no exported symbol, the node starts | ||
| 17 | //! with a zero byte. If there is exported info, it follows the length. | ||
| 18 | //! | ||
| 19 | //! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded | ||
| 20 | //! offset which is location of the content named by the symbol from the mach_header | ||
| 21 | //! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags | ||
| 22 | //! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string | ||
| 23 | //! is zero length, then the symbol is re-export from the specified dylib with the same name. | ||
| 24 | //! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two | ||
| 25 | //! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers. | ||
| 26 | //! The resolver is used by lazy pointers and must be called to get the actual address to use. | ||
| 27 | //! | ||
| 28 | //! After the optional exported symbol information is a byte of how many edges (0-255) that | ||
| 29 | //! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of | ||
| 30 | //! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to. | ||
| 31 | const Trie = @This(); | ||
| 32 | |||
| 33 | const std = @import("std"); | ||
| 34 | const mem = std.mem; | ||
| 35 | const leb = std.leb; | ||
| 36 | const log = std.log.scoped(.macho); | ||
| 37 | const macho = std.macho; | ||
| 38 | const testing = std.testing; | ||
| 39 | const assert = std.debug.assert; | ||
| 40 | const Allocator = mem.Allocator; | ||
| 41 | |||
| 42 | pub const Node = struct { | ||
| 43 | base: *Trie, | ||
| 44 | |||
| 45 | /// Terminal info associated with this node. | ||
| 46 | /// If this node is not a terminal node, info is null. | ||
| 47 | terminal_info: ?struct { | ||
| 48 | /// Export flags associated with this exported symbol. | ||
| 49 | export_flags: u64, | ||
| 50 | /// VM address offset wrt to the section this symbol is defined against. | ||
| 51 | vmaddr_offset: u64, | ||
| 52 | } = null, | ||
| 53 | |||
| 54 | /// Offset of this node in the trie output byte stream. | ||
| 55 | trie_offset: ?u64 = null, | ||
| 56 | |||
| 57 | /// List of all edges originating from this node. | ||
| 58 | edges: std.ArrayListUnmanaged(Edge) = .{}, | ||
| 59 | |||
| 60 | node_dirty: bool = true, | ||
| 61 | |||
| 62 | /// Edge connecting to nodes in the trie. | ||
| 63 | pub const Edge = struct { | ||
| 64 | from: *Node, | ||
| 65 | to: *Node, | ||
| 66 | label: []u8, | ||
| 67 | |||
| 68 | fn deinit(self: *Edge, allocator: Allocator) void { | ||
| 69 | self.to.deinit(allocator); | ||
| 70 | allocator.destroy(self.to); | ||
| 71 | allocator.free(self.label); | ||
| 72 | self.from = undefined; | ||
| 73 | self.to = undefined; | ||
| 74 | self.label = undefined; | ||
| 75 | } | ||
| 76 | }; | ||
| 77 | |||
| 78 | fn deinit(self: *Node, allocator: Allocator) void { | ||
| 79 | for (self.edges.items) |*edge| { | ||
| 80 | edge.deinit(allocator); | ||
| 81 | } | ||
| 82 | self.edges.deinit(allocator); | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Inserts a new node starting from `self`. | ||
| 86 | fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node { | ||
| 87 | // Check for match with edges from this node. | ||
| 88 | for (self.edges.items) |*edge| { | ||
| 89 | const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to; | ||
| 90 | if (match == 0) continue; | ||
| 91 | if (match == edge.label.len) return edge.to.put(allocator, label[match..]); | ||
| 92 | |||
| 93 | // Found a match, need to splice up nodes. | ||
| 94 | // From: A -> B | ||
| 95 | // To: A -> C -> B | ||
| 96 | const mid = try allocator.create(Node); | ||
| 97 | mid.* = .{ .base = self.base }; | ||
| 98 | const to_label = try allocator.dupe(u8, edge.label[match..]); | ||
| 99 | allocator.free(edge.label); | ||
| 100 | const to_node = edge.to; | ||
| 101 | edge.to = mid; | ||
| 102 | edge.label = try allocator.dupe(u8, label[0..match]); | ||
| 103 | self.base.node_count += 1; | ||
| 104 | |||
| 105 | try mid.edges.append(allocator, .{ | ||
| 106 | .from = mid, | ||
| 107 | .to = to_node, | ||
| 108 | .label = to_label, | ||
| 109 | }); | ||
| 110 | |||
| 111 | return if (match == label.len) mid else mid.put(allocator, label[match..]); | ||
| 112 | } | ||
| 113 | |||
| 114 | // Add a new node. | ||
| 115 | const node = try allocator.create(Node); | ||
| 116 | node.* = .{ .base = self.base }; | ||
| 117 | self.base.node_count += 1; | ||
| 118 | |||
| 119 | try self.edges.append(allocator, .{ | ||
| 120 | .from = self, | ||
| 121 | .to = node, | ||
| 122 | .label = try allocator.dupe(u8, label), | ||
| 123 | }); | ||
| 124 | |||
| 125 | return node; | ||
| 126 | } | ||
| 127 | |||
| 128 | /// Recursively parses the node from the input byte stream. | ||
| 129 | fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize { | ||
| 130 | self.node_dirty = true; | ||
| 131 | const trie_offset = try reader.context.getPos(); | ||
| 132 | self.trie_offset = trie_offset; | ||
| 133 | |||
| 134 | var nread: usize = 0; | ||
| 135 | |||
| 136 | const node_size = try leb.readULEB128(u64, reader); | ||
| 137 | if (node_size > 0) { | ||
| 138 | const export_flags = try leb.readULEB128(u64, reader); | ||
| 139 | // TODO Parse special flags. | ||
| 140 | assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and | ||
| 141 | export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0); | ||
| 142 | |||
| 143 | const vmaddr_offset = try leb.readULEB128(u64, reader); | ||
| 144 | |||
| 145 | self.terminal_info = .{ | ||
| 146 | .export_flags = export_flags, | ||
| 147 | .vmaddr_offset = vmaddr_offset, | ||
| 148 | }; | ||
| 149 | } | ||
| 150 | |||
| 151 | const nedges = try reader.readByte(); | ||
| 152 | self.base.node_count += nedges; | ||
| 153 | |||
| 154 | nread += (try reader.context.getPos()) - trie_offset; | ||
| 155 | |||
| 156 | var i: usize = 0; | ||
| 157 | while (i < nedges) : (i += 1) { | ||
| 158 | const edge_start_pos = try reader.context.getPos(); | ||
| 159 | |||
| 160 | const label = blk: { | ||
| 161 | var label_buf = std.ArrayList(u8).init(allocator); | ||
| 162 | while (true) { | ||
| 163 | const next = try reader.readByte(); | ||
| 164 | if (next == @as(u8, 0)) | ||
| 165 | break; | ||
| 166 | try label_buf.append(next); | ||
| 167 | } | ||
| 168 | break :blk try label_buf.toOwnedSlice(); | ||
| 169 | }; | ||
| 170 | |||
| 171 | const seek_to = try leb.readULEB128(u64, reader); | ||
| 172 | const return_pos = try reader.context.getPos(); | ||
| 173 | |||
| 174 | nread += return_pos - edge_start_pos; | ||
| 175 | try reader.context.seekTo(seek_to); | ||
| 176 | |||
| 177 | const node = try allocator.create(Node); | ||
| 178 | node.* = .{ .base = self.base }; | ||
| 179 | |||
| 180 | nread += try node.read(allocator, reader); | ||
| 181 | try self.edges.append(allocator, .{ | ||
| 182 | .from = self, | ||
| 183 | .to = node, | ||
| 184 | .label = label, | ||
| 185 | }); | ||
| 186 | try reader.context.seekTo(return_pos); | ||
| 187 | } | ||
| 188 | |||
| 189 | return nread; | ||
| 190 | } | ||
| 191 | |||
| 192 | /// Writes this node to a byte stream. | ||
| 193 | /// The children of this node *are* not written to the byte stream | ||
| 194 | /// recursively. To write all nodes to a byte stream in sequence, | ||
| 195 | /// iterate over `Trie.ordered_nodes` and call this method on each node. | ||
| 196 | /// This is one of the requirements of the MachO. | ||
| 197 | /// Panics if `finalize` was not called before calling this method. | ||
| 198 | fn write(self: Node, writer: anytype) !void { | ||
| 199 | assert(!self.node_dirty); | ||
| 200 | if (self.terminal_info) |info| { | ||
| 201 | // Terminal node info: encode export flags and vmaddr offset of this symbol. | ||
| 202 | var info_buf: [@sizeOf(u64) * 2]u8 = undefined; | ||
| 203 | var info_stream = std.io.fixedBufferStream(&info_buf); | ||
| 204 | // TODO Implement for special flags. | ||
| 205 | assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and | ||
| 206 | info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0); | ||
| 207 | try leb.writeULEB128(info_stream.writer(), info.export_flags); | ||
| 208 | try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset); | ||
| 209 | |||
| 210 | // Encode the size of the terminal node info. | ||
| 211 | var size_buf: [@sizeOf(u64)]u8 = undefined; | ||
| 212 | var size_stream = std.io.fixedBufferStream(&size_buf); | ||
| 213 | try leb.writeULEB128(size_stream.writer(), info_stream.pos); | ||
| 214 | |||
| 215 | // Now, write them to the output stream. | ||
| 216 | try writer.writeAll(size_buf[0..size_stream.pos]); | ||
| 217 | try writer.writeAll(info_buf[0..info_stream.pos]); | ||
| 218 | } else { | ||
| 219 | // Non-terminal node is delimited by 0 byte. | ||
| 220 | try writer.writeByte(0); | ||
| 221 | } | ||
| 222 | // Write number of edges (max legal number of edges is 256). | ||
| 223 | try writer.writeByte(@as(u8, @intCast(self.edges.items.len))); | ||
| 224 | |||
| 225 | for (self.edges.items) |edge| { | ||
| 226 | // Write edge label and offset to next node in trie. | ||
| 227 | try writer.writeAll(edge.label); | ||
| 228 | try writer.writeByte(0); | ||
| 229 | try leb.writeULEB128(writer, edge.to.trie_offset.?); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | const FinalizeResult = struct { | ||
| 234 | /// Current size of this node in bytes. | ||
| 235 | node_size: u64, | ||
| 236 | |||
| 237 | /// True if the trie offset of this node in the output byte stream | ||
| 238 | /// would need updating; false otherwise. | ||
| 239 | updated: bool, | ||
| 240 | }; | ||
| 241 | |||
| 242 | /// Updates offset of this node in the output byte stream. | ||
| 243 | fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult { | ||
| 244 | var stream = std.io.countingWriter(std.io.null_writer); | ||
| 245 | const writer = stream.writer(); | ||
| 246 | |||
| 247 | var node_size: u64 = 0; | ||
| 248 | if (self.terminal_info) |info| { | ||
| 249 | try leb.writeULEB128(writer, info.export_flags); | ||
| 250 | try leb.writeULEB128(writer, info.vmaddr_offset); | ||
| 251 | try leb.writeULEB128(writer, stream.bytes_written); | ||
| 252 | } else { | ||
| 253 | node_size += 1; // 0x0 for non-terminal nodes | ||
| 254 | } | ||
| 255 | node_size += 1; // 1 byte for edge count | ||
| 256 | |||
| 257 | for (self.edges.items) |edge| { | ||
| 258 | const next_node_offset = edge.to.trie_offset orelse 0; | ||
| 259 | node_size += edge.label.len + 1; | ||
| 260 | try leb.writeULEB128(writer, next_node_offset); | ||
| 261 | } | ||
| 262 | |||
| 263 | const trie_offset = self.trie_offset orelse 0; | ||
| 264 | const updated = offset_in_trie != trie_offset; | ||
| 265 | self.trie_offset = offset_in_trie; | ||
| 266 | self.node_dirty = false; | ||
| 267 | node_size += stream.bytes_written; | ||
| 268 | |||
| 269 | return FinalizeResult{ .node_size = node_size, .updated = updated }; | ||
| 270 | } | ||
| 271 | }; | ||
| 272 | |||
| 273 | /// The root node of the trie. | ||
| 274 | root: ?*Node = null, | ||
| 275 | |||
| 276 | /// If you want to access nodes ordered in DFS fashion, | ||
| 277 | /// you should call `finalize` first since the nodes | ||
| 278 | /// in this container are not guaranteed to not be stale | ||
| 279 | /// if more insertions took place after the last `finalize` | ||
| 280 | /// call. | ||
| 281 | ordered_nodes: std.ArrayListUnmanaged(*Node) = .{}, | ||
| 282 | |||
| 283 | /// The size of the trie in bytes. | ||
| 284 | /// This value may be outdated if there were additional | ||
| 285 | /// insertions performed after `finalize` was called. | ||
| 286 | /// Call `finalize` before accessing this value to ensure | ||
| 287 | /// it is up-to-date. | ||
| 288 | size: u64 = 0, | ||
| 289 | |||
| 290 | /// Number of nodes currently in the trie. | ||
| 291 | node_count: usize = 0, | ||
| 292 | |||
| 293 | trie_dirty: bool = true, | ||
| 294 | |||
| 295 | /// Export symbol that is to be placed in the trie. | ||
| 296 | pub const ExportSymbol = struct { | ||
| 297 | /// Name of the symbol. | ||
| 298 | name: []const u8, | ||
| 299 | |||
| 300 | /// Offset of this symbol's virtual memory address from the beginning | ||
| 301 | /// of the __TEXT segment. | ||
| 302 | vmaddr_offset: u64, | ||
| 303 | |||
| 304 | /// Export flags of this exported symbol. | ||
| 305 | export_flags: u64, | ||
| 306 | }; | ||
| 307 | |||
| 308 | /// Insert a symbol into the trie, updating the prefixes in the process. | ||
| 309 | /// This operation may change the layout of the trie by splicing edges in | ||
| 310 | /// certain circumstances. | ||
| 311 | pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void { | ||
| 312 | const node = try self.root.?.put(allocator, symbol.name); | ||
| 313 | node.terminal_info = .{ | ||
| 314 | .vmaddr_offset = symbol.vmaddr_offset, | ||
| 315 | .export_flags = symbol.export_flags, | ||
| 316 | }; | ||
| 317 | self.trie_dirty = true; | ||
| 318 | } | ||
| 319 | |||
| 320 | /// Finalizes this trie for writing to a byte stream. | ||
| 321 | /// This step performs multiple passes through the trie ensuring | ||
| 322 | /// there are no gaps after every `Node` is ULEB128 encoded. | ||
| 323 | /// Call this method before trying to `write` the trie to a byte stream. | ||
| 324 | pub fn finalize(self: *Trie, allocator: Allocator) !void { | ||
| 325 | if (!self.trie_dirty) return; | ||
| 326 | |||
| 327 | self.ordered_nodes.shrinkRetainingCapacity(0); | ||
| 328 | try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count); | ||
| 329 | |||
| 330 | var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator); | ||
| 331 | defer fifo.deinit(); | ||
| 332 | |||
| 333 | try fifo.writeItem(self.root.?); | ||
| 334 | |||
| 335 | while (fifo.readItem()) |next| { | ||
| 336 | for (next.edges.items) |*edge| { | ||
| 337 | try fifo.writeItem(edge.to); | ||
| 338 | } | ||
| 339 | self.ordered_nodes.appendAssumeCapacity(next); | ||
| 340 | } | ||
| 341 | |||
| 342 | var more: bool = true; | ||
| 343 | while (more) { | ||
| 344 | self.size = 0; | ||
| 345 | more = false; | ||
| 346 | for (self.ordered_nodes.items) |node| { | ||
| 347 | const res = try node.finalize(self.size); | ||
| 348 | self.size += res.node_size; | ||
| 349 | if (res.updated) more = true; | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | self.trie_dirty = false; | ||
| 354 | } | ||
| 355 | |||
| 356 | const ReadError = error{ | ||
| 357 | OutOfMemory, | ||
| 358 | EndOfStream, | ||
| 359 | Overflow, | ||
| 360 | }; | ||
| 361 | |||
| 362 | /// Parse the trie from a byte stream. | ||
| 363 | pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize { | ||
| 364 | return self.root.?.read(allocator, reader); | ||
| 365 | } | ||
| 366 | |||
| 367 | /// Write the trie to a byte stream. | ||
| 368 | /// Panics if the trie was not finalized using `finalize` before calling this method. | ||
| 369 | pub fn write(self: Trie, writer: anytype) !void { | ||
| 370 | assert(!self.trie_dirty); | ||
| 371 | for (self.ordered_nodes.items) |node| { | ||
| 372 | try node.write(writer); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | pub fn init(self: *Trie, allocator: Allocator) !void { | ||
| 377 | assert(self.root == null); | ||
| 378 | const root = try allocator.create(Node); | ||
| 379 | root.* = .{ .base = self }; | ||
| 380 | self.root = root; | ||
| 381 | self.node_count += 1; | ||
| 382 | } | ||
| 383 | |||
| 384 | pub fn deinit(self: *Trie, allocator: Allocator) void { | ||
| 385 | if (self.root) |root| { | ||
| 386 | root.deinit(allocator); | ||
| 387 | allocator.destroy(root); | ||
| 388 | } | ||
| 389 | self.ordered_nodes.deinit(allocator); | ||
| 390 | } | ||
| 391 | |||
| 392 | test "Trie node count" { | ||
| 393 | const gpa = testing.allocator; | ||
| 394 | var trie: Trie = .{}; | ||
| 395 | defer trie.deinit(gpa); | ||
| 396 | try trie.init(gpa); | ||
| 397 | |||
| 398 | try testing.expectEqual(@as(usize, 1), trie.node_count); | ||
| 399 | try testing.expect(trie.root != null); | ||
| 400 | |||
| 401 | try trie.put(gpa, .{ | ||
| 402 | .name = "_main", | ||
| 403 | .vmaddr_offset = 0, | ||
| 404 | .export_flags = 0, | ||
| 405 | }); | ||
| 406 | try testing.expectEqual(@as(usize, 2), trie.node_count); | ||
| 407 | |||
| 408 | // Inserting the same node shouldn't update the trie. | ||
| 409 | try trie.put(gpa, .{ | ||
| 410 | .name = "_main", | ||
| 411 | .vmaddr_offset = 0, | ||
| 412 | .export_flags = 0, | ||
| 413 | }); | ||
| 414 | try testing.expectEqual(@as(usize, 2), trie.node_count); | ||
| 415 | |||
| 416 | try trie.put(gpa, .{ | ||
| 417 | .name = "__mh_execute_header", | ||
| 418 | .vmaddr_offset = 0x1000, | ||
| 419 | .export_flags = 0, | ||
| 420 | }); | ||
| 421 | try testing.expectEqual(@as(usize, 4), trie.node_count); | ||
| 422 | |||
| 423 | // Inserting the same node shouldn't update the trie. | ||
| 424 | try trie.put(gpa, .{ | ||
| 425 | .name = "__mh_execute_header", | ||
| 426 | .vmaddr_offset = 0x1000, | ||
| 427 | .export_flags = 0, | ||
| 428 | }); | ||
| 429 | try testing.expectEqual(@as(usize, 4), trie.node_count); | ||
| 430 | try trie.put(gpa, .{ | ||
| 431 | .name = "_main", | ||
| 432 | .vmaddr_offset = 0, | ||
| 433 | .export_flags = 0, | ||
| 434 | }); | ||
| 435 | try testing.expectEqual(@as(usize, 4), trie.node_count); | ||
| 436 | } | ||
| 437 | |||
| 438 | test "Trie basic" { | ||
| 439 | const gpa = testing.allocator; | ||
| 440 | var trie: Trie = .{}; | ||
| 441 | defer trie.deinit(gpa); | ||
| 442 | try trie.init(gpa); | ||
| 443 | |||
| 444 | // root --- _st ---> node | ||
| 445 | try trie.put(gpa, .{ | ||
| 446 | .name = "_st", | ||
| 447 | .vmaddr_offset = 0, | ||
| 448 | .export_flags = 0, | ||
| 449 | }); | ||
| 450 | try testing.expect(trie.root.?.edges.items.len == 1); | ||
| 451 | try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st")); | ||
| 452 | |||
| 453 | { | ||
| 454 | // root --- _st ---> node --- art ---> node | ||
| 455 | try trie.put(gpa, .{ | ||
| 456 | .name = "_start", | ||
| 457 | .vmaddr_offset = 0, | ||
| 458 | .export_flags = 0, | ||
| 459 | }); | ||
| 460 | try testing.expect(trie.root.?.edges.items.len == 1); | ||
| 461 | |||
| 462 | const nextEdge = &trie.root.?.edges.items[0]; | ||
| 463 | try testing.expect(mem.eql(u8, nextEdge.label, "_st")); | ||
| 464 | try testing.expect(nextEdge.to.edges.items.len == 1); | ||
| 465 | try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art")); | ||
| 466 | } | ||
| 467 | { | ||
| 468 | // root --- _ ---> node --- st ---> node --- art ---> node | ||
| 469 | // | | ||
| 470 | // | --- main ---> node | ||
| 471 | try trie.put(gpa, .{ | ||
| 472 | .name = "_main", | ||
| 473 | .vmaddr_offset = 0, | ||
| 474 | .export_flags = 0, | ||
| 475 | }); | ||
| 476 | try testing.expect(trie.root.?.edges.items.len == 1); | ||
| 477 | |||
| 478 | const nextEdge = &trie.root.?.edges.items[0]; | ||
| 479 | try testing.expect(mem.eql(u8, nextEdge.label, "_")); | ||
| 480 | try testing.expect(nextEdge.to.edges.items.len == 2); | ||
| 481 | try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st")); | ||
| 482 | try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main")); | ||
| 483 | |||
| 484 | const nextNextEdge = &nextEdge.to.edges.items[0]; | ||
| 485 | try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art")); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | |||
| 489 | fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void { | ||
| 490 | assert(expected.len > 0); | ||
| 491 | if (mem.eql(u8, expected, given)) return; | ||
| 492 | const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); | ||
| 493 | defer testing.allocator.free(expected_fmt); | ||
| 494 | const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); | ||
| 495 | defer testing.allocator.free(given_fmt); | ||
| 496 | const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?; | ||
| 497 | const padding = try testing.allocator.alloc(u8, idx + 5); | ||
| 498 | defer testing.allocator.free(padding); | ||
| 499 | @memset(padding, ' '); | ||
| 500 | std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding }); | ||
| 501 | return error.TestFailed; | ||
| 502 | } | ||
| 503 | |||
| 504 | test "write Trie to a byte stream" { | ||
| 505 | var gpa = testing.allocator; | ||
| 506 | var trie: Trie = .{}; | ||
| 507 | defer trie.deinit(gpa); | ||
| 508 | try trie.init(gpa); | ||
| 509 | |||
| 510 | try trie.put(gpa, .{ | ||
| 511 | .name = "__mh_execute_header", | ||
| 512 | .vmaddr_offset = 0, | ||
| 513 | .export_flags = 0, | ||
| 514 | }); | ||
| 515 | try trie.put(gpa, .{ | ||
| 516 | .name = "_main", | ||
| 517 | .vmaddr_offset = 0x1000, | ||
| 518 | .export_flags = 0, | ||
| 519 | }); | ||
| 520 | |||
| 521 | try trie.finalize(gpa); | ||
| 522 | try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes. | ||
| 523 | |||
| 524 | const exp_buffer = [_]u8{ | ||
| 525 | 0x0, 0x1, // node root | ||
| 526 | 0x5f, 0x0, 0x5, // edge '_' | ||
| 527 | 0x0, 0x2, // non-terminal node | ||
| 528 | 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header' | ||
| 529 | 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header' | ||
| 530 | 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main' | ||
| 531 | 0x2, 0x0, 0x0, 0x0, // terminal node | ||
| 532 | 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node | ||
| 533 | }; | ||
| 534 | |||
| 535 | const buffer = try gpa.alloc(u8, trie.size); | ||
| 536 | defer gpa.free(buffer); | ||
| 537 | var stream = std.io.fixedBufferStream(buffer); | ||
| 538 | { | ||
| 539 | _ = try trie.write(stream.writer()); | ||
| 540 | try expectEqualHexStrings(&exp_buffer, buffer); | ||
| 541 | } | ||
| 542 | { | ||
| 543 | // Writing finalized trie again should yield the same result. | ||
| 544 | try stream.seekTo(0); | ||
| 545 | _ = try trie.write(stream.writer()); | ||
| 546 | try expectEqualHexStrings(&exp_buffer, buffer); | ||
| 547 | } | ||
| 548 | } | ||
| 549 | |||
| 550 | test "parse Trie from byte stream" { | ||
| 551 | const gpa = testing.allocator; | ||
| 552 | |||
| 553 | const in_buffer = [_]u8{ | ||
| 554 | 0x0, 0x1, // node root | ||
| 555 | 0x5f, 0x0, 0x5, // edge '_' | ||
| 556 | 0x0, 0x2, // non-terminal node | ||
| 557 | 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header' | ||
| 558 | 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header' | ||
| 559 | 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main' | ||
| 560 | 0x2, 0x0, 0x0, 0x0, // terminal node | ||
| 561 | 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node | ||
| 562 | }; | ||
| 563 | |||
| 564 | var in_stream = std.io.fixedBufferStream(&in_buffer); | ||
| 565 | var trie: Trie = .{}; | ||
| 566 | defer trie.deinit(gpa); | ||
| 567 | try trie.init(gpa); | ||
| 568 | const nread = try trie.read(gpa, in_stream.reader()); | ||
| 569 | |||
| 570 | try testing.expect(nread == in_buffer.len); | ||
| 571 | |||
| 572 | try trie.finalize(gpa); | ||
| 573 | |||
| 574 | const out_buffer = try gpa.alloc(u8, trie.size); | ||
| 575 | defer gpa.free(out_buffer); | ||
| 576 | var out_stream = std.io.fixedBufferStream(out_buffer); | ||
| 577 | _ = try trie.write(out_stream.writer()); | ||
| 578 | try expectEqualHexStrings(&in_buffer, out_buffer); | ||
| 579 | } | ||
| 580 | |||
| 581 | test "ordering bug" { | ||
| 582 | const gpa = testing.allocator; | ||
| 583 | var trie: Trie = .{}; | ||
| 584 | defer trie.deinit(gpa); | ||
| 585 | try trie.init(gpa); | ||
| 586 | |||
| 587 | try trie.put(gpa, .{ | ||
| 588 | .name = "_asStr", | ||
| 589 | .vmaddr_offset = 0x558, | ||
| 590 | .export_flags = 0, | ||
| 591 | }); | ||
| 592 | try trie.put(gpa, .{ | ||
| 593 | .name = "_a", | ||
| 594 | .vmaddr_offset = 0x8008, | ||
| 595 | .export_flags = 0, | ||
| 596 | }); | ||
| 597 | |||
| 598 | try trie.finalize(gpa); | ||
| 599 | |||
| 600 | const exp_buffer = [_]u8{ | ||
| 601 | 0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00, | ||
| 602 | 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72, | ||
| 603 | 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00, | ||
| 604 | }; | ||
| 605 | |||
| 606 | const buffer = try gpa.alloc(u8, trie.size); | ||
| 607 | defer gpa.free(buffer); | ||
| 608 | var stream = std.io.fixedBufferStream(buffer); | ||
| 609 | // Writing finalized trie again should yield the same result. | ||
| 610 | _ = try trie.write(stream.writer()); | ||
| 611 | try expectEqualHexStrings(&exp_buffer, buffer); | ||
| 612 | } | ||
src/link/MachO/dyld_info/bind.zig+348-614| ... | @@ -1,231 +1,391 @@ | ... | @@ -1,231 +1,391 @@ |
| 1 | pub fn Bind(comptime Ctx: type, comptime Target: type) type { | 1 | const std = @import("std"); |
| 2 | return struct { | 2 | const assert = std.debug.assert; |
| 3 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 3 | const leb = std.leb; |
| 4 | buffer: std.ArrayListUnmanaged(u8) = .{}, | 4 | const log = std.log.scoped(.dyld_info); |
| 5 | 5 | const macho = std.macho; | |
| 6 | const Self = @This(); | 6 | const testing = std.testing; |
| 7 | |||
| 8 | const Entry = struct { | ||
| 9 | target: Target, | ||
| 10 | offset: u64, | ||
| 11 | segment_id: u8, | ||
| 12 | addend: i64, | ||
| 13 | |||
| 14 | pub fn lessThan(ctx: Ctx, entry: Entry, other: Entry) bool { | ||
| 15 | if (entry.segment_id == other.segment_id) { | ||
| 16 | if (entry.target.eql(other.target)) { | ||
| 17 | return entry.offset < other.offset; | ||
| 18 | } | ||
| 19 | const entry_name = ctx.getSymbolName(entry.target); | ||
| 20 | const other_name = ctx.getSymbolName(other.target); | ||
| 21 | return std.mem.lessThan(u8, entry_name, other_name); | ||
| 22 | } | ||
| 23 | return entry.segment_id < other.segment_id; | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | 7 | ||
| 27 | pub fn deinit(self: *Self, gpa: Allocator) void { | 8 | const Allocator = std.mem.Allocator; |
| 28 | self.entries.deinit(gpa); | 9 | const MachO = @import("../../MachO.zig"); |
| 29 | self.buffer.deinit(gpa); | 10 | const Symbol = @import("../Symbol.zig"); |
| 11 | |||
| 12 | pub const Entry = struct { | ||
| 13 | target: Symbol.Index, | ||
| 14 | offset: u64, | ||
| 15 | segment_id: u8, | ||
| 16 | addend: i64, | ||
| 17 | |||
| 18 | pub fn lessThan(ctx: *MachO, entry: Entry, other: Entry) bool { | ||
| 19 | if (entry.segment_id == other.segment_id) { | ||
| 20 | if (entry.target == other.target) { | ||
| 21 | return entry.offset < other.offset; | ||
| 22 | } | ||
| 23 | const entry_name = ctx.getSymbol(entry.target).getName(ctx); | ||
| 24 | const other_name = ctx.getSymbol(other.target).getName(ctx); | ||
| 25 | return std.mem.lessThan(u8, entry_name, other_name); | ||
| 30 | } | 26 | } |
| 27 | return entry.segment_id < other.segment_id; | ||
| 28 | } | ||
| 29 | }; | ||
| 31 | 30 | ||
| 32 | pub fn size(self: Self) u64 { | 31 | pub const Bind = struct { |
| 33 | return @as(u64, @intCast(self.buffer.items.len)); | 32 | entries: std.ArrayListUnmanaged(Entry) = .{}, |
| 34 | } | 33 | buffer: std.ArrayListUnmanaged(u8) = .{}, |
| 35 | 34 | ||
| 36 | pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void { | 35 | const Self = @This(); |
| 37 | if (self.entries.items.len == 0) return; | ||
| 38 | 36 | ||
| 39 | const writer = self.buffer.writer(gpa); | 37 | pub fn deinit(self: *Self, gpa: Allocator) void { |
| 38 | self.entries.deinit(gpa); | ||
| 39 | self.buffer.deinit(gpa); | ||
| 40 | } | ||
| 40 | 41 | ||
| 41 | std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan); | 42 | pub fn size(self: Self) u64 { |
| 43 | return @as(u64, @intCast(self.buffer.items.len)); | ||
| 44 | } | ||
| 42 | 45 | ||
| 43 | var start: usize = 0; | 46 | pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { |
| 44 | var seg_id: ?u8 = null; | 47 | if (self.entries.items.len == 0) return; |
| 45 | for (self.entries.items, 0..) |entry, i| { | ||
| 46 | if (seg_id != null and seg_id.? == entry.segment_id) continue; | ||
| 47 | try finalizeSegment(self.entries.items[start..i], ctx, writer); | ||
| 48 | seg_id = entry.segment_id; | ||
| 49 | start = i; | ||
| 50 | } | ||
| 51 | 48 | ||
| 52 | try finalizeSegment(self.entries.items[start..], ctx, writer); | 49 | const writer = self.buffer.writer(gpa); |
| 53 | try done(writer); | 50 | |
| 51 | std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan); | ||
| 52 | |||
| 53 | var start: usize = 0; | ||
| 54 | var seg_id: ?u8 = null; | ||
| 55 | for (self.entries.items, 0..) |entry, i| { | ||
| 56 | if (seg_id != null and seg_id.? == entry.segment_id) continue; | ||
| 57 | try finalizeSegment(self.entries.items[start..i], ctx, writer); | ||
| 58 | seg_id = entry.segment_id; | ||
| 59 | start = i; | ||
| 54 | } | 60 | } |
| 55 | 61 | ||
| 56 | fn finalizeSegment(entries: []const Entry, ctx: Ctx, writer: anytype) !void { | 62 | try finalizeSegment(self.entries.items[start..], ctx, writer); |
| 57 | if (entries.len == 0) return; | 63 | try done(writer); |
| 58 | 64 | } | |
| 59 | const seg_id = entries[0].segment_id; | ||
| 60 | try setSegmentOffset(seg_id, 0, writer); | ||
| 61 | |||
| 62 | var offset: u64 = 0; | ||
| 63 | var addend: i64 = 0; | ||
| 64 | var count: usize = 0; | ||
| 65 | var skip: u64 = 0; | ||
| 66 | var target: ?Target = null; | ||
| 67 | |||
| 68 | var state: enum { | ||
| 69 | start, | ||
| 70 | bind_single, | ||
| 71 | bind_times_skip, | ||
| 72 | } = .start; | ||
| 73 | |||
| 74 | var i: usize = 0; | ||
| 75 | while (i < entries.len) : (i += 1) { | ||
| 76 | const current = entries[i]; | ||
| 77 | if (target == null or !target.?.eql(current.target)) { | ||
| 78 | switch (state) { | ||
| 79 | .start => {}, | ||
| 80 | .bind_single => try doBind(writer), | ||
| 81 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | ||
| 82 | } | ||
| 83 | state = .start; | ||
| 84 | target = current.target; | ||
| 85 | 65 | ||
| 86 | const sym = ctx.getSymbol(current.target); | 66 | fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void { |
| 87 | const name = ctx.getSymbolName(current.target); | 67 | if (entries.len == 0) return; |
| 88 | const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; | ||
| 89 | const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER); | ||
| 90 | 68 | ||
| 91 | try setSymbol(name, flags, writer); | 69 | const seg_id = entries[0].segment_id; |
| 92 | try setTypePointer(writer); | 70 | try setSegmentOffset(seg_id, 0, writer); |
| 93 | try setDylibOrdinal(ordinal, writer); | ||
| 94 | 71 | ||
| 95 | if (current.addend != addend) { | 72 | var offset: u64 = 0; |
| 96 | addend = current.addend; | 73 | var addend: i64 = 0; |
| 97 | try setAddend(addend, writer); | 74 | var count: usize = 0; |
| 98 | } | 75 | var skip: u64 = 0; |
| 99 | } | 76 | var target: ?Symbol.Index = null; |
| 100 | 77 | ||
| 101 | log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) }); | 78 | var state: enum { |
| 102 | log.debug(" => {x}", .{current.offset}); | 79 | start, |
| 80 | bind_single, | ||
| 81 | bind_times_skip, | ||
| 82 | } = .start; | ||
| 83 | |||
| 84 | var i: usize = 0; | ||
| 85 | while (i < entries.len) : (i += 1) { | ||
| 86 | const current = entries[i]; | ||
| 87 | if (target == null or target.? != current.target) { | ||
| 103 | switch (state) { | 88 | switch (state) { |
| 104 | .start => { | 89 | .start => {}, |
| 105 | if (current.offset < offset) { | 90 | .bind_single => try doBind(writer), |
| 106 | try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer); | 91 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), |
| 107 | offset = offset - (offset - current.offset); | 92 | } |
| 108 | } else if (current.offset > offset) { | 93 | state = .start; |
| 109 | const delta = current.offset - offset; | 94 | target = current.target; |
| 110 | try addAddr(delta, writer); | 95 | |
| 111 | offset += delta; | 96 | const sym = ctx.getSymbol(current.target); |
| 112 | } | 97 | const name = sym.getName(ctx); |
| 113 | state = .bind_single; | 98 | const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; |
| 114 | offset += @sizeOf(u64); | 99 | const ordinal: i16 = ord: { |
| 115 | count = 1; | 100 | if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; |
| 116 | }, | 101 | if (sym.flags.import) { |
| 117 | .bind_single => { | 102 | if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; |
| 118 | if (current.offset == offset) { | 103 | if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord); |
| 119 | try doBind(writer); | 104 | } |
| 120 | state = .start; | 105 | if (ctx.options.undefined_treatment == .dynamic_lookup) |
| 121 | } else if (current.offset > offset) { | 106 | break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; |
| 122 | const delta = current.offset - offset; | 107 | break :ord macho.BIND_SPECIAL_DYLIB_SELF; |
| 123 | state = .bind_times_skip; | 108 | }; |
| 124 | skip = @as(u64, @intCast(delta)); | 109 | |
| 125 | offset += skip; | 110 | try setSymbol(name, flags, writer); |
| 126 | } else unreachable; | 111 | try setTypePointer(writer); |
| 127 | i -= 1; | 112 | try setDylibOrdinal(ordinal, writer); |
| 128 | }, | 113 | |
| 129 | .bind_times_skip => { | 114 | if (current.addend != addend) { |
| 130 | if (current.offset < offset) { | 115 | addend = current.addend; |
| 131 | count -= 1; | 116 | try setAddend(addend, writer); |
| 132 | if (count == 1) { | ||
| 133 | try doBindAddAddr(skip, writer); | ||
| 134 | } else { | ||
| 135 | try doBindTimesSkip(count, skip, writer); | ||
| 136 | } | ||
| 137 | state = .start; | ||
| 138 | offset = offset - (@sizeOf(u64) + skip); | ||
| 139 | i -= 2; | ||
| 140 | } else if (current.offset == offset) { | ||
| 141 | count += 1; | ||
| 142 | offset += @sizeOf(u64) + skip; | ||
| 143 | } else { | ||
| 144 | try doBindTimesSkip(count, skip, writer); | ||
| 145 | state = .start; | ||
| 146 | i -= 1; | ||
| 147 | } | ||
| 148 | }, | ||
| 149 | } | 117 | } |
| 150 | } | 118 | } |
| 151 | 119 | ||
| 120 | log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) }); | ||
| 121 | log.debug(" => {x}", .{current.offset}); | ||
| 152 | switch (state) { | 122 | switch (state) { |
| 153 | .start => unreachable, | 123 | .start => { |
| 154 | .bind_single => try doBind(writer), | 124 | if (current.offset < offset) { |
| 155 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | 125 | try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer); |
| 126 | offset = offset - (offset - current.offset); | ||
| 127 | } else if (current.offset > offset) { | ||
| 128 | const delta = current.offset - offset; | ||
| 129 | try addAddr(delta, writer); | ||
| 130 | offset += delta; | ||
| 131 | } | ||
| 132 | state = .bind_single; | ||
| 133 | offset += @sizeOf(u64); | ||
| 134 | count = 1; | ||
| 135 | }, | ||
| 136 | .bind_single => { | ||
| 137 | if (current.offset == offset) { | ||
| 138 | try doBind(writer); | ||
| 139 | state = .start; | ||
| 140 | } else if (current.offset > offset) { | ||
| 141 | const delta = current.offset - offset; | ||
| 142 | state = .bind_times_skip; | ||
| 143 | skip = @as(u64, @intCast(delta)); | ||
| 144 | offset += skip; | ||
| 145 | } else unreachable; | ||
| 146 | i -= 1; | ||
| 147 | }, | ||
| 148 | .bind_times_skip => { | ||
| 149 | if (current.offset < offset) { | ||
| 150 | count -= 1; | ||
| 151 | if (count == 1) { | ||
| 152 | try doBindAddAddr(skip, writer); | ||
| 153 | } else { | ||
| 154 | try doBindTimesSkip(count, skip, writer); | ||
| 155 | } | ||
| 156 | state = .start; | ||
| 157 | offset = offset - (@sizeOf(u64) + skip); | ||
| 158 | i -= 2; | ||
| 159 | } else if (current.offset == offset) { | ||
| 160 | count += 1; | ||
| 161 | offset += @sizeOf(u64) + skip; | ||
| 162 | } else { | ||
| 163 | try doBindTimesSkip(count, skip, writer); | ||
| 164 | state = .start; | ||
| 165 | i -= 1; | ||
| 166 | } | ||
| 167 | }, | ||
| 156 | } | 168 | } |
| 157 | } | 169 | } |
| 158 | 170 | ||
| 159 | pub fn write(self: Self, writer: anytype) !void { | 171 | switch (state) { |
| 160 | if (self.size() == 0) return; | 172 | .start => unreachable, |
| 161 | try writer.writeAll(self.buffer.items); | 173 | .bind_single => try doBind(writer), |
| 174 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | ||
| 162 | } | 175 | } |
| 163 | }; | 176 | } |
| 164 | } | ||
| 165 | 177 | ||
| 166 | pub fn LazyBind(comptime Ctx: type, comptime Target: type) type { | 178 | pub fn write(self: Self, writer: anytype) !void { |
| 167 | return struct { | 179 | if (self.size() == 0) return; |
| 168 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 180 | try writer.writeAll(self.buffer.items); |
| 169 | buffer: std.ArrayListUnmanaged(u8) = .{}, | 181 | } |
| 170 | offsets: std.ArrayListUnmanaged(u32) = .{}, | 182 | }; |
| 171 | 183 | ||
| 172 | const Self = @This(); | 184 | pub const WeakBind = struct { |
| 173 | 185 | entries: std.ArrayListUnmanaged(Entry) = .{}, | |
| 174 | const Entry = struct { | 186 | buffer: std.ArrayListUnmanaged(u8) = .{}, |
| 175 | target: Target, | 187 | |
| 176 | offset: u64, | 188 | const Self = @This(); |
| 177 | segment_id: u8, | 189 | |
| 178 | addend: i64, | 190 | pub fn deinit(self: *Self, gpa: Allocator) void { |
| 179 | }; | 191 | self.entries.deinit(gpa); |
| 180 | 192 | self.buffer.deinit(gpa); | |
| 181 | pub fn deinit(self: *Self, gpa: Allocator) void { | 193 | } |
| 182 | self.entries.deinit(gpa); | ||
| 183 | self.buffer.deinit(gpa); | ||
| 184 | self.offsets.deinit(gpa); | ||
| 185 | } | ||
| 186 | 194 | ||
| 187 | pub fn size(self: Self) u64 { | 195 | pub fn size(self: Self) u64 { |
| 188 | return @as(u64, @intCast(self.buffer.items.len)); | 196 | return @as(u64, @intCast(self.buffer.items.len)); |
| 197 | } | ||
| 198 | |||
| 199 | pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { | ||
| 200 | if (self.entries.items.len == 0) return; | ||
| 201 | |||
| 202 | const writer = self.buffer.writer(gpa); | ||
| 203 | |||
| 204 | std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan); | ||
| 205 | |||
| 206 | var start: usize = 0; | ||
| 207 | var seg_id: ?u8 = null; | ||
| 208 | for (self.entries.items, 0..) |entry, i| { | ||
| 209 | if (seg_id != null and seg_id.? == entry.segment_id) continue; | ||
| 210 | try finalizeSegment(self.entries.items[start..i], ctx, writer); | ||
| 211 | seg_id = entry.segment_id; | ||
| 212 | start = i; | ||
| 189 | } | 213 | } |
| 190 | 214 | ||
| 191 | pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void { | 215 | try finalizeSegment(self.entries.items[start..], ctx, writer); |
| 192 | if (self.entries.items.len == 0) return; | 216 | try done(writer); |
| 217 | } | ||
| 218 | |||
| 219 | fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void { | ||
| 220 | if (entries.len == 0) return; | ||
| 193 | 221 | ||
| 194 | try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len); | 222 | const seg_id = entries[0].segment_id; |
| 223 | try setSegmentOffset(seg_id, 0, writer); | ||
| 195 | 224 | ||
| 196 | var cwriter = std.io.countingWriter(self.buffer.writer(gpa)); | 225 | var offset: u64 = 0; |
| 197 | const writer = cwriter.writer(); | 226 | var addend: i64 = 0; |
| 227 | var count: usize = 0; | ||
| 228 | var skip: u64 = 0; | ||
| 229 | var target: ?Symbol.Index = null; | ||
| 198 | 230 | ||
| 199 | var addend: i64 = 0; | 231 | var state: enum { |
| 232 | start, | ||
| 233 | bind_single, | ||
| 234 | bind_times_skip, | ||
| 235 | } = .start; | ||
| 200 | 236 | ||
| 201 | for (self.entries.items) |entry| { | 237 | var i: usize = 0; |
| 202 | self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written))); | 238 | while (i < entries.len) : (i += 1) { |
| 239 | const current = entries[i]; | ||
| 240 | if (target == null or target.? != current.target) { | ||
| 241 | switch (state) { | ||
| 242 | .start => {}, | ||
| 243 | .bind_single => try doBind(writer), | ||
| 244 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | ||
| 245 | } | ||
| 246 | state = .start; | ||
| 247 | target = current.target; | ||
| 203 | 248 | ||
| 204 | const sym = ctx.getSymbol(entry.target); | 249 | const sym = ctx.getSymbol(current.target); |
| 205 | const name = ctx.getSymbolName(entry.target); | 250 | const name = sym.getName(ctx); |
| 206 | const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; | 251 | const flags: u8 = 0; // TODO NON_WEAK_DEFINITION |
| 207 | const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER); | ||
| 208 | 252 | ||
| 209 | try setSegmentOffset(entry.segment_id, entry.offset, writer); | ||
| 210 | try setSymbol(name, flags, writer); | 253 | try setSymbol(name, flags, writer); |
| 211 | try setDylibOrdinal(ordinal, writer); | 254 | try setTypePointer(writer); |
| 212 | 255 | ||
| 213 | if (entry.addend != addend) { | 256 | if (current.addend != addend) { |
| 214 | try setAddend(entry.addend, writer); | 257 | addend = current.addend; |
| 215 | addend = entry.addend; | 258 | try setAddend(addend, writer); |
| 216 | } | 259 | } |
| 260 | } | ||
| 217 | 261 | ||
| 218 | try doBind(writer); | 262 | log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) }); |
| 219 | try done(writer); | 263 | log.debug(" => {x}", .{current.offset}); |
| 264 | switch (state) { | ||
| 265 | .start => { | ||
| 266 | if (current.offset < offset) { | ||
| 267 | try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer); | ||
| 268 | offset = offset - (offset - current.offset); | ||
| 269 | } else if (current.offset > offset) { | ||
| 270 | const delta = current.offset - offset; | ||
| 271 | try addAddr(delta, writer); | ||
| 272 | offset += delta; | ||
| 273 | } | ||
| 274 | state = .bind_single; | ||
| 275 | offset += @sizeOf(u64); | ||
| 276 | count = 1; | ||
| 277 | }, | ||
| 278 | .bind_single => { | ||
| 279 | if (current.offset == offset) { | ||
| 280 | try doBind(writer); | ||
| 281 | state = .start; | ||
| 282 | } else if (current.offset > offset) { | ||
| 283 | const delta = current.offset - offset; | ||
| 284 | state = .bind_times_skip; | ||
| 285 | skip = @as(u64, @intCast(delta)); | ||
| 286 | offset += skip; | ||
| 287 | } else unreachable; | ||
| 288 | i -= 1; | ||
| 289 | }, | ||
| 290 | .bind_times_skip => { | ||
| 291 | if (current.offset < offset) { | ||
| 292 | count -= 1; | ||
| 293 | if (count == 1) { | ||
| 294 | try doBindAddAddr(skip, writer); | ||
| 295 | } else { | ||
| 296 | try doBindTimesSkip(count, skip, writer); | ||
| 297 | } | ||
| 298 | state = .start; | ||
| 299 | offset = offset - (@sizeOf(u64) + skip); | ||
| 300 | i -= 2; | ||
| 301 | } else if (current.offset == offset) { | ||
| 302 | count += 1; | ||
| 303 | offset += @sizeOf(u64) + skip; | ||
| 304 | } else { | ||
| 305 | try doBindTimesSkip(count, skip, writer); | ||
| 306 | state = .start; | ||
| 307 | i -= 1; | ||
| 308 | } | ||
| 309 | }, | ||
| 220 | } | 310 | } |
| 221 | } | 311 | } |
| 222 | 312 | ||
| 223 | pub fn write(self: Self, writer: anytype) !void { | 313 | switch (state) { |
| 224 | if (self.size() == 0) return; | 314 | .start => unreachable, |
| 225 | try writer.writeAll(self.buffer.items); | 315 | .bind_single => try doBind(writer), |
| 316 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | ||
| 226 | } | 317 | } |
| 227 | }; | 318 | } |
| 228 | } | 319 | |
| 320 | pub fn write(self: Self, writer: anytype) !void { | ||
| 321 | if (self.size() == 0) return; | ||
| 322 | try writer.writeAll(self.buffer.items); | ||
| 323 | } | ||
| 324 | }; | ||
| 325 | |||
| 326 | pub const LazyBind = struct { | ||
| 327 | entries: std.ArrayListUnmanaged(Entry) = .{}, | ||
| 328 | buffer: std.ArrayListUnmanaged(u8) = .{}, | ||
| 329 | offsets: std.ArrayListUnmanaged(u32) = .{}, | ||
| 330 | |||
| 331 | const Self = @This(); | ||
| 332 | |||
| 333 | pub fn deinit(self: *Self, gpa: Allocator) void { | ||
| 334 | self.entries.deinit(gpa); | ||
| 335 | self.buffer.deinit(gpa); | ||
| 336 | self.offsets.deinit(gpa); | ||
| 337 | } | ||
| 338 | |||
| 339 | pub fn size(self: Self) u64 { | ||
| 340 | return @as(u64, @intCast(self.buffer.items.len)); | ||
| 341 | } | ||
| 342 | |||
| 343 | pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { | ||
| 344 | if (self.entries.items.len == 0) return; | ||
| 345 | |||
| 346 | try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len); | ||
| 347 | |||
| 348 | var cwriter = std.io.countingWriter(self.buffer.writer(gpa)); | ||
| 349 | const writer = cwriter.writer(); | ||
| 350 | |||
| 351 | var addend: i64 = 0; | ||
| 352 | |||
| 353 | for (self.entries.items) |entry| { | ||
| 354 | self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written))); | ||
| 355 | |||
| 356 | const sym = ctx.getSymbol(entry.target); | ||
| 357 | const name = sym.getName(ctx); | ||
| 358 | const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; | ||
| 359 | const ordinal: i16 = ord: { | ||
| 360 | if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; | ||
| 361 | if (sym.flags.import) { | ||
| 362 | if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; | ||
| 363 | if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord); | ||
| 364 | } | ||
| 365 | if (ctx.options.undefined_treatment == .dynamic_lookup) | ||
| 366 | break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; | ||
| 367 | break :ord macho.BIND_SPECIAL_DYLIB_SELF; | ||
| 368 | }; | ||
| 369 | |||
| 370 | try setSegmentOffset(entry.segment_id, entry.offset, writer); | ||
| 371 | try setSymbol(name, flags, writer); | ||
| 372 | try setDylibOrdinal(ordinal, writer); | ||
| 373 | |||
| 374 | if (entry.addend != addend) { | ||
| 375 | try setAddend(entry.addend, writer); | ||
| 376 | addend = entry.addend; | ||
| 377 | } | ||
| 378 | |||
| 379 | try doBind(writer); | ||
| 380 | try done(writer); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | pub fn write(self: Self, writer: anytype) !void { | ||
| 385 | if (self.size() == 0) return; | ||
| 386 | try writer.writeAll(self.buffer.items); | ||
| 387 | } | ||
| 388 | }; | ||
| 229 | 389 | ||
| 230 | fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void { | 390 | fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void { |
| 231 | log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset }); | 391 | log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset }); |
| ... | @@ -282,7 +442,7 @@ fn doBind(writer: anytype) !void { | ... | @@ -282,7 +442,7 @@ fn doBind(writer: anytype) !void { |
| 282 | 442 | ||
| 283 | fn doBindAddAddr(addr: u64, writer: anytype) !void { | 443 | fn doBindAddAddr(addr: u64, writer: anytype) !void { |
| 284 | log.debug(">>> bind with add: {x}", .{addr}); | 444 | log.debug(">>> bind with add: {x}", .{addr}); |
| 285 | if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) { | 445 | if (std.mem.isAligned(addr, @sizeOf(u64))) { |
| 286 | const imm = @divExact(addr, @sizeOf(u64)); | 446 | const imm = @divExact(addr, @sizeOf(u64)); |
| 287 | if (imm <= 0xf) { | 447 | if (imm <= 0xf) { |
| 288 | try writer.writeByte( | 448 | try writer.writeByte( |
| ... | @@ -312,429 +472,3 @@ fn done(writer: anytype) !void { | ... | @@ -312,429 +472,3 @@ fn done(writer: anytype) !void { |
| 312 | log.debug(">>> done", .{}); | 472 | log.debug(">>> done", .{}); |
| 313 | try writer.writeByte(macho.BIND_OPCODE_DONE); | 473 | try writer.writeByte(macho.BIND_OPCODE_DONE); |
| 314 | } | 474 | } |
| 315 | |||
| 316 | const TestContext = struct { | ||
| 317 | symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | ||
| 318 | strtab: std.ArrayListUnmanaged(u8) = .{}, | ||
| 319 | |||
| 320 | const Target = struct { | ||
| 321 | index: u32, | ||
| 322 | |||
| 323 | fn eql(this: Target, other: Target) bool { | ||
| 324 | return this.index == other.index; | ||
| 325 | } | ||
| 326 | }; | ||
| 327 | |||
| 328 | fn deinit(ctx: *TestContext, gpa: Allocator) void { | ||
| 329 | ctx.symbols.deinit(gpa); | ||
| 330 | ctx.strtab.deinit(gpa); | ||
| 331 | } | ||
| 332 | |||
| 333 | fn addSymbol(ctx: *TestContext, gpa: Allocator, name: []const u8, ordinal: i16, flags: u16) !void { | ||
| 334 | const n_strx = try ctx.addString(gpa, name); | ||
| 335 | var n_desc = @as(u16, @bitCast(ordinal * macho.N_SYMBOL_RESOLVER)); | ||
| 336 | n_desc |= flags; | ||
| 337 | try ctx.symbols.append(gpa, .{ | ||
| 338 | .n_value = 0, | ||
| 339 | .n_strx = n_strx, | ||
| 340 | .n_desc = n_desc, | ||
| 341 | .n_type = macho.N_EXT, | ||
| 342 | .n_sect = 0, | ||
| 343 | }); | ||
| 344 | } | ||
| 345 | |||
| 346 | fn addString(ctx: *TestContext, gpa: Allocator, name: []const u8) !u32 { | ||
| 347 | const n_strx = @as(u32, @intCast(ctx.strtab.items.len)); | ||
| 348 | try ctx.strtab.appendSlice(gpa, name); | ||
| 349 | try ctx.strtab.append(gpa, 0); | ||
| 350 | return n_strx; | ||
| 351 | } | ||
| 352 | |||
| 353 | fn getSymbol(ctx: TestContext, target: Target) macho.nlist_64 { | ||
| 354 | return ctx.symbols.items[target.index]; | ||
| 355 | } | ||
| 356 | |||
| 357 | fn getSymbolName(ctx: TestContext, target: Target) []const u8 { | ||
| 358 | const sym = ctx.getSymbol(target); | ||
| 359 | assert(sym.n_strx < ctx.strtab.items.len); | ||
| 360 | return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + sym.n_strx)), 0); | ||
| 361 | } | ||
| 362 | }; | ||
| 363 | |||
| 364 | fn generateTestContext() !TestContext { | ||
| 365 | const gpa = testing.allocator; | ||
| 366 | var ctx = TestContext{}; | ||
| 367 | try ctx.addSymbol(gpa, "_import_1", 1, 0); | ||
| 368 | try ctx.addSymbol(gpa, "_import_2", 1, 0); | ||
| 369 | try ctx.addSymbol(gpa, "_import_3", 1, 0); | ||
| 370 | try ctx.addSymbol(gpa, "_import_4", 2, 0); | ||
| 371 | try ctx.addSymbol(gpa, "_import_5_weak", 2, macho.N_WEAK_REF); | ||
| 372 | try ctx.addSymbol(gpa, "_import_6", 2, 0); | ||
| 373 | return ctx; | ||
| 374 | } | ||
| 375 | |||
| 376 | test "bind - no entries" { | ||
| 377 | const gpa = testing.allocator; | ||
| 378 | |||
| 379 | var test_context = try generateTestContext(); | ||
| 380 | defer test_context.deinit(gpa); | ||
| 381 | |||
| 382 | var bind = Bind(TestContext, TestContext.Target){}; | ||
| 383 | defer bind.deinit(gpa); | ||
| 384 | |||
| 385 | try bind.finalize(gpa, test_context); | ||
| 386 | try testing.expectEqual(@as(u64, 0), bind.size()); | ||
| 387 | } | ||
| 388 | |||
| 389 | test "bind - single entry" { | ||
| 390 | const gpa = testing.allocator; | ||
| 391 | |||
| 392 | var test_context = try generateTestContext(); | ||
| 393 | defer test_context.deinit(gpa); | ||
| 394 | |||
| 395 | var bind = Bind(TestContext, TestContext.Target){}; | ||
| 396 | defer bind.deinit(gpa); | ||
| 397 | |||
| 398 | try bind.entries.append(gpa, .{ | ||
| 399 | .offset = 0x10, | ||
| 400 | .segment_id = 1, | ||
| 401 | .target = TestContext.Target{ .index = 0 }, | ||
| 402 | .addend = 0, | ||
| 403 | }); | ||
| 404 | try bind.finalize(gpa, test_context); | ||
| 405 | try testing.expectEqualSlices(u8, &[_]u8{ | ||
| 406 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1, | ||
| 407 | 0x0, | ||
| 408 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 409 | 0x5f, | ||
| 410 | 0x69, | ||
| 411 | 0x6d, | ||
| 412 | 0x70, | ||
| 413 | 0x6f, | ||
| 414 | 0x72, | ||
| 415 | 0x74, | ||
| 416 | 0x5f, | ||
| 417 | 0x31, | ||
| 418 | 0x0, | ||
| 419 | macho.BIND_OPCODE_SET_TYPE_IMM | 1, | ||
| 420 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 421 | macho.BIND_OPCODE_ADD_ADDR_ULEB, | ||
| 422 | 0x10, | ||
| 423 | macho.BIND_OPCODE_DO_BIND, | ||
| 424 | macho.BIND_OPCODE_DONE, | ||
| 425 | }, bind.buffer.items); | ||
| 426 | } | ||
| 427 | |||
| 428 | test "bind - multiple occurrences within the same segment" { | ||
| 429 | const gpa = testing.allocator; | ||
| 430 | |||
| 431 | var test_context = try generateTestContext(); | ||
| 432 | defer test_context.deinit(gpa); | ||
| 433 | |||
| 434 | var bind = Bind(TestContext, TestContext.Target){}; | ||
| 435 | defer bind.deinit(gpa); | ||
| 436 | |||
| 437 | try bind.entries.append(gpa, .{ | ||
| 438 | .offset = 0x10, | ||
| 439 | .segment_id = 1, | ||
| 440 | .target = TestContext.Target{ .index = 0 }, | ||
| 441 | .addend = 0, | ||
| 442 | }); | ||
| 443 | try bind.entries.append(gpa, .{ | ||
| 444 | .offset = 0x18, | ||
| 445 | .segment_id = 1, | ||
| 446 | .target = TestContext.Target{ .index = 0 }, | ||
| 447 | .addend = 0, | ||
| 448 | }); | ||
| 449 | try bind.entries.append(gpa, .{ | ||
| 450 | .offset = 0x20, | ||
| 451 | .segment_id = 1, | ||
| 452 | .target = TestContext.Target{ .index = 0 }, | ||
| 453 | .addend = 0, | ||
| 454 | }); | ||
| 455 | try bind.entries.append(gpa, .{ | ||
| 456 | .offset = 0x28, | ||
| 457 | .segment_id = 1, | ||
| 458 | .target = TestContext.Target{ .index = 0 }, | ||
| 459 | .addend = 0, | ||
| 460 | }); | ||
| 461 | |||
| 462 | try bind.finalize(gpa, test_context); | ||
| 463 | try testing.expectEqualSlices(u8, &[_]u8{ | ||
| 464 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1, | ||
| 465 | 0x0, | ||
| 466 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 467 | 0x5f, | ||
| 468 | 0x69, | ||
| 469 | 0x6d, | ||
| 470 | 0x70, | ||
| 471 | 0x6f, | ||
| 472 | 0x72, | ||
| 473 | 0x74, | ||
| 474 | 0x5f, | ||
| 475 | 0x31, | ||
| 476 | 0x0, | ||
| 477 | macho.BIND_OPCODE_SET_TYPE_IMM | 1, | ||
| 478 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 479 | macho.BIND_OPCODE_ADD_ADDR_ULEB, | ||
| 480 | 0x10, | ||
| 481 | macho.BIND_OPCODE_DO_BIND, | ||
| 482 | macho.BIND_OPCODE_DO_BIND, | ||
| 483 | macho.BIND_OPCODE_DO_BIND, | ||
| 484 | macho.BIND_OPCODE_DO_BIND, | ||
| 485 | macho.BIND_OPCODE_DONE, | ||
| 486 | }, bind.buffer.items); | ||
| 487 | } | ||
| 488 | |||
| 489 | test "bind - multiple occurrences with skip and addend" { | ||
| 490 | const gpa = testing.allocator; | ||
| 491 | |||
| 492 | var test_context = try generateTestContext(); | ||
| 493 | defer test_context.deinit(gpa); | ||
| 494 | |||
| 495 | var bind = Bind(TestContext, TestContext.Target){}; | ||
| 496 | defer bind.deinit(gpa); | ||
| 497 | |||
| 498 | try bind.entries.append(gpa, .{ | ||
| 499 | .offset = 0x0, | ||
| 500 | .segment_id = 1, | ||
| 501 | .target = TestContext.Target{ .index = 0 }, | ||
| 502 | .addend = 0x10, | ||
| 503 | }); | ||
| 504 | try bind.entries.append(gpa, .{ | ||
| 505 | .offset = 0x10, | ||
| 506 | .segment_id = 1, | ||
| 507 | .target = TestContext.Target{ .index = 0 }, | ||
| 508 | .addend = 0x10, | ||
| 509 | }); | ||
| 510 | try bind.entries.append(gpa, .{ | ||
| 511 | .offset = 0x20, | ||
| 512 | .segment_id = 1, | ||
| 513 | .target = TestContext.Target{ .index = 0 }, | ||
| 514 | .addend = 0x10, | ||
| 515 | }); | ||
| 516 | try bind.entries.append(gpa, .{ | ||
| 517 | .offset = 0x30, | ||
| 518 | .segment_id = 1, | ||
| 519 | .target = TestContext.Target{ .index = 0 }, | ||
| 520 | .addend = 0x10, | ||
| 521 | }); | ||
| 522 | |||
| 523 | try bind.finalize(gpa, test_context); | ||
| 524 | try testing.expectEqualSlices(u8, &[_]u8{ | ||
| 525 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1, | ||
| 526 | 0x0, | ||
| 527 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 528 | 0x5f, | ||
| 529 | 0x69, | ||
| 530 | 0x6d, | ||
| 531 | 0x70, | ||
| 532 | 0x6f, | ||
| 533 | 0x72, | ||
| 534 | 0x74, | ||
| 535 | 0x5f, | ||
| 536 | 0x31, | ||
| 537 | 0x0, | ||
| 538 | macho.BIND_OPCODE_SET_TYPE_IMM | 1, | ||
| 539 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 540 | macho.BIND_OPCODE_SET_ADDEND_SLEB, | ||
| 541 | 0x10, | ||
| 542 | macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB, | ||
| 543 | 0x4, | ||
| 544 | 0x8, | ||
| 545 | macho.BIND_OPCODE_DONE, | ||
| 546 | }, bind.buffer.items); | ||
| 547 | } | ||
| 548 | |||
| 549 | test "bind - complex" { | ||
| 550 | const gpa = testing.allocator; | ||
| 551 | |||
| 552 | var test_context = try generateTestContext(); | ||
| 553 | defer test_context.deinit(gpa); | ||
| 554 | |||
| 555 | var bind = Bind(TestContext, TestContext.Target){}; | ||
| 556 | defer bind.deinit(gpa); | ||
| 557 | |||
| 558 | try bind.entries.append(gpa, .{ | ||
| 559 | .offset = 0x58, | ||
| 560 | .segment_id = 1, | ||
| 561 | .target = TestContext.Target{ .index = 0 }, | ||
| 562 | .addend = 0, | ||
| 563 | }); | ||
| 564 | try bind.entries.append(gpa, .{ | ||
| 565 | .offset = 0x100, | ||
| 566 | .segment_id = 1, | ||
| 567 | .target = TestContext.Target{ .index = 1 }, | ||
| 568 | .addend = 0x10, | ||
| 569 | }); | ||
| 570 | try bind.entries.append(gpa, .{ | ||
| 571 | .offset = 0x110, | ||
| 572 | .segment_id = 1, | ||
| 573 | .target = TestContext.Target{ .index = 1 }, | ||
| 574 | .addend = 0x10, | ||
| 575 | }); | ||
| 576 | try bind.entries.append(gpa, .{ | ||
| 577 | .offset = 0x130, | ||
| 578 | .segment_id = 1, | ||
| 579 | .target = TestContext.Target{ .index = 1 }, | ||
| 580 | .addend = 0x10, | ||
| 581 | }); | ||
| 582 | try bind.entries.append(gpa, .{ | ||
| 583 | .offset = 0x140, | ||
| 584 | .segment_id = 1, | ||
| 585 | .target = TestContext.Target{ .index = 1 }, | ||
| 586 | .addend = 0x10, | ||
| 587 | }); | ||
| 588 | try bind.entries.append(gpa, .{ | ||
| 589 | .offset = 0x148, | ||
| 590 | .segment_id = 1, | ||
| 591 | .target = TestContext.Target{ .index = 2 }, | ||
| 592 | .addend = 0, | ||
| 593 | }); | ||
| 594 | |||
| 595 | try bind.finalize(gpa, test_context); | ||
| 596 | try testing.expectEqualSlices(u8, &[_]u8{ | ||
| 597 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1, | ||
| 598 | 0x0, | ||
| 599 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 600 | 0x5f, | ||
| 601 | 0x69, | ||
| 602 | 0x6d, | ||
| 603 | 0x70, | ||
| 604 | 0x6f, | ||
| 605 | 0x72, | ||
| 606 | 0x74, | ||
| 607 | 0x5f, | ||
| 608 | 0x31, | ||
| 609 | 0x0, | ||
| 610 | macho.BIND_OPCODE_SET_TYPE_IMM | 1, | ||
| 611 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 612 | macho.BIND_OPCODE_ADD_ADDR_ULEB, | ||
| 613 | 0x58, | ||
| 614 | macho.BIND_OPCODE_DO_BIND, | ||
| 615 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 616 | 0x5f, | ||
| 617 | 0x69, | ||
| 618 | 0x6d, | ||
| 619 | 0x70, | ||
| 620 | 0x6f, | ||
| 621 | 0x72, | ||
| 622 | 0x74, | ||
| 623 | 0x5f, | ||
| 624 | 0x32, | ||
| 625 | 0x0, | ||
| 626 | macho.BIND_OPCODE_SET_TYPE_IMM | 1, | ||
| 627 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 628 | macho.BIND_OPCODE_SET_ADDEND_SLEB, | ||
| 629 | 0x10, | ||
| 630 | macho.BIND_OPCODE_ADD_ADDR_ULEB, | ||
| 631 | 0xa0, | ||
| 632 | 0x1, | ||
| 633 | macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB, | ||
| 634 | 0x2, | ||
| 635 | 0x8, | ||
| 636 | macho.BIND_OPCODE_ADD_ADDR_ULEB, | ||
| 637 | 0x10, | ||
| 638 | macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB, | ||
| 639 | 0x2, | ||
| 640 | 0x8, | ||
| 641 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 642 | 0x5f, | ||
| 643 | 0x69, | ||
| 644 | 0x6d, | ||
| 645 | 0x70, | ||
| 646 | 0x6f, | ||
| 647 | 0x72, | ||
| 648 | 0x74, | ||
| 649 | 0x5f, | ||
| 650 | 0x33, | ||
| 651 | 0x0, | ||
| 652 | macho.BIND_OPCODE_SET_TYPE_IMM | 1, | ||
| 653 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 654 | macho.BIND_OPCODE_SET_ADDEND_SLEB, | ||
| 655 | 0x0, | ||
| 656 | macho.BIND_OPCODE_ADD_ADDR_ULEB, | ||
| 657 | 0xf8, | ||
| 658 | 0xff, | ||
| 659 | 0xff, | ||
| 660 | 0xff, | ||
| 661 | 0xff, | ||
| 662 | 0xff, | ||
| 663 | 0xff, | ||
| 664 | 0xff, | ||
| 665 | 0xff, | ||
| 666 | 0x1, | ||
| 667 | macho.BIND_OPCODE_DO_BIND, | ||
| 668 | macho.BIND_OPCODE_DONE, | ||
| 669 | }, bind.buffer.items); | ||
| 670 | } | ||
| 671 | |||
| 672 | test "lazy bind" { | ||
| 673 | const gpa = testing.allocator; | ||
| 674 | |||
| 675 | var test_context = try generateTestContext(); | ||
| 676 | defer test_context.deinit(gpa); | ||
| 677 | |||
| 678 | var bind = LazyBind(TestContext, TestContext.Target){}; | ||
| 679 | defer bind.deinit(gpa); | ||
| 680 | |||
| 681 | try bind.entries.append(gpa, .{ | ||
| 682 | .offset = 0x10, | ||
| 683 | .segment_id = 1, | ||
| 684 | .target = TestContext.Target{ .index = 0 }, | ||
| 685 | .addend = 0, | ||
| 686 | }); | ||
| 687 | try bind.entries.append(gpa, .{ | ||
| 688 | .offset = 0x20, | ||
| 689 | .segment_id = 2, | ||
| 690 | .target = TestContext.Target{ .index = 1 }, | ||
| 691 | .addend = 0x10, | ||
| 692 | }); | ||
| 693 | |||
| 694 | try bind.finalize(gpa, test_context); | ||
| 695 | try testing.expectEqualSlices(u8, &[_]u8{ | ||
| 696 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1, | ||
| 697 | 0x10, | ||
| 698 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 699 | 0x5f, | ||
| 700 | 0x69, | ||
| 701 | 0x6d, | ||
| 702 | 0x70, | ||
| 703 | 0x6f, | ||
| 704 | 0x72, | ||
| 705 | 0x74, | ||
| 706 | 0x5f, | ||
| 707 | 0x31, | ||
| 708 | 0x0, | ||
| 709 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 710 | macho.BIND_OPCODE_DO_BIND, | ||
| 711 | macho.BIND_OPCODE_DONE, | ||
| 712 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2, | ||
| 713 | 0x20, | ||
| 714 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0, | ||
| 715 | 0x5f, | ||
| 716 | 0x69, | ||
| 717 | 0x6d, | ||
| 718 | 0x70, | ||
| 719 | 0x6f, | ||
| 720 | 0x72, | ||
| 721 | 0x74, | ||
| 722 | 0x5f, | ||
| 723 | 0x32, | ||
| 724 | 0x0, | ||
| 725 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, | ||
| 726 | macho.BIND_OPCODE_SET_ADDEND_SLEB, | ||
| 727 | 0x10, | ||
| 728 | macho.BIND_OPCODE_DO_BIND, | ||
| 729 | macho.BIND_OPCODE_DONE, | ||
| 730 | }, bind.buffer.items); | ||
| 731 | } | ||
| 732 | |||
| 733 | const std = @import("std"); | ||
| 734 | const assert = std.debug.assert; | ||
| 735 | const leb = std.leb; | ||
| 736 | const log = std.log.scoped(.dyld_info); | ||
| 737 | const macho = std.macho; | ||
| 738 | const testing = std.testing; | ||
| 739 | |||
| 740 | const Allocator = std.mem.Allocator; |
src/link/MachO/eh_frame.zig+468-558| ... | @@ -1,628 +1,538 @@ | ... | @@ -1,628 +1,538 @@ |
| 1 | pub fn scanRelocs(macho_file: *MachO) !void { | 1 | pub const Cie = struct { |
| 2 | const comp = macho_file.base.comp; | 2 | /// Includes 4byte size cell. |
| 3 | const gpa = comp.gpa; | 3 | offset: u32, |
| 4 | 4 | out_offset: u32 = 0, | |
| 5 | for (macho_file.objects.items, 0..) |*object, object_id| { | 5 | size: u32, |
| 6 | var cies = std.AutoHashMap(u32, void).init(gpa); | 6 | lsda_size: ?enum { p32, p64 } = null, |
| 7 | defer cies.deinit(); | 7 | personality: ?Personality = null, |
| 8 | 8 | file: File.Index = 0, | |
| 9 | var it = object.getEhFrameRecordsIterator(); | 9 | alive: bool = false, |
| 10 | 10 | ||
| 11 | for (object.exec_atoms.items) |atom_index| { | 11 | pub fn parse(cie: *Cie, macho_file: *MachO) !void { |
| 12 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 12 | const tracy = trace(@src()); |
| 13 | while (inner_syms_it.next()) |sym| { | 13 | defer tracy.end(); |
| 14 | const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; | 14 | |
| 15 | if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue; | 15 | const data = cie.getData(macho_file); |
| 16 | it.seekTo(fde_offset); | 16 | const aug = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(data.ptr + 9)), 0); |
| 17 | const fde = (it.next() catch continue).?; // We don't care about this error since we already handled it | 17 | |
| 18 | 18 | if (aug[0] != 'z') return; // TODO should we error out? | |
| 19 | const cie_ptr = fde.getCiePointerSource(@intCast(object_id), macho_file, fde_offset); | 19 | |
| 20 | const cie_offset = fde_offset + 4 - cie_ptr; | 20 | var stream = std.io.fixedBufferStream(data[9 + aug.len + 1 ..]); |
| 21 | 21 | var creader = std.io.countingReader(stream.reader()); | |
| 22 | if (!cies.contains(cie_offset)) { | 22 | const reader = creader.reader(); |
| 23 | try cies.putNoClobber(cie_offset, {}); | 23 | |
| 24 | it.seekTo(cie_offset); | 24 | _ = try leb.readULEB128(u64, reader); // code alignment factor |
| 25 | const cie = (it.next() catch continue).?; // We don't care about this error since we already handled it | 25 | _ = try leb.readULEB128(u64, reader); // data alignment factor |
| 26 | try cie.scanRelocs(macho_file, @as(u32, @intCast(object_id)), cie_offset); | 26 | _ = try leb.readULEB128(u64, reader); // return address register |
| 27 | _ = try leb.readULEB128(u64, reader); // augmentation data length | ||
| 28 | |||
| 29 | for (aug[1..]) |ch| switch (ch) { | ||
| 30 | 'R' => { | ||
| 31 | const enc = try reader.readByte(); | ||
| 32 | if (enc & 0xf != EH_PE.absptr or enc & EH_PE.pcrel == 0) { | ||
| 33 | @panic("unexpected pointer encoding"); // TODO error | ||
| 27 | } | 34 | } |
| 28 | } | 35 | }, |
| 29 | } | 36 | 'P' => { |
| 30 | } | 37 | const enc = try reader.readByte(); |
| 31 | } | 38 | if (enc != EH_PE.pcrel | EH_PE.indirect | EH_PE.sdata4) { |
| 32 | 39 | @panic("unexpected personality pointer encoding"); // TODO error | |
| 33 | pub fn calcSectionSize(macho_file: *MachO, unwind_info: *const UnwindInfo) error{OutOfMemory}!void { | ||
| 34 | const sect_id = macho_file.eh_frame_section_index orelse return; | ||
| 35 | const sect = &macho_file.sections.items(.header)[sect_id]; | ||
| 36 | sect.@"align" = 3; | ||
| 37 | sect.size = 0; | ||
| 38 | |||
| 39 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 40 | const cpu_arch = target.cpu.arch; | ||
| 41 | const comp = macho_file.base.comp; | ||
| 42 | const gpa = comp.gpa; | ||
| 43 | var size: u32 = 0; | ||
| 44 | |||
| 45 | for (macho_file.objects.items, 0..) |*object, object_id| { | ||
| 46 | var cies = std.AutoHashMap(u32, u32).init(gpa); | ||
| 47 | defer cies.deinit(); | ||
| 48 | |||
| 49 | var eh_it = object.getEhFrameRecordsIterator(); | ||
| 50 | |||
| 51 | for (object.exec_atoms.items) |atom_index| { | ||
| 52 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | ||
| 53 | while (inner_syms_it.next()) |sym| { | ||
| 54 | const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue; | ||
| 55 | if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue; | ||
| 56 | |||
| 57 | const record_id = unwind_info.records_lookup.get(sym) orelse continue; | ||
| 58 | const record = unwind_info.records.items[record_id]; | ||
| 59 | |||
| 60 | // TODO skip this check if no __compact_unwind is present | ||
| 61 | const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | ||
| 62 | if (!is_dwarf) continue; | ||
| 63 | |||
| 64 | eh_it.seekTo(fde_record_offset); | ||
| 65 | const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error | ||
| 66 | |||
| 67 | const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset); | ||
| 68 | const cie_offset = fde_record_offset + 4 - cie_ptr; | ||
| 69 | |||
| 70 | const gop = try cies.getOrPut(cie_offset); | ||
| 71 | if (!gop.found_existing) { | ||
| 72 | eh_it.seekTo(cie_offset); | ||
| 73 | const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error | ||
| 74 | gop.value_ptr.* = size; | ||
| 75 | size += source_cie_record.getSize(); | ||
| 76 | } | 40 | } |
| 41 | _ = try reader.readInt(u32, .little); // personality pointer | ||
| 42 | }, | ||
| 43 | 'L' => { | ||
| 44 | const enc = try reader.readByte(); | ||
| 45 | switch (enc & 0xf) { | ||
| 46 | EH_PE.sdata4 => cie.lsda_size = .p32, | ||
| 47 | EH_PE.absptr => cie.lsda_size = .p64, | ||
| 48 | else => unreachable, // TODO error | ||
| 49 | } | ||
| 50 | }, | ||
| 51 | else => @panic("unexpected augmentation string"), // TODO error | ||
| 52 | }; | ||
| 53 | } | ||
| 77 | 54 | ||
| 78 | size += source_fde_record.getSize(); | 55 | pub inline fn getSize(cie: Cie) u32 { |
| 79 | } | 56 | return cie.size + 4; |
| 80 | } | 57 | } |
| 81 | 58 | ||
| 82 | sect.size = size; | 59 | pub fn getObject(cie: Cie, macho_file: *MachO) *Object { |
| 60 | const file = macho_file.getFile(cie.file).?; | ||
| 61 | return file.object; | ||
| 83 | } | 62 | } |
| 84 | } | ||
| 85 | 63 | ||
| 86 | pub fn write(macho_file: *MachO, unwind_info: *UnwindInfo) !void { | 64 | pub fn getData(cie: Cie, macho_file: *MachO) []const u8 { |
| 87 | const sect_id = macho_file.eh_frame_section_index orelse return; | 65 | const object = cie.getObject(macho_file); |
| 88 | const sect = macho_file.sections.items(.header)[sect_id]; | 66 | return object.eh_frame_data.items[cie.offset..][0..cie.getSize()]; |
| 89 | const seg_id = macho_file.sections.items(.segment_index)[sect_id]; | ||
| 90 | const seg = macho_file.segments.items[seg_id]; | ||
| 91 | |||
| 92 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 93 | const cpu_arch = target.cpu.arch; | ||
| 94 | const comp = macho_file.base.comp; | ||
| 95 | const gpa = comp.gpa; | ||
| 96 | |||
| 97 | var eh_records = std.AutoArrayHashMap(u32, EhFrameRecord(true)).init(gpa); | ||
| 98 | defer { | ||
| 99 | for (eh_records.values()) |*rec| { | ||
| 100 | rec.deinit(gpa); | ||
| 101 | } | ||
| 102 | eh_records.deinit(); | ||
| 103 | } | 67 | } |
| 104 | 68 | ||
| 105 | var eh_frame_offset: u32 = 0; | 69 | pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol { |
| 70 | const personality = cie.personality orelse return null; | ||
| 71 | return macho_file.getSymbol(personality.index); | ||
| 72 | } | ||
| 106 | 73 | ||
| 107 | for (macho_file.objects.items, 0..) |*object, object_id| { | 74 | pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool { |
| 108 | try eh_records.ensureUnusedCapacity(2 * @as(u32, @intCast(object.exec_atoms.items.len))); | 75 | if (!std.mem.eql(u8, cie.getData(macho_file), other.getData(macho_file))) return false; |
| 76 | if (cie.personality != null and other.personality != null) { | ||
| 77 | if (cie.personality.?.index != other.personality.?.index) return false; | ||
| 78 | } | ||
| 79 | if (cie.personality != null or other.personality != null) return false; | ||
| 80 | return true; | ||
| 81 | } | ||
| 109 | 82 | ||
| 110 | var cies = std.AutoHashMap(u32, u32).init(gpa); | 83 | pub fn format( |
| 111 | defer cies.deinit(); | 84 | cie: Cie, |
| 85 | comptime unused_fmt_string: []const u8, | ||
| 86 | options: std.fmt.FormatOptions, | ||
| 87 | writer: anytype, | ||
| 88 | ) !void { | ||
| 89 | _ = cie; | ||
| 90 | _ = unused_fmt_string; | ||
| 91 | _ = options; | ||
| 92 | _ = writer; | ||
| 93 | @compileError("do not format CIEs directly"); | ||
| 94 | } | ||
| 112 | 95 | ||
| 113 | var eh_it = object.getEhFrameRecordsIterator(); | 96 | pub fn fmt(cie: Cie, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 97 | return .{ .data = .{ | ||
| 98 | .cie = cie, | ||
| 99 | .macho_file = macho_file, | ||
| 100 | } }; | ||
| 101 | } | ||
| 114 | 102 | ||
| 115 | for (object.exec_atoms.items) |atom_index| { | 103 | const FormatContext = struct { |
| 116 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 104 | cie: Cie, |
| 117 | while (inner_syms_it.next()) |reloc_target| { | 105 | macho_file: *MachO, |
| 118 | const fde_record_offset = object.eh_frame_records_lookup.get(reloc_target) orelse continue; | 106 | }; |
| 119 | if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue; | ||
| 120 | 107 | ||
| 121 | const record_id = unwind_info.records_lookup.get(reloc_target) orelse continue; | 108 | fn format2( |
| 122 | const record = &unwind_info.records.items[record_id]; | 109 | ctx: FormatContext, |
| 110 | comptime unused_fmt_string: []const u8, | ||
| 111 | options: std.fmt.FormatOptions, | ||
| 112 | writer: anytype, | ||
| 113 | ) !void { | ||
| 114 | _ = unused_fmt_string; | ||
| 115 | _ = options; | ||
| 116 | const cie = ctx.cie; | ||
| 117 | try writer.print("@{x} : size({x})", .{ | ||
| 118 | cie.offset, | ||
| 119 | cie.getSize(), | ||
| 120 | }); | ||
| 121 | if (!cie.alive) try writer.writeAll(" : [*]"); | ||
| 122 | } | ||
| 123 | 123 | ||
| 124 | // TODO skip this check if no __compact_unwind is present | 124 | pub const Index = u32; |
| 125 | const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | ||
| 126 | if (!is_dwarf) continue; | ||
| 127 | 125 | ||
| 128 | eh_it.seekTo(fde_record_offset); | 126 | pub const Personality = struct { |
| 129 | const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error | 127 | index: Symbol.Index = 0, |
| 128 | offset: u32 = 0, | ||
| 129 | }; | ||
| 130 | }; | ||
| 130 | 131 | ||
| 131 | const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset); | 132 | pub const Fde = struct { |
| 132 | const cie_offset = fde_record_offset + 4 - cie_ptr; | 133 | /// Includes 4byte size cell. |
| 134 | offset: u32, | ||
| 135 | out_offset: u32 = 0, | ||
| 136 | size: u32, | ||
| 137 | cie: Cie.Index, | ||
| 138 | atom: Atom.Index = 0, | ||
| 139 | atom_offset: u32 = 0, | ||
| 140 | lsda: Atom.Index = 0, | ||
| 141 | lsda_offset: u32 = 0, | ||
| 142 | lsda_ptr_offset: u32 = 0, | ||
| 143 | file: File.Index = 0, | ||
| 144 | alive: bool = true, | ||
| 145 | |||
| 146 | pub fn parse(fde: *Fde, macho_file: *MachO) !void { | ||
| 147 | const tracy = trace(@src()); | ||
| 148 | defer tracy.end(); | ||
| 149 | |||
| 150 | const data = fde.getData(macho_file); | ||
| 151 | const object = fde.getObject(macho_file); | ||
| 152 | const sect = object.sections.items(.header)[object.eh_frame_sect_index.?]; | ||
| 153 | |||
| 154 | // Parse target atom index | ||
| 155 | const pc_begin = std.mem.readInt(i64, data[8..][0..8], .little); | ||
| 156 | const taddr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + 8)) + pc_begin); | ||
| 157 | fde.atom = object.findAtom(taddr) orelse { | ||
| 158 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: invalid function reference in FDE", .{ | ||
| 159 | object.fmtPath(), sect.segName(), sect.sectName(), fde.offset + 8, | ||
| 160 | }); | ||
| 161 | return error.ParseFailed; | ||
| 162 | }; | ||
| 163 | const atom = fde.getAtom(macho_file); | ||
| 164 | fde.atom_offset = @intCast(taddr - atom.getInputAddress(macho_file)); | ||
| 165 | |||
| 166 | // Associate with a CIE | ||
| 167 | const cie_ptr = std.mem.readInt(u32, data[4..8], .little); | ||
| 168 | const cie_offset = fde.offset + 4 - cie_ptr; | ||
| 169 | const cie_index = for (object.cies.items, 0..) |cie, cie_index| { | ||
| 170 | if (cie.offset == cie_offset) break @as(Cie.Index, @intCast(cie_index)); | ||
| 171 | } else null; | ||
| 172 | if (cie_index) |cie| { | ||
| 173 | fde.cie = cie; | ||
| 174 | } else { | ||
| 175 | macho_file.base.fatal("{}: no matching CIE found for FDE at offset {x}", .{ | ||
| 176 | object.fmtPath(), | ||
| 177 | fde.offset, | ||
| 178 | }); | ||
| 179 | return error.ParseFailed; | ||
| 180 | } | ||
| 133 | 181 | ||
| 134 | const gop = try cies.getOrPut(cie_offset); | 182 | const cie = fde.getCie(macho_file); |
| 135 | if (!gop.found_existing) { | ||
| 136 | eh_it.seekTo(cie_offset); | ||
| 137 | const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error | ||
| 138 | var cie_record = try source_cie_record.toOwned(gpa); | ||
| 139 | try cie_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{ | ||
| 140 | .source_offset = cie_offset, | ||
| 141 | .out_offset = eh_frame_offset, | ||
| 142 | .sect_addr = sect.addr, | ||
| 143 | }); | ||
| 144 | eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record); | ||
| 145 | gop.value_ptr.* = eh_frame_offset; | ||
| 146 | eh_frame_offset += cie_record.getSize(); | ||
| 147 | } | ||
| 148 | 183 | ||
| 149 | var fde_record = try source_fde_record.toOwned(gpa); | 184 | // Parse LSDA atom index if any |
| 150 | try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{ | 185 | if (cie.lsda_size) |lsda_size| { |
| 151 | .source_offset = fde_record_offset, | 186 | var stream = std.io.fixedBufferStream(data[24..]); |
| 152 | .out_offset = eh_frame_offset, | 187 | var creader = std.io.countingReader(stream.reader()); |
| 153 | .sect_addr = sect.addr, | 188 | const reader = creader.reader(); |
| 189 | _ = try leb.readULEB128(u64, reader); // augmentation length | ||
| 190 | fde.lsda_ptr_offset = @intCast(creader.bytes_read + 24); | ||
| 191 | const lsda_ptr = switch (lsda_size) { | ||
| 192 | .p32 => try reader.readInt(i32, .little), | ||
| 193 | .p64 => try reader.readInt(i64, .little), | ||
| 194 | }; | ||
| 195 | const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr); | ||
| 196 | fde.lsda = object.findAtom(lsda_addr) orelse { | ||
| 197 | macho_file.base.fatal("{}: {s},{s}: 0x{x}: invalid LSDA reference in FDE", .{ | ||
| 198 | object.fmtPath(), sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset, | ||
| 154 | }); | 199 | }); |
| 155 | fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*); | 200 | return error.ParseFailed; |
| 156 | 201 | }; | |
| 157 | switch (cpu_arch) { | 202 | const lsda_atom = fde.getLsdaAtom(macho_file).?; |
| 158 | .aarch64 => {}, // relocs take care of LSDA pointers | 203 | fde.lsda_offset = @intCast(lsda_addr - lsda_atom.getInputAddress(macho_file)); |
| 159 | .x86_64 => { | ||
| 160 | // We need to relocate target symbol address ourselves. | ||
| 161 | const atom_sym = macho_file.getSymbol(reloc_target); | ||
| 162 | try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{ | ||
| 163 | .base_addr = sect.addr, | ||
| 164 | .base_offset = eh_frame_offset, | ||
| 165 | }); | ||
| 166 | |||
| 167 | // We need to parse LSDA pointer and relocate ourselves. | ||
| 168 | const cie_record = eh_records.get( | ||
| 169 | eh_frame_offset + 4 - fde_record.getCiePointer(), | ||
| 170 | ).?; | ||
| 171 | const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?); | ||
| 172 | const source_lsda_ptr = fde_record.getLsdaPointer(cie_record, .{ | ||
| 173 | .base_addr = eh_frame_sect.addr, | ||
| 174 | .base_offset = fde_record_offset, | ||
| 175 | }) catch continue; // We already handled this error | ||
| 176 | if (source_lsda_ptr) |ptr| { | ||
| 177 | const sym_index = object.getSymbolByAddress(ptr, null); | ||
| 178 | const sym = object.symtab[sym_index]; | ||
| 179 | fde_record.setLsdaPointer(cie_record, sym.n_value, .{ | ||
| 180 | .base_addr = sect.addr, | ||
| 181 | .base_offset = eh_frame_offset, | ||
| 182 | }) catch continue; // We already handled this error | ||
| 183 | } | ||
| 184 | }, | ||
| 185 | else => unreachable, | ||
| 186 | } | ||
| 187 | |||
| 188 | eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record); | ||
| 189 | |||
| 190 | UnwindInfo.UnwindEncoding.setDwarfSectionOffset( | ||
| 191 | &record.compactUnwindEncoding, | ||
| 192 | cpu_arch, | ||
| 193 | @as(u24, @intCast(eh_frame_offset)), | ||
| 194 | ); | ||
| 195 | |||
| 196 | const cie_record = eh_records.get( | ||
| 197 | eh_frame_offset + 4 - fde_record.getCiePointer(), | ||
| 198 | ).?; | ||
| 199 | const lsda_ptr = fde_record.getLsdaPointer(cie_record, .{ | ||
| 200 | .base_addr = sect.addr, | ||
| 201 | .base_offset = eh_frame_offset, | ||
| 202 | }) catch continue; // We already handled this error | ||
| 203 | if (lsda_ptr) |ptr| { | ||
| 204 | record.lsda = ptr - seg.vmaddr; | ||
| 205 | } | ||
| 206 | |||
| 207 | eh_frame_offset += fde_record.getSize(); | ||
| 208 | } | ||
| 209 | } | 204 | } |
| 210 | } | 205 | } |
| 211 | 206 | ||
| 212 | var buffer = std.ArrayList(u8).init(gpa); | 207 | pub inline fn getSize(fde: Fde) u32 { |
| 213 | defer buffer.deinit(); | 208 | return fde.size + 4; |
| 214 | const writer = buffer.writer(); | 209 | } |
| 215 | 210 | ||
| 216 | for (eh_records.values()) |record| { | 211 | pub fn getObject(fde: Fde, macho_file: *MachO) *Object { |
| 217 | try writer.writeInt(u32, record.size, .little); | 212 | const file = macho_file.getFile(fde.file).?; |
| 218 | try buffer.appendSlice(record.data); | 213 | return file.object; |
| 219 | } | 214 | } |
| 220 | 215 | ||
| 221 | try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset); | 216 | pub fn getData(fde: Fde, macho_file: *MachO) []const u8 { |
| 222 | } | 217 | const object = fde.getObject(macho_file); |
| 223 | const EhFrameRecordTag = enum { cie, fde }; | 218 | return object.eh_frame_data.items[fde.offset..][0..fde.getSize()]; |
| 219 | } | ||
| 224 | 220 | ||
| 225 | pub fn EhFrameRecord(comptime is_mutable: bool) type { | 221 | pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie { |
| 226 | return struct { | 222 | const object = fde.getObject(macho_file); |
| 227 | tag: EhFrameRecordTag, | 223 | return &object.cies.items[fde.cie]; |
| 228 | size: u32, | 224 | } |
| 229 | data: if (is_mutable) []u8 else []const u8, | ||
| 230 | 225 | ||
| 231 | const Record = @This(); | 226 | pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom { |
| 227 | return macho_file.getAtom(fde.atom).?; | ||
| 228 | } | ||
| 232 | 229 | ||
| 233 | pub fn deinit(rec: *Record, gpa: Allocator) void { | 230 | pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom { |
| 234 | comptime assert(is_mutable); | 231 | return macho_file.getAtom(fde.lsda); |
| 235 | gpa.free(rec.data); | 232 | } |
| 236 | } | ||
| 237 | 233 | ||
| 238 | pub fn toOwned(rec: Record, gpa: Allocator) Allocator.Error!EhFrameRecord(true) { | 234 | pub fn format( |
| 239 | const data = try gpa.dupe(u8, rec.data); | 235 | fde: Fde, |
| 240 | return EhFrameRecord(true){ | 236 | comptime unused_fmt_string: []const u8, |
| 241 | .tag = rec.tag, | 237 | options: std.fmt.FormatOptions, |
| 242 | .size = rec.size, | 238 | writer: anytype, |
| 243 | .data = data, | 239 | ) !void { |
| 244 | }; | 240 | _ = fde; |
| 245 | } | 241 | _ = unused_fmt_string; |
| 242 | _ = options; | ||
| 243 | _ = writer; | ||
| 244 | @compileError("do not format FDEs directly"); | ||
| 245 | } | ||
| 246 | 246 | ||
| 247 | pub inline fn getSize(rec: Record) u32 { | 247 | pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 248 | return 4 + rec.size; | 248 | return .{ .data = .{ |
| 249 | } | 249 | .fde = fde, |
| 250 | .macho_file = macho_file, | ||
| 251 | } }; | ||
| 252 | } | ||
| 250 | 253 | ||
| 251 | pub fn scanRelocs( | 254 | const FormatContext = struct { |
| 252 | rec: Record, | 255 | fde: Fde, |
| 253 | macho_file: *MachO, | 256 | macho_file: *MachO, |
| 254 | object_id: u32, | 257 | }; |
| 255 | source_offset: u32, | ||
| 256 | ) !void { | ||
| 257 | if (rec.getPersonalityPointerReloc(macho_file, object_id, source_offset)) |target| { | ||
| 258 | try macho_file.addGotEntry(target); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | 258 | ||
| 262 | pub fn getTargetSymbolAddress(rec: Record, ctx: struct { | 259 | fn format2( |
| 263 | base_addr: u64, | 260 | ctx: FormatContext, |
| 264 | base_offset: u64, | 261 | comptime unused_fmt_string: []const u8, |
| 265 | }) u64 { | 262 | options: std.fmt.FormatOptions, |
| 266 | assert(rec.tag == .fde); | 263 | writer: anytype, |
| 267 | const addend = mem.readInt(i64, rec.data[4..][0..8], .little); | 264 | ) !void { |
| 268 | return @as(u64, @intCast(@as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8)) + addend)); | 265 | _ = unused_fmt_string; |
| 269 | } | 266 | _ = options; |
| 267 | const fde = ctx.fde; | ||
| 268 | const macho_file = ctx.macho_file; | ||
| 269 | try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{ | ||
| 270 | fde.offset, | ||
| 271 | fde.getSize(), | ||
| 272 | fde.cie, | ||
| 273 | fde.getAtom(macho_file).getName(macho_file), | ||
| 274 | }); | ||
| 275 | if (!fde.alive) try writer.writeAll(" : [*]"); | ||
| 276 | } | ||
| 270 | 277 | ||
| 271 | pub fn setTargetSymbolAddress(rec: *Record, value: u64, ctx: struct { | 278 | pub const Index = u32; |
| 272 | base_addr: u64, | 279 | }; |
| 273 | base_offset: u64, | ||
| 274 | }) !void { | ||
| 275 | assert(rec.tag == .fde); | ||
| 276 | const addend = @as(i64, @intCast(value)) - @as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8)); | ||
| 277 | mem.writeInt(i64, rec.data[4..][0..8], addend, .little); | ||
| 278 | } | ||
| 279 | 280 | ||
| 280 | pub fn getPersonalityPointerReloc( | 281 | pub const Iterator = struct { |
| 281 | rec: Record, | 282 | data: []const u8, |
| 282 | macho_file: *MachO, | 283 | pos: u32 = 0, |
| 283 | object_id: u32, | ||
| 284 | source_offset: u32, | ||
| 285 | ) ?SymbolWithLoc { | ||
| 286 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 287 | const cpu_arch = target.cpu.arch; | ||
| 288 | const relocs = getRelocs(macho_file, object_id, source_offset); | ||
| 289 | for (relocs) |rel| { | ||
| 290 | switch (cpu_arch) { | ||
| 291 | .aarch64 => { | ||
| 292 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | ||
| 293 | switch (rel_type) { | ||
| 294 | .ARM64_RELOC_SUBTRACTOR, | ||
| 295 | .ARM64_RELOC_UNSIGNED, | ||
| 296 | => continue, | ||
| 297 | .ARM64_RELOC_POINTER_TO_GOT => {}, | ||
| 298 | else => unreachable, | ||
| 299 | } | ||
| 300 | }, | ||
| 301 | .x86_64 => { | ||
| 302 | const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type)); | ||
| 303 | switch (rel_type) { | ||
| 304 | .X86_64_RELOC_GOT => {}, | ||
| 305 | else => unreachable, | ||
| 306 | } | ||
| 307 | }, | ||
| 308 | else => unreachable, | ||
| 309 | } | ||
| 310 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | ||
| 311 | .object_id = object_id, | ||
| 312 | .rel = rel, | ||
| 313 | .code = rec.data, | ||
| 314 | .base_offset = @as(i32, @intCast(source_offset)) + 4, | ||
| 315 | }); | ||
| 316 | return reloc_target; | ||
| 317 | } | ||
| 318 | return null; | ||
| 319 | } | ||
| 320 | 284 | ||
| 321 | pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct { | 285 | pub const Record = struct { |
| 322 | source_offset: u32, | 286 | tag: enum { fde, cie }, |
| 323 | out_offset: u32, | 287 | offset: u32, |
| 324 | sect_addr: u64, | 288 | size: u32, |
| 325 | }) !void { | 289 | }; |
| 326 | comptime assert(is_mutable); | ||
| 327 | |||
| 328 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 329 | const cpu_arch = target.cpu.arch; | ||
| 330 | const relocs = getRelocs(macho_file, object_id, ctx.source_offset); | ||
| 331 | |||
| 332 | for (relocs) |rel| { | ||
| 333 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | ||
| 334 | .object_id = object_id, | ||
| 335 | .rel = rel, | ||
| 336 | .code = rec.data, | ||
| 337 | .base_offset = @as(i32, @intCast(ctx.source_offset)) + 4, | ||
| 338 | }); | ||
| 339 | const rel_offset = @as(u32, @intCast(rel.r_address - @as(i32, @intCast(ctx.source_offset)) - 4)); | ||
| 340 | const source_addr = ctx.sect_addr + rel_offset + ctx.out_offset + 4; | ||
| 341 | |||
| 342 | switch (cpu_arch) { | ||
| 343 | .aarch64 => { | ||
| 344 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | ||
| 345 | switch (rel_type) { | ||
| 346 | .ARM64_RELOC_SUBTRACTOR => { | ||
| 347 | // Address of the __eh_frame in the source object file | ||
| 348 | }, | ||
| 349 | .ARM64_RELOC_POINTER_TO_GOT => { | ||
| 350 | const target_addr = macho_file.getGotEntryAddress(reloc_target).?; | ||
| 351 | const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse | ||
| 352 | return error.Overflow; | ||
| 353 | mem.writeInt(i32, rec.data[rel_offset..][0..4], result, .little); | ||
| 354 | }, | ||
| 355 | .ARM64_RELOC_UNSIGNED => { | ||
| 356 | assert(rel.r_extern == 1); | ||
| 357 | const target_addr = Atom.getRelocTargetAddress(macho_file, reloc_target, false); | ||
| 358 | const result = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)); | ||
| 359 | mem.writeInt(i64, rec.data[rel_offset..][0..8], @as(i64, @intCast(result)), .little); | ||
| 360 | }, | ||
| 361 | else => unreachable, | ||
| 362 | } | ||
| 363 | }, | ||
| 364 | .x86_64 => { | ||
| 365 | const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type)); | ||
| 366 | switch (rel_type) { | ||
| 367 | .X86_64_RELOC_GOT => { | ||
| 368 | const target_addr = macho_file.getGotEntryAddress(reloc_target).?; | ||
| 369 | const addend = mem.readInt(i32, rec.data[rel_offset..][0..4], .little); | ||
| 370 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | ||
| 371 | const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0); | ||
| 372 | mem.writeInt(i32, rec.data[rel_offset..][0..4], disp, .little); | ||
| 373 | }, | ||
| 374 | else => unreachable, | ||
| 375 | } | ||
| 376 | }, | ||
| 377 | else => unreachable, | ||
| 378 | } | ||
| 379 | } | ||
| 380 | } | ||
| 381 | 290 | ||
| 382 | pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 { | 291 | pub fn next(it: *Iterator) !?Record { |
| 383 | assert(rec.tag == .fde); | 292 | if (it.pos >= it.data.len) return null; |
| 384 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 385 | const cpu_arch = target.cpu.arch; | ||
| 386 | const addend = mem.readInt(u32, rec.data[0..4], .little); | ||
| 387 | switch (cpu_arch) { | ||
| 388 | .aarch64 => { | ||
| 389 | const relocs = getRelocs(macho_file, object_id, offset); | ||
| 390 | const maybe_rel = for (relocs) |rel| { | ||
| 391 | if (rel.r_address - @as(i32, @intCast(offset)) == 4 and | ||
| 392 | @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_SUBTRACTOR) | ||
| 393 | break rel; | ||
| 394 | } else null; | ||
| 395 | const rel = maybe_rel orelse return addend; | ||
| 396 | const object = &macho_file.objects.items[object_id]; | ||
| 397 | const target_addr = object.in_symtab.?[rel.r_symbolnum].n_value; | ||
| 398 | const sect = object.getSourceSection(object.eh_frame_sect_id.?); | ||
| 399 | return @intCast(sect.addr + offset - target_addr + addend); | ||
| 400 | }, | ||
| 401 | .x86_64 => return addend, | ||
| 402 | else => unreachable, | ||
| 403 | } | ||
| 404 | } | ||
| 405 | 293 | ||
| 406 | pub fn getCiePointer(rec: Record) u32 { | 294 | var stream = std.io.fixedBufferStream(it.data[it.pos..]); |
| 407 | assert(rec.tag == .fde); | 295 | const reader = stream.reader(); |
| 408 | return mem.readInt(u32, rec.data[0..4], .little); | ||
| 409 | } | ||
| 410 | 296 | ||
| 411 | pub fn setCiePointer(rec: *Record, ptr: u32) void { | 297 | const size = try reader.readInt(u32, .little); |
| 412 | assert(rec.tag == .fde); | 298 | if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS"); |
| 413 | mem.writeInt(u32, rec.data[0..4], ptr, .little); | ||
| 414 | } | ||
| 415 | 299 | ||
| 416 | pub fn getAugmentationString(rec: Record) []const u8 { | 300 | const id = try reader.readInt(u32, .little); |
| 417 | assert(rec.tag == .cie); | 301 | const record = Record{ |
| 418 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(rec.data.ptr + 5)), 0); | 302 | .tag = if (id == 0) .cie else .fde, |
| 419 | } | 303 | .offset = it.pos, |
| 304 | .size = size, | ||
| 305 | }; | ||
| 306 | it.pos += size + 4; | ||
| 420 | 307 | ||
| 421 | pub fn getPersonalityPointer(rec: Record, ctx: struct { | 308 | return record; |
| 422 | base_addr: u64, | 309 | } |
| 423 | base_offset: u64, | 310 | }; |
| 424 | }) !?u64 { | ||
| 425 | assert(rec.tag == .cie); | ||
| 426 | const aug_str = rec.getAugmentationString(); | ||
| 427 | 311 | ||
| 428 | var stream = std.io.fixedBufferStream(rec.data[9 + aug_str.len ..]); | 312 | pub fn calcSize(macho_file: *MachO) !u32 { |
| 429 | var creader = std.io.countingReader(stream.reader()); | 313 | const tracy = trace(@src()); |
| 430 | const reader = creader.reader(); | 314 | defer tracy.end(); |
| 431 | 315 | ||
| 432 | for (aug_str, 0..) |ch, i| switch (ch) { | 316 | var offset: u32 = 0; |
| 433 | 'z' => if (i > 0) { | ||
| 434 | return error.BadDwarfCfi; | ||
| 435 | } else { | ||
| 436 | _ = try leb.readULEB128(u64, reader); | ||
| 437 | }, | ||
| 438 | 'R' => { | ||
| 439 | _ = try reader.readByte(); | ||
| 440 | }, | ||
| 441 | 'P' => { | ||
| 442 | const enc = try reader.readByte(); | ||
| 443 | const offset = ctx.base_offset + 13 + aug_str.len + creader.bytes_read; | ||
| 444 | const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader); | ||
| 445 | return ptr; | ||
| 446 | }, | ||
| 447 | 'L' => { | ||
| 448 | _ = try reader.readByte(); | ||
| 449 | }, | ||
| 450 | 'S', 'B', 'G' => {}, | ||
| 451 | else => return error.BadDwarfCfi, | ||
| 452 | }; | ||
| 453 | 317 | ||
| 454 | return null; | 318 | var cies = std.ArrayList(Cie).init(macho_file.base.allocator); |
| 455 | } | 319 | defer cies.deinit(); |
| 320 | |||
| 321 | for (macho_file.objects.items) |index| { | ||
| 322 | const object = macho_file.getFile(index).?.object; | ||
| 456 | 323 | ||
| 457 | pub fn getLsdaPointer(rec: Record, cie: Record, ctx: struct { | 324 | outer: for (object.cies.items) |*cie| { |
| 458 | base_addr: u64, | 325 | for (cies.items) |other| { |
| 459 | base_offset: u64, | 326 | if (other.eql(cie.*, macho_file)) { |
| 460 | }) !?u64 { | 327 | // We already have a CIE record that has the exact same contents, so instead of |
| 461 | assert(rec.tag == .fde); | 328 | // duplicating them, we mark this one dead and set its output offset to be |
| 462 | const enc = (try cie.getLsdaEncoding()) orelse return null; | 329 | // equal to that of the alive record. This way, we won't have to rewrite |
| 463 | var stream = std.io.fixedBufferStream(rec.data[20..]); | 330 | // Fde.cie_index field when committing the records to file. |
| 464 | const reader = stream.reader(); | 331 | cie.out_offset = other.out_offset; |
| 465 | _ = try reader.readByte(); | 332 | continue :outer; |
| 466 | const offset = ctx.base_offset + 25; | 333 | } |
| 467 | const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader); | 334 | } |
| 468 | return ptr; | 335 | cie.alive = true; |
| 336 | cie.out_offset = offset; | ||
| 337 | offset += cie.getSize(); | ||
| 338 | try cies.append(cie.*); | ||
| 469 | } | 339 | } |
| 340 | } | ||
| 470 | 341 | ||
| 471 | pub fn setLsdaPointer(rec: *Record, cie: Record, value: u64, ctx: struct { | 342 | for (macho_file.objects.items) |index| { |
| 472 | base_addr: u64, | 343 | const object = macho_file.getFile(index).?.object; |
| 473 | base_offset: u64, | 344 | for (object.fdes.items) |*fde| { |
| 474 | }) !void { | 345 | if (!fde.alive) continue; |
| 475 | assert(rec.tag == .fde); | 346 | fde.out_offset = offset; |
| 476 | const enc = (try cie.getLsdaEncoding()) orelse unreachable; | 347 | offset += fde.getSize(); |
| 477 | var stream = std.io.fixedBufferStream(rec.data[21..]); | ||
| 478 | const writer = stream.writer(); | ||
| 479 | const offset = ctx.base_offset + 25; | ||
| 480 | try setEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), value, writer); | ||
| 481 | } | 348 | } |
| 349 | } | ||
| 482 | 350 | ||
| 483 | fn getLsdaEncoding(rec: Record) !?u8 { | 351 | return offset; |
| 484 | assert(rec.tag == .cie); | 352 | } |
| 485 | const aug_str = rec.getAugmentationString(); | ||
| 486 | 353 | ||
| 487 | const base_offset = 9 + aug_str.len; | 354 | pub fn calcNumRelocs(macho_file: *MachO) u32 { |
| 488 | var stream = std.io.fixedBufferStream(rec.data[base_offset..]); | 355 | const tracy = trace(@src()); |
| 489 | var creader = std.io.countingReader(stream.reader()); | 356 | defer tracy.end(); |
| 490 | const reader = creader.reader(); | ||
| 491 | 357 | ||
| 492 | for (aug_str, 0..) |ch, i| switch (ch) { | 358 | var nreloc: u32 = 0; |
| 493 | 'z' => if (i > 0) { | ||
| 494 | return error.BadDwarfCfi; | ||
| 495 | } else { | ||
| 496 | _ = try leb.readULEB128(u64, reader); | ||
| 497 | }, | ||
| 498 | 'R' => { | ||
| 499 | _ = try reader.readByte(); | ||
| 500 | }, | ||
| 501 | 'P' => { | ||
| 502 | const enc = try reader.readByte(); | ||
| 503 | _ = try getEncodedPointer(enc, 0, reader); | ||
| 504 | }, | ||
| 505 | 'L' => { | ||
| 506 | const enc = try reader.readByte(); | ||
| 507 | return enc; | ||
| 508 | }, | ||
| 509 | 'S', 'B', 'G' => {}, | ||
| 510 | else => return error.BadDwarfCfi, | ||
| 511 | }; | ||
| 512 | 359 | ||
| 513 | return null; | 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 | if (cie.getPersonality(macho_file)) |_| { | ||
| 365 | nreloc += 1; // personality | ||
| 366 | } | ||
| 514 | } | 367 | } |
| 368 | } | ||
| 515 | 369 | ||
| 516 | fn getEncodedPointer(enc: u8, pcrel_offset: i64, reader: anytype) !?u64 { | 370 | return nreloc; |
| 517 | if (enc == EH_PE.omit) return null; | 371 | } |
| 518 | |||
| 519 | var ptr: i64 = switch (enc & 0x0F) { | ||
| 520 | EH_PE.absptr => @as(i64, @bitCast(try reader.readInt(u64, .little))), | ||
| 521 | EH_PE.udata2 => @as(i16, @bitCast(try reader.readInt(u16, .little))), | ||
| 522 | EH_PE.udata4 => @as(i32, @bitCast(try reader.readInt(u32, .little))), | ||
| 523 | EH_PE.udata8 => @as(i64, @bitCast(try reader.readInt(u64, .little))), | ||
| 524 | EH_PE.uleb128 => @as(i64, @bitCast(try leb.readULEB128(u64, reader))), | ||
| 525 | EH_PE.sdata2 => try reader.readInt(i16, .little), | ||
| 526 | EH_PE.sdata4 => try reader.readInt(i32, .little), | ||
| 527 | EH_PE.sdata8 => try reader.readInt(i64, .little), | ||
| 528 | EH_PE.sleb128 => try leb.readILEB128(i64, reader), | ||
| 529 | else => return null, | ||
| 530 | }; | ||
| 531 | 372 | ||
| 532 | switch (enc & 0x70) { | 373 | pub fn write(macho_file: *MachO, buffer: []u8) void { |
| 533 | EH_PE.absptr => {}, | 374 | const tracy = trace(@src()); |
| 534 | EH_PE.pcrel => ptr += pcrel_offset, | 375 | defer tracy.end(); |
| 535 | EH_PE.datarel, | 376 | |
| 536 | EH_PE.textrel, | 377 | const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; |
| 537 | EH_PE.funcrel, | 378 | const addend: i64 = switch (macho_file.options.cpu_arch.?) { |
| 538 | EH_PE.aligned, | 379 | .x86_64 => 4, |
| 539 | => return null, | 380 | else => 0, |
| 540 | else => return null, | 381 | }; |
| 541 | } | ||
| 542 | 382 | ||
| 543 | return @as(u64, @bitCast(ptr)); | 383 | for (macho_file.objects.items) |index| { |
| 384 | const object = macho_file.getFile(index).?.object; | ||
| 385 | for (object.cies.items) |cie| { | ||
| 386 | if (!cie.alive) continue; | ||
| 387 | |||
| 388 | @memcpy(buffer[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file)); | ||
| 389 | |||
| 390 | if (cie.getPersonality(macho_file)) |sym| { | ||
| 391 | const offset = cie.out_offset + cie.personality.?.offset; | ||
| 392 | const saddr = sect.addr + offset; | ||
| 393 | const taddr = sym.getGotAddress(macho_file); | ||
| 394 | std.mem.writeInt( | ||
| 395 | i32, | ||
| 396 | buffer[offset..][0..4], | ||
| 397 | @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend), | ||
| 398 | .little, | ||
| 399 | ); | ||
| 400 | } | ||
| 544 | } | 401 | } |
| 402 | } | ||
| 403 | |||
| 404 | for (macho_file.objects.items) |index| { | ||
| 405 | const object = macho_file.getFile(index).?.object; | ||
| 406 | for (object.fdes.items) |fde| { | ||
| 407 | if (!fde.alive) continue; | ||
| 545 | 408 | ||
| 546 | fn setEncodedPointer(enc: u8, pcrel_offset: i64, value: u64, writer: anytype) !void { | 409 | @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file)); |
| 547 | if (enc == EH_PE.omit) return; | ||
| 548 | 410 | ||
| 549 | var actual = @as(i64, @intCast(value)); | 411 | { |
| 412 | const offset = fde.out_offset + 4; | ||
| 413 | const value = offset - fde.getCie(macho_file).out_offset; | ||
| 414 | std.mem.writeInt(u32, buffer[offset..][0..4], value, .little); | ||
| 415 | } | ||
| 550 | 416 | ||
| 551 | switch (enc & 0x70) { | 417 | { |
| 552 | EH_PE.absptr => {}, | 418 | const offset = fde.out_offset + 8; |
| 553 | EH_PE.pcrel => actual -= pcrel_offset, | 419 | const saddr = sect.addr + offset; |
| 554 | EH_PE.datarel, | 420 | const taddr = fde.getAtom(macho_file).value; |
| 555 | EH_PE.textrel, | 421 | std.mem.writeInt( |
| 556 | EH_PE.funcrel, | 422 | i64, |
| 557 | EH_PE.aligned, | 423 | buffer[offset..][0..8], |
| 558 | => unreachable, | 424 | @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), |
| 559 | else => unreachable, | 425 | .little, |
| 426 | ); | ||
| 560 | } | 427 | } |
| 561 | 428 | ||
| 562 | switch (enc & 0x0F) { | 429 | if (fde.getLsdaAtom(macho_file)) |atom| { |
| 563 | EH_PE.absptr => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little), | 430 | const offset = fde.out_offset + fde.lsda_offset; |
| 564 | EH_PE.udata2 => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(actual)))), .little), | 431 | const saddr = sect.addr + offset; |
| 565 | EH_PE.udata4 => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(actual)))), .little), | 432 | const taddr = atom.value; |
| 566 | EH_PE.udata8 => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little), | 433 | switch (fde.getCie(macho_file).lsda_size.?) { |
| 567 | EH_PE.uleb128 => try leb.writeULEB128(writer, @as(u64, @bitCast(actual))), | 434 | .p32 => std.mem.writeInt( |
| 568 | EH_PE.sdata2 => try writer.writeInt(i16, @as(i16, @intCast(actual)), .little), | 435 | i32, |
| 569 | EH_PE.sdata4 => try writer.writeInt(i32, @as(i32, @intCast(actual)), .little), | 436 | buffer[offset..][0..4], |
| 570 | EH_PE.sdata8 => try writer.writeInt(i64, actual, .little), | 437 | @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend), |
| 571 | EH_PE.sleb128 => try leb.writeILEB128(writer, actual), | 438 | .little, |
| 572 | else => unreachable, | 439 | ), |
| 440 | .p64 => std.mem.writeInt( | ||
| 441 | i64, | ||
| 442 | buffer[offset..][0..8], | ||
| 443 | @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), | ||
| 444 | .little, | ||
| 445 | ), | ||
| 446 | } | ||
| 573 | } | 447 | } |
| 574 | } | 448 | } |
| 575 | }; | 449 | } |
| 576 | } | ||
| 577 | |||
| 578 | pub fn getRelocs(macho_file: *MachO, object_id: u32, source_offset: u32) []const macho.relocation_info { | ||
| 579 | const object = &macho_file.objects.items[object_id]; | ||
| 580 | assert(object.hasEhFrameRecords()); | ||
| 581 | const urel = object.eh_frame_relocs_lookup.get(source_offset) orelse | ||
| 582 | return &[0]macho.relocation_info{}; | ||
| 583 | const all_relocs = object.getRelocs(object.eh_frame_sect_id.?); | ||
| 584 | return all_relocs[urel.reloc.start..][0..urel.reloc.len]; | ||
| 585 | } | 450 | } |
| 586 | 451 | ||
| 587 | pub const Iterator = struct { | 452 | pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void { |
| 588 | data: []const u8, | 453 | const tracy = trace(@src()); |
| 589 | pos: u32 = 0, | 454 | defer tracy.end(); |
| 590 | 455 | ||
| 591 | pub fn next(it: *Iterator) !?EhFrameRecord(false) { | 456 | const cpu_arch = macho_file.options.cpu_arch.?; |
| 592 | if (it.pos >= it.data.len) return null; | 457 | const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; |
| 593 | 458 | const addend: i64 = switch (cpu_arch) { | |
| 594 | var stream = std.io.fixedBufferStream(it.data[it.pos..]); | 459 | .x86_64 => 4, |
| 595 | const reader = stream.reader(); | 460 | else => 0, |
| 461 | }; | ||
| 596 | 462 | ||
| 597 | const size = try reader.readInt(u32, .little); | 463 | for (macho_file.objects.items) |index| { |
| 598 | if (size == 0xFFFFFFFF) { | 464 | const object = macho_file.getFile(index).?.object; |
| 599 | log.debug("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{}); | 465 | for (object.cies.items) |cie| { |
| 600 | return error.BadDwarfCfi; | 466 | if (!cie.alive) continue; |
| 467 | |||
| 468 | @memcpy(code[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file)); | ||
| 469 | |||
| 470 | if (cie.getPersonality(macho_file)) |sym| { | ||
| 471 | const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow; | ||
| 472 | const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; | ||
| 473 | relocs.appendAssumeCapacity(.{ | ||
| 474 | .r_address = r_address, | ||
| 475 | .r_symbolnum = r_symbolnum, | ||
| 476 | .r_length = 2, | ||
| 477 | .r_extern = 1, | ||
| 478 | .r_pcrel = 1, | ||
| 479 | .r_type = switch (cpu_arch) { | ||
| 480 | .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_POINTER_TO_GOT), | ||
| 481 | .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT), | ||
| 482 | else => unreachable, | ||
| 483 | }, | ||
| 484 | }); | ||
| 485 | } | ||
| 601 | } | 486 | } |
| 487 | } | ||
| 602 | 488 | ||
| 603 | const id = try reader.readInt(u32, .little); | 489 | for (macho_file.objects.items) |index| { |
| 604 | const tag: EhFrameRecordTag = if (id == 0) .cie else .fde; | 490 | const object = macho_file.getFile(index).?.object; |
| 605 | const offset: u32 = 4; | 491 | for (object.fdes.items) |fde| { |
| 606 | const record = EhFrameRecord(false){ | 492 | if (!fde.alive) continue; |
| 607 | .tag = tag, | ||
| 608 | .size = size, | ||
| 609 | .data = it.data[it.pos + offset ..][0..size], | ||
| 610 | }; | ||
| 611 | 493 | ||
| 612 | it.pos += size + offset; | 494 | @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file)); |
| 613 | 495 | ||
| 614 | return record; | 496 | { |
| 615 | } | 497 | const offset = fde.out_offset + 4; |
| 498 | const value = offset - fde.getCie(macho_file).out_offset; | ||
| 499 | std.mem.writeInt(u32, code[offset..][0..4], value, .little); | ||
| 500 | } | ||
| 616 | 501 | ||
| 617 | pub fn reset(it: *Iterator) void { | 502 | { |
| 618 | it.pos = 0; | 503 | const offset = fde.out_offset + 8; |
| 619 | } | 504 | const saddr = sect.addr + offset; |
| 505 | const taddr = fde.getAtom(macho_file).value; | ||
| 506 | std.mem.writeInt( | ||
| 507 | i64, | ||
| 508 | code[offset..][0..8], | ||
| 509 | @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), | ||
| 510 | .little, | ||
| 511 | ); | ||
| 512 | } | ||
| 620 | 513 | ||
| 621 | pub fn seekTo(it: *Iterator, pos: u32) void { | 514 | if (fde.getLsdaAtom(macho_file)) |atom| { |
| 622 | assert(pos >= 0 and pos < it.data.len); | 515 | const offset = fde.out_offset + fde.lsda_ptr_offset; |
| 623 | it.pos = pos; | 516 | const saddr = sect.addr + offset; |
| 517 | const taddr = atom.value + fde.lsda_offset; | ||
| 518 | switch (fde.getCie(macho_file).lsda_size.?) { | ||
| 519 | .p32 => std.mem.writeInt( | ||
| 520 | i32, | ||
| 521 | code[offset..][0..4], | ||
| 522 | @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend), | ||
| 523 | .little, | ||
| 524 | ), | ||
| 525 | .p64 => std.mem.writeInt( | ||
| 526 | i64, | ||
| 527 | code[offset..][0..8], | ||
| 528 | @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), | ||
| 529 | .little, | ||
| 530 | ), | ||
| 531 | } | ||
| 532 | } | ||
| 533 | } | ||
| 624 | } | 534 | } |
| 625 | }; | 535 | } |
| 626 | 536 | ||
| 627 | pub const EH_PE = struct { | 537 | pub const EH_PE = struct { |
| 628 | pub const absptr = 0x00; | 538 | pub const absptr = 0x00; |
| ... | @@ -643,17 +553,17 @@ pub const EH_PE = struct { | ... | @@ -643,17 +553,17 @@ pub const EH_PE = struct { |
| 643 | pub const omit = 0xFF; | 553 | pub const omit = 0xFF; |
| 644 | }; | 554 | }; |
| 645 | 555 | ||
| 646 | const std = @import("std"); | ||
| 647 | const assert = std.debug.assert; | 556 | const assert = std.debug.assert; |
| 557 | const leb = std.leb; | ||
| 648 | const macho = std.macho; | 558 | const macho = std.macho; |
| 649 | const math = std.math; | 559 | const math = std.math; |
| 650 | const mem = std.mem; | 560 | const mem = std.mem; |
| 651 | const leb = std.leb; | 561 | const std = @import("std"); |
| 652 | const log = std.log.scoped(.eh_frame); | 562 | const trace = @import("../tracy.zig").trace; |
| 653 | 563 | ||
| 654 | const Allocator = mem.Allocator; | 564 | const Allocator = std.mem.Allocator; |
| 655 | const Atom = @import("Atom.zig"); | 565 | const Atom = @import("Atom.zig"); |
| 566 | const File = @import("file.zig").File; | ||
| 656 | const MachO = @import("../MachO.zig"); | 567 | const MachO = @import("../MachO.zig"); |
| 657 | const Relocation = @import("Relocation.zig"); | 568 | const Object = @import("Object.zig"); |
| 658 | const SymbolWithLoc = MachO.SymbolWithLoc; | 569 | const Symbol = @import("Symbol.zig"); |
| 659 | const UnwindInfo = @import("UnwindInfo.zig"); |
src/link/MachO/fat.zig+17-16| ... | @@ -1,3 +1,13 @@ | ... | @@ -1,3 +1,13 @@ |
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const builtin = @import("builtin"); | ||
| 4 | const log = std.log.scoped(.macho); | ||
| 5 | const macho = std.macho; | ||
| 6 | const mem = std.mem; | ||
| 7 | const native_endian = builtin.target.cpu.arch.endian(); | ||
| 8 | |||
| 9 | const MachO = @import("../MachO.zig"); | ||
| 10 | |||
| 1 | pub fn isFatLibrary(file: std.fs.File) bool { | 11 | pub fn isFatLibrary(file: std.fs.File) bool { |
| 2 | const reader = file.reader(); | 12 | const reader = file.reader(); |
| 3 | const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false; | 13 | const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false; |
| ... | @@ -7,18 +17,16 @@ pub fn isFatLibrary(file: std.fs.File) bool { | ... | @@ -7,18 +17,16 @@ pub fn isFatLibrary(file: std.fs.File) bool { |
| 7 | 17 | ||
| 8 | pub const Arch = struct { | 18 | pub const Arch = struct { |
| 9 | tag: std.Target.Cpu.Arch, | 19 | tag: std.Target.Cpu.Arch, |
| 10 | offset: u64, | 20 | offset: u32, |
| 21 | size: u32, | ||
| 11 | }; | 22 | }; |
| 12 | 23 | ||
| 13 | /// Caller owns the memory. | 24 | pub fn parseArchs(file: std.fs.File, buffer: *[2]Arch) ![]const Arch { |
| 14 | pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch { | ||
| 15 | const reader = file.reader(); | 25 | const reader = file.reader(); |
| 16 | const fat_header = try reader.readStructEndian(macho.fat_header, .big); | 26 | const fat_header = try reader.readStructEndian(macho.fat_header, .big); |
| 17 | assert(fat_header.magic == macho.FAT_MAGIC); | 27 | assert(fat_header.magic == macho.FAT_MAGIC); |
| 18 | 28 | ||
| 19 | var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch); | 29 | var count: usize = 0; |
| 20 | defer archs.deinit(); | ||
| 21 | |||
| 22 | var fat_arch_index: u32 = 0; | 30 | var fat_arch_index: u32 = 0; |
| 23 | while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { | 31 | while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { |
| 24 | const fat_arch = try reader.readStructEndian(macho.fat_arch, .big); | 32 | const fat_arch = try reader.readStructEndian(macho.fat_arch, .big); |
| ... | @@ -29,16 +37,9 @@ pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch { | ... | @@ -29,16 +37,9 @@ pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch { |
| 29 | macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, | 37 | macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, |
| 30 | else => continue, | 38 | else => continue, |
| 31 | }; | 39 | }; |
| 32 | 40 | buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size }; | |
| 33 | archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset }); | 41 | count += 1; |
| 34 | } | 42 | } |
| 35 | 43 | ||
| 36 | return archs.toOwnedSlice(); | 44 | return buffer[0..count]; |
| 37 | } | 45 | } |
| 38 | |||
| 39 | const std = @import("std"); | ||
| 40 | const assert = std.debug.assert; | ||
| 41 | const log = std.log.scoped(.archive); | ||
| 42 | const macho = std.macho; | ||
| 43 | const mem = std.mem; | ||
| 44 | const Allocator = mem.Allocator; |
src/link/MachO/file.zig created+116| ... | @@ -0,0 +1,116 @@ | ||
| 1 | pub const File = union(enum) { | ||
| 2 | internal: *InternalObject, | ||
| 3 | object: *Object, | ||
| 4 | dylib: *Dylib, | ||
| 5 | |||
| 6 | pub fn getIndex(file: File) Index { | ||
| 7 | return switch (file) { | ||
| 8 | inline else => |x| x.index, | ||
| 9 | }; | ||
| 10 | } | ||
| 11 | |||
| 12 | pub fn fmtPath(file: File) std.fmt.Formatter(formatPath) { | ||
| 13 | return .{ .data = file }; | ||
| 14 | } | ||
| 15 | |||
| 16 | fn formatPath( | ||
| 17 | file: File, | ||
| 18 | comptime unused_fmt_string: []const u8, | ||
| 19 | options: std.fmt.FormatOptions, | ||
| 20 | writer: anytype, | ||
| 21 | ) !void { | ||
| 22 | _ = unused_fmt_string; | ||
| 23 | _ = options; | ||
| 24 | switch (file) { | ||
| 25 | .internal => try writer.writeAll(""), | ||
| 26 | .object => |x| try writer.print("{}", .{x.fmtPath()}), | ||
| 27 | .dylib => |x| try writer.writeAll(x.path), | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | pub fn resolveSymbols(file: File, macho_file: *MachO) void { | ||
| 32 | switch (file) { | ||
| 33 | .internal => unreachable, | ||
| 34 | inline else => |x| x.resolveSymbols(macho_file), | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | pub fn resetGlobals(file: File, macho_file: *MachO) void { | ||
| 39 | switch (file) { | ||
| 40 | .internal => unreachable, | ||
| 41 | inline else => |x| x.resetGlobals(macho_file), | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// Encodes symbol rank so that the following ordering applies: | ||
| 46 | /// * strong in object | ||
| 47 | /// * weak in object | ||
| 48 | /// * tentative in object | ||
| 49 | /// * strong in archive/dylib | ||
| 50 | /// * weak in archive/dylib | ||
| 51 | /// * tentative in archive | ||
| 52 | /// * unclaimed | ||
| 53 | pub fn getSymbolRank(file: File, args: struct { | ||
| 54 | archive: bool = false, | ||
| 55 | weak: bool = false, | ||
| 56 | tentative: bool = false, | ||
| 57 | }) u32 { | ||
| 58 | if (file == .object and !args.archive) { | ||
| 59 | const base: u32 = blk: { | ||
| 60 | if (args.tentative) break :blk 3; | ||
| 61 | break :blk if (args.weak) 2 else 1; | ||
| 62 | }; | ||
| 63 | return (base << 16) + file.getIndex(); | ||
| 64 | } | ||
| 65 | const base: u32 = blk: { | ||
| 66 | if (args.tentative) break :blk 3; | ||
| 67 | break :blk if (args.weak) 2 else 1; | ||
| 68 | }; | ||
| 69 | return base + (file.getIndex() << 24); | ||
| 70 | } | ||
| 71 | |||
| 72 | pub fn getSymbols(file: File) []const Symbol.Index { | ||
| 73 | return switch (file) { | ||
| 74 | inline else => |x| x.symbols.items, | ||
| 75 | }; | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn getAtoms(file: File) []const Atom.Index { | ||
| 79 | return switch (file) { | ||
| 80 | .dylib => unreachable, | ||
| 81 | inline else => |x| x.atoms.items, | ||
| 82 | }; | ||
| 83 | } | ||
| 84 | |||
| 85 | pub fn calcSymtabSize(file: File, macho_file: *MachO) !void { | ||
| 86 | return switch (file) { | ||
| 87 | inline else => |x| x.calcSymtabSize(macho_file), | ||
| 88 | }; | ||
| 89 | } | ||
| 90 | |||
| 91 | pub fn writeSymtab(file: File, macho_file: *MachO) void { | ||
| 92 | return switch (file) { | ||
| 93 | inline else => |x| x.writeSymtab(macho_file), | ||
| 94 | }; | ||
| 95 | } | ||
| 96 | |||
| 97 | pub const Index = u32; | ||
| 98 | |||
| 99 | pub const Entry = union(enum) { | ||
| 100 | null: void, | ||
| 101 | internal: InternalObject, | ||
| 102 | object: Object, | ||
| 103 | dylib: Dylib, | ||
| 104 | }; | ||
| 105 | }; | ||
| 106 | |||
| 107 | const macho = std.macho; | ||
| 108 | const std = @import("std"); | ||
| 109 | |||
| 110 | const Allocator = std.mem.Allocator; | ||
| 111 | const Atom = @import("Atom.zig"); | ||
| 112 | const InternalObject = @import("InternalObject.zig"); | ||
| 113 | const MachO = @import("../MachO.zig"); | ||
| 114 | const Object = @import("Object.zig"); | ||
| 115 | const Dylib = @import("Dylib.zig"); | ||
| 116 | const Symbol = @import("Symbol.zig"); | ||
src/link/MachO/hasher.zig+12-9| ... | @@ -9,15 +9,14 @@ pub fn ParallelHasher(comptime Hasher: type) type { | ... | @@ -9,15 +9,14 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 9 | chunk_size: u64 = 0x4000, | 9 | chunk_size: u64 = 0x4000, |
| 10 | max_file_size: ?u64 = null, | 10 | max_file_size: ?u64 = null, |
| 11 | }) !void { | 11 | }) !void { |
| 12 | const tracy = trace(@src()); | ||
| 13 | defer tracy.end(); | ||
| 14 | |||
| 12 | var wg: WaitGroup = .{}; | 15 | var wg: WaitGroup = .{}; |
| 13 | 16 | ||
| 14 | const file_size = blk: { | 17 | const file_size = opts.max_file_size orelse try file.getEndPos(); |
| 15 | const file_size = opts.max_file_size orelse try file.getEndPos(); | ||
| 16 | break :blk std.math.cast(usize, file_size) orelse return error.Overflow; | ||
| 17 | }; | ||
| 18 | const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow; | ||
| 19 | 18 | ||
| 20 | const buffer = try self.allocator.alloc(u8, chunk_size * out.len); | 19 | const buffer = try self.allocator.alloc(u8, opts.chunk_size * out.len); |
| 21 | defer self.allocator.free(buffer); | 20 | defer self.allocator.free(buffer); |
| 22 | 21 | ||
| 23 | const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len); | 22 | const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len); |
| ... | @@ -28,8 +27,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { | ... | @@ -28,8 +27,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 28 | defer wg.wait(); | 27 | defer wg.wait(); |
| 29 | 28 | ||
| 30 | for (out, results, 0..) |*out_buf, *result, i| { | 29 | for (out, results, 0..) |*out_buf, *result, i| { |
| 31 | const fstart = i * chunk_size; | 30 | const fstart = i * opts.chunk_size; |
| 32 | const fsize = if (fstart + chunk_size > file_size) file_size - fstart else chunk_size; | 31 | const fsize = if (fstart + opts.chunk_size > file_size) |
| 32 | file_size - fstart | ||
| 33 | else | ||
| 34 | opts.chunk_size; | ||
| 33 | wg.start(); | 35 | wg.start(); |
| 34 | try self.thread_pool.spawn(worker, .{ | 36 | try self.thread_pool.spawn(worker, .{ |
| 35 | file, | 37 | file, |
| ... | @@ -61,10 +63,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { | ... | @@ -61,10 +63,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 61 | }; | 63 | }; |
| 62 | } | 64 | } |
| 63 | 65 | ||
| 64 | const std = @import("std"); | ||
| 65 | const assert = std.debug.assert; | 66 | const assert = std.debug.assert; |
| 66 | const fs = std.fs; | 67 | const fs = std.fs; |
| 67 | const mem = std.mem; | 68 | const mem = std.mem; |
| 69 | const std = @import("std"); | ||
| 70 | const trace = @import("../tracy.zig").trace; | ||
| 68 | 71 | ||
| 69 | const Allocator = mem.Allocator; | 72 | const Allocator = mem.Allocator; |
| 70 | const ThreadPool = std.Thread.Pool; | 73 | const ThreadPool = std.Thread.Pool; |
src/link/MachO/load_commands.zig+110-412| ... | @@ -1,4 +1,14 @@ | ... | @@ -1,4 +1,14 @@ |
| 1 | /// Default path to dyld. | 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; | ||
| 3 | const log = std.log.scoped(.link); | ||
| 4 | const macho = std.macho; | ||
| 5 | const mem = std.mem; | ||
| 6 | |||
| 7 | const Allocator = mem.Allocator; | ||
| 8 | const Dylib = @import("Dylib.zig"); | ||
| 9 | const MachO = @import("../MachO.zig"); | ||
| 10 | const Options = @import("../MachO.zig").Options; | ||
| 11 | |||
| 2 | pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; | 12 | pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; |
| 3 | 13 | ||
| 4 | fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { | 14 | fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { |
| ... | @@ -7,31 +17,20 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool | ... | @@ -7,31 +17,20 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool |
| 7 | return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64)); | 17 | return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64)); |
| 8 | } | 18 | } |
| 9 | 19 | ||
| 10 | const CalcLCsSizeCtx = struct { | 20 | pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 { |
| 11 | segments: []const macho.segment_command_64, | 21 | const options = &macho_file.options; |
| 12 | dylibs: []const Dylib, | ||
| 13 | referenced_dylibs: []u16, | ||
| 14 | wants_function_starts: bool = true, | ||
| 15 | }; | ||
| 16 | |||
| 17 | fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ||
| 18 | const comp = m.base.comp; | ||
| 19 | const gpa = comp.gpa; | ||
| 20 | var has_text_segment: bool = false; | ||
| 21 | var sizeofcmds: u64 = 0; | 22 | var sizeofcmds: u64 = 0; |
| 22 | for (ctx.segments) |seg| { | 23 | |
| 23 | sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64); | 24 | // LC_SEGMENT_64 |
| 24 | if (mem.eql(u8, seg.segName(), "__TEXT")) { | 25 | sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len; |
| 25 | has_text_segment = true; | 26 | for (macho_file.segments.items) |seg| { |
| 26 | } | 27 | sizeofcmds += seg.nsects * @sizeOf(macho.section_64); |
| 27 | } | 28 | } |
| 28 | 29 | ||
| 29 | // LC_DYLD_INFO_ONLY | 30 | // LC_DYLD_INFO_ONLY |
| 30 | sizeofcmds += @sizeOf(macho.dyld_info_command); | 31 | sizeofcmds += @sizeOf(macho.dyld_info_command); |
| 31 | // LC_FUNCTION_STARTS | 32 | // LC_FUNCTION_STARTS |
| 32 | if (has_text_segment and ctx.wants_function_starts) { | 33 | sizeofcmds += @sizeOf(macho.linkedit_data_command); |
| 33 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | ||
| 34 | } | ||
| 35 | // LC_DATA_IN_CODE | 34 | // LC_DATA_IN_CODE |
| 36 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | 35 | sizeofcmds += @sizeOf(macho.linkedit_data_command); |
| 37 | // LC_SYMTAB | 36 | // LC_SYMTAB |
| ... | @@ -45,15 +44,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -45,15 +44,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 45 | false, | 44 | false, |
| 46 | ); | 45 | ); |
| 47 | // LC_MAIN | 46 | // LC_MAIN |
| 48 | if (comp.config.output_mode == .Exe) { | 47 | if (!options.dylib) { |
| 49 | sizeofcmds += @sizeOf(macho.entry_point_command); | 48 | sizeofcmds += @sizeOf(macho.entry_point_command); |
| 50 | } | 49 | } |
| 51 | // LC_ID_DYLIB | 50 | // LC_ID_DYLIB |
| 52 | if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) { | 51 | if (options.dylib) { |
| 53 | sizeofcmds += blk: { | 52 | sizeofcmds += blk: { |
| 54 | const emit = m.base.emit; | 53 | const emit = options.emit; |
| 55 | const install_name = m.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path}); | 54 | const install_name = options.install_name orelse emit.sub_path; |
| 56 | defer if (m.install_name == null) gpa.free(install_name); | ||
| 57 | break :blk calcInstallNameLen( | 55 | break :blk calcInstallNameLen( |
| 58 | @sizeOf(macho.dylib_command), | 56 | @sizeOf(macho.dylib_command), |
| 59 | install_name, | 57 | install_name, |
| ... | @@ -63,9 +61,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -63,9 +61,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 63 | } | 61 | } |
| 64 | // LC_RPATH | 62 | // LC_RPATH |
| 65 | { | 63 | { |
| 66 | var it = RpathIterator.init(gpa, m.base.rpath_list); | 64 | for (options.rpath_list) |rpath| { |
| 67 | defer it.deinit(); | ||
| 68 | while (try it.next()) |rpath| { | ||
| 69 | sizeofcmds += calcInstallNameLen( | 65 | sizeofcmds += calcInstallNameLen( |
| 70 | @sizeOf(macho.rpath_command), | 66 | @sizeOf(macho.rpath_command), |
| 71 | rpath, | 67 | rpath, |
| ... | @@ -75,24 +71,22 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -75,24 +71,22 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 75 | } | 71 | } |
| 76 | // LC_SOURCE_VERSION | 72 | // LC_SOURCE_VERSION |
| 77 | sizeofcmds += @sizeOf(macho.source_version_command); | 73 | sizeofcmds += @sizeOf(macho.source_version_command); |
| 78 | // LC_BUILD_VERSION or LC_VERSION_MIN_ or nothing | 74 | if (options.platform) |platform| { |
| 79 | { | ||
| 80 | const target = comp.root_mod.resolved_target.result; | ||
| 81 | const platform = Platform.fromTarget(target); | ||
| 82 | if (platform.isBuildVersionCompatible()) { | 75 | if (platform.isBuildVersionCompatible()) { |
| 83 | // LC_BUILD_VERSION | 76 | // LC_BUILD_VERSION |
| 84 | sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); | 77 | sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); |
| 85 | } else if (platform.isVersionMinCompatible()) { | 78 | } else { |
| 86 | // LC_VERSION_MIN_ | 79 | // LC_VERSION_MIN_* |
| 87 | sizeofcmds += @sizeOf(macho.version_min_command); | 80 | sizeofcmds += @sizeOf(macho.version_min_command); |
| 88 | } | 81 | } |
| 89 | } | 82 | } |
| 90 | // LC_UUID | 83 | // LC_UUID |
| 91 | sizeofcmds += @sizeOf(macho.uuid_command); | 84 | sizeofcmds += @sizeOf(macho.uuid_command); |
| 92 | // LC_LOAD_DYLIB | 85 | // LC_LOAD_DYLIB |
| 93 | for (ctx.referenced_dylibs) |id| { | 86 | for (macho_file.dylibs.items) |index| { |
| 94 | const dylib = ctx.dylibs[id]; | 87 | const dylib = macho_file.getFile(index).?.dylib; |
| 95 | const dylib_id = dylib.id orelse unreachable; | 88 | assert(dylib.isAlive(macho_file)); |
| 89 | const dylib_id = dylib.id.?; | ||
| 96 | sizeofcmds += calcInstallNameLen( | 90 | sizeofcmds += calcInstallNameLen( |
| 97 | @sizeOf(macho.dylib_command), | 91 | @sizeOf(macho.dylib_command), |
| 98 | dylib_id.name, | 92 | dylib_id.name, |
| ... | @@ -100,19 +94,52 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -100,19 +94,52 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 100 | ); | 94 | ); |
| 101 | } | 95 | } |
| 102 | // LC_CODE_SIGNATURE | 96 | // LC_CODE_SIGNATURE |
| 103 | if (m.requiresCodeSignature()) { | 97 | if (macho_file.requiresCodeSig()) { |
| 104 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | 98 | sizeofcmds += @sizeOf(macho.linkedit_data_command); |
| 105 | } | 99 | } |
| 106 | 100 | ||
| 107 | return @intCast(sizeofcmds); | 101 | return @as(u32, @intCast(sizeofcmds)); |
| 102 | } | ||
| 103 | |||
| 104 | pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 { | ||
| 105 | const options = &macho_file.options; | ||
| 106 | var sizeofcmds: u64 = 0; | ||
| 107 | |||
| 108 | // LC_SEGMENT_64 | ||
| 109 | { | ||
| 110 | assert(macho_file.segments.items.len == 1); | ||
| 111 | sizeofcmds += @sizeOf(macho.segment_command_64); | ||
| 112 | const seg = macho_file.segments.items[0]; | ||
| 113 | sizeofcmds += seg.nsects * @sizeOf(macho.section_64); | ||
| 114 | } | ||
| 115 | |||
| 116 | // LC_DATA_IN_CODE | ||
| 117 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | ||
| 118 | // LC_SYMTAB | ||
| 119 | sizeofcmds += @sizeOf(macho.symtab_command); | ||
| 120 | // LC_DYSYMTAB | ||
| 121 | sizeofcmds += @sizeOf(macho.dysymtab_command); | ||
| 122 | |||
| 123 | if (options.platform) |platform| { | ||
| 124 | if (platform.isBuildVersionCompatible()) { | ||
| 125 | // LC_BUILD_VERSION | ||
| 126 | sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); | ||
| 127 | } else { | ||
| 128 | // LC_VERSION_MIN_* | ||
| 129 | sizeofcmds += @sizeOf(macho.version_min_command); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | return @as(u32, @intCast(sizeofcmds)); | ||
| 108 | } | 134 | } |
| 109 | 135 | ||
| 110 | pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { | 136 | pub fn calcMinHeaderPadSize(macho_file: *MachO) u32 { |
| 111 | var padding: u32 = (try calcLCsSize(m, ctx, false)) + m.headerpad_size; | 137 | const options = &macho_file.options; |
| 138 | var padding: u32 = calcLoadCommandsSize(macho_file, false) + (options.headerpad orelse 0); | ||
| 112 | log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); | 139 | log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); |
| 113 | 140 | ||
| 114 | if (m.headerpad_max_install_names) { | 141 | if (options.headerpad_max_install_names) { |
| 115 | const min_headerpad_size: u32 = try calcLCsSize(m, ctx, true); | 142 | const min_headerpad_size: u32 = calcLoadCommandsSize(macho_file, true); |
| 116 | log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ | 143 | log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ |
| 117 | min_headerpad_size + @sizeOf(macho.mach_header_64), | 144 | min_headerpad_size + @sizeOf(macho.mach_header_64), |
| 118 | }); | 145 | }); |
| ... | @@ -125,34 +152,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { | ... | @@ -125,34 +152,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { |
| 125 | return offset; | 152 | return offset; |
| 126 | } | 153 | } |
| 127 | 154 | ||
| 128 | pub fn calcNumOfLCs(lc_buffer: []const u8) u32 { | 155 | pub fn writeDylinkerLC(writer: anytype) !void { |
| 129 | var ncmds: u32 = 0; | ||
| 130 | var pos: usize = 0; | ||
| 131 | while (true) { | ||
| 132 | if (pos >= lc_buffer.len) break; | ||
| 133 | const cmd = @as(*align(1) const macho.load_command, @ptrCast(lc_buffer.ptr + pos)).*; | ||
| 134 | ncmds += 1; | ||
| 135 | pos += cmd.cmdsize; | ||
| 136 | } | ||
| 137 | return ncmds; | ||
| 138 | } | ||
| 139 | |||
| 140 | pub fn writeDylinkerLC(lc_writer: anytype) !void { | ||
| 141 | const name_len = mem.sliceTo(default_dyld_path, 0).len; | 156 | const name_len = mem.sliceTo(default_dyld_path, 0).len; |
| 142 | const cmdsize = @as(u32, @intCast(mem.alignForward( | 157 | const cmdsize = @as(u32, @intCast(mem.alignForward( |
| 143 | u64, | 158 | u64, |
| 144 | @sizeOf(macho.dylinker_command) + name_len, | 159 | @sizeOf(macho.dylinker_command) + name_len, |
| 145 | @sizeOf(u64), | 160 | @sizeOf(u64), |
| 146 | ))); | 161 | ))); |
| 147 | try lc_writer.writeStruct(macho.dylinker_command{ | 162 | try writer.writeStruct(macho.dylinker_command{ |
| 148 | .cmd = .LOAD_DYLINKER, | 163 | .cmd = .LOAD_DYLINKER, |
| 149 | .cmdsize = cmdsize, | 164 | .cmdsize = cmdsize, |
| 150 | .name = @sizeOf(macho.dylinker_command), | 165 | .name = @sizeOf(macho.dylinker_command), |
| 151 | }); | 166 | }); |
| 152 | try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0)); | 167 | try writer.writeAll(mem.sliceTo(default_dyld_path, 0)); |
| 153 | const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; | 168 | const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; |
| 154 | if (padding > 0) { | 169 | if (padding > 0) { |
| 155 | try lc_writer.writeByteNTimes(0, padding); | 170 | try writer.writeByteNTimes(0, padding); |
| 156 | } | 171 | } |
| 157 | } | 172 | } |
| 158 | 173 | ||
| ... | @@ -164,14 +179,14 @@ const WriteDylibLCCtx = struct { | ... | @@ -164,14 +179,14 @@ const WriteDylibLCCtx = struct { |
| 164 | compatibility_version: u32 = 0x10000, | 179 | compatibility_version: u32 = 0x10000, |
| 165 | }; | 180 | }; |
| 166 | 181 | ||
| 167 | fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { | 182 | pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void { |
| 168 | const name_len = ctx.name.len + 1; | 183 | const name_len = ctx.name.len + 1; |
| 169 | const cmdsize = @as(u32, @intCast(mem.alignForward( | 184 | const cmdsize = @as(u32, @intCast(mem.alignForward( |
| 170 | u64, | 185 | u64, |
| 171 | @sizeOf(macho.dylib_command) + name_len, | 186 | @sizeOf(macho.dylib_command) + name_len, |
| 172 | @sizeOf(u64), | 187 | @sizeOf(u64), |
| 173 | ))); | 188 | ))); |
| 174 | try lc_writer.writeStruct(macho.dylib_command{ | 189 | try writer.writeStruct(macho.dylib_command{ |
| 175 | .cmd = ctx.cmd, | 190 | .cmd = ctx.cmd, |
| 176 | .cmdsize = cmdsize, | 191 | .cmdsize = cmdsize, |
| 177 | .dylib = .{ | 192 | .dylib = .{ |
| ... | @@ -181,392 +196,75 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { | ... | @@ -181,392 +196,75 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { |
| 181 | .compatibility_version = ctx.compatibility_version, | 196 | .compatibility_version = ctx.compatibility_version, |
| 182 | }, | 197 | }, |
| 183 | }); | 198 | }); |
| 184 | try lc_writer.writeAll(ctx.name); | 199 | try writer.writeAll(ctx.name); |
| 185 | try lc_writer.writeByte(0); | 200 | try writer.writeByte(0); |
| 186 | const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; | 201 | const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; |
| 187 | if (padding > 0) { | 202 | if (padding > 0) { |
| 188 | try lc_writer.writeByteNTimes(0, padding); | 203 | try writer.writeByteNTimes(0, padding); |
| 189 | } | 204 | } |
| 190 | } | 205 | } |
| 191 | 206 | ||
| 192 | pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void { | 207 | pub fn writeDylibIdLC(options: *const Options, writer: anytype) !void { |
| 193 | const comp = macho_file.base.comp; | 208 | assert(options.dylib); |
| 194 | const gpa = comp.gpa; | 209 | const emit = options.emit; |
| 195 | assert(comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic); | 210 | const install_name = options.install_name orelse emit.sub_path; |
| 196 | const emit = macho_file.base.emit; | 211 | const curr = options.current_version orelse Options.Version.new(1, 0, 0); |
| 197 | const install_name = macho_file.install_name orelse | 212 | const compat = options.compatibility_version orelse Options.Version.new(1, 0, 0); |
| 198 | try emit.directory.join(gpa, &.{emit.sub_path}); | ||
| 199 | defer if (macho_file.install_name == null) gpa.free(install_name); | ||
| 200 | const curr = comp.version orelse std.SemanticVersion{ | ||
| 201 | .major = 1, | ||
| 202 | .minor = 0, | ||
| 203 | .patch = 0, | ||
| 204 | }; | ||
| 205 | const compat = macho_file.compatibility_version orelse std.SemanticVersion{ | ||
| 206 | .major = 1, | ||
| 207 | .minor = 0, | ||
| 208 | .patch = 0, | ||
| 209 | }; | ||
| 210 | try writeDylibLC(.{ | 213 | try writeDylibLC(.{ |
| 211 | .cmd = .ID_DYLIB, | 214 | .cmd = .ID_DYLIB, |
| 212 | .name = install_name, | 215 | .name = install_name, |
| 213 | .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)), | 216 | .current_version = curr.value, |
| 214 | .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)), | 217 | .compatibility_version = compat.value, |
| 215 | }, lc_writer); | 218 | }, writer); |
| 216 | } | 219 | } |
| 217 | 220 | ||
| 218 | const RpathIterator = struct { | 221 | pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void { |
| 219 | buffer: []const []const u8, | 222 | for (rpaths) |rpath| { |
| 220 | table: std.StringHashMap(void), | ||
| 221 | count: usize = 0, | ||
| 222 | |||
| 223 | fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator { | ||
| 224 | return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) }; | ||
| 225 | } | ||
| 226 | |||
| 227 | fn deinit(it: *RpathIterator) void { | ||
| 228 | it.table.deinit(); | ||
| 229 | } | ||
| 230 | |||
| 231 | fn next(it: *RpathIterator) !?[]const u8 { | ||
| 232 | while (true) { | ||
| 233 | if (it.count >= it.buffer.len) return null; | ||
| 234 | const rpath = it.buffer[it.count]; | ||
| 235 | it.count += 1; | ||
| 236 | const gop = try it.table.getOrPut(rpath); | ||
| 237 | if (gop.found_existing) continue; | ||
| 238 | return rpath; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | }; | ||
| 242 | |||
| 243 | pub fn writeRpathLCs(macho_file: *MachO, lc_writer: anytype) !void { | ||
| 244 | const comp = macho_file.base.comp; | ||
| 245 | const gpa = comp.gpa; | ||
| 246 | |||
| 247 | var it = RpathIterator.init(gpa, macho_file.base.rpath_list); | ||
| 248 | defer it.deinit(); | ||
| 249 | |||
| 250 | while (try it.next()) |rpath| { | ||
| 251 | const rpath_len = rpath.len + 1; | 223 | const rpath_len = rpath.len + 1; |
| 252 | const cmdsize = @as(u32, @intCast(mem.alignForward( | 224 | const cmdsize = @as(u32, @intCast(mem.alignForward( |
| 253 | u64, | 225 | u64, |
| 254 | @sizeOf(macho.rpath_command) + rpath_len, | 226 | @sizeOf(macho.rpath_command) + rpath_len, |
| 255 | @sizeOf(u64), | 227 | @sizeOf(u64), |
| 256 | ))); | 228 | ))); |
| 257 | try lc_writer.writeStruct(macho.rpath_command{ | 229 | try writer.writeStruct(macho.rpath_command{ |
| 258 | .cmdsize = cmdsize, | 230 | .cmdsize = cmdsize, |
| 259 | .path = @sizeOf(macho.rpath_command), | 231 | .path = @sizeOf(macho.rpath_command), |
| 260 | }); | 232 | }); |
| 261 | try lc_writer.writeAll(rpath); | 233 | try writer.writeAll(rpath); |
| 262 | try lc_writer.writeByte(0); | 234 | try writer.writeByte(0); |
| 263 | const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; | 235 | const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; |
| 264 | if (padding > 0) { | 236 | if (padding > 0) { |
| 265 | try lc_writer.writeByteNTimes(0, padding); | 237 | try writer.writeByteNTimes(0, padding); |
| 266 | } | 238 | } |
| 267 | } | 239 | } |
| 268 | } | 240 | } |
| 269 | 241 | ||
| 270 | pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { | 242 | pub fn writeVersionMinLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void { |
| 271 | const cmd: macho.LC = switch (platform.os_tag) { | 243 | const cmd: macho.LC = switch (platform.platform) { |
| 272 | .macos => .VERSION_MIN_MACOSX, | 244 | .MACOS => .VERSION_MIN_MACOSX, |
| 273 | .ios => .VERSION_MIN_IPHONEOS, | 245 | .IOS, .IOSSIMULATOR => .VERSION_MIN_IPHONEOS, |
| 274 | .tvos => .VERSION_MIN_TVOS, | 246 | .TVOS, .TVOSSIMULATOR => .VERSION_MIN_TVOS, |
| 275 | .watchos => .VERSION_MIN_WATCHOS, | 247 | .WATCHOS, .WATCHOSSIMULATOR => .VERSION_MIN_WATCHOS, |
| 276 | else => unreachable, | 248 | else => unreachable, |
| 277 | }; | 249 | }; |
| 278 | try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{ | 250 | try writer.writeAll(mem.asBytes(&macho.version_min_command{ |
| 279 | .cmd = cmd, | 251 | .cmd = cmd, |
| 280 | .version = platform.toAppleVersion(), | 252 | .version = platform.version.value, |
| 281 | .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(), | 253 | .sdk = if (sdk_version) |ver| ver.value else platform.version.value, |
| 282 | })); | 254 | })); |
| 283 | } | 255 | } |
| 284 | 256 | ||
| 285 | pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { | 257 | pub fn writeBuildVersionLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void { |
| 286 | const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); | 258 | const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); |
| 287 | try lc_writer.writeStruct(macho.build_version_command{ | 259 | try writer.writeStruct(macho.build_version_command{ |
| 288 | .cmdsize = cmdsize, | 260 | .cmdsize = cmdsize, |
| 289 | .platform = platform.toApplePlatform(), | 261 | .platform = platform.platform, |
| 290 | .minos = platform.toAppleVersion(), | 262 | .minos = platform.version.value, |
| 291 | .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(), | 263 | .sdk = if (sdk_version) |ver| ver.value else platform.version.value, |
| 292 | .ntools = 1, | 264 | .ntools = 1, |
| 293 | }); | 265 | }); |
| 294 | try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{ | 266 | try writer.writeAll(mem.asBytes(&macho.build_tool_version{ |
| 295 | .tool = .ZIG, | 267 | .tool = @as(macho.TOOL, @enumFromInt(0x6)), |
| 296 | .version = 0x0, | 268 | .version = 0x0, |
| 297 | })); | 269 | })); |
| 298 | } | 270 | } |
| 299 | |||
| 300 | pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void { | ||
| 301 | for (referenced) |index| { | ||
| 302 | const dylib = dylibs[index]; | ||
| 303 | const dylib_id = dylib.id orelse unreachable; | ||
| 304 | try writeDylibLC(.{ | ||
| 305 | .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB, | ||
| 306 | .name = dylib_id.name, | ||
| 307 | .timestamp = dylib_id.timestamp, | ||
| 308 | .current_version = dylib_id.current_version, | ||
| 309 | .compatibility_version = dylib_id.compatibility_version, | ||
| 310 | }, lc_writer); | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | pub const Platform = struct { | ||
| 315 | os_tag: std.Target.Os.Tag, | ||
| 316 | abi: std.Target.Abi, | ||
| 317 | version: std.SemanticVersion, | ||
| 318 | |||
| 319 | /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to | ||
| 320 | /// the extracted minimum platform version. | ||
| 321 | pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform { | ||
| 322 | switch (lc.cmd()) { | ||
| 323 | .BUILD_VERSION => { | ||
| 324 | const cmd = lc.cast(macho.build_version_command).?; | ||
| 325 | return .{ | ||
| 326 | .os_tag = switch (cmd.platform) { | ||
| 327 | .MACOS => .macos, | ||
| 328 | .IOS, .IOSSIMULATOR => .ios, | ||
| 329 | .TVOS, .TVOSSIMULATOR => .tvos, | ||
| 330 | .WATCHOS, .WATCHOSSIMULATOR => .watchos, | ||
| 331 | else => @panic("TODO"), | ||
| 332 | }, | ||
| 333 | .abi = switch (cmd.platform) { | ||
| 334 | .IOSSIMULATOR, | ||
| 335 | .TVOSSIMULATOR, | ||
| 336 | .WATCHOSSIMULATOR, | ||
| 337 | => .simulator, | ||
| 338 | else => .none, | ||
| 339 | }, | ||
| 340 | .version = appleVersionToSemanticVersion(cmd.minos), | ||
| 341 | }; | ||
| 342 | }, | ||
| 343 | .VERSION_MIN_MACOSX, | ||
| 344 | .VERSION_MIN_IPHONEOS, | ||
| 345 | .VERSION_MIN_TVOS, | ||
| 346 | .VERSION_MIN_WATCHOS, | ||
| 347 | => { | ||
| 348 | const cmd = lc.cast(macho.version_min_command).?; | ||
| 349 | return .{ | ||
| 350 | .os_tag = switch (lc.cmd()) { | ||
| 351 | .VERSION_MIN_MACOSX => .macos, | ||
| 352 | .VERSION_MIN_IPHONEOS => .ios, | ||
| 353 | .VERSION_MIN_TVOS => .tvos, | ||
| 354 | .VERSION_MIN_WATCHOS => .watchos, | ||
| 355 | else => unreachable, | ||
| 356 | }, | ||
| 357 | .abi = .none, | ||
| 358 | .version = appleVersionToSemanticVersion(cmd.version), | ||
| 359 | }; | ||
| 360 | }, | ||
| 361 | else => unreachable, | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | pub fn fromTarget(target: std.Target) Platform { | ||
| 366 | return .{ | ||
| 367 | .os_tag = target.os.tag, | ||
| 368 | .abi = target.abi, | ||
| 369 | .version = target.os.version_range.semver.min, | ||
| 370 | }; | ||
| 371 | } | ||
| 372 | |||
| 373 | pub fn toAppleVersion(plat: Platform) u32 { | ||
| 374 | return semanticVersionToAppleVersion(plat.version); | ||
| 375 | } | ||
| 376 | |||
| 377 | pub fn toApplePlatform(plat: Platform) macho.PLATFORM { | ||
| 378 | return switch (plat.os_tag) { | ||
| 379 | .macos => .MACOS, | ||
| 380 | .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS, | ||
| 381 | .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS, | ||
| 382 | .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS, | ||
| 383 | else => unreachable, | ||
| 384 | }; | ||
| 385 | } | ||
| 386 | |||
| 387 | pub fn isBuildVersionCompatible(plat: Platform) bool { | ||
| 388 | inline for (supported_platforms) |sup_plat| { | ||
| 389 | if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { | ||
| 390 | return sup_plat[2] <= plat.toAppleVersion(); | ||
| 391 | } | ||
| 392 | } | ||
| 393 | return false; | ||
| 394 | } | ||
| 395 | |||
| 396 | pub fn isVersionMinCompatible(plat: Platform) bool { | ||
| 397 | inline for (supported_platforms) |sup_plat| { | ||
| 398 | if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { | ||
| 399 | return sup_plat[3] <= plat.toAppleVersion(); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | return false; | ||
| 403 | } | ||
| 404 | |||
| 405 | pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) { | ||
| 406 | return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } }; | ||
| 407 | } | ||
| 408 | |||
| 409 | const FmtCtx = struct { | ||
| 410 | platform: Platform, | ||
| 411 | cpu_arch: std.Target.Cpu.Arch, | ||
| 412 | }; | ||
| 413 | |||
| 414 | pub fn formatTarget( | ||
| 415 | ctx: FmtCtx, | ||
| 416 | comptime unused_fmt_string: []const u8, | ||
| 417 | options: std.fmt.FormatOptions, | ||
| 418 | writer: anytype, | ||
| 419 | ) !void { | ||
| 420 | _ = unused_fmt_string; | ||
| 421 | _ = options; | ||
| 422 | try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) }); | ||
| 423 | if (ctx.platform.abi != .none) { | ||
| 424 | try writer.print("-{s}", .{@tagName(ctx.platform.abi)}); | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | /// Caller owns the memory. | ||
| 429 | pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 { | ||
| 430 | var buffer = std.ArrayList(u8).init(gpa); | ||
| 431 | defer buffer.deinit(); | ||
| 432 | try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)}); | ||
| 433 | return buffer.toOwnedSlice(); | ||
| 434 | } | ||
| 435 | |||
| 436 | pub fn eqlTarget(plat: Platform, other: Platform) bool { | ||
| 437 | return plat.os_tag == other.os_tag and plat.abi == other.abi; | ||
| 438 | } | ||
| 439 | }; | ||
| 440 | |||
| 441 | const SupportedPlatforms = struct { | ||
| 442 | std.Target.Os.Tag, | ||
| 443 | std.Target.Abi, | ||
| 444 | u32, // Min platform version for which to emit LC_BUILD_VERSION | ||
| 445 | u32, // Min supported platform version | ||
| 446 | }; | ||
| 447 | |||
| 448 | // Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52 | ||
| 449 | // zig fmt: off | ||
| 450 | const supported_platforms = [_]SupportedPlatforms{ | ||
| 451 | .{ .macos, .none, 0xA0E00, 0xA0800 }, | ||
| 452 | .{ .ios, .none, 0xC0000, 0x70000 }, | ||
| 453 | .{ .tvos, .none, 0xC0000, 0x70000 }, | ||
| 454 | .{ .watchos, .none, 0x50000, 0x20000 }, | ||
| 455 | .{ .ios, .simulator, 0xD0000, 0x80000 }, | ||
| 456 | .{ .tvos, .simulator, 0xD0000, 0x80000 }, | ||
| 457 | .{ .watchos, .simulator, 0x60000, 0x20000 }, | ||
| 458 | }; | ||
| 459 | // zig fmt: on | ||
| 460 | |||
| 461 | inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { | ||
| 462 | const major = version.major; | ||
| 463 | const minor = version.minor; | ||
| 464 | const patch = version.patch; | ||
| 465 | return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch)); | ||
| 466 | } | ||
| 467 | |||
| 468 | pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion { | ||
| 469 | return .{ | ||
| 470 | .major = @as(u16, @truncate(version >> 16)), | ||
| 471 | .minor = @as(u8, @truncate(version >> 8)), | ||
| 472 | .patch = @as(u8, @truncate(version)), | ||
| 473 | }; | ||
| 474 | } | ||
| 475 | |||
| 476 | pub fn inferSdkVersion(macho_file: *MachO) ?std.SemanticVersion { | ||
| 477 | const comp = macho_file.base.comp; | ||
| 478 | const gpa = comp.gpa; | ||
| 479 | |||
| 480 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | ||
| 481 | defer arena_allocator.deinit(); | ||
| 482 | const arena = arena_allocator.allocator(); | ||
| 483 | |||
| 484 | const sdk_layout = macho_file.sdk_layout orelse return null; | ||
| 485 | const sdk_dir = switch (sdk_layout) { | ||
| 486 | .sdk => comp.sysroot.?, | ||
| 487 | .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null, | ||
| 488 | }; | ||
| 489 | if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| { | ||
| 490 | return parseSdkVersion(ver); | ||
| 491 | } else |_| { | ||
| 492 | // Read from settings should always succeed when vendored. | ||
| 493 | if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version"); | ||
| 494 | } | ||
| 495 | |||
| 496 | // infer from pathname | ||
| 497 | const stem = std.fs.path.stem(sdk_dir); | ||
| 498 | const start = for (stem, 0..) |c, i| { | ||
| 499 | if (std.ascii.isDigit(c)) break i; | ||
| 500 | } else stem.len; | ||
| 501 | const end = for (stem[start..], start..) |c, i| { | ||
| 502 | if (std.ascii.isDigit(c) or c == '.') continue; | ||
| 503 | break i; | ||
| 504 | } else stem.len; | ||
| 505 | return parseSdkVersion(stem[start..end]); | ||
| 506 | } | ||
| 507 | |||
| 508 | // Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout. | ||
| 509 | // Use property `MinimalDisplayName` to determine version. | ||
| 510 | // The file/property is also available with vendored libc. | ||
| 511 | fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { | ||
| 512 | const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" }); | ||
| 513 | const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16)); | ||
| 514 | const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); | ||
| 515 | if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; | ||
| 516 | return error.SdkVersionFailure; | ||
| 517 | } | ||
| 518 | |||
| 519 | // Versions reported by Apple aren't exactly semantically valid as they usually omit | ||
| 520 | // the patch component, so we parse SDK value by hand. | ||
| 521 | fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion { | ||
| 522 | var parsed: std.SemanticVersion = .{ | ||
| 523 | .major = 0, | ||
| 524 | .minor = 0, | ||
| 525 | .patch = 0, | ||
| 526 | }; | ||
| 527 | |||
| 528 | const parseNext = struct { | ||
| 529 | fn parseNext(it: anytype) ?u16 { | ||
| 530 | const nn = it.next() orelse return null; | ||
| 531 | return std.fmt.parseInt(u16, nn, 10) catch null; | ||
| 532 | } | ||
| 533 | }.parseNext; | ||
| 534 | |||
| 535 | var it = std.mem.splitAny(u8, raw, "."); | ||
| 536 | parsed.major = parseNext(&it) orelse return null; | ||
| 537 | parsed.minor = parseNext(&it) orelse return null; | ||
| 538 | parsed.patch = parseNext(&it) orelse 0; | ||
| 539 | return parsed; | ||
| 540 | } | ||
| 541 | |||
| 542 | const expect = std.testing.expect; | ||
| 543 | const expectEqual = std.testing.expectEqual; | ||
| 544 | |||
| 545 | fn testParseSdkVersionSuccess(exp: std.SemanticVersion, raw: []const u8) !void { | ||
| 546 | const maybe_ver = parseSdkVersion(raw); | ||
| 547 | try expect(maybe_ver != null); | ||
| 548 | const ver = maybe_ver.?; | ||
| 549 | try expectEqual(exp.major, ver.major); | ||
| 550 | try expectEqual(exp.minor, ver.minor); | ||
| 551 | try expectEqual(exp.patch, ver.patch); | ||
| 552 | } | ||
| 553 | |||
| 554 | test "parseSdkVersion" { | ||
| 555 | try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4"); | ||
| 556 | try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1"); | ||
| 557 | try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15"); | ||
| 558 | |||
| 559 | try expect(parseSdkVersion("11") == null); | ||
| 560 | } | ||
| 561 | |||
| 562 | const std = @import("std"); | ||
| 563 | const assert = std.debug.assert; | ||
| 564 | const link = @import("../../link.zig"); | ||
| 565 | const log = std.log.scoped(.link); | ||
| 566 | const macho = std.macho; | ||
| 567 | const mem = std.mem; | ||
| 568 | |||
| 569 | const Allocator = mem.Allocator; | ||
| 570 | const Dylib = @import("Dylib.zig"); | ||
| 571 | const MachO = @import("../MachO.zig"); | ||
| 572 | const Compilation = @import("../../Compilation.zig"); |
src/link/MachO/relocatable.zig created+452| ... | @@ -0,0 +1,452 @@ | ||
| 1 | pub fn flush(macho_file: *MachO) !void { | ||
| 2 | markExports(macho_file); | ||
| 3 | claimUnresolved(macho_file); | ||
| 4 | try initOutputSections(macho_file); | ||
| 5 | try macho_file.sortSections(); | ||
| 6 | try macho_file.addAtomsToSections(); | ||
| 7 | try calcSectionSizes(macho_file); | ||
| 8 | |||
| 9 | { | ||
| 10 | // For relocatable, we only ever need a single segment so create it now. | ||
| 11 | const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC; | ||
| 12 | try macho_file.segments.append(macho_file.base.allocator, .{ | ||
| 13 | .cmdsize = @sizeOf(macho.segment_command_64), | ||
| 14 | .segname = MachO.makeStaticString(""), | ||
| 15 | .maxprot = prot, | ||
| 16 | .initprot = prot, | ||
| 17 | }); | ||
| 18 | const seg = &macho_file.segments.items[0]; | ||
| 19 | seg.nsects = @intCast(macho_file.sections.items(.header).len); | ||
| 20 | seg.cmdsize += seg.nsects * @sizeOf(macho.section_64); | ||
| 21 | } | ||
| 22 | |||
| 23 | var off = try allocateSections(macho_file); | ||
| 24 | |||
| 25 | { | ||
| 26 | // Allocate the single segment. | ||
| 27 | assert(macho_file.segments.items.len == 1); | ||
| 28 | const seg = &macho_file.segments.items[0]; | ||
| 29 | var vmaddr: u64 = 0; | ||
| 30 | var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64); | ||
| 31 | seg.vmaddr = vmaddr; | ||
| 32 | seg.fileoff = fileoff; | ||
| 33 | |||
| 34 | for (macho_file.sections.items(.header)) |header| { | ||
| 35 | vmaddr = header.addr + header.size; | ||
| 36 | if (!header.isZerofill()) { | ||
| 37 | fileoff = header.offset + header.size; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | seg.vmsize = vmaddr - seg.vmaddr; | ||
| 42 | seg.filesize = fileoff - seg.fileoff; | ||
| 43 | } | ||
| 44 | |||
| 45 | macho_file.allocateAtoms(); | ||
| 46 | |||
| 47 | state_log.debug("{}", .{macho_file.dumpState()}); | ||
| 48 | |||
| 49 | try macho_file.calcSymtabSize(); | ||
| 50 | try writeAtoms(macho_file); | ||
| 51 | try writeCompactUnwind(macho_file); | ||
| 52 | try writeEhFrame(macho_file); | ||
| 53 | |||
| 54 | off = mem.alignForward(u32, off, @alignOf(u64)); | ||
| 55 | off = try macho_file.writeDataInCode(0, off); | ||
| 56 | off = mem.alignForward(u32, off, @alignOf(u64)); | ||
| 57 | off = try macho_file.writeSymtab(off); | ||
| 58 | off = mem.alignForward(u32, off, @alignOf(u64)); | ||
| 59 | off = try macho_file.writeStrtab(off); | ||
| 60 | |||
| 61 | const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); | ||
| 62 | try writeHeader(macho_file, ncmds, sizeofcmds); | ||
| 63 | } | ||
| 64 | |||
| 65 | fn markExports(macho_file: *MachO) void { | ||
| 66 | for (macho_file.objects.items) |index| { | ||
| 67 | for (macho_file.getFile(index).?.getSymbols()) |sym_index| { | ||
| 68 | const sym = macho_file.getSymbol(sym_index); | ||
| 69 | const file = sym.getFile(macho_file) orelse continue; | ||
| 70 | if (sym.visibility != .global) continue; | ||
| 71 | if (file.getIndex() == index) { | ||
| 72 | sym.flags.@"export" = true; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | fn claimUnresolved(macho_file: *MachO) void { | ||
| 79 | for (macho_file.objects.items) |index| { | ||
| 80 | const object = macho_file.getFile(index).?.object; | ||
| 81 | |||
| 82 | for (object.symbols.items, 0..) |sym_index, i| { | ||
| 83 | const nlist_idx = @as(Symbol.Index, @intCast(i)); | ||
| 84 | const nlist = object.symtab.items(.nlist)[nlist_idx]; | ||
| 85 | if (!nlist.ext()) continue; | ||
| 86 | if (!nlist.undf()) continue; | ||
| 87 | |||
| 88 | const sym = macho_file.getSymbol(sym_index); | ||
| 89 | if (sym.getFile(macho_file) != null) continue; | ||
| 90 | |||
| 91 | sym.value = 0; | ||
| 92 | sym.atom = 0; | ||
| 93 | sym.nlist_idx = nlist_idx; | ||
| 94 | sym.file = index; | ||
| 95 | sym.flags.weak_ref = nlist.weakRef(); | ||
| 96 | sym.flags.import = true; | ||
| 97 | sym.visibility = .global; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | fn initOutputSections(macho_file: *MachO) !void { | ||
| 103 | for (macho_file.objects.items) |index| { | ||
| 104 | const object = macho_file.getFile(index).?.object; | ||
| 105 | for (object.atoms.items) |atom_index| { | ||
| 106 | const atom = macho_file.getAtom(atom_index) orelse continue; | ||
| 107 | if (!atom.flags.alive) continue; | ||
| 108 | atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | const needs_unwind_info = for (macho_file.objects.items) |index| { | ||
| 113 | if (macho_file.getFile(index).?.object.compact_unwind_sect_index != null) break true; | ||
| 114 | } else false; | ||
| 115 | if (needs_unwind_info) { | ||
| 116 | macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{ | ||
| 117 | .flags = macho.S_ATTR_DEBUG, | ||
| 118 | }); | ||
| 119 | } | ||
| 120 | |||
| 121 | const needs_eh_frame = for (macho_file.objects.items) |index| { | ||
| 122 | if (macho_file.getFile(index).?.object.eh_frame_sect_index != null) break true; | ||
| 123 | } else false; | ||
| 124 | if (needs_eh_frame) { | ||
| 125 | assert(needs_unwind_info); | ||
| 126 | macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{}); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | fn calcSectionSizes(macho_file: *MachO) !void { | ||
| 131 | const slice = macho_file.sections.slice(); | ||
| 132 | for (slice.items(.header), slice.items(.atoms)) |*header, atoms| { | ||
| 133 | if (atoms.items.len == 0) continue; | ||
| 134 | for (atoms.items) |atom_index| { | ||
| 135 | const atom = macho_file.getAtom(atom_index).?; | ||
| 136 | const atom_alignment = try math.powi(u32, 2, atom.alignment); | ||
| 137 | const offset = mem.alignForward(u64, header.size, atom_alignment); | ||
| 138 | const padding = offset - header.size; | ||
| 139 | atom.value = offset; | ||
| 140 | header.size += padding + atom.size; | ||
| 141 | header.@"align" = @max(header.@"align", atom.alignment); | ||
| 142 | header.nreloc += atom.calcNumRelocs(macho_file); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | if (macho_file.unwind_info_sect_index) |index| { | ||
| 147 | calcCompactUnwindSize(macho_file, index); | ||
| 148 | } | ||
| 149 | |||
| 150 | if (macho_file.eh_frame_sect_index) |index| { | ||
| 151 | const sect = &macho_file.sections.items(.header)[index]; | ||
| 152 | sect.size = try eh_frame.calcSize(macho_file); | ||
| 153 | sect.@"align" = 3; | ||
| 154 | sect.nreloc = eh_frame.calcNumRelocs(macho_file); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void { | ||
| 159 | var size: u32 = 0; | ||
| 160 | var nreloc: u32 = 0; | ||
| 161 | |||
| 162 | for (macho_file.objects.items) |index| { | ||
| 163 | const object = macho_file.getFile(index).?.object; | ||
| 164 | for (object.unwind_records.items) |irec| { | ||
| 165 | const rec = macho_file.getUnwindRecord(irec); | ||
| 166 | if (!rec.alive) continue; | ||
| 167 | size += @sizeOf(macho.compact_unwind_entry); | ||
| 168 | nreloc += 1; | ||
| 169 | if (rec.getPersonality(macho_file)) |_| { | ||
| 170 | nreloc += 1; | ||
| 171 | } | ||
| 172 | if (rec.getLsdaAtom(macho_file)) |_| { | ||
| 173 | nreloc += 1; | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | const sect = &macho_file.sections.items(.header)[sect_index]; | ||
| 179 | sect.size = size; | ||
| 180 | sect.nreloc = nreloc; | ||
| 181 | sect.@"align" = 3; | ||
| 182 | } | ||
| 183 | |||
| 184 | fn allocateSections(macho_file: *MachO) !u32 { | ||
| 185 | var fileoff = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64); | ||
| 186 | var vmaddr: u64 = 0; | ||
| 187 | const slice = macho_file.sections.slice(); | ||
| 188 | |||
| 189 | for (slice.items(.header)) |*header| { | ||
| 190 | const alignment = try math.powi(u32, 2, header.@"align"); | ||
| 191 | vmaddr = mem.alignForward(u64, vmaddr, alignment); | ||
| 192 | header.addr = vmaddr; | ||
| 193 | vmaddr += header.size; | ||
| 194 | |||
| 195 | if (!header.isZerofill()) { | ||
| 196 | fileoff = mem.alignForward(u32, fileoff, alignment); | ||
| 197 | header.offset = fileoff; | ||
| 198 | fileoff += @intCast(header.size); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | for (slice.items(.header)) |*header| { | ||
| 203 | if (header.nreloc == 0) continue; | ||
| 204 | header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info)); | ||
| 205 | fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info); | ||
| 206 | } | ||
| 207 | |||
| 208 | return fileoff; | ||
| 209 | } | ||
| 210 | |||
| 211 | // We need to sort relocations in descending order to be compatible with Apple's linker. | ||
| 212 | fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { | ||
| 213 | _ = ctx; | ||
| 214 | return lhs.r_address > rhs.r_address; | ||
| 215 | } | ||
| 216 | |||
| 217 | fn writeAtoms(macho_file: *MachO) !void { | ||
| 218 | const tracy = trace(@src()); | ||
| 219 | defer tracy.end(); | ||
| 220 | |||
| 221 | const gpa = macho_file.base.allocator; | ||
| 222 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 223 | const slice = macho_file.sections.slice(); | ||
| 224 | |||
| 225 | for (slice.items(.header), slice.items(.atoms)) |header, atoms| { | ||
| 226 | if (atoms.items.len == 0) continue; | ||
| 227 | if (header.isZerofill()) continue; | ||
| 228 | |||
| 229 | const code = try gpa.alloc(u8, header.size); | ||
| 230 | defer gpa.free(code); | ||
| 231 | const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; | ||
| 232 | @memset(code, padding_byte); | ||
| 233 | |||
| 234 | var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); | ||
| 235 | defer relocs.deinit(); | ||
| 236 | |||
| 237 | for (atoms.items) |atom_index| { | ||
| 238 | const atom = macho_file.getAtom(atom_index).?; | ||
| 239 | assert(atom.flags.alive); | ||
| 240 | const off = atom.value - header.addr; | ||
| 241 | @memcpy(code[off..][0..atom.size], atom.getCode(macho_file)); | ||
| 242 | try atom.writeRelocs(macho_file, code[off..][0..atom.size], &relocs); | ||
| 243 | } | ||
| 244 | |||
| 245 | assert(relocs.items.len == header.nreloc); | ||
| 246 | |||
| 247 | mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); | ||
| 248 | |||
| 249 | // TODO scattered writes? | ||
| 250 | try macho_file.base.file.pwriteAll(code, header.offset); | ||
| 251 | try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | fn writeCompactUnwind(macho_file: *MachO) !void { | ||
| 256 | const sect_index = macho_file.unwind_info_sect_index orelse return; | ||
| 257 | const gpa = macho_file.base.allocator; | ||
| 258 | const header = macho_file.sections.items(.header)[sect_index]; | ||
| 259 | |||
| 260 | const nrecs = @divExact(header.size, @sizeOf(macho.compact_unwind_entry)); | ||
| 261 | var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs); | ||
| 262 | defer entries.deinit(); | ||
| 263 | |||
| 264 | var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); | ||
| 265 | defer relocs.deinit(); | ||
| 266 | |||
| 267 | const addReloc = struct { | ||
| 268 | fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info { | ||
| 269 | return .{ | ||
| 270 | .r_address = offset, | ||
| 271 | .r_symbolnum = 0, | ||
| 272 | .r_pcrel = 0, | ||
| 273 | .r_length = 3, | ||
| 274 | .r_extern = 0, | ||
| 275 | .r_type = switch (cpu_arch) { | ||
| 276 | .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED), | ||
| 277 | .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED), | ||
| 278 | else => unreachable, | ||
| 279 | }, | ||
| 280 | }; | ||
| 281 | } | ||
| 282 | }.addReloc; | ||
| 283 | |||
| 284 | var offset: i32 = 0; | ||
| 285 | for (macho_file.objects.items) |index| { | ||
| 286 | const object = macho_file.getFile(index).?.object; | ||
| 287 | for (object.unwind_records.items) |irec| { | ||
| 288 | const rec = macho_file.getUnwindRecord(irec); | ||
| 289 | if (!rec.alive) continue; | ||
| 290 | |||
| 291 | var out: macho.compact_unwind_entry = .{ | ||
| 292 | .rangeStart = 0, | ||
| 293 | .rangeLength = rec.length, | ||
| 294 | .compactUnwindEncoding = rec.enc.enc, | ||
| 295 | .personalityFunction = 0, | ||
| 296 | .lsda = 0, | ||
| 297 | }; | ||
| 298 | |||
| 299 | { | ||
| 300 | // Function address | ||
| 301 | const atom = rec.getAtom(macho_file); | ||
| 302 | const addr = rec.getAtomAddress(macho_file); | ||
| 303 | out.rangeStart = addr; | ||
| 304 | var reloc = addReloc(offset, macho_file.options.cpu_arch.?); | ||
| 305 | reloc.r_symbolnum = atom.out_n_sect + 1; | ||
| 306 | relocs.appendAssumeCapacity(reloc); | ||
| 307 | } | ||
| 308 | |||
| 309 | // Personality function | ||
| 310 | if (rec.getPersonality(macho_file)) |sym| { | ||
| 311 | const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; | ||
| 312 | var reloc = addReloc(offset + 16, macho_file.options.cpu_arch.?); | ||
| 313 | reloc.r_symbolnum = r_symbolnum; | ||
| 314 | reloc.r_extern = 1; | ||
| 315 | relocs.appendAssumeCapacity(reloc); | ||
| 316 | } | ||
| 317 | |||
| 318 | // LSDA address | ||
| 319 | if (rec.getLsdaAtom(macho_file)) |atom| { | ||
| 320 | const addr = rec.getLsdaAddress(macho_file); | ||
| 321 | out.lsda = addr; | ||
| 322 | var reloc = addReloc(offset + 24, macho_file.options.cpu_arch.?); | ||
| 323 | reloc.r_symbolnum = atom.out_n_sect + 1; | ||
| 324 | relocs.appendAssumeCapacity(reloc); | ||
| 325 | } | ||
| 326 | |||
| 327 | entries.appendAssumeCapacity(out); | ||
| 328 | offset += @sizeOf(macho.compact_unwind_entry); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | assert(entries.items.len == nrecs); | ||
| 333 | assert(relocs.items.len == header.nreloc); | ||
| 334 | |||
| 335 | mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); | ||
| 336 | |||
| 337 | // TODO scattered writes? | ||
| 338 | try macho_file.base.file.pwriteAll(mem.sliceAsBytes(entries.items), header.offset); | ||
| 339 | try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); | ||
| 340 | } | ||
| 341 | |||
| 342 | fn writeEhFrame(macho_file: *MachO) !void { | ||
| 343 | const sect_index = macho_file.eh_frame_sect_index orelse return; | ||
| 344 | const gpa = macho_file.base.allocator; | ||
| 345 | const header = macho_file.sections.items(.header)[sect_index]; | ||
| 346 | |||
| 347 | const code = try gpa.alloc(u8, header.size); | ||
| 348 | defer gpa.free(code); | ||
| 349 | |||
| 350 | var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); | ||
| 351 | defer relocs.deinit(); | ||
| 352 | |||
| 353 | try eh_frame.writeRelocs(macho_file, code, &relocs); | ||
| 354 | assert(relocs.items.len == header.nreloc); | ||
| 355 | |||
| 356 | mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); | ||
| 357 | |||
| 358 | // TODO scattered writes? | ||
| 359 | try macho_file.base.file.pwriteAll(code, header.offset); | ||
| 360 | try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); | ||
| 361 | } | ||
| 362 | |||
| 363 | fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } { | ||
| 364 | const gpa = macho_file.base.allocator; | ||
| 365 | const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file); | ||
| 366 | const buffer = try gpa.alloc(u8, needed_size); | ||
| 367 | defer gpa.free(buffer); | ||
| 368 | |||
| 369 | var stream = std.io.fixedBufferStream(buffer); | ||
| 370 | var cwriter = std.io.countingWriter(stream.writer()); | ||
| 371 | const writer = cwriter.writer(); | ||
| 372 | |||
| 373 | var ncmds: usize = 0; | ||
| 374 | |||
| 375 | // Segment and section load commands | ||
| 376 | { | ||
| 377 | assert(macho_file.segments.items.len == 1); | ||
| 378 | const seg = macho_file.segments.items[0]; | ||
| 379 | try writer.writeStruct(seg); | ||
| 380 | for (macho_file.sections.items(.header)) |header| { | ||
| 381 | try writer.writeStruct(header); | ||
| 382 | } | ||
| 383 | ncmds += 1; | ||
| 384 | } | ||
| 385 | |||
| 386 | try writer.writeStruct(macho_file.data_in_code_cmd); | ||
| 387 | ncmds += 1; | ||
| 388 | try writer.writeStruct(macho_file.symtab_cmd); | ||
| 389 | ncmds += 1; | ||
| 390 | try writer.writeStruct(macho_file.dysymtab_cmd); | ||
| 391 | ncmds += 1; | ||
| 392 | |||
| 393 | if (macho_file.options.platform) |platform| { | ||
| 394 | if (platform.isBuildVersionCompatible()) { | ||
| 395 | try load_commands.writeBuildVersionLC(platform, macho_file.options.sdk_version, writer); | ||
| 396 | ncmds += 1; | ||
| 397 | } else { | ||
| 398 | try load_commands.writeVersionMinLC(platform, macho_file.options.sdk_version, writer); | ||
| 399 | ncmds += 1; | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | assert(cwriter.bytes_written == needed_size); | ||
| 404 | |||
| 405 | try macho_file.base.file.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); | ||
| 406 | |||
| 407 | return .{ ncmds, buffer.len }; | ||
| 408 | } | ||
| 409 | |||
| 410 | fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { | ||
| 411 | var header: macho.mach_header_64 = .{}; | ||
| 412 | header.filetype = macho.MH_OBJECT; | ||
| 413 | |||
| 414 | const subsections_via_symbols = for (macho_file.objects.items) |index| { | ||
| 415 | const object = macho_file.getFile(index).?.object; | ||
| 416 | if (object.hasSubsections()) break true; | ||
| 417 | } else false; | ||
| 418 | if (subsections_via_symbols) { | ||
| 419 | header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS; | ||
| 420 | } | ||
| 421 | |||
| 422 | switch (macho_file.options.cpu_arch.?) { | ||
| 423 | .aarch64 => { | ||
| 424 | header.cputype = macho.CPU_TYPE_ARM64; | ||
| 425 | header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL; | ||
| 426 | }, | ||
| 427 | .x86_64 => { | ||
| 428 | header.cputype = macho.CPU_TYPE_X86_64; | ||
| 429 | header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL; | ||
| 430 | }, | ||
| 431 | else => {}, | ||
| 432 | } | ||
| 433 | |||
| 434 | header.ncmds = @intCast(ncmds); | ||
| 435 | header.sizeofcmds = @intCast(sizeofcmds); | ||
| 436 | |||
| 437 | try macho_file.base.file.pwriteAll(mem.asBytes(&header), 0); | ||
| 438 | } | ||
| 439 | |||
| 440 | const assert = std.debug.assert; | ||
| 441 | const eh_frame = @import("eh_frame.zig"); | ||
| 442 | const load_commands = @import("load_commands.zig"); | ||
| 443 | const macho = std.macho; | ||
| 444 | const math = std.math; | ||
| 445 | const mem = std.mem; | ||
| 446 | const state_log = std.log.scoped(.state); | ||
| 447 | const std = @import("std"); | ||
| 448 | const trace = @import("../tracy.zig").trace; | ||
| 449 | |||
| 450 | const Atom = @import("Atom.zig"); | ||
| 451 | const MachO = @import("../MachO.zig"); | ||
| 452 | const Symbol = @import("Symbol.zig"); | ||
src/link/MachO/stubs.zig deleted-169| ... | @@ -1,169 +0,0 @@ | ||
| 1 | pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 { | ||
| 2 | return switch (cpu_arch) { | ||
| 3 | .x86_64 => 15, | ||
| 4 | .aarch64 => 6 * @sizeOf(u32), | ||
| 5 | else => unreachable, // unhandled architecture type | ||
| 6 | }; | ||
| 7 | } | ||
| 8 | |||
| 9 | pub inline fn stubHelperSize(cpu_arch: std.Target.Cpu.Arch) u8 { | ||
| 10 | return switch (cpu_arch) { | ||
| 11 | .x86_64 => 10, | ||
| 12 | .aarch64 => 3 * @sizeOf(u32), | ||
| 13 | else => unreachable, // unhandled architecture type | ||
| 14 | }; | ||
| 15 | } | ||
| 16 | |||
| 17 | pub inline fn stubSize(cpu_arch: std.Target.Cpu.Arch) u8 { | ||
| 18 | return switch (cpu_arch) { | ||
| 19 | .x86_64 => 6, | ||
| 20 | .aarch64 => 3 * @sizeOf(u32), | ||
| 21 | else => unreachable, // unhandled architecture type | ||
| 22 | }; | ||
| 23 | } | ||
| 24 | |||
| 25 | pub inline fn stubAlignment(cpu_arch: std.Target.Cpu.Arch) u8 { | ||
| 26 | return switch (cpu_arch) { | ||
| 27 | .x86_64 => 1, | ||
| 28 | .aarch64 => 4, | ||
| 29 | else => unreachable, // unhandled architecture type | ||
| 30 | }; | ||
| 31 | } | ||
| 32 | |||
| 33 | pub inline fn stubOffsetInStubHelper(cpu_arch: std.Target.Cpu.Arch) u8 { | ||
| 34 | return switch (cpu_arch) { | ||
| 35 | .x86_64 => 1, | ||
| 36 | .aarch64 => 2 * @sizeOf(u32), | ||
| 37 | else => unreachable, | ||
| 38 | }; | ||
| 39 | } | ||
| 40 | |||
| 41 | pub fn writeStubHelperPreambleCode(args: struct { | ||
| 42 | cpu_arch: std.Target.Cpu.Arch, | ||
| 43 | source_addr: u64, | ||
| 44 | dyld_private_addr: u64, | ||
| 45 | dyld_stub_binder_got_addr: u64, | ||
| 46 | }, writer: anytype) !void { | ||
| 47 | switch (args.cpu_arch) { | ||
| 48 | .x86_64 => { | ||
| 49 | try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d }); | ||
| 50 | { | ||
| 51 | const disp = try Relocation.calcPcRelativeDisplacementX86( | ||
| 52 | args.source_addr + 3, | ||
| 53 | args.dyld_private_addr, | ||
| 54 | 0, | ||
| 55 | ); | ||
| 56 | try writer.writeInt(i32, disp, .little); | ||
| 57 | } | ||
| 58 | try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 }); | ||
| 59 | { | ||
| 60 | const disp = try Relocation.calcPcRelativeDisplacementX86( | ||
| 61 | args.source_addr + 11, | ||
| 62 | args.dyld_stub_binder_got_addr, | ||
| 63 | 0, | ||
| 64 | ); | ||
| 65 | try writer.writeInt(i32, disp, .little); | ||
| 66 | } | ||
| 67 | }, | ||
| 68 | .aarch64 => { | ||
| 69 | { | ||
| 70 | const pages = Relocation.calcNumberOfPages(args.source_addr, args.dyld_private_addr); | ||
| 71 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little); | ||
| 72 | } | ||
| 73 | { | ||
| 74 | const off = try Relocation.calcPageOffset(args.dyld_private_addr, .arithmetic); | ||
| 75 | try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little); | ||
| 76 | } | ||
| 77 | try writer.writeInt(u32, aarch64.Instruction.stp( | ||
| 78 | .x16, | ||
| 79 | .x17, | ||
| 80 | aarch64.Register.sp, | ||
| 81 | aarch64.Instruction.LoadStorePairOffset.pre_index(-16), | ||
| 82 | ).toU32(), .little); | ||
| 83 | { | ||
| 84 | const pages = Relocation.calcNumberOfPages(args.source_addr + 12, args.dyld_stub_binder_got_addr); | ||
| 85 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 86 | } | ||
| 87 | { | ||
| 88 | const off = try Relocation.calcPageOffset(args.dyld_stub_binder_got_addr, .load_store_64); | ||
| 89 | try writer.writeInt(u32, aarch64.Instruction.ldr( | ||
| 90 | .x16, | ||
| 91 | .x16, | ||
| 92 | aarch64.Instruction.LoadStoreOffset.imm(off), | ||
| 93 | ).toU32(), .little); | ||
| 94 | } | ||
| 95 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 96 | }, | ||
| 97 | else => unreachable, | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | pub fn writeStubHelperCode(args: struct { | ||
| 102 | cpu_arch: std.Target.Cpu.Arch, | ||
| 103 | source_addr: u64, | ||
| 104 | target_addr: u64, | ||
| 105 | }, writer: anytype) !void { | ||
| 106 | switch (args.cpu_arch) { | ||
| 107 | .x86_64 => { | ||
| 108 | try writer.writeAll(&.{ 0x68, 0x0, 0x0, 0x0, 0x0, 0xe9 }); | ||
| 109 | { | ||
| 110 | const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 6, args.target_addr, 0); | ||
| 111 | try writer.writeInt(i32, disp, .little); | ||
| 112 | } | ||
| 113 | }, | ||
| 114 | .aarch64 => { | ||
| 115 | const stub_size: u4 = 3 * @sizeOf(u32); | ||
| 116 | const literal = blk: { | ||
| 117 | const div_res = try std.math.divExact(u64, stub_size - @sizeOf(u32), 4); | ||
| 118 | break :blk std.math.cast(u18, div_res) orelse return error.Overflow; | ||
| 119 | }; | ||
| 120 | try writer.writeInt(u32, aarch64.Instruction.ldrLiteral( | ||
| 121 | .w16, | ||
| 122 | literal, | ||
| 123 | ).toU32(), .little); | ||
| 124 | { | ||
| 125 | const disp = try Relocation.calcPcRelativeDisplacementArm64(args.source_addr + 4, args.target_addr); | ||
| 126 | try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little); | ||
| 127 | } | ||
| 128 | try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 }); | ||
| 129 | }, | ||
| 130 | else => unreachable, | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | pub fn writeStubCode(args: struct { | ||
| 135 | cpu_arch: std.Target.Cpu.Arch, | ||
| 136 | source_addr: u64, | ||
| 137 | target_addr: u64, | ||
| 138 | }, writer: anytype) !void { | ||
| 139 | switch (args.cpu_arch) { | ||
| 140 | .x86_64 => { | ||
| 141 | try writer.writeAll(&.{ 0xff, 0x25 }); | ||
| 142 | { | ||
| 143 | const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 2, args.target_addr, 0); | ||
| 144 | try writer.writeInt(i32, disp, .little); | ||
| 145 | } | ||
| 146 | }, | ||
| 147 | .aarch64 => { | ||
| 148 | { | ||
| 149 | const pages = Relocation.calcNumberOfPages(args.source_addr, args.target_addr); | ||
| 150 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 151 | } | ||
| 152 | { | ||
| 153 | const off = try Relocation.calcPageOffset(args.target_addr, .load_store_64); | ||
| 154 | try writer.writeInt(u32, aarch64.Instruction.ldr( | ||
| 155 | .x16, | ||
| 156 | .x16, | ||
| 157 | aarch64.Instruction.LoadStoreOffset.imm(off), | ||
| 158 | ).toU32(), .little); | ||
| 159 | } | ||
| 160 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 161 | }, | ||
| 162 | else => unreachable, | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | const std = @import("std"); | ||
| 167 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 168 | |||
| 169 | const Relocation = @import("Relocation.zig"); | ||
src/link/MachO/synthetic.zig created+669| ... | @@ -0,0 +1,669 @@ | ||
| 1 | pub const GotSection = struct { | ||
| 2 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 3 | |||
| 4 | pub const Index = u32; | ||
| 5 | |||
| 6 | pub fn deinit(got: *GotSection, allocator: Allocator) void { | ||
| 7 | got.symbols.deinit(allocator); | ||
| 8 | } | ||
| 9 | |||
| 10 | pub fn addSymbol(got: *GotSection, sym_index: Symbol.Index, macho_file: *MachO) !void { | ||
| 11 | const gpa = macho_file.base.allocator; | ||
| 12 | const index = @as(Index, @intCast(got.symbols.items.len)); | ||
| 13 | const entry = try got.symbols.addOne(gpa); | ||
| 14 | entry.* = sym_index; | ||
| 15 | const symbol = macho_file.getSymbol(sym_index); | ||
| 16 | try symbol.addExtra(.{ .got = index }, macho_file); | ||
| 17 | } | ||
| 18 | |||
| 19 | pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 { | ||
| 20 | assert(index < got.symbols.items.len); | ||
| 21 | const header = macho_file.sections.items(.header)[macho_file.got_sect_index.?]; | ||
| 22 | return header.addr + index * @sizeOf(u64); | ||
| 23 | } | ||
| 24 | |||
| 25 | pub fn size(got: GotSection) usize { | ||
| 26 | return got.symbols.items.len * @sizeOf(u64); | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void { | ||
| 30 | const tracy = trace(@src()); | ||
| 31 | defer tracy.end(); | ||
| 32 | const gpa = macho_file.base.allocator; | ||
| 33 | const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?]; | ||
| 34 | const seg = macho_file.segments.items[seg_id]; | ||
| 35 | |||
| 36 | for (got.symbols.items, 0..) |sym_index, idx| { | ||
| 37 | const sym = macho_file.getSymbol(sym_index); | ||
| 38 | const addr = got.getAddress(@intCast(idx), macho_file); | ||
| 39 | const entry = bind.Entry{ | ||
| 40 | .target = sym_index, | ||
| 41 | .offset = addr - seg.vmaddr, | ||
| 42 | .segment_id = seg_id, | ||
| 43 | .addend = 0, | ||
| 44 | }; | ||
| 45 | if (sym.flags.import) { | ||
| 46 | try macho_file.bind.entries.append(gpa, entry); | ||
| 47 | if (sym.flags.weak) { | ||
| 48 | try macho_file.weak_bind.entries.append(gpa, entry); | ||
| 49 | } | ||
| 50 | } else { | ||
| 51 | try macho_file.rebase.entries.append(gpa, .{ | ||
| 52 | .offset = addr - seg.vmaddr, | ||
| 53 | .segment_id = seg_id, | ||
| 54 | }); | ||
| 55 | if (sym.flags.weak) { | ||
| 56 | try macho_file.weak_bind.entries.append(gpa, entry); | ||
| 57 | } else if (sym.flags.interposable) { | ||
| 58 | try macho_file.bind.entries.append(gpa, entry); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void { | ||
| 65 | const tracy = trace(@src()); | ||
| 66 | defer tracy.end(); | ||
| 67 | for (got.symbols.items) |sym_index| { | ||
| 68 | const sym = macho_file.getSymbol(sym_index); | ||
| 69 | const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file); | ||
| 70 | try writer.writeInt(u64, value, .little); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | const FormatCtx = struct { | ||
| 75 | got: GotSection, | ||
| 76 | macho_file: *MachO, | ||
| 77 | }; | ||
| 78 | |||
| 79 | pub fn fmt(got: GotSection, macho_file: *MachO) std.fmt.Formatter(format2) { | ||
| 80 | return .{ .data = .{ .got = got, .macho_file = macho_file } }; | ||
| 81 | } | ||
| 82 | |||
| 83 | pub fn format2( | ||
| 84 | ctx: FormatCtx, | ||
| 85 | comptime unused_fmt_string: []const u8, | ||
| 86 | options: std.fmt.FormatOptions, | ||
| 87 | writer: anytype, | ||
| 88 | ) !void { | ||
| 89 | _ = options; | ||
| 90 | _ = unused_fmt_string; | ||
| 91 | for (ctx.got.symbols.items, 0..) |entry, i| { | ||
| 92 | const symbol = ctx.macho_file.getSymbol(entry); | ||
| 93 | try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ | ||
| 94 | i, | ||
| 95 | symbol.getGotAddress(ctx.macho_file), | ||
| 96 | entry, | ||
| 97 | symbol.getAddress(.{}, ctx.macho_file), | ||
| 98 | symbol.getName(ctx.macho_file), | ||
| 99 | }); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | |||
| 104 | pub const StubsSection = struct { | ||
| 105 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 106 | |||
| 107 | pub const Index = u32; | ||
| 108 | |||
| 109 | pub fn deinit(stubs: *StubsSection, allocator: Allocator) void { | ||
| 110 | stubs.symbols.deinit(allocator); | ||
| 111 | } | ||
| 112 | |||
| 113 | pub fn addSymbol(stubs: *StubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void { | ||
| 114 | const gpa = macho_file.base.allocator; | ||
| 115 | const index = @as(Index, @intCast(stubs.symbols.items.len)); | ||
| 116 | const entry = try stubs.symbols.addOne(gpa); | ||
| 117 | entry.* = sym_index; | ||
| 118 | const symbol = macho_file.getSymbol(sym_index); | ||
| 119 | try symbol.addExtra(.{ .stubs = index }, macho_file); | ||
| 120 | } | ||
| 121 | |||
| 122 | pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 { | ||
| 123 | assert(index < stubs.symbols.items.len); | ||
| 124 | const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?]; | ||
| 125 | return header.addr + index * header.reserved2; | ||
| 126 | } | ||
| 127 | |||
| 128 | pub fn size(stubs: StubsSection, macho_file: *MachO) usize { | ||
| 129 | const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?]; | ||
| 130 | return stubs.symbols.items.len * header.reserved2; | ||
| 131 | } | ||
| 132 | |||
| 133 | pub fn write(stubs: StubsSection, macho_file: *MachO, writer: anytype) !void { | ||
| 134 | const tracy = trace(@src()); | ||
| 135 | defer tracy.end(); | ||
| 136 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 137 | const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?]; | ||
| 138 | |||
| 139 | for (stubs.symbols.items, 0..) |sym_index, idx| { | ||
| 140 | const sym = macho_file.getSymbol(sym_index); | ||
| 141 | const source = sym.getAddress(.{ .stubs = true }, macho_file); | ||
| 142 | const target = laptr_sect.addr + idx * @sizeOf(u64); | ||
| 143 | switch (cpu_arch) { | ||
| 144 | .x86_64 => { | ||
| 145 | try writer.writeAll(&.{ 0xff, 0x25 }); | ||
| 146 | try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little); | ||
| 147 | }, | ||
| 148 | .aarch64 => { | ||
| 149 | // TODO relax if possible | ||
| 150 | const pages = try Relocation.calcNumberOfPages(source, target); | ||
| 151 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 152 | const off = try Relocation.calcPageOffset(target, .load_store_64); | ||
| 153 | try writer.writeInt( | ||
| 154 | u32, | ||
| 155 | aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(), | ||
| 156 | .little, | ||
| 157 | ); | ||
| 158 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 159 | }, | ||
| 160 | else => unreachable, | ||
| 161 | } | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | const FormatCtx = struct { | ||
| 166 | stubs: StubsSection, | ||
| 167 | macho_file: *MachO, | ||
| 168 | }; | ||
| 169 | |||
| 170 | pub fn fmt(stubs: StubsSection, macho_file: *MachO) std.fmt.Formatter(format2) { | ||
| 171 | return .{ .data = .{ .stubs = stubs, .macho_file = macho_file } }; | ||
| 172 | } | ||
| 173 | |||
| 174 | pub fn format2( | ||
| 175 | ctx: FormatCtx, | ||
| 176 | comptime unused_fmt_string: []const u8, | ||
| 177 | options: std.fmt.FormatOptions, | ||
| 178 | writer: anytype, | ||
| 179 | ) !void { | ||
| 180 | _ = options; | ||
| 181 | _ = unused_fmt_string; | ||
| 182 | for (ctx.stubs.symbols.items, 0..) |entry, i| { | ||
| 183 | const symbol = ctx.macho_file.getSymbol(entry); | ||
| 184 | try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ | ||
| 185 | i, | ||
| 186 | symbol.getStubsAddress(ctx.macho_file), | ||
| 187 | entry, | ||
| 188 | symbol.getAddress(.{}, ctx.macho_file), | ||
| 189 | symbol.getName(ctx.macho_file), | ||
| 190 | }); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | }; | ||
| 194 | |||
| 195 | pub const StubsHelperSection = struct { | ||
| 196 | pub inline fn preambleSize(cpu_arch: std.Target.Cpu.Arch) usize { | ||
| 197 | return switch (cpu_arch) { | ||
| 198 | .x86_64 => 15, | ||
| 199 | .aarch64 => 6 * @sizeOf(u32), | ||
| 200 | else => 0, | ||
| 201 | }; | ||
| 202 | } | ||
| 203 | |||
| 204 | pub inline fn entrySize(cpu_arch: std.Target.Cpu.Arch) usize { | ||
| 205 | return switch (cpu_arch) { | ||
| 206 | .x86_64 => 10, | ||
| 207 | .aarch64 => 3 * @sizeOf(u32), | ||
| 208 | else => 0, | ||
| 209 | }; | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn size(stubs_helper: StubsHelperSection, macho_file: *MachO) usize { | ||
| 213 | const tracy = trace(@src()); | ||
| 214 | defer tracy.end(); | ||
| 215 | _ = stubs_helper; | ||
| 216 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 217 | var s: usize = preambleSize(cpu_arch); | ||
| 218 | for (macho_file.stubs.symbols.items) |sym_index| { | ||
| 219 | const sym = macho_file.getSymbol(sym_index); | ||
| 220 | if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) { | ||
| 221 | s += entrySize(cpu_arch); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | return s; | ||
| 225 | } | ||
| 226 | |||
| 227 | pub fn write(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void { | ||
| 228 | const tracy = trace(@src()); | ||
| 229 | defer tracy.end(); | ||
| 230 | |||
| 231 | try stubs_helper.writePreamble(macho_file, writer); | ||
| 232 | |||
| 233 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 234 | const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?]; | ||
| 235 | const preamble_size = preambleSize(cpu_arch); | ||
| 236 | const entry_size = entrySize(cpu_arch); | ||
| 237 | |||
| 238 | var idx: usize = 0; | ||
| 239 | for (macho_file.stubs.symbols.items) |sym_index| { | ||
| 240 | const sym = macho_file.getSymbol(sym_index); | ||
| 241 | if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) { | ||
| 242 | const offset = macho_file.lazy_bind.offsets.items[idx]; | ||
| 243 | const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx); | ||
| 244 | const target: i64 = @intCast(sect.addr); | ||
| 245 | switch (cpu_arch) { | ||
| 246 | .x86_64 => { | ||
| 247 | try writer.writeByte(0x68); | ||
| 248 | try writer.writeInt(u32, offset, .little); | ||
| 249 | try writer.writeByte(0xe9); | ||
| 250 | try writer.writeInt(i32, @intCast(target - source - 6 - 4), .little); | ||
| 251 | }, | ||
| 252 | .aarch64 => { | ||
| 253 | const literal = blk: { | ||
| 254 | const div_res = try std.math.divExact(u64, entry_size - @sizeOf(u32), 4); | ||
| 255 | break :blk std.math.cast(u18, div_res) orelse return error.Overflow; | ||
| 256 | }; | ||
| 257 | try writer.writeInt(u32, aarch64.Instruction.ldrLiteral( | ||
| 258 | .w16, | ||
| 259 | literal, | ||
| 260 | ).toU32(), .little); | ||
| 261 | const disp = math.cast(i28, @as(i64, @intCast(target)) - @as(i64, @intCast(source + 4))) orelse | ||
| 262 | return error.Overflow; | ||
| 263 | try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little); | ||
| 264 | try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 }); | ||
| 265 | }, | ||
| 266 | else => unreachable, | ||
| 267 | } | ||
| 268 | idx += 1; | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void { | ||
| 274 | _ = stubs_helper; | ||
| 275 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 276 | const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?]; | ||
| 277 | const dyld_private_addr = target: { | ||
| 278 | const sym = macho_file.getSymbol(macho_file.dyld_private_index.?); | ||
| 279 | break :target sym.getAddress(.{}, macho_file); | ||
| 280 | }; | ||
| 281 | const dyld_stub_binder_addr = target: { | ||
| 282 | const sym = macho_file.getSymbol(macho_file.dyld_stub_binder_index.?); | ||
| 283 | break :target sym.getGotAddress(macho_file); | ||
| 284 | }; | ||
| 285 | switch (cpu_arch) { | ||
| 286 | .x86_64 => { | ||
| 287 | try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d }); | ||
| 288 | try writer.writeInt(i32, @intCast(dyld_private_addr - sect.addr - 3 - 4), .little); | ||
| 289 | try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 }); | ||
| 290 | try writer.writeInt(i32, @intCast(dyld_stub_binder_addr - sect.addr - 11 - 4), .little); | ||
| 291 | }, | ||
| 292 | .aarch64 => { | ||
| 293 | { | ||
| 294 | // TODO relax if possible | ||
| 295 | const pages = try Relocation.calcNumberOfPages(sect.addr, dyld_private_addr); | ||
| 296 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little); | ||
| 297 | const off = try Relocation.calcPageOffset(dyld_private_addr, .arithmetic); | ||
| 298 | try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little); | ||
| 299 | } | ||
| 300 | try writer.writeInt(u32, aarch64.Instruction.stp( | ||
| 301 | .x16, | ||
| 302 | .x17, | ||
| 303 | aarch64.Register.sp, | ||
| 304 | aarch64.Instruction.LoadStorePairOffset.pre_index(-16), | ||
| 305 | ).toU32(), .little); | ||
| 306 | { | ||
| 307 | // TODO relax if possible | ||
| 308 | const pages = try Relocation.calcNumberOfPages(sect.addr + 12, dyld_stub_binder_addr); | ||
| 309 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 310 | const off = try Relocation.calcPageOffset(dyld_stub_binder_addr, .load_store_64); | ||
| 311 | try writer.writeInt(u32, aarch64.Instruction.ldr( | ||
| 312 | .x16, | ||
| 313 | .x16, | ||
| 314 | aarch64.Instruction.LoadStoreOffset.imm(off), | ||
| 315 | ).toU32(), .little); | ||
| 316 | } | ||
| 317 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 318 | }, | ||
| 319 | else => unreachable, | ||
| 320 | } | ||
| 321 | } | ||
| 322 | }; | ||
| 323 | |||
| 324 | pub const LaSymbolPtrSection = struct { | ||
| 325 | pub fn size(laptr: LaSymbolPtrSection, macho_file: *MachO) usize { | ||
| 326 | _ = laptr; | ||
| 327 | return macho_file.stubs.symbols.items.len * @sizeOf(u64); | ||
| 328 | } | ||
| 329 | |||
| 330 | pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void { | ||
| 331 | const tracy = trace(@src()); | ||
| 332 | defer tracy.end(); | ||
| 333 | _ = laptr; | ||
| 334 | const gpa = macho_file.base.allocator; | ||
| 335 | |||
| 336 | const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?]; | ||
| 337 | const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?]; | ||
| 338 | const seg = macho_file.segments.items[seg_id]; | ||
| 339 | |||
| 340 | for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| { | ||
| 341 | const sym = macho_file.getSymbol(sym_index); | ||
| 342 | const addr = sect.addr + idx * @sizeOf(u64); | ||
| 343 | const entry = bind.Entry{ | ||
| 344 | .target = sym_index, | ||
| 345 | .offset = addr - seg.vmaddr, | ||
| 346 | .segment_id = seg_id, | ||
| 347 | .addend = 0, | ||
| 348 | }; | ||
| 349 | if (sym.flags.import) { | ||
| 350 | if (sym.flags.weak) { | ||
| 351 | try macho_file.bind.entries.append(gpa, entry); | ||
| 352 | try macho_file.weak_bind.entries.append(gpa, entry); | ||
| 353 | } else { | ||
| 354 | try macho_file.lazy_bind.entries.append(gpa, entry); | ||
| 355 | } | ||
| 356 | } else { | ||
| 357 | if (sym.flags.weak) { | ||
| 358 | try macho_file.rebase.entries.append(gpa, .{ | ||
| 359 | .offset = addr - seg.vmaddr, | ||
| 360 | .segment_id = seg_id, | ||
| 361 | }); | ||
| 362 | try macho_file.weak_bind.entries.append(gpa, entry); | ||
| 363 | } else if (sym.flags.interposable) { | ||
| 364 | try macho_file.lazy_bind.entries.append(gpa, entry); | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void { | ||
| 371 | const tracy = trace(@src()); | ||
| 372 | defer tracy.end(); | ||
| 373 | _ = laptr; | ||
| 374 | const cpu_arch = macho_file.options.cpu_arch.?; | ||
| 375 | const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?]; | ||
| 376 | for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| { | ||
| 377 | const sym = macho_file.getSymbol(sym_index); | ||
| 378 | const value: u64 = if (sym.flags.@"export") | ||
| 379 | sym.getAddress(.{ .stubs = false }, macho_file) | ||
| 380 | else if (sym.flags.weak) | ||
| 381 | @as(u64, 0) | ||
| 382 | else | ||
| 383 | sect.addr + StubsHelperSection.preambleSize(cpu_arch) + | ||
| 384 | StubsHelperSection.entrySize(cpu_arch) * idx; | ||
| 385 | try writer.writeInt(u64, @intCast(value), .little); | ||
| 386 | } | ||
| 387 | } | ||
| 388 | }; | ||
| 389 | |||
| 390 | pub const TlvPtrSection = struct { | ||
| 391 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 392 | |||
| 393 | pub const Index = u32; | ||
| 394 | |||
| 395 | pub fn deinit(tlv: *TlvPtrSection, allocator: Allocator) void { | ||
| 396 | tlv.symbols.deinit(allocator); | ||
| 397 | } | ||
| 398 | |||
| 399 | pub fn addSymbol(tlv: *TlvPtrSection, sym_index: Symbol.Index, macho_file: *MachO) !void { | ||
| 400 | const gpa = macho_file.base.allocator; | ||
| 401 | const index = @as(Index, @intCast(tlv.symbols.items.len)); | ||
| 402 | const entry = try tlv.symbols.addOne(gpa); | ||
| 403 | entry.* = sym_index; | ||
| 404 | const symbol = macho_file.getSymbol(sym_index); | ||
| 405 | try symbol.addExtra(.{ .tlv_ptr = index }, macho_file); | ||
| 406 | } | ||
| 407 | |||
| 408 | pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 { | ||
| 409 | assert(index < tlv.symbols.items.len); | ||
| 410 | const header = macho_file.sections.items(.header)[macho_file.tlv_ptr_sect_index.?]; | ||
| 411 | return header.addr + index * @sizeOf(u64) * 3; | ||
| 412 | } | ||
| 413 | |||
| 414 | pub fn size(tlv: TlvPtrSection) usize { | ||
| 415 | return tlv.symbols.items.len * @sizeOf(u64); | ||
| 416 | } | ||
| 417 | |||
| 418 | pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void { | ||
| 419 | const tracy = trace(@src()); | ||
| 420 | defer tracy.end(); | ||
| 421 | const gpa = macho_file.base.allocator; | ||
| 422 | const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?]; | ||
| 423 | const seg = macho_file.segments.items[seg_id]; | ||
| 424 | |||
| 425 | for (tlv.symbols.items, 0..) |sym_index, idx| { | ||
| 426 | const sym = macho_file.getSymbol(sym_index); | ||
| 427 | const addr = tlv.getAddress(@intCast(idx), macho_file); | ||
| 428 | const entry = bind.Entry{ | ||
| 429 | .target = sym_index, | ||
| 430 | .offset = addr - seg.vmaddr, | ||
| 431 | .segment_id = seg_id, | ||
| 432 | .addend = 0, | ||
| 433 | }; | ||
| 434 | if (sym.flags.import) { | ||
| 435 | try macho_file.bind.entries.append(gpa, entry); | ||
| 436 | if (sym.flags.weak) { | ||
| 437 | try macho_file.weak_bind.entries.append(gpa, entry); | ||
| 438 | } | ||
| 439 | } else { | ||
| 440 | try macho_file.rebase.entries.append(gpa, .{ | ||
| 441 | .offset = addr - seg.vmaddr, | ||
| 442 | .segment_id = seg_id, | ||
| 443 | }); | ||
| 444 | if (sym.flags.weak) { | ||
| 445 | try macho_file.weak_bind.entries.append(gpa, entry); | ||
| 446 | } else if (sym.flags.interposable) { | ||
| 447 | try macho_file.bind.entries.append(gpa, entry); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void { | ||
| 454 | const tracy = trace(@src()); | ||
| 455 | defer tracy.end(); | ||
| 456 | |||
| 457 | for (tlv.symbols.items) |sym_index| { | ||
| 458 | const sym = macho_file.getSymbol(sym_index); | ||
| 459 | if (sym.flags.import) { | ||
| 460 | try writer.writeInt(u64, 0, .little); | ||
| 461 | } else { | ||
| 462 | try writer.writeInt(u64, sym.getAddress(.{}, macho_file), .little); | ||
| 463 | } | ||
| 464 | } | ||
| 465 | } | ||
| 466 | |||
| 467 | const FormatCtx = struct { | ||
| 468 | tlv: TlvPtrSection, | ||
| 469 | macho_file: *MachO, | ||
| 470 | }; | ||
| 471 | |||
| 472 | pub fn fmt(tlv: TlvPtrSection, macho_file: *MachO) std.fmt.Formatter(format2) { | ||
| 473 | return .{ .data = .{ .tlv = tlv, .macho_file = macho_file } }; | ||
| 474 | } | ||
| 475 | |||
| 476 | pub fn format2( | ||
| 477 | ctx: FormatCtx, | ||
| 478 | comptime unused_fmt_string: []const u8, | ||
| 479 | options: std.fmt.FormatOptions, | ||
| 480 | writer: anytype, | ||
| 481 | ) !void { | ||
| 482 | _ = options; | ||
| 483 | _ = unused_fmt_string; | ||
| 484 | for (ctx.tlv.symbols.items, 0..) |entry, i| { | ||
| 485 | const symbol = ctx.macho_file.getSymbol(entry); | ||
| 486 | try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ | ||
| 487 | i, | ||
| 488 | symbol.getTlvPtrAddress(ctx.macho_file), | ||
| 489 | entry, | ||
| 490 | symbol.getAddress(.{}, ctx.macho_file), | ||
| 491 | symbol.getName(ctx.macho_file), | ||
| 492 | }); | ||
| 493 | } | ||
| 494 | } | ||
| 495 | }; | ||
| 496 | |||
| 497 | pub const ObjcStubsSection = struct { | ||
| 498 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 499 | |||
| 500 | pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void { | ||
| 501 | objc.symbols.deinit(allocator); | ||
| 502 | } | ||
| 503 | |||
| 504 | pub fn entrySize(cpu_arch: std.Target.Cpu.Arch) u8 { | ||
| 505 | return switch (cpu_arch) { | ||
| 506 | .x86_64 => 13, | ||
| 507 | .aarch64 => 8 * @sizeOf(u32), | ||
| 508 | else => unreachable, | ||
| 509 | }; | ||
| 510 | } | ||
| 511 | |||
| 512 | pub fn addSymbol(objc: *ObjcStubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void { | ||
| 513 | const gpa = macho_file.base.allocator; | ||
| 514 | const index = @as(Index, @intCast(objc.symbols.items.len)); | ||
| 515 | const entry = try objc.symbols.addOne(gpa); | ||
| 516 | entry.* = sym_index; | ||
| 517 | const symbol = macho_file.getSymbol(sym_index); | ||
| 518 | try symbol.addExtra(.{ .objc_stubs = index }, macho_file); | ||
| 519 | } | ||
| 520 | |||
| 521 | pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 { | ||
| 522 | assert(index < objc.symbols.items.len); | ||
| 523 | const header = macho_file.sections.items(.header)[macho_file.objc_stubs_sect_index.?]; | ||
| 524 | return header.addr + index * entrySize(macho_file.options.cpu_arch.?); | ||
| 525 | } | ||
| 526 | |||
| 527 | pub fn size(objc: ObjcStubsSection, macho_file: *MachO) usize { | ||
| 528 | return objc.symbols.items.len * entrySize(macho_file.options.cpu_arch.?); | ||
| 529 | } | ||
| 530 | |||
| 531 | pub fn write(objc: ObjcStubsSection, macho_file: *MachO, writer: anytype) !void { | ||
| 532 | const tracy = trace(@src()); | ||
| 533 | defer tracy.end(); | ||
| 534 | |||
| 535 | for (objc.symbols.items, 0..) |sym_index, idx| { | ||
| 536 | const sym = macho_file.getSymbol(sym_index); | ||
| 537 | const addr = objc.getAddress(@intCast(idx), macho_file); | ||
| 538 | switch (macho_file.options.cpu_arch.?) { | ||
| 539 | .x86_64 => { | ||
| 540 | try writer.writeAll(&.{ 0x48, 0x8b, 0x35 }); | ||
| 541 | { | ||
| 542 | const target = sym.getObjcSelrefsAddress(macho_file); | ||
| 543 | const source = addr; | ||
| 544 | try writer.writeInt(i32, @intCast(target - source - 3 - 4), .little); | ||
| 545 | } | ||
| 546 | try writer.writeAll(&.{ 0xff, 0x25 }); | ||
| 547 | { | ||
| 548 | const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?); | ||
| 549 | const target = target_sym.getGotAddress(macho_file); | ||
| 550 | const source = addr + 7; | ||
| 551 | try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little); | ||
| 552 | } | ||
| 553 | }, | ||
| 554 | .aarch64 => { | ||
| 555 | { | ||
| 556 | const target = sym.getObjcSelrefsAddress(macho_file); | ||
| 557 | const source = addr; | ||
| 558 | const pages = try Relocation.calcNumberOfPages(source, target); | ||
| 559 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x1, pages).toU32(), .little); | ||
| 560 | const off = try Relocation.calcPageOffset(target, .load_store_64); | ||
| 561 | try writer.writeInt( | ||
| 562 | u32, | ||
| 563 | aarch64.Instruction.ldr(.x1, .x1, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(), | ||
| 564 | .little, | ||
| 565 | ); | ||
| 566 | } | ||
| 567 | { | ||
| 568 | const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?); | ||
| 569 | const target = target_sym.getGotAddress(macho_file); | ||
| 570 | const source = addr + 2 * @sizeOf(u32); | ||
| 571 | const pages = try Relocation.calcNumberOfPages(source, target); | ||
| 572 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 573 | const off = try Relocation.calcPageOffset(target, .load_store_64); | ||
| 574 | try writer.writeInt( | ||
| 575 | u32, | ||
| 576 | aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(), | ||
| 577 | .little, | ||
| 578 | ); | ||
| 579 | } | ||
| 580 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 581 | try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little); | ||
| 582 | try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little); | ||
| 583 | try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little); | ||
| 584 | }, | ||
| 585 | else => unreachable, | ||
| 586 | } | ||
| 587 | } | ||
| 588 | } | ||
| 589 | |||
| 590 | const FormatCtx = struct { | ||
| 591 | objc: ObjcStubsSection, | ||
| 592 | macho_file: *MachO, | ||
| 593 | }; | ||
| 594 | |||
| 595 | pub fn fmt(objc: ObjcStubsSection, macho_file: *MachO) std.fmt.Formatter(format2) { | ||
| 596 | return .{ .data = .{ .objc = objc, .macho_file = macho_file } }; | ||
| 597 | } | ||
| 598 | |||
| 599 | pub fn format2( | ||
| 600 | ctx: FormatCtx, | ||
| 601 | comptime unused_fmt_string: []const u8, | ||
| 602 | options: std.fmt.FormatOptions, | ||
| 603 | writer: anytype, | ||
| 604 | ) !void { | ||
| 605 | _ = options; | ||
| 606 | _ = unused_fmt_string; | ||
| 607 | for (ctx.objc.symbols.items, 0..) |entry, i| { | ||
| 608 | const symbol = ctx.macho_file.getSymbol(entry); | ||
| 609 | try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ | ||
| 610 | i, | ||
| 611 | symbol.getObjcStubsAddress(ctx.macho_file), | ||
| 612 | entry, | ||
| 613 | symbol.getAddress(.{}, ctx.macho_file), | ||
| 614 | symbol.getName(ctx.macho_file), | ||
| 615 | }); | ||
| 616 | } | ||
| 617 | } | ||
| 618 | |||
| 619 | pub const Index = u32; | ||
| 620 | }; | ||
| 621 | |||
| 622 | pub const Indsymtab = struct { | ||
| 623 | pub inline fn nsyms(ind: Indsymtab, macho_file: *MachO) u32 { | ||
| 624 | _ = ind; | ||
| 625 | return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len); | ||
| 626 | } | ||
| 627 | |||
| 628 | pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void { | ||
| 629 | const tracy = trace(@src()); | ||
| 630 | defer tracy.end(); | ||
| 631 | |||
| 632 | _ = ind; | ||
| 633 | |||
| 634 | for (macho_file.stubs.symbols.items) |sym_index| { | ||
| 635 | const sym = macho_file.getSymbol(sym_index); | ||
| 636 | try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little); | ||
| 637 | } | ||
| 638 | |||
| 639 | for (macho_file.got.symbols.items) |sym_index| { | ||
| 640 | const sym = macho_file.getSymbol(sym_index); | ||
| 641 | try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little); | ||
| 642 | } | ||
| 643 | |||
| 644 | for (macho_file.stubs.symbols.items) |sym_index| { | ||
| 645 | const sym = macho_file.getSymbol(sym_index); | ||
| 646 | try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little); | ||
| 647 | } | ||
| 648 | } | ||
| 649 | }; | ||
| 650 | |||
| 651 | pub const RebaseSection = Rebase; | ||
| 652 | pub const BindSection = bind.Bind; | ||
| 653 | pub const WeakBindSection = bind.WeakBind; | ||
| 654 | pub const LazyBindSection = bind.LazyBind; | ||
| 655 | pub const ExportTrieSection = Trie; | ||
| 656 | |||
| 657 | const aarch64 = @import("../aarch64.zig"); | ||
| 658 | const assert = std.debug.assert; | ||
| 659 | const bind = @import("dyld_info/bind.zig"); | ||
| 660 | const math = std.math; | ||
| 661 | const std = @import("std"); | ||
| 662 | const trace = @import("../tracy.zig").trace; | ||
| 663 | |||
| 664 | const Allocator = std.mem.Allocator; | ||
| 665 | const MachO = @import("../MachO.zig"); | ||
| 666 | const Rebase = @import("dyld_info/Rebase.zig"); | ||
| 667 | const Relocation = @import("Relocation.zig"); | ||
| 668 | const Symbol = @import("Symbol.zig"); | ||
| 669 | const Trie = @import("dyld_info/Trie.zig"); | ||
src/link/MachO/thunks.zig+137-335| ... | @@ -1,374 +1,176 @@ | ... | @@ -1,374 +1,176 @@ |
| 1 | //! An algorithm for allocating output machine code section (aka `__TEXT,__text`), | 1 | pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { |
| 2 | //! and insertion of range extending thunks. As such, this algorithm is only run | 2 | const tracy = trace(@src()); |
| 3 | //! for a target that requires range extenders such as arm64. | 3 | defer tracy.end(); |
| 4 | //! | 4 | |
| 5 | //! The algorithm works pessimistically and assumes that any reference to an Atom in | 5 | const gpa = macho_file.base.allocator; |
| 6 | //! another output section is out of range. | 6 | const slice = macho_file.sections.slice(); |
| 7 | 7 | const header = &slice.items(.header)[sect_id]; | |
| 8 | /// Branch instruction has 26 bits immediate but 4 byte aligned. | 8 | const atoms = slice.items(.atoms)[sect_id].items; |
| 9 | const jump_bits = @bitSizeOf(i28); | 9 | assert(atoms.len > 0); |
| 10 | 10 | ||
| 11 | const max_distance = (1 << (jump_bits - 1)); | 11 | for (atoms) |atom_index| { |
| 12 | 12 | macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1)); | |
| 13 | /// A branch will need an extender if its target is larger than | ||
| 14 | /// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. | ||
| 15 | /// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold | ||
| 16 | /// and assume margin to be 5MiB. | ||
| 17 | const max_allowed_distance = max_distance - 0x500_000; | ||
| 18 | |||
| 19 | pub const Thunk = struct { | ||
| 20 | start_index: Atom.Index, | ||
| 21 | len: u32, | ||
| 22 | |||
| 23 | targets: std.MultiArrayList(Target) = .{}, | ||
| 24 | lookup: std.AutoHashMapUnmanaged(Target, u32) = .{}, | ||
| 25 | |||
| 26 | pub const Tag = enum { | ||
| 27 | stub, | ||
| 28 | atom, | ||
| 29 | }; | ||
| 30 | |||
| 31 | pub const Target = struct { | ||
| 32 | tag: Tag, | ||
| 33 | target: SymbolWithLoc, | ||
| 34 | }; | ||
| 35 | |||
| 36 | pub const Index = u32; | ||
| 37 | |||
| 38 | pub fn deinit(self: *Thunk, gpa: Allocator) void { | ||
| 39 | self.targets.deinit(gpa); | ||
| 40 | self.lookup.deinit(gpa); | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn getStartAtomIndex(self: Thunk) Atom.Index { | ||
| 44 | assert(self.len != 0); | ||
| 45 | return self.start_index; | ||
| 46 | } | ||
| 47 | |||
| 48 | pub fn getEndAtomIndex(self: Thunk) Atom.Index { | ||
| 49 | assert(self.len != 0); | ||
| 50 | return self.start_index + self.len - 1; | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn getSize(self: Thunk) u64 { | ||
| 54 | return 12 * self.len; | ||
| 55 | } | 13 | } |
| 56 | 14 | ||
| 57 | pub fn getAlignment() u32 { | 15 | var i: usize = 0; |
| 58 | return @alignOf(u32); | 16 | while (i < atoms.len) { |
| 59 | } | 17 | const start = i; |
| 60 | 18 | const start_atom = macho_file.getAtom(atoms[start]).?; | |
| 61 | pub fn getTrampoline(self: Thunk, macho_file: *MachO, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc { | 19 | assert(start_atom.flags.alive); |
| 62 | const atom_index = self.lookup.get(.{ .tag = tag, .target = target }) orelse return null; | 20 | start_atom.value = try advance(header, start_atom.size, start_atom.alignment); |
| 63 | return macho_file.getAtom(atom_index).getSymbolWithLoc(); | 21 | i += 1; |
| 64 | } | 22 | |
| 65 | }; | 23 | while (i < atoms.len and |
| 66 | 24 | header.size - start_atom.value < max_allowed_distance) : (i += 1) | |
| 67 | pub fn createThunks(macho_file: *MachO, sect_id: u8) !void { | 25 | { |
| 68 | const header = &macho_file.sections.items(.header)[sect_id]; | 26 | const atom_index = atoms[i]; |
| 69 | if (header.size == 0) return; | 27 | const atom = macho_file.getAtom(atom_index).?; |
| 70 | 28 | assert(atom.flags.alive); | |
| 71 | const comp = macho_file.base.comp; | 29 | atom.value = try advance(header, atom.size, atom.alignment); |
| 72 | const gpa = comp.gpa; | ||
| 73 | const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id].?; | ||
| 74 | |||
| 75 | header.size = 0; | ||
| 76 | header.@"align" = 0; | ||
| 77 | |||
| 78 | var atom_count: u32 = 0; | ||
| 79 | |||
| 80 | { | ||
| 81 | var atom_index = first_atom_index; | ||
| 82 | while (true) { | ||
| 83 | const atom = macho_file.getAtom(atom_index); | ||
| 84 | const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); | ||
| 85 | sym.n_value = 0; | ||
| 86 | atom_count += 1; | ||
| 87 | |||
| 88 | if (atom.next_index) |next_index| { | ||
| 89 | atom_index = next_index; | ||
| 90 | } else break; | ||
| 91 | } | 30 | } |
| 92 | } | ||
| 93 | |||
| 94 | var allocated = std.AutoHashMap(Atom.Index, void).init(gpa); | ||
| 95 | defer allocated.deinit(); | ||
| 96 | try allocated.ensureTotalCapacity(atom_count); | ||
| 97 | |||
| 98 | var group_start = first_atom_index; | ||
| 99 | var group_end = first_atom_index; | ||
| 100 | var offset: u64 = 0; | ||
| 101 | |||
| 102 | while (true) { | ||
| 103 | const group_start_atom = macho_file.getAtom(group_start); | ||
| 104 | log.debug("GROUP START at {d}", .{group_start}); | ||
| 105 | |||
| 106 | while (true) { | ||
| 107 | const atom = macho_file.getAtom(group_end); | ||
| 108 | offset = atom.alignment.forward(offset); | ||
| 109 | |||
| 110 | const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); | ||
| 111 | sym.n_value = offset; | ||
| 112 | offset += atom.size; | ||
| 113 | |||
| 114 | macho_file.logAtom(group_end, log); | ||
| 115 | |||
| 116 | header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); | ||
| 117 | |||
| 118 | allocated.putAssumeCapacityNoClobber(group_end, {}); | ||
| 119 | |||
| 120 | const group_start_sym = macho_file.getSymbol(group_start_atom.getSymbolWithLoc()); | ||
| 121 | if (offset - group_start_sym.n_value >= max_allowed_distance) break; | ||
| 122 | |||
| 123 | if (atom.next_index) |next_index| { | ||
| 124 | group_end = next_index; | ||
| 125 | } else break; | ||
| 126 | } | ||
| 127 | log.debug("GROUP END at {d}", .{group_end}); | ||
| 128 | |||
| 129 | // Insert thunk at group_end | ||
| 130 | const thunk_index = @as(u32, @intCast(macho_file.thunks.items.len)); | ||
| 131 | try macho_file.thunks.append(gpa, .{ .start_index = undefined, .len = 0 }); | ||
| 132 | |||
| 133 | // Scan relocs in the group and create trampolines for any unreachable callsite. | ||
| 134 | var atom_index = group_start; | ||
| 135 | while (true) { | ||
| 136 | const atom = macho_file.getAtom(atom_index); | ||
| 137 | try scanRelocs( | ||
| 138 | macho_file, | ||
| 139 | atom_index, | ||
| 140 | allocated, | ||
| 141 | thunk_index, | ||
| 142 | group_end, | ||
| 143 | ); | ||
| 144 | |||
| 145 | if (atom_index == group_end) break; | ||
| 146 | 31 | ||
| 147 | if (atom.next_index) |next_index| { | 32 | // Insert a thunk at the group end |
| 148 | atom_index = next_index; | 33 | const thunk_index = try macho_file.addThunk(); |
| 149 | } else break; | 34 | const thunk = macho_file.getThunk(thunk_index); |
| 35 | thunk.out_n_sect = sect_id; | ||
| 36 | |||
| 37 | // Scan relocs in the group and create trampolines for any unreachable callsite | ||
| 38 | for (atoms[start..i]) |atom_index| { | ||
| 39 | const atom = macho_file.getAtom(atom_index).?; | ||
| 40 | log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) }); | ||
| 41 | for (atom.getRelocs(macho_file)) |rel| { | ||
| 42 | if (rel.type != .branch) continue; | ||
| 43 | if (isReachable(atom, rel, macho_file)) continue; | ||
| 44 | try thunk.symbols.put(gpa, rel.target, {}); | ||
| 45 | } | ||
| 46 | atom.thunk_index = thunk_index; | ||
| 150 | } | 47 | } |
| 151 | 48 | ||
| 152 | offset = mem.alignForward(u64, offset, Thunk.getAlignment()); | 49 | thunk.value = try advance(header, thunk.size(), 2); |
| 153 | allocateThunk(macho_file, thunk_index, offset, header); | ||
| 154 | offset += macho_file.thunks.items[thunk_index].getSize(); | ||
| 155 | 50 | ||
| 156 | const thunk = macho_file.thunks.items[thunk_index]; | 51 | log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); |
| 157 | if (thunk.len == 0) { | ||
| 158 | const group_end_atom = macho_file.getAtom(group_end); | ||
| 159 | if (group_end_atom.next_index) |next_index| { | ||
| 160 | group_start = next_index; | ||
| 161 | group_end = next_index; | ||
| 162 | } else break; | ||
| 163 | } else { | ||
| 164 | const thunk_end_atom_index = thunk.getEndAtomIndex(); | ||
| 165 | const thunk_end_atom = macho_file.getAtom(thunk_end_atom_index); | ||
| 166 | if (thunk_end_atom.next_index) |next_index| { | ||
| 167 | group_start = next_index; | ||
| 168 | group_end = next_index; | ||
| 169 | } else break; | ||
| 170 | } | ||
| 171 | } | 52 | } |
| 172 | |||
| 173 | header.size = @as(u32, @intCast(offset)); | ||
| 174 | } | 53 | } |
| 175 | 54 | ||
| 176 | fn allocateThunk( | 55 | fn advance(sect: *macho.section_64, size: u64, pow2_align: u32) !u64 { |
| 177 | macho_file: *MachO, | 56 | const alignment = try math.powi(u32, 2, pow2_align); |
| 178 | thunk_index: Thunk.Index, | 57 | const offset = mem.alignForward(u64, sect.size, alignment); |
| 179 | base_offset: u64, | 58 | const padding = offset - sect.size; |
| 180 | header: *macho.section_64, | 59 | sect.size += padding + size; |
| 181 | ) void { | 60 | sect.@"align" = @max(sect.@"align", pow2_align); |
| 182 | const thunk = macho_file.thunks.items[thunk_index]; | 61 | return offset; |
| 183 | if (thunk.len == 0) return; | ||
| 184 | |||
| 185 | const first_atom_index = thunk.getStartAtomIndex(); | ||
| 186 | const end_atom_index = thunk.getEndAtomIndex(); | ||
| 187 | |||
| 188 | var atom_index = first_atom_index; | ||
| 189 | var offset = base_offset; | ||
| 190 | while (true) { | ||
| 191 | const atom = macho_file.getAtom(atom_index); | ||
| 192 | offset = mem.alignForward(u64, offset, Thunk.getAlignment()); | ||
| 193 | |||
| 194 | const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); | ||
| 195 | sym.n_value = offset; | ||
| 196 | offset += atom.size; | ||
| 197 | |||
| 198 | macho_file.logAtom(atom_index, log); | ||
| 199 | |||
| 200 | header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); | ||
| 201 | |||
| 202 | if (end_atom_index == atom_index) break; | ||
| 203 | |||
| 204 | if (atom.next_index) |next_index| { | ||
| 205 | atom_index = next_index; | ||
| 206 | } else break; | ||
| 207 | } | ||
| 208 | } | 62 | } |
| 209 | 63 | ||
| 210 | fn scanRelocs( | 64 | fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { |
| 211 | macho_file: *MachO, | 65 | const target = rel.getTargetSymbol(macho_file); |
| 212 | atom_index: Atom.Index, | 66 | if (target.flags.stubs or target.flags.objc_stubs) return false; |
| 213 | allocated: std.AutoHashMap(Atom.Index, void), | 67 | if (atom.out_n_sect != target.out_n_sect) return false; |
| 214 | thunk_index: Thunk.Index, | 68 | const target_atom = target.getAtom(macho_file).?; |
| 215 | group_end: Atom.Index, | 69 | if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; |
| 216 | ) !void { | 70 | const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off)); |
| 217 | const atom = macho_file.getAtom(atom_index); | 71 | const taddr: i64 = @intCast(rel.getTargetAddress(macho_file)); |
| 218 | const object = macho_file.objects.items[atom.getFile().?]; | 72 | _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; |
| 219 | 73 | return true; | |
| 220 | const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: { | 74 | } |
| 221 | const source_sect = object.getSourceSection(source_sym.n_sect - 1); | ||
| 222 | break :blk @as(i32, @intCast(source_sym.n_value - source_sect.addr)); | ||
| 223 | } else 0; | ||
| 224 | |||
| 225 | const code = Atom.getAtomCode(macho_file, atom_index); | ||
| 226 | const relocs = Atom.getAtomRelocs(macho_file, atom_index); | ||
| 227 | const ctx = Atom.getRelocContext(macho_file, atom_index); | ||
| 228 | 75 | ||
| 229 | for (relocs) |rel| { | 76 | pub const Thunk = struct { |
| 230 | if (!relocNeedsThunk(rel)) continue; | 77 | value: u64 = 0, |
| 78 | out_n_sect: u8 = 0, | ||
| 79 | symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{}, | ||
| 231 | 80 | ||
| 232 | const target = Atom.parseRelocTarget(macho_file, .{ | 81 | pub fn deinit(thunk: *Thunk, allocator: Allocator) void { |
| 233 | .object_id = atom.getFile().?, | 82 | thunk.symbols.deinit(allocator); |
| 234 | .rel = rel, | 83 | } |
| 235 | .code = code, | ||
| 236 | .base_offset = ctx.base_offset, | ||
| 237 | .base_addr = ctx.base_addr, | ||
| 238 | }); | ||
| 239 | if (isReachable(macho_file, atom_index, rel, base_offset, target, allocated)) continue; | ||
| 240 | 84 | ||
| 241 | log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{ | 85 | pub fn size(thunk: Thunk) usize { |
| 242 | rel.r_address - base_offset, | 86 | return thunk.symbols.keys().len * trampoline_size; |
| 243 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | 87 | } |
| 244 | macho_file.getSymbol(atom.getSymbolWithLoc()).n_value, | ||
| 245 | macho_file.getSymbolName(target), | ||
| 246 | macho_file.getSymbol(target).n_value, | ||
| 247 | }); | ||
| 248 | 88 | ||
| 249 | const comp = macho_file.base.comp; | 89 | pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 { |
| 250 | const gpa = comp.gpa; | 90 | return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size; |
| 251 | const target_sym = macho_file.getSymbol(target); | 91 | } |
| 252 | const thunk = &macho_file.thunks.items[thunk_index]; | ||
| 253 | 92 | ||
| 254 | const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom; | 93 | pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { |
| 255 | const thunk_target: Thunk.Target = .{ .tag = tag, .target = target }; | 94 | for (thunk.symbols.keys(), 0..) |sym_index, i| { |
| 256 | const gop = try thunk.lookup.getOrPut(gpa, thunk_target); | 95 | const sym = macho_file.getSymbol(sym_index); |
| 257 | if (!gop.found_existing) { | 96 | const saddr = thunk.value + i * trampoline_size; |
| 258 | gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end); | 97 | const taddr = sym.getAddress(.{}, macho_file); |
| 259 | try thunk.targets.append(gpa, thunk_target); | 98 | const pages = try Relocation.calcNumberOfPages(saddr, taddr); |
| 99 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 100 | const off = try Relocation.calcPageOffset(taddr, .arithmetic); | ||
| 101 | try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little); | ||
| 102 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 260 | } | 103 | } |
| 261 | |||
| 262 | try macho_file.thunk_table.put(gpa, atom_index, thunk_index); | ||
| 263 | } | 104 | } |
| 264 | } | ||
| 265 | 105 | ||
| 266 | fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index { | 106 | pub fn format( |
| 267 | const thunk_atom_index = try createThunkAtom(macho_file); | 107 | thunk: Thunk, |
| 268 | 108 | comptime unused_fmt_string: []const u8, | |
| 269 | const thunk_atom = macho_file.getAtomPtr(thunk_atom_index); | 109 | options: std.fmt.FormatOptions, |
| 270 | const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex(); | 110 | writer: anytype, |
| 271 | const end_atom = macho_file.getAtomPtr(end_atom_index); | 111 | ) !void { |
| 272 | 112 | _ = thunk; | |
| 273 | if (end_atom.next_index) |first_after_index| { | 113 | _ = unused_fmt_string; |
| 274 | const first_after_atom = macho_file.getAtomPtr(first_after_index); | 114 | _ = options; |
| 275 | first_after_atom.prev_index = thunk_atom_index; | 115 | _ = writer; |
| 276 | thunk_atom.next_index = first_after_index; | 116 | @compileError("do not format Thunk directly"); |
| 277 | } | 117 | } |
| 278 | 118 | ||
| 279 | end_atom.next_index = thunk_atom_index; | 119 | pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 280 | thunk_atom.prev_index = end_atom_index; | 120 | return .{ .data = .{ |
| 281 | 121 | .thunk = thunk, | |
| 282 | if (thunk.len == 0) { | 122 | .macho_file = macho_file, |
| 283 | thunk.start_index = thunk_atom_index; | 123 | } }; |
| 284 | } | 124 | } |
| 285 | 125 | ||
| 286 | thunk.len += 1; | 126 | const FormatContext = struct { |
| 287 | 127 | thunk: Thunk, | |
| 288 | return thunk_atom_index; | 128 | macho_file: *MachO, |
| 289 | } | 129 | }; |
| 290 | |||
| 291 | inline fn relocNeedsThunk(rel: macho.relocation_info) bool { | ||
| 292 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | ||
| 293 | return rel_type == .ARM64_RELOC_BRANCH26; | ||
| 294 | } | ||
| 295 | |||
| 296 | fn isReachable( | ||
| 297 | macho_file: *MachO, | ||
| 298 | atom_index: Atom.Index, | ||
| 299 | rel: macho.relocation_info, | ||
| 300 | base_offset: i32, | ||
| 301 | target: SymbolWithLoc, | ||
| 302 | allocated: std.AutoHashMap(Atom.Index, void), | ||
| 303 | ) bool { | ||
| 304 | if (macho_file.stub_table.lookup.contains(target)) return false; | ||
| 305 | |||
| 306 | const source_atom = macho_file.getAtom(atom_index); | ||
| 307 | const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc()); | ||
| 308 | |||
| 309 | const target_object = macho_file.objects.items[target.getFile().?]; | ||
| 310 | const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?; | ||
| 311 | const target_atom = macho_file.getAtom(target_atom_index); | ||
| 312 | const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc()); | ||
| 313 | |||
| 314 | if (source_sym.n_sect != target_sym.n_sect) return false; | ||
| 315 | 130 | ||
| 316 | if (!allocated.contains(target_atom_index)) return false; | 131 | fn format2( |
| 132 | ctx: FormatContext, | ||
| 133 | comptime unused_fmt_string: []const u8, | ||
| 134 | options: std.fmt.FormatOptions, | ||
| 135 | writer: anytype, | ||
| 136 | ) !void { | ||
| 137 | _ = options; | ||
| 138 | _ = unused_fmt_string; | ||
| 139 | const thunk = ctx.thunk; | ||
| 140 | const macho_file = ctx.macho_file; | ||
| 141 | try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() }); | ||
| 142 | for (thunk.symbols.keys()) |index| { | ||
| 143 | const sym = macho_file.getSymbol(index); | ||
| 144 | try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value }); | ||
| 145 | } | ||
| 146 | } | ||
| 317 | 147 | ||
| 318 | const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset)); | 148 | const trampoline_size = 3 * @sizeOf(u32); |
| 319 | const target_addr = if (Atom.relocRequiresGot(macho_file, rel)) | ||
| 320 | macho_file.getGotEntryAddress(target).? | ||
| 321 | else | ||
| 322 | Atom.getRelocTargetAddress(macho_file, target, false); | ||
| 323 | _ = Relocation.calcPcRelativeDisplacementArm64(source_addr, target_addr) catch | ||
| 324 | return false; | ||
| 325 | 149 | ||
| 326 | return true; | 150 | pub const Index = u32; |
| 327 | } | 151 | }; |
| 328 | 152 | ||
| 329 | fn createThunkAtom(macho_file: *MachO) !Atom.Index { | 153 | /// Branch instruction has 26 bits immediate but is 4 byte aligned. |
| 330 | const sym_index = try macho_file.allocateSymbol(); | 154 | const jump_bits = @bitSizeOf(i28); |
| 331 | const atom_index = try macho_file.createAtom(sym_index, .{ | 155 | const max_distance = (1 << (jump_bits - 1)); |
| 332 | .size = @sizeOf(u32) * 3, | ||
| 333 | .alignment = .@"4", | ||
| 334 | }); | ||
| 335 | const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index }); | ||
| 336 | sym.n_type = macho.N_SECT; | ||
| 337 | sym.n_sect = macho_file.text_section_index.? + 1; | ||
| 338 | return atom_index; | ||
| 339 | } | ||
| 340 | 156 | ||
| 341 | pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void { | 157 | /// A branch will need an extender if its target is larger than |
| 342 | const slice = thunk.targets.slice(); | 158 | /// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. |
| 343 | for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| { | 159 | /// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold |
| 344 | const atom = macho_file.getAtom(@intCast(atom_index)); | 160 | /// and assume margin to be 5MiB. |
| 345 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | 161 | const max_allowed_distance = max_distance - 0x500_000; |
| 346 | const source_addr = sym.n_value; | ||
| 347 | const tag = slice.items(.tag)[target_index]; | ||
| 348 | const target = slice.items(.target)[target_index]; | ||
| 349 | const target_addr = switch (tag) { | ||
| 350 | .stub => macho_file.getStubsEntryAddress(target).?, | ||
| 351 | .atom => macho_file.getSymbol(target).n_value, | ||
| 352 | }; | ||
| 353 | const pages = Relocation.calcNumberOfPages(source_addr, target_addr); | ||
| 354 | try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); | ||
| 355 | const off = try Relocation.calcPageOffset(target_addr, .arithmetic); | ||
| 356 | try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little); | ||
| 357 | try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); | ||
| 358 | } | ||
| 359 | } | ||
| 360 | 162 | ||
| 361 | const std = @import("std"); | 163 | const aarch64 = @import("../aarch64.zig"); |
| 362 | const assert = std.debug.assert; | 164 | const assert = std.debug.assert; |
| 363 | const log = std.log.scoped(.thunks); | 165 | const log = std.log.scoped(.link); |
| 364 | const macho = std.macho; | 166 | const macho = std.macho; |
| 365 | const math = std.math; | 167 | const math = std.math; |
| 366 | const mem = std.mem; | 168 | const mem = std.mem; |
| 367 | 169 | const std = @import("std"); | |
| 368 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | 170 | const trace = @import("../tracy.zig").trace; |
| 369 | 171 | ||
| 370 | const Allocator = mem.Allocator; | 172 | const Allocator = mem.Allocator; |
| 371 | const Atom = @import("Atom.zig"); | 173 | const Atom = @import("Atom.zig"); |
| 372 | const MachO = @import("../MachO.zig"); | 174 | const MachO = @import("../MachO.zig"); |
| 373 | const Relocation = @import("Relocation.zig"); | 175 | const Relocation = @import("Relocation.zig"); |
| 374 | const SymbolWithLoc = MachO.SymbolWithLoc; | 176 | const Symbol = @import("Symbol.zig"); |
src/link/MachO/uuid.zig+18-8| ... | @@ -4,22 +4,31 @@ | ... | @@ -4,22 +4,31 @@ |
| 4 | /// and we will use it too as it seems accepted by Apple OSes. | 4 | /// and we will use it too as it seems accepted by Apple OSes. |
| 5 | /// TODO LLD also hashes the output filename to disambiguate between same builds with different | 5 | /// TODO LLD also hashes the output filename to disambiguate between same builds with different |
| 6 | /// output files. Should we also do that? | 6 | /// output files. Should we also do that? |
| 7 | pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { | 7 | pub fn calcUuid( |
| 8 | allocator: Allocator, | ||
| 9 | thread_pool: *ThreadPool, | ||
| 10 | file: fs.File, | ||
| 11 | file_size: u64, | ||
| 12 | out: *[Md5.digest_length]u8, | ||
| 13 | ) !void { | ||
| 14 | const tracy = trace(@src()); | ||
| 15 | defer tracy.end(); | ||
| 16 | |||
| 8 | const chunk_size: usize = 1024 * 1024; | 17 | const chunk_size: usize = 1024 * 1024; |
| 9 | const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; | 18 | const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; |
| 10 | const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; | 19 | const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; |
| 11 | 20 | ||
| 12 | const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks); | 21 | const hashes = try allocator.alloc([Md5.digest_length]u8, actual_num_chunks); |
| 13 | defer comp.gpa.free(hashes); | 22 | defer allocator.free(hashes); |
| 14 | 23 | ||
| 15 | var hasher = Hasher(Md5){ .allocator = comp.gpa, .thread_pool = comp.thread_pool }; | 24 | var hasher = Hasher(Md5){ .allocator = allocator, .thread_pool = thread_pool }; |
| 16 | try hasher.hash(file, hashes, .{ | 25 | try hasher.hash(file, hashes, .{ |
| 17 | .chunk_size = chunk_size, | 26 | .chunk_size = chunk_size, |
| 18 | .max_file_size = file_size, | 27 | .max_file_size = file_size, |
| 19 | }); | 28 | }); |
| 20 | 29 | ||
| 21 | const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length); | 30 | const final_buffer = try allocator.alloc(u8, actual_num_chunks * Md5.digest_length); |
| 22 | defer comp.gpa.free(final_buffer); | 31 | defer allocator.free(final_buffer); |
| 23 | 32 | ||
| 24 | for (hashes, 0..) |hash, i| { | 33 | for (hashes, 0..) |hash, i| { |
| 25 | @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash); | 34 | @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash); |
| ... | @@ -35,11 +44,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void { | ... | @@ -35,11 +44,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void { |
| 35 | out[8] = (out[8] & 0x3F) | 0x80; | 44 | out[8] = (out[8] & 0x3F) | 0x80; |
| 36 | } | 45 | } |
| 37 | 46 | ||
| 38 | const std = @import("std"); | ||
| 39 | const fs = std.fs; | 47 | const fs = std.fs; |
| 40 | const mem = std.mem; | 48 | const mem = std.mem; |
| 49 | const std = @import("std"); | ||
| 50 | const trace = @import("../tracy.zig").trace; | ||
| 41 | 51 | ||
| 42 | const Allocator = mem.Allocator; | 52 | const Allocator = mem.Allocator; |
| 43 | const Compilation = @import("../../Compilation.zig"); | ||
| 44 | const Md5 = std.crypto.hash.Md5; | 53 | const Md5 = std.crypto.hash.Md5; |
| 45 | const Hasher = @import("hasher.zig").ParallelHasher; | 54 | const Hasher = @import("hasher.zig").ParallelHasher; |
| 55 | const ThreadPool = std.Thread.Pool; |
src/link/MachO/zld.zig deleted-1230| ... | @@ -1,1230 +0,0 @@ | ||
| 1 | pub fn linkWithZld( | ||
| 2 | macho_file: *MachO, | ||
| 3 | arena: Allocator, | ||
| 4 | prog_node: *std.Progress.Node, | ||
| 5 | ) link.File.FlushError!void { | ||
| 6 | const tracy = trace(@src()); | ||
| 7 | defer tracy.end(); | ||
| 8 | |||
| 9 | const comp = macho_file.base.comp; | ||
| 10 | const gpa = comp.gpa; | ||
| 11 | const target = comp.root_mod.resolved_target.result; | ||
| 12 | const emit = macho_file.base.emit; | ||
| 13 | |||
| 14 | const directory = emit.directory; // Just an alias to make it shorter to type. | ||
| 15 | const full_out_path = try directory.join(arena, &[_][]const u8{emit.sub_path}); | ||
| 16 | const opt_zcu = comp.module; | ||
| 17 | |||
| 18 | // If there is no Zig code to compile, then we should skip flushing the output file because it | ||
| 19 | // will not be part of the linker line anyway. | ||
| 20 | const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { | ||
| 21 | try macho_file.flushModule(arena, prog_node); | ||
| 22 | |||
| 23 | if (fs.path.dirname(full_out_path)) |dirname| { | ||
| 24 | break :blk try fs.path.join(arena, &.{ dirname, macho_file.base.zcu_object_sub_path.? }); | ||
| 25 | } else { | ||
| 26 | break :blk macho_file.base.zcu_object_sub_path.?; | ||
| 27 | } | ||
| 28 | } else null; | ||
| 29 | |||
| 30 | var sub_prog_node = prog_node.start("MachO Flush", 0); | ||
| 31 | sub_prog_node.activate(); | ||
| 32 | sub_prog_node.context.refresh(); | ||
| 33 | defer sub_prog_node.end(); | ||
| 34 | |||
| 35 | const output_mode = comp.config.output_mode; | ||
| 36 | const link_mode = comp.config.link_mode; | ||
| 37 | const cpu_arch = target.cpu.arch; | ||
| 38 | const is_lib = output_mode == .Lib; | ||
| 39 | const is_dyn_lib = link_mode == .Dynamic and is_lib; | ||
| 40 | const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; | ||
| 41 | const stack_size = macho_file.base.stack_size; | ||
| 42 | |||
| 43 | const id_symlink_basename = "zld.id"; | ||
| 44 | |||
| 45 | var man: Cache.Manifest = undefined; | ||
| 46 | defer if (!macho_file.base.disable_lld_caching) man.deinit(); | ||
| 47 | |||
| 48 | var digest: [Cache.hex_digest_len]u8 = undefined; | ||
| 49 | |||
| 50 | const objects = comp.objects; | ||
| 51 | |||
| 52 | if (!macho_file.base.disable_lld_caching) { | ||
| 53 | man = comp.cache_parent.obtain(); | ||
| 54 | |||
| 55 | // We are about to obtain this lock, so here we give other processes a chance first. | ||
| 56 | macho_file.base.releaseLock(); | ||
| 57 | |||
| 58 | comptime assert(Compilation.link_hash_implementation_version == 11); | ||
| 59 | |||
| 60 | for (objects) |obj| { | ||
| 61 | _ = try man.addFile(obj.path, null); | ||
| 62 | man.hash.add(obj.must_link); | ||
| 63 | } | ||
| 64 | for (comp.c_object_table.keys()) |key| { | ||
| 65 | _ = try man.addFile(key.status.success.object_path, null); | ||
| 66 | } | ||
| 67 | try man.addOptionalFile(module_obj_path); | ||
| 68 | // We can skip hashing libc and libc++ components that we are in charge of building from Zig | ||
| 69 | // installation sources because they are always a product of the compiler version + target information. | ||
| 70 | man.hash.add(stack_size); | ||
| 71 | man.hash.add(macho_file.pagezero_vmsize); | ||
| 72 | man.hash.add(macho_file.headerpad_size); | ||
| 73 | man.hash.add(macho_file.headerpad_max_install_names); | ||
| 74 | man.hash.add(macho_file.base.gc_sections); | ||
| 75 | man.hash.add(macho_file.dead_strip_dylibs); | ||
| 76 | man.hash.add(comp.root_mod.strip); | ||
| 77 | try MachO.hashAddFrameworks(&man, macho_file.frameworks); | ||
| 78 | man.hash.addListOfBytes(macho_file.base.rpath_list); | ||
| 79 | if (is_dyn_lib) { | ||
| 80 | man.hash.addOptionalBytes(macho_file.install_name); | ||
| 81 | man.hash.addOptional(comp.version); | ||
| 82 | } | ||
| 83 | try link.hashAddSystemLibs(&man, comp.system_libs); | ||
| 84 | man.hash.addOptionalBytes(comp.sysroot); | ||
| 85 | man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); | ||
| 86 | try man.addOptionalFile(macho_file.entitlements); | ||
| 87 | |||
| 88 | // We don't actually care whether it's a cache hit or miss; we just | ||
| 89 | // need the digest and the lock. | ||
| 90 | _ = try man.hit(); | ||
| 91 | digest = man.final(); | ||
| 92 | |||
| 93 | var prev_digest_buf: [digest.len]u8 = undefined; | ||
| 94 | const prev_digest: []u8 = Cache.readSmallFile( | ||
| 95 | directory.handle, | ||
| 96 | id_symlink_basename, | ||
| 97 | &prev_digest_buf, | ||
| 98 | ) catch |err| blk: { | ||
| 99 | log.debug("MachO Zld new_digest={s} error: {s}", .{ | ||
| 100 | std.fmt.fmtSliceHexLower(&digest), | ||
| 101 | @errorName(err), | ||
| 102 | }); | ||
| 103 | // Handle this as a cache miss. | ||
| 104 | break :blk prev_digest_buf[0..0]; | ||
| 105 | }; | ||
| 106 | if (mem.eql(u8, prev_digest, &digest)) { | ||
| 107 | // Hot diggity dog! The output binary is already there. | ||
| 108 | log.debug("MachO Zld digest={s} match - skipping invocation", .{ | ||
| 109 | std.fmt.fmtSliceHexLower(&digest), | ||
| 110 | }); | ||
| 111 | macho_file.base.lock = man.toOwnedLock(); | ||
| 112 | return; | ||
| 113 | } | ||
| 114 | log.debug("MachO Zld prev_digest={s} new_digest={s}", .{ | ||
| 115 | std.fmt.fmtSliceHexLower(prev_digest), | ||
| 116 | std.fmt.fmtSliceHexLower(&digest), | ||
| 117 | }); | ||
| 118 | |||
| 119 | // We are about to change the output file to be different, so we invalidate the build hash now. | ||
| 120 | directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { | ||
| 121 | error.FileNotFound => {}, | ||
| 122 | else => |e| return e, | ||
| 123 | }; | ||
| 124 | } | ||
| 125 | |||
| 126 | if (output_mode == .Obj) { | ||
| 127 | // LLD's MachO driver does not support the equivalent of `-r` so we do a simple file copy | ||
| 128 | // here. TODO: think carefully about how we can avoid this redundant operation when doing | ||
| 129 | // build-obj. See also the corresponding TODO in linkAsArchive. | ||
| 130 | const the_object_path = blk: { | ||
| 131 | if (objects.len != 0) { | ||
| 132 | break :blk objects[0].path; | ||
| 133 | } | ||
| 134 | |||
| 135 | if (comp.c_object_table.count() != 0) | ||
| 136 | break :blk comp.c_object_table.keys()[0].status.success.object_path; | ||
| 137 | |||
| 138 | if (module_obj_path) |p| | ||
| 139 | break :blk p; | ||
| 140 | |||
| 141 | // TODO I think this is unreachable. Audit this situation when solving the above TODO | ||
| 142 | // regarding eliding redundant object -> object transformations. | ||
| 143 | return error.NoObjectsToLink; | ||
| 144 | }; | ||
| 145 | // This can happen when using --enable-cache and using the stage1 backend. In this case | ||
| 146 | // we can skip the file copy. | ||
| 147 | if (!mem.eql(u8, the_object_path, full_out_path)) { | ||
| 148 | try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); | ||
| 149 | } | ||
| 150 | } else { | ||
| 151 | const sub_path = emit.sub_path; | ||
| 152 | |||
| 153 | const old_file = macho_file.base.file; // TODO is this needed at all? | ||
| 154 | defer macho_file.base.file = old_file; | ||
| 155 | |||
| 156 | const file = try directory.handle.createFile(sub_path, .{ | ||
| 157 | .truncate = true, | ||
| 158 | .read = true, | ||
| 159 | .mode = link.File.determineMode(false, output_mode, link_mode), | ||
| 160 | }); | ||
| 161 | defer file.close(); | ||
| 162 | macho_file.base.file = file; | ||
| 163 | |||
| 164 | // Index 0 is always a null symbol. | ||
| 165 | try macho_file.locals.append(gpa, .{ | ||
| 166 | .n_strx = 0, | ||
| 167 | .n_type = 0, | ||
| 168 | .n_sect = 0, | ||
| 169 | .n_desc = 0, | ||
| 170 | .n_value = 0, | ||
| 171 | }); | ||
| 172 | try macho_file.strtab.buffer.append(gpa, 0); | ||
| 173 | |||
| 174 | // Positional arguments to the linker such as object files and static archives. | ||
| 175 | var positionals = std.ArrayList(Compilation.LinkObject).init(arena); | ||
| 176 | try positionals.ensureUnusedCapacity(objects.len); | ||
| 177 | positionals.appendSliceAssumeCapacity(objects); | ||
| 178 | |||
| 179 | for (comp.c_object_table.keys()) |key| { | ||
| 180 | try positionals.append(.{ .path = key.status.success.object_path }); | ||
| 181 | } | ||
| 182 | |||
| 183 | if (module_obj_path) |p| { | ||
| 184 | try positionals.append(.{ .path = p }); | ||
| 185 | } | ||
| 186 | |||
| 187 | if (comp.compiler_rt_lib) |lib| try positionals.append(.{ .path = lib.full_object_path }); | ||
| 188 | if (comp.compiler_rt_obj) |obj| try positionals.append(.{ .path = obj.full_object_path }); | ||
| 189 | |||
| 190 | // libc++ dep | ||
| 191 | if (comp.config.link_libcpp) { | ||
| 192 | try positionals.ensureUnusedCapacity(2); | ||
| 193 | positionals.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path }); | ||
| 194 | positionals.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path }); | ||
| 195 | } | ||
| 196 | |||
| 197 | var libs = std.StringArrayHashMap(link.SystemLib).init(arena); | ||
| 198 | |||
| 199 | { | ||
| 200 | const vals = comp.system_libs.values(); | ||
| 201 | try libs.ensureUnusedCapacity(vals.len); | ||
| 202 | for (vals) |v| libs.putAssumeCapacity(v.path.?, v); | ||
| 203 | } | ||
| 204 | |||
| 205 | { | ||
| 206 | try libs.ensureUnusedCapacity(macho_file.frameworks.len); | ||
| 207 | for (macho_file.frameworks) |v| libs.putAssumeCapacity(v.path, .{ | ||
| 208 | .needed = v.needed, | ||
| 209 | .weak = v.weak, | ||
| 210 | .path = v.path, | ||
| 211 | }); | ||
| 212 | } | ||
| 213 | |||
| 214 | try macho_file.resolveLibSystem(arena, comp, &libs); | ||
| 215 | |||
| 216 | if (comp.verbose_link) { | ||
| 217 | var argv = std.ArrayList([]const u8).init(arena); | ||
| 218 | |||
| 219 | try argv.append("zig"); | ||
| 220 | try argv.append("ld"); | ||
| 221 | |||
| 222 | if (is_exe_or_dyn_lib) { | ||
| 223 | try argv.append("-dynamic"); | ||
| 224 | } | ||
| 225 | |||
| 226 | if (is_dyn_lib) { | ||
| 227 | try argv.append("-dylib"); | ||
| 228 | |||
| 229 | if (macho_file.install_name) |install_name| { | ||
| 230 | try argv.append("-install_name"); | ||
| 231 | try argv.append(install_name); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | { | ||
| 236 | const platform = Platform.fromTarget(target); | ||
| 237 | try argv.append("-platform_version"); | ||
| 238 | try argv.append(@tagName(platform.os_tag)); | ||
| 239 | try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version})); | ||
| 240 | |||
| 241 | const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file); | ||
| 242 | if (sdk_version) |ver| { | ||
| 243 | try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor })); | ||
| 244 | } else { | ||
| 245 | try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version})); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | if (comp.sysroot) |syslibroot| { | ||
| 250 | try argv.append("-syslibroot"); | ||
| 251 | try argv.append(syslibroot); | ||
| 252 | } | ||
| 253 | |||
| 254 | for (macho_file.base.rpath_list) |rpath| { | ||
| 255 | try argv.append("-rpath"); | ||
| 256 | try argv.append(rpath); | ||
| 257 | } | ||
| 258 | |||
| 259 | try argv.appendSlice(&.{ | ||
| 260 | "-pagezero_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.pagezero_vmsize}), | ||
| 261 | "-headerpad_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.headerpad_size}), | ||
| 262 | }); | ||
| 263 | |||
| 264 | if (macho_file.headerpad_max_install_names) { | ||
| 265 | try argv.append("-headerpad_max_install_names"); | ||
| 266 | } | ||
| 267 | |||
| 268 | if (macho_file.base.gc_sections) { | ||
| 269 | try argv.append("-dead_strip"); | ||
| 270 | } | ||
| 271 | |||
| 272 | if (macho_file.dead_strip_dylibs) { | ||
| 273 | try argv.append("-dead_strip_dylibs"); | ||
| 274 | } | ||
| 275 | |||
| 276 | if (macho_file.entry_name) |entry_name| { | ||
| 277 | try argv.appendSlice(&.{ "-e", entry_name }); | ||
| 278 | } | ||
| 279 | |||
| 280 | for (objects) |obj| { | ||
| 281 | if (obj.must_link) { | ||
| 282 | try argv.append("-force_load"); | ||
| 283 | } | ||
| 284 | try argv.append(obj.path); | ||
| 285 | } | ||
| 286 | |||
| 287 | for (comp.c_object_table.keys()) |key| { | ||
| 288 | try argv.append(key.status.success.object_path); | ||
| 289 | } | ||
| 290 | |||
| 291 | if (module_obj_path) |p| { | ||
| 292 | try argv.append(p); | ||
| 293 | } | ||
| 294 | |||
| 295 | if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path); | ||
| 296 | if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path); | ||
| 297 | |||
| 298 | if (comp.config.link_libcpp) { | ||
| 299 | try argv.append(comp.libcxxabi_static_lib.?.full_object_path); | ||
| 300 | try argv.append(comp.libcxx_static_lib.?.full_object_path); | ||
| 301 | } | ||
| 302 | |||
| 303 | try argv.append("-o"); | ||
| 304 | try argv.append(full_out_path); | ||
| 305 | |||
| 306 | try argv.append("-lSystem"); | ||
| 307 | |||
| 308 | for (comp.system_libs.keys()) |l_name| { | ||
| 309 | const info = comp.system_libs.get(l_name).?; | ||
| 310 | const arg = if (info.needed) | ||
| 311 | try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name}) | ||
| 312 | else if (info.weak) | ||
| 313 | try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name}) | ||
| 314 | else | ||
| 315 | try std.fmt.allocPrint(arena, "-l{s}", .{l_name}); | ||
| 316 | try argv.append(arg); | ||
| 317 | } | ||
| 318 | |||
| 319 | for (macho_file.frameworks) |framework| { | ||
| 320 | const name = std.fs.path.stem(framework.path); | ||
| 321 | const arg = if (framework.needed) | ||
| 322 | try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name}) | ||
| 323 | else if (framework.weak) | ||
| 324 | try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name}) | ||
| 325 | else | ||
| 326 | try std.fmt.allocPrint(arena, "-framework {s}", .{name}); | ||
| 327 | try argv.append(arg); | ||
| 328 | } | ||
| 329 | |||
| 330 | if (is_dyn_lib and macho_file.base.allow_shlib_undefined) { | ||
| 331 | try argv.append("-undefined"); | ||
| 332 | try argv.append("dynamic_lookup"); | ||
| 333 | } | ||
| 334 | |||
| 335 | Compilation.dump_argv(argv.items); | ||
| 336 | } | ||
| 337 | |||
| 338 | var dependent_libs = std.fifo.LinearFifo(MachO.DylibReExportInfo, .Dynamic).init(arena); | ||
| 339 | |||
| 340 | for (positionals.items) |obj| { | ||
| 341 | const in_file = try std.fs.cwd().openFile(obj.path, .{}); | ||
| 342 | defer in_file.close(); | ||
| 343 | |||
| 344 | var parse_ctx = MachO.ParseErrorCtx.init(gpa); | ||
| 345 | defer parse_ctx.deinit(); | ||
| 346 | |||
| 347 | macho_file.parsePositional( | ||
| 348 | in_file, | ||
| 349 | obj.path, | ||
| 350 | obj.must_link, | ||
| 351 | &dependent_libs, | ||
| 352 | &parse_ctx, | ||
| 353 | ) catch |err| try macho_file.handleAndReportParseError(obj.path, err, &parse_ctx); | ||
| 354 | } | ||
| 355 | |||
| 356 | for (libs.keys(), libs.values()) |path, lib| { | ||
| 357 | const in_file = try std.fs.cwd().openFile(path, .{}); | ||
| 358 | defer in_file.close(); | ||
| 359 | |||
| 360 | var parse_ctx = MachO.ParseErrorCtx.init(gpa); | ||
| 361 | defer parse_ctx.deinit(); | ||
| 362 | |||
| 363 | macho_file.parseLibrary( | ||
| 364 | in_file, | ||
| 365 | path, | ||
| 366 | lib, | ||
| 367 | false, | ||
| 368 | false, | ||
| 369 | null, | ||
| 370 | &dependent_libs, | ||
| 371 | &parse_ctx, | ||
| 372 | ) catch |err| try macho_file.handleAndReportParseError(path, err, &parse_ctx); | ||
| 373 | } | ||
| 374 | |||
| 375 | try macho_file.parseDependentLibs(&dependent_libs); | ||
| 376 | |||
| 377 | try macho_file.resolveSymbols(); | ||
| 378 | if (macho_file.unresolved.count() > 0) { | ||
| 379 | try macho_file.reportUndefined(); | ||
| 380 | return error.FlushFailure; | ||
| 381 | } | ||
| 382 | |||
| 383 | for (macho_file.objects.items, 0..) |*object, object_id| { | ||
| 384 | object.splitIntoAtoms(macho_file, @as(u32, @intCast(object_id))) catch |err| switch (err) { | ||
| 385 | error.MissingEhFrameSection => try macho_file.reportParseError( | ||
| 386 | object.name, | ||
| 387 | "missing section: '__TEXT,__eh_frame' is required but could not be found", | ||
| 388 | .{}, | ||
| 389 | ), | ||
| 390 | error.BadDwarfCfi => try macho_file.reportParseError( | ||
| 391 | object.name, | ||
| 392 | "invalid DWARF: failed to parse '__TEXT,__eh_frame' section", | ||
| 393 | .{}, | ||
| 394 | ), | ||
| 395 | else => |e| return e, | ||
| 396 | }; | ||
| 397 | } | ||
| 398 | |||
| 399 | if (macho_file.base.gc_sections) { | ||
| 400 | try dead_strip.gcAtoms(macho_file); | ||
| 401 | } | ||
| 402 | |||
| 403 | try macho_file.createDyldPrivateAtom(); | ||
| 404 | try macho_file.createTentativeDefAtoms(); | ||
| 405 | |||
| 406 | if (comp.config.output_mode == .Exe) { | ||
| 407 | const global = macho_file.getEntryPoint().?; | ||
| 408 | if (macho_file.getSymbol(global).undf()) { | ||
| 409 | // We do one additional check here in case the entry point was found in one of the dylibs. | ||
| 410 | // (I actually have no idea what this would imply but it is a possible outcome and so we | ||
| 411 | // support it.) | ||
| 412 | try macho_file.addStubEntry(global); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | for (macho_file.objects.items) |object| { | ||
| 417 | for (object.atoms.items) |atom_index| { | ||
| 418 | const atom = macho_file.getAtom(atom_index); | ||
| 419 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 420 | const header = macho_file.sections.items(.header)[sym.n_sect - 1]; | ||
| 421 | if (header.isZerofill()) continue; | ||
| 422 | |||
| 423 | const relocs = Atom.getAtomRelocs(macho_file, atom_index); | ||
| 424 | try Atom.scanAtomRelocs(macho_file, atom_index, relocs); | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | try eh_frame.scanRelocs(macho_file); | ||
| 429 | try UnwindInfo.scanRelocs(macho_file); | ||
| 430 | |||
| 431 | if (macho_file.dyld_stub_binder_index) |index| | ||
| 432 | try macho_file.addGotEntry(macho_file.globals.items[index]); | ||
| 433 | |||
| 434 | try calcSectionSizes(macho_file); | ||
| 435 | |||
| 436 | var unwind_info = UnwindInfo{ .gpa = gpa }; | ||
| 437 | defer unwind_info.deinit(); | ||
| 438 | try unwind_info.collect(macho_file); | ||
| 439 | |||
| 440 | try eh_frame.calcSectionSize(macho_file, &unwind_info); | ||
| 441 | unwind_info.calcSectionSize(macho_file); | ||
| 442 | |||
| 443 | try pruneAndSortSections(macho_file); | ||
| 444 | try createSegments(macho_file); | ||
| 445 | try allocateSegments(macho_file); | ||
| 446 | |||
| 447 | try macho_file.allocateSpecialSymbols(); | ||
| 448 | |||
| 449 | if (build_options.enable_logging) { | ||
| 450 | macho_file.logSymtab(); | ||
| 451 | macho_file.logSegments(); | ||
| 452 | macho_file.logSections(); | ||
| 453 | macho_file.logAtoms(); | ||
| 454 | } | ||
| 455 | |||
| 456 | try writeAtoms(macho_file); | ||
| 457 | if (target.cpu.arch == .aarch64) try writeThunks(macho_file); | ||
| 458 | try writeDyldPrivateAtom(macho_file); | ||
| 459 | |||
| 460 | if (macho_file.stubs_section_index) |_| { | ||
| 461 | try writeStubs(macho_file); | ||
| 462 | try writeStubHelpers(macho_file); | ||
| 463 | try writeLaSymbolPtrs(macho_file); | ||
| 464 | } | ||
| 465 | if (macho_file.got_section_index) |sect_id| | ||
| 466 | try writePointerEntries(macho_file, sect_id, &macho_file.got_table); | ||
| 467 | if (macho_file.tlv_ptr_section_index) |sect_id| | ||
| 468 | try writePointerEntries(macho_file, sect_id, &macho_file.tlv_ptr_table); | ||
| 469 | |||
| 470 | try eh_frame.write(macho_file, &unwind_info); | ||
| 471 | try unwind_info.write(macho_file); | ||
| 472 | try macho_file.writeLinkeditSegmentData(); | ||
| 473 | |||
| 474 | // If the last section of __DATA segment is zerofill section, we need to ensure | ||
| 475 | // that the free space between the end of the last non-zerofill section of __DATA | ||
| 476 | // segment and the beginning of __LINKEDIT segment is zerofilled as the loader will | ||
| 477 | // copy-paste this space into memory for quicker zerofill operation. | ||
| 478 | if (macho_file.data_segment_cmd_index) |data_seg_id| blk: { | ||
| 479 | var physical_zerofill_start: ?u64 = null; | ||
| 480 | const section_indexes = macho_file.getSectionIndexes(data_seg_id); | ||
| 481 | for (macho_file.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| { | ||
| 482 | if (header.isZerofill() and header.size > 0) break; | ||
| 483 | physical_zerofill_start = header.offset + header.size; | ||
| 484 | } else break :blk; | ||
| 485 | const start = physical_zerofill_start orelse break :blk; | ||
| 486 | const linkedit = macho_file.getLinkeditSegmentPtr(); | ||
| 487 | const size = math.cast(usize, linkedit.fileoff - start) orelse return error.Overflow; | ||
| 488 | if (size > 0) { | ||
| 489 | log.debug("zeroing out zerofill area of length {x} at {x}", .{ size, start }); | ||
| 490 | const padding = try gpa.alloc(u8, size); | ||
| 491 | defer gpa.free(padding); | ||
| 492 | @memset(padding, 0); | ||
| 493 | try macho_file.base.file.?.pwriteAll(padding, start); | ||
| 494 | } | ||
| 495 | } | ||
| 496 | |||
| 497 | // Write code signature padding if required | ||
| 498 | var codesig: ?CodeSignature = if (macho_file.requiresCodeSignature()) blk: { | ||
| 499 | // Preallocate space for the code signature. | ||
| 500 | // We need to do this at this stage so that we have the load commands with proper values | ||
| 501 | // written out to the file. | ||
| 502 | // The most important here is to have the correct vm and filesize of the __LINKEDIT segment | ||
| 503 | // where the code signature goes into. | ||
| 504 | var codesig = CodeSignature.init(MachO.getPageSize(cpu_arch)); | ||
| 505 | codesig.code_directory.ident = fs.path.basename(full_out_path); | ||
| 506 | if (macho_file.entitlements) |path| { | ||
| 507 | try codesig.addEntitlements(gpa, path); | ||
| 508 | } | ||
| 509 | try macho_file.writeCodeSignaturePadding(&codesig); | ||
| 510 | break :blk codesig; | ||
| 511 | } else null; | ||
| 512 | defer if (codesig) |*csig| csig.deinit(gpa); | ||
| 513 | |||
| 514 | // Write load commands | ||
| 515 | var lc_buffer = std.ArrayList(u8).init(arena); | ||
| 516 | const lc_writer = lc_buffer.writer(); | ||
| 517 | |||
| 518 | try macho_file.writeSegmentHeaders(lc_writer); | ||
| 519 | try lc_writer.writeStruct(macho_file.dyld_info_cmd); | ||
| 520 | try lc_writer.writeStruct(macho_file.function_starts_cmd); | ||
| 521 | try lc_writer.writeStruct(macho_file.data_in_code_cmd); | ||
| 522 | try lc_writer.writeStruct(macho_file.symtab_cmd); | ||
| 523 | try lc_writer.writeStruct(macho_file.dysymtab_cmd); | ||
| 524 | try load_commands.writeDylinkerLC(lc_writer); | ||
| 525 | |||
| 526 | switch (output_mode) { | ||
| 527 | .Exe => blk: { | ||
| 528 | const seg_id = macho_file.header_segment_cmd_index.?; | ||
| 529 | const seg = macho_file.segments.items[seg_id]; | ||
| 530 | const global = macho_file.getEntryPoint() orelse break :blk; | ||
| 531 | const sym = macho_file.getSymbol(global); | ||
| 532 | |||
| 533 | const addr: u64 = if (sym.undf()) | ||
| 534 | // In this case, the symbol has been resolved in one of dylibs and so we point | ||
| 535 | // to the stub as its vmaddr value. | ||
| 536 | macho_file.getStubsEntryAddress(global).? | ||
| 537 | else | ||
| 538 | sym.n_value; | ||
| 539 | |||
| 540 | try lc_writer.writeStruct(macho.entry_point_command{ | ||
| 541 | .entryoff = @as(u32, @intCast(addr - seg.vmaddr)), | ||
| 542 | .stacksize = macho_file.base.stack_size, | ||
| 543 | }); | ||
| 544 | }, | ||
| 545 | .Lib => if (link_mode == .Dynamic) { | ||
| 546 | try load_commands.writeDylibIdLC(macho_file, lc_writer); | ||
| 547 | }, | ||
| 548 | else => {}, | ||
| 549 | } | ||
| 550 | |||
| 551 | try load_commands.writeRpathLCs(macho_file, lc_writer); | ||
| 552 | try lc_writer.writeStruct(macho.source_version_command{ | ||
| 553 | .version = 0, | ||
| 554 | }); | ||
| 555 | { | ||
| 556 | const platform = Platform.fromTarget(target); | ||
| 557 | const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file); | ||
| 558 | if (platform.isBuildVersionCompatible()) { | ||
| 559 | try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer); | ||
| 560 | } else { | ||
| 561 | try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer); | ||
| 562 | } | ||
| 563 | } | ||
| 564 | |||
| 565 | const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len)); | ||
| 566 | try lc_writer.writeStruct(macho_file.uuid_cmd); | ||
| 567 | |||
| 568 | try load_commands.writeLoadDylibLCs( | ||
| 569 | macho_file.dylibs.items, | ||
| 570 | macho_file.referenced_dylibs.keys(), | ||
| 571 | lc_writer, | ||
| 572 | ); | ||
| 573 | |||
| 574 | if (codesig != null) { | ||
| 575 | try lc_writer.writeStruct(macho_file.codesig_cmd); | ||
| 576 | } | ||
| 577 | |||
| 578 | const ncmds = load_commands.calcNumOfLCs(lc_buffer.items); | ||
| 579 | try macho_file.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64)); | ||
| 580 | try macho_file.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len))); | ||
| 581 | try macho_file.writeUuid(comp, uuid_cmd_offset, codesig != null); | ||
| 582 | |||
| 583 | if (codesig) |*csig| { | ||
| 584 | try macho_file.writeCodeSignature(comp, csig); // code signing always comes last | ||
| 585 | try MachO.invalidateKernelCache(directory.handle, macho_file.base.emit.sub_path); | ||
| 586 | } | ||
| 587 | } | ||
| 588 | |||
| 589 | if (!macho_file.base.disable_lld_caching) { | ||
| 590 | // Update the file with the digest. If it fails we can continue; it only | ||
| 591 | // means that the next invocation will have an unnecessary cache miss. | ||
| 592 | Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { | ||
| 593 | log.debug("failed to save linking hash digest file: {s}", .{@errorName(err)}); | ||
| 594 | }; | ||
| 595 | // Again failure here only means an unnecessary cache miss. | ||
| 596 | if (man.have_exclusive_lock) { | ||
| 597 | man.writeManifest() catch |err| { | ||
| 598 | log.debug("failed to write cache manifest when linking: {s}", .{@errorName(err)}); | ||
| 599 | }; | ||
| 600 | } | ||
| 601 | // We hang on to this lock so that the output file path can be used without | ||
| 602 | // other processes clobbering it. | ||
| 603 | macho_file.base.lock = man.toOwnedLock(); | ||
| 604 | } | ||
| 605 | } | ||
| 606 | |||
| 607 | fn createSegments(macho_file: *MachO) !void { | ||
| 608 | const comp = macho_file.base.comp; | ||
| 609 | const gpa = comp.gpa; | ||
| 610 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 611 | const page_size = MachO.getPageSize(target.cpu.arch); | ||
| 612 | const aligned_pagezero_vmsize = mem.alignBackward(u64, macho_file.pagezero_vmsize, page_size); | ||
| 613 | if (macho_file.base.comp.config.output_mode != .Lib and aligned_pagezero_vmsize > 0) { | ||
| 614 | if (aligned_pagezero_vmsize != macho_file.pagezero_vmsize) { | ||
| 615 | log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{macho_file.pagezero_vmsize}); | ||
| 616 | log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize}); | ||
| 617 | } | ||
| 618 | macho_file.pagezero_segment_cmd_index = @intCast(macho_file.segments.items.len); | ||
| 619 | try macho_file.segments.append(gpa, .{ | ||
| 620 | .cmdsize = @sizeOf(macho.segment_command_64), | ||
| 621 | .segname = MachO.makeStaticString("__PAGEZERO"), | ||
| 622 | .vmsize = aligned_pagezero_vmsize, | ||
| 623 | }); | ||
| 624 | } | ||
| 625 | |||
| 626 | // __TEXT segment is non-optional | ||
| 627 | { | ||
| 628 | const protection = MachO.getSegmentMemoryProtection("__TEXT"); | ||
| 629 | macho_file.text_segment_cmd_index = @intCast(macho_file.segments.items.len); | ||
| 630 | macho_file.header_segment_cmd_index = macho_file.text_segment_cmd_index.?; | ||
| 631 | try macho_file.segments.append(gpa, .{ | ||
| 632 | .cmdsize = @sizeOf(macho.segment_command_64), | ||
| 633 | .segname = MachO.makeStaticString("__TEXT"), | ||
| 634 | .maxprot = protection, | ||
| 635 | .initprot = protection, | ||
| 636 | }); | ||
| 637 | } | ||
| 638 | |||
| 639 | for (macho_file.sections.items(.header), 0..) |header, sect_id| { | ||
| 640 | if (header.size == 0) continue; // empty section | ||
| 641 | |||
| 642 | const segname = header.segName(); | ||
| 643 | const segment_id = macho_file.getSegmentByName(segname) orelse blk: { | ||
| 644 | log.debug("creating segment '{s}'", .{segname}); | ||
| 645 | const segment_id = @as(u8, @intCast(macho_file.segments.items.len)); | ||
| 646 | const protection = MachO.getSegmentMemoryProtection(segname); | ||
| 647 | try macho_file.segments.append(gpa, .{ | ||
| 648 | .cmdsize = @sizeOf(macho.segment_command_64), | ||
| 649 | .segname = MachO.makeStaticString(segname), | ||
| 650 | .maxprot = protection, | ||
| 651 | .initprot = protection, | ||
| 652 | }); | ||
| 653 | break :blk segment_id; | ||
| 654 | }; | ||
| 655 | const segment = &macho_file.segments.items[segment_id]; | ||
| 656 | segment.cmdsize += @sizeOf(macho.section_64); | ||
| 657 | segment.nsects += 1; | ||
| 658 | macho_file.sections.items(.segment_index)[sect_id] = segment_id; | ||
| 659 | } | ||
| 660 | |||
| 661 | if (macho_file.getSegmentByName("__DATA_CONST")) |index| { | ||
| 662 | macho_file.data_const_segment_cmd_index = index; | ||
| 663 | } | ||
| 664 | |||
| 665 | if (macho_file.getSegmentByName("__DATA")) |index| { | ||
| 666 | macho_file.data_segment_cmd_index = index; | ||
| 667 | } | ||
| 668 | |||
| 669 | // __LINKEDIT always comes last | ||
| 670 | { | ||
| 671 | const protection = MachO.getSegmentMemoryProtection("__LINKEDIT"); | ||
| 672 | macho_file.linkedit_segment_cmd_index = @intCast(macho_file.segments.items.len); | ||
| 673 | try macho_file.segments.append(gpa, .{ | ||
| 674 | .cmdsize = @sizeOf(macho.segment_command_64), | ||
| 675 | .segname = MachO.makeStaticString("__LINKEDIT"), | ||
| 676 | .maxprot = protection, | ||
| 677 | .initprot = protection, | ||
| 678 | }); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | |||
| 682 | fn writeAtoms(macho_file: *MachO) !void { | ||
| 683 | const comp = macho_file.base.comp; | ||
| 684 | const gpa = comp.gpa; | ||
| 685 | const slice = macho_file.sections.slice(); | ||
| 686 | |||
| 687 | for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| { | ||
| 688 | const header = slice.items(.header)[sect_id]; | ||
| 689 | if (header.isZerofill()) continue; | ||
| 690 | |||
| 691 | var atom_index = first_atom_index orelse continue; | ||
| 692 | |||
| 693 | var buffer = try gpa.alloc(u8, math.cast(usize, header.size) orelse return error.Overflow); | ||
| 694 | defer gpa.free(buffer); | ||
| 695 | @memset(buffer, 0); // TODO with NOPs | ||
| 696 | |||
| 697 | log.debug("writing atoms in {s},{s}", .{ header.segName(), header.sectName() }); | ||
| 698 | |||
| 699 | while (true) { | ||
| 700 | const atom = macho_file.getAtom(atom_index); | ||
| 701 | if (atom.getFile()) |file| { | ||
| 702 | const this_sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 703 | const padding_size: usize = if (atom.next_index) |next_index| blk: { | ||
| 704 | const next_sym = macho_file.getSymbol(macho_file.getAtom(next_index).getSymbolWithLoc()); | ||
| 705 | const size = next_sym.n_value - (this_sym.n_value + atom.size); | ||
| 706 | break :blk math.cast(usize, size) orelse return error.Overflow; | ||
| 707 | } else 0; | ||
| 708 | |||
| 709 | log.debug(" (adding ATOM(%{d}, '{s}') from object({d}) to buffer)", .{ | ||
| 710 | atom.sym_index, | ||
| 711 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | ||
| 712 | file, | ||
| 713 | }); | ||
| 714 | if (padding_size > 0) { | ||
| 715 | log.debug(" (with padding {x})", .{padding_size}); | ||
| 716 | } | ||
| 717 | |||
| 718 | const offset = math.cast(usize, this_sym.n_value - header.addr) orelse | ||
| 719 | return error.Overflow; | ||
| 720 | log.debug(" (at offset 0x{x})", .{offset}); | ||
| 721 | |||
| 722 | const code = Atom.getAtomCode(macho_file, atom_index); | ||
| 723 | const relocs = Atom.getAtomRelocs(macho_file, atom_index); | ||
| 724 | const size = math.cast(usize, atom.size) orelse return error.Overflow; | ||
| 725 | @memcpy(buffer[offset .. offset + size], code); | ||
| 726 | try Atom.resolveRelocs( | ||
| 727 | macho_file, | ||
| 728 | atom_index, | ||
| 729 | buffer[offset..][0..size], | ||
| 730 | relocs, | ||
| 731 | ); | ||
| 732 | } | ||
| 733 | |||
| 734 | if (atom.next_index) |next_index| { | ||
| 735 | atom_index = next_index; | ||
| 736 | } else break; | ||
| 737 | } | ||
| 738 | |||
| 739 | log.debug(" (writing at file offset 0x{x})", .{header.offset}); | ||
| 740 | try macho_file.base.file.?.pwriteAll(buffer, header.offset); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 744 | fn writeDyldPrivateAtom(macho_file: *MachO) !void { | ||
| 745 | const atom_index = macho_file.dyld_private_atom_index orelse return; | ||
| 746 | const atom = macho_file.getAtom(atom_index); | ||
| 747 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 748 | const sect_id = macho_file.data_section_index.?; | ||
| 749 | const header = macho_file.sections.items(.header)[sect_id]; | ||
| 750 | const offset = sym.n_value - header.addr + header.offset; | ||
| 751 | log.debug("writing __dyld_private at offset 0x{x}", .{offset}); | ||
| 752 | const buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); | ||
| 753 | try macho_file.base.file.?.pwriteAll(&buffer, offset); | ||
| 754 | } | ||
| 755 | |||
| 756 | fn writeThunks(macho_file: *MachO) !void { | ||
| 757 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 758 | assert(target.cpu.arch == .aarch64); | ||
| 759 | const comp = macho_file.base.comp; | ||
| 760 | const gpa = comp.gpa; | ||
| 761 | |||
| 762 | const sect_id = macho_file.text_section_index orelse return; | ||
| 763 | const header = macho_file.sections.items(.header)[sect_id]; | ||
| 764 | |||
| 765 | for (macho_file.thunks.items, 0..) |*thunk, i| { | ||
| 766 | if (thunk.getSize() == 0) continue; | ||
| 767 | const thunk_size = math.cast(usize, thunk.getSize()) orelse return error.Overflow; | ||
| 768 | var buffer = try std.ArrayList(u8).initCapacity(gpa, thunk_size); | ||
| 769 | defer buffer.deinit(); | ||
| 770 | try thunks.writeThunkCode(macho_file, thunk, buffer.writer()); | ||
| 771 | const thunk_atom = macho_file.getAtom(thunk.getStartAtomIndex()); | ||
| 772 | const thunk_sym = macho_file.getSymbol(thunk_atom.getSymbolWithLoc()); | ||
| 773 | const offset = thunk_sym.n_value - header.addr + header.offset; | ||
| 774 | log.debug("writing thunk({d}) at offset 0x{x}", .{ i, offset }); | ||
| 775 | try macho_file.base.file.?.pwriteAll(buffer.items, offset); | ||
| 776 | } | ||
| 777 | } | ||
| 778 | |||
| 779 | fn writePointerEntries(macho_file: *MachO, sect_id: u8, table: anytype) !void { | ||
| 780 | const comp = macho_file.base.comp; | ||
| 781 | const gpa = comp.gpa; | ||
| 782 | const header = macho_file.sections.items(.header)[sect_id]; | ||
| 783 | const capacity = math.cast(usize, header.size) orelse return error.Overflow; | ||
| 784 | var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity); | ||
| 785 | defer buffer.deinit(); | ||
| 786 | for (table.entries.items) |entry| { | ||
| 787 | const sym = macho_file.getSymbol(entry); | ||
| 788 | buffer.writer().writeInt(u64, sym.n_value, .little) catch unreachable; | ||
| 789 | } | ||
| 790 | log.debug("writing __DATA_CONST,__got contents at file offset 0x{x}", .{header.offset}); | ||
| 791 | try macho_file.base.file.?.pwriteAll(buffer.items, header.offset); | ||
| 792 | } | ||
| 793 | |||
| 794 | fn writeStubs(macho_file: *MachO) !void { | ||
| 795 | const comp = macho_file.base.comp; | ||
| 796 | const gpa = comp.gpa; | ||
| 797 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 798 | const cpu_arch = target.cpu.arch; | ||
| 799 | const stubs_header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?]; | ||
| 800 | const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?]; | ||
| 801 | |||
| 802 | const capacity = math.cast(usize, stubs_header.size) orelse return error.Overflow; | ||
| 803 | var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity); | ||
| 804 | defer buffer.deinit(); | ||
| 805 | |||
| 806 | for (0..macho_file.stub_table.count()) |index| { | ||
| 807 | try stubs.writeStubCode(.{ | ||
| 808 | .cpu_arch = cpu_arch, | ||
| 809 | .source_addr = stubs_header.addr + stubs.stubSize(cpu_arch) * index, | ||
| 810 | .target_addr = la_symbol_ptr_header.addr + index * @sizeOf(u64), | ||
| 811 | }, buffer.writer()); | ||
| 812 | } | ||
| 813 | |||
| 814 | log.debug("writing __TEXT,__stubs contents at file offset 0x{x}", .{stubs_header.offset}); | ||
| 815 | try macho_file.base.file.?.pwriteAll(buffer.items, stubs_header.offset); | ||
| 816 | } | ||
| 817 | |||
| 818 | fn writeStubHelpers(macho_file: *MachO) !void { | ||
| 819 | const comp = macho_file.base.comp; | ||
| 820 | const gpa = comp.gpa; | ||
| 821 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 822 | const cpu_arch = target.cpu.arch; | ||
| 823 | const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?]; | ||
| 824 | |||
| 825 | const capacity = math.cast(usize, stub_helper_header.size) orelse return error.Overflow; | ||
| 826 | var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity); | ||
| 827 | defer buffer.deinit(); | ||
| 828 | |||
| 829 | { | ||
| 830 | const dyld_private_addr = blk: { | ||
| 831 | const atom = macho_file.getAtom(macho_file.dyld_private_atom_index.?); | ||
| 832 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 833 | break :blk sym.n_value; | ||
| 834 | }; | ||
| 835 | const dyld_stub_binder_got_addr = blk: { | ||
| 836 | const sym_loc = macho_file.globals.items[macho_file.dyld_stub_binder_index.?]; | ||
| 837 | break :blk macho_file.getGotEntryAddress(sym_loc).?; | ||
| 838 | }; | ||
| 839 | try stubs.writeStubHelperPreambleCode(.{ | ||
| 840 | .cpu_arch = cpu_arch, | ||
| 841 | .source_addr = stub_helper_header.addr, | ||
| 842 | .dyld_private_addr = dyld_private_addr, | ||
| 843 | .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr, | ||
| 844 | }, buffer.writer()); | ||
| 845 | } | ||
| 846 | |||
| 847 | for (0..macho_file.stub_table.count()) |index| { | ||
| 848 | const source_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) + | ||
| 849 | stubs.stubHelperSize(cpu_arch) * index; | ||
| 850 | try stubs.writeStubHelperCode(.{ | ||
| 851 | .cpu_arch = cpu_arch, | ||
| 852 | .source_addr = source_addr, | ||
| 853 | .target_addr = stub_helper_header.addr, | ||
| 854 | }, buffer.writer()); | ||
| 855 | } | ||
| 856 | |||
| 857 | log.debug("writing __TEXT,__stub_helper contents at file offset 0x{x}", .{ | ||
| 858 | stub_helper_header.offset, | ||
| 859 | }); | ||
| 860 | try macho_file.base.file.?.pwriteAll(buffer.items, stub_helper_header.offset); | ||
| 861 | } | ||
| 862 | |||
| 863 | fn writeLaSymbolPtrs(macho_file: *MachO) !void { | ||
| 864 | const comp = macho_file.base.comp; | ||
| 865 | const gpa = comp.gpa; | ||
| 866 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 867 | const cpu_arch = target.cpu.arch; | ||
| 868 | const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?]; | ||
| 869 | const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?]; | ||
| 870 | |||
| 871 | const capacity = math.cast(usize, la_symbol_ptr_header.size) orelse return error.Overflow; | ||
| 872 | var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity); | ||
| 873 | defer buffer.deinit(); | ||
| 874 | |||
| 875 | for (0..macho_file.stub_table.count()) |index| { | ||
| 876 | const target_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) + | ||
| 877 | stubs.stubHelperSize(cpu_arch) * index; | ||
| 878 | buffer.writer().writeInt(u64, target_addr, .little) catch unreachable; | ||
| 879 | } | ||
| 880 | |||
| 881 | log.debug("writing __DATA,__la_symbol_ptr contents at file offset 0x{x}", .{ | ||
| 882 | la_symbol_ptr_header.offset, | ||
| 883 | }); | ||
| 884 | try macho_file.base.file.?.pwriteAll(buffer.items, la_symbol_ptr_header.offset); | ||
| 885 | } | ||
| 886 | |||
| 887 | fn pruneAndSortSections(macho_file: *MachO) !void { | ||
| 888 | const Entry = struct { | ||
| 889 | index: u8, | ||
| 890 | |||
| 891 | pub fn lessThan(ctx: *MachO, lhs: @This(), rhs: @This()) bool { | ||
| 892 | const lhs_header = ctx.sections.items(.header)[lhs.index]; | ||
| 893 | const rhs_header = ctx.sections.items(.header)[rhs.index]; | ||
| 894 | return MachO.getSectionPrecedence(lhs_header) < MachO.getSectionPrecedence(rhs_header); | ||
| 895 | } | ||
| 896 | }; | ||
| 897 | |||
| 898 | const comp = macho_file.base.comp; | ||
| 899 | const gpa = comp.gpa; | ||
| 900 | |||
| 901 | var entries = try std.ArrayList(Entry).initCapacity(gpa, macho_file.sections.slice().len); | ||
| 902 | defer entries.deinit(); | ||
| 903 | |||
| 904 | for (0..macho_file.sections.slice().len) |index| { | ||
| 905 | const section = macho_file.sections.get(index); | ||
| 906 | if (section.header.size == 0) { | ||
| 907 | log.debug("pruning section {s},{s} {?d}", .{ | ||
| 908 | section.header.segName(), | ||
| 909 | section.header.sectName(), | ||
| 910 | section.first_atom_index, | ||
| 911 | }); | ||
| 912 | for (&[_]*?u8{ | ||
| 913 | &macho_file.text_section_index, | ||
| 914 | &macho_file.data_const_section_index, | ||
| 915 | &macho_file.data_section_index, | ||
| 916 | &macho_file.bss_section_index, | ||
| 917 | &macho_file.thread_vars_section_index, | ||
| 918 | &macho_file.thread_data_section_index, | ||
| 919 | &macho_file.thread_bss_section_index, | ||
| 920 | &macho_file.eh_frame_section_index, | ||
| 921 | &macho_file.unwind_info_section_index, | ||
| 922 | &macho_file.got_section_index, | ||
| 923 | &macho_file.tlv_ptr_section_index, | ||
| 924 | &macho_file.stubs_section_index, | ||
| 925 | &macho_file.stub_helper_section_index, | ||
| 926 | &macho_file.la_symbol_ptr_section_index, | ||
| 927 | }) |maybe_index| { | ||
| 928 | if (maybe_index.* != null and maybe_index.*.? == index) { | ||
| 929 | maybe_index.* = null; | ||
| 930 | } | ||
| 931 | } | ||
| 932 | continue; | ||
| 933 | } | ||
| 934 | entries.appendAssumeCapacity(.{ .index = @intCast(index) }); | ||
| 935 | } | ||
| 936 | |||
| 937 | mem.sort(Entry, entries.items, macho_file, Entry.lessThan); | ||
| 938 | |||
| 939 | var slice = macho_file.sections.toOwnedSlice(); | ||
| 940 | defer slice.deinit(gpa); | ||
| 941 | |||
| 942 | const backlinks = try gpa.alloc(u8, slice.len); | ||
| 943 | defer gpa.free(backlinks); | ||
| 944 | for (entries.items, 0..) |entry, i| { | ||
| 945 | backlinks[entry.index] = @as(u8, @intCast(i)); | ||
| 946 | } | ||
| 947 | |||
| 948 | try macho_file.sections.ensureTotalCapacity(gpa, entries.items.len); | ||
| 949 | for (entries.items) |entry| { | ||
| 950 | macho_file.sections.appendAssumeCapacity(slice.get(entry.index)); | ||
| 951 | } | ||
| 952 | |||
| 953 | for (&[_]*?u8{ | ||
| 954 | &macho_file.text_section_index, | ||
| 955 | &macho_file.data_const_section_index, | ||
| 956 | &macho_file.data_section_index, | ||
| 957 | &macho_file.bss_section_index, | ||
| 958 | &macho_file.thread_vars_section_index, | ||
| 959 | &macho_file.thread_data_section_index, | ||
| 960 | &macho_file.thread_bss_section_index, | ||
| 961 | &macho_file.eh_frame_section_index, | ||
| 962 | &macho_file.unwind_info_section_index, | ||
| 963 | &macho_file.got_section_index, | ||
| 964 | &macho_file.tlv_ptr_section_index, | ||
| 965 | &macho_file.stubs_section_index, | ||
| 966 | &macho_file.stub_helper_section_index, | ||
| 967 | &macho_file.la_symbol_ptr_section_index, | ||
| 968 | }) |maybe_index| { | ||
| 969 | if (maybe_index.*) |*index| { | ||
| 970 | index.* = backlinks[index.*]; | ||
| 971 | } | ||
| 972 | } | ||
| 973 | } | ||
| 974 | |||
| 975 | fn calcSectionSizes(macho_file: *MachO) !void { | ||
| 976 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 977 | const slice = macho_file.sections.slice(); | ||
| 978 | for (slice.items(.header), 0..) |*header, sect_id| { | ||
| 979 | if (header.size == 0) continue; | ||
| 980 | if (macho_file.text_section_index) |txt| { | ||
| 981 | if (txt == sect_id and target.cpu.arch == .aarch64) continue; | ||
| 982 | } | ||
| 983 | |||
| 984 | var atom_index = slice.items(.first_atom_index)[sect_id] orelse continue; | ||
| 985 | |||
| 986 | header.size = 0; | ||
| 987 | header.@"align" = 0; | ||
| 988 | |||
| 989 | while (true) { | ||
| 990 | const atom = macho_file.getAtom(atom_index); | ||
| 991 | const atom_offset = atom.alignment.forward(header.size); | ||
| 992 | const padding = atom_offset - header.size; | ||
| 993 | |||
| 994 | const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); | ||
| 995 | sym.n_value = atom_offset; | ||
| 996 | |||
| 997 | header.size += padding + atom.size; | ||
| 998 | header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); | ||
| 999 | |||
| 1000 | atom_index = atom.next_index orelse break; | ||
| 1001 | } | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | if (macho_file.text_section_index != null and target.cpu.arch == .aarch64) { | ||
| 1005 | // Create jump/branch range extenders if needed. | ||
| 1006 | try thunks.createThunks(macho_file, macho_file.text_section_index.?); | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | // Update offsets of all symbols contained within each Atom. | ||
| 1010 | // We need to do this since our unwind info synthesiser relies on | ||
| 1011 | // traversing the symbols when synthesising unwind info and DWARF CFI records. | ||
| 1012 | for (slice.items(.first_atom_index)) |first_atom_index| { | ||
| 1013 | var atom_index = first_atom_index orelse continue; | ||
| 1014 | |||
| 1015 | while (true) { | ||
| 1016 | const atom = macho_file.getAtom(atom_index); | ||
| 1017 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | ||
| 1018 | |||
| 1019 | if (atom.getFile() != null) { | ||
| 1020 | // Update each symbol contained within the atom | ||
| 1021 | var it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | ||
| 1022 | while (it.next()) |sym_loc| { | ||
| 1023 | const inner_sym = macho_file.getSymbolPtr(sym_loc); | ||
| 1024 | inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset( | ||
| 1025 | macho_file, | ||
| 1026 | atom_index, | ||
| 1027 | sym_loc.sym_index, | ||
| 1028 | ); | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | // If there is a section alias, update it now too | ||
| 1032 | if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| { | ||
| 1033 | const alias = macho_file.getSymbolPtr(sym_loc); | ||
| 1034 | alias.n_value = sym.n_value; | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | if (atom.next_index) |next_index| { | ||
| 1039 | atom_index = next_index; | ||
| 1040 | } else break; | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | if (macho_file.got_section_index) |sect_id| { | ||
| 1045 | const header = &macho_file.sections.items(.header)[sect_id]; | ||
| 1046 | header.size = macho_file.got_table.count() * @sizeOf(u64); | ||
| 1047 | header.@"align" = 3; | ||
| 1048 | } | ||
| 1049 | |||
| 1050 | if (macho_file.tlv_ptr_section_index) |sect_id| { | ||
| 1051 | const header = &macho_file.sections.items(.header)[sect_id]; | ||
| 1052 | header.size = macho_file.tlv_ptr_table.count() * @sizeOf(u64); | ||
| 1053 | header.@"align" = 3; | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | const cpu_arch = target.cpu.arch; | ||
| 1057 | |||
| 1058 | if (macho_file.stubs_section_index) |sect_id| { | ||
| 1059 | const header = &macho_file.sections.items(.header)[sect_id]; | ||
| 1060 | header.size = macho_file.stub_table.count() * stubs.stubSize(cpu_arch); | ||
| 1061 | header.@"align" = math.log2(stubs.stubAlignment(cpu_arch)); | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | if (macho_file.stub_helper_section_index) |sect_id| { | ||
| 1065 | const header = &macho_file.sections.items(.header)[sect_id]; | ||
| 1066 | header.size = macho_file.stub_table.count() * stubs.stubHelperSize(cpu_arch) + | ||
| 1067 | stubs.stubHelperPreambleSize(cpu_arch); | ||
| 1068 | header.@"align" = math.log2(stubs.stubAlignment(cpu_arch)); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | if (macho_file.la_symbol_ptr_section_index) |sect_id| { | ||
| 1072 | const header = &macho_file.sections.items(.header)[sect_id]; | ||
| 1073 | header.size = macho_file.stub_table.count() * @sizeOf(u64); | ||
| 1074 | header.@"align" = 3; | ||
| 1075 | } | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | fn allocateSegments(macho_file: *MachO) !void { | ||
| 1079 | for (macho_file.segments.items, 0..) |*segment, segment_index| { | ||
| 1080 | const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT"); | ||
| 1081 | const base_size = if (is_text_segment) | ||
| 1082 | try load_commands.calcMinHeaderPad(macho_file, .{ | ||
| 1083 | .segments = macho_file.segments.items, | ||
| 1084 | .dylibs = macho_file.dylibs.items, | ||
| 1085 | .referenced_dylibs = macho_file.referenced_dylibs.keys(), | ||
| 1086 | }) | ||
| 1087 | else | ||
| 1088 | 0; | ||
| 1089 | try allocateSegment(macho_file, @as(u8, @intCast(segment_index)), base_size); | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | fn getSegmentAllocBase(macho_file: *MachO, segment_index: u8) struct { vmaddr: u64, fileoff: u64 } { | ||
| 1094 | if (segment_index > 0) { | ||
| 1095 | const prev_segment = macho_file.segments.items[segment_index - 1]; | ||
| 1096 | return .{ | ||
| 1097 | .vmaddr = prev_segment.vmaddr + prev_segment.vmsize, | ||
| 1098 | .fileoff = prev_segment.fileoff + prev_segment.filesize, | ||
| 1099 | }; | ||
| 1100 | } | ||
| 1101 | return .{ .vmaddr = 0, .fileoff = 0 }; | ||
| 1102 | } | ||
| 1103 | |||
| 1104 | fn allocateSegment(macho_file: *MachO, segment_index: u8, init_size: u64) !void { | ||
| 1105 | const target = macho_file.base.comp.root_mod.resolved_target.result; | ||
| 1106 | const segment = &macho_file.segments.items[segment_index]; | ||
| 1107 | |||
| 1108 | if (mem.eql(u8, segment.segName(), "__PAGEZERO")) return; // allocated upon creation | ||
| 1109 | |||
| 1110 | const base = getSegmentAllocBase(macho_file, segment_index); | ||
| 1111 | segment.vmaddr = base.vmaddr; | ||
| 1112 | segment.fileoff = base.fileoff; | ||
| 1113 | segment.filesize = init_size; | ||
| 1114 | segment.vmsize = init_size; | ||
| 1115 | |||
| 1116 | // Allocate the sections according to their alignment at the beginning of the segment. | ||
| 1117 | const indexes = macho_file.getSectionIndexes(segment_index); | ||
| 1118 | var start = init_size; | ||
| 1119 | |||
| 1120 | const slice = macho_file.sections.slice(); | ||
| 1121 | for (slice.items(.header)[indexes.start..indexes.end], 0..) |*header, sect_id| { | ||
| 1122 | const alignment = try math.powi(u32, 2, header.@"align"); | ||
| 1123 | const start_aligned = mem.alignForward(u64, start, alignment); | ||
| 1124 | const n_sect = @as(u8, @intCast(indexes.start + sect_id + 1)); | ||
| 1125 | |||
| 1126 | header.offset = if (header.isZerofill()) | ||
| 1127 | 0 | ||
| 1128 | else | ||
| 1129 | @as(u32, @intCast(segment.fileoff + start_aligned)); | ||
| 1130 | header.addr = segment.vmaddr + start_aligned; | ||
| 1131 | |||
| 1132 | if (slice.items(.first_atom_index)[indexes.start + sect_id]) |first_atom_index| { | ||
| 1133 | var atom_index = first_atom_index; | ||
| 1134 | |||
| 1135 | log.debug("allocating local symbols in sect({d}, '{s},{s}')", .{ | ||
| 1136 | n_sect, | ||
| 1137 | header.segName(), | ||
| 1138 | header.sectName(), | ||
| 1139 | }); | ||
| 1140 | |||
| 1141 | while (true) { | ||
| 1142 | const atom = macho_file.getAtom(atom_index); | ||
| 1143 | const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); | ||
| 1144 | sym.n_value += header.addr; | ||
| 1145 | sym.n_sect = n_sect; | ||
| 1146 | |||
| 1147 | log.debug(" ATOM(%{d}, '{s}') @{x}", .{ | ||
| 1148 | atom.sym_index, | ||
| 1149 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | ||
| 1150 | sym.n_value, | ||
| 1151 | }); | ||
| 1152 | |||
| 1153 | if (atom.getFile() != null) { | ||
| 1154 | // Update each symbol contained within the atom | ||
| 1155 | var it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | ||
| 1156 | while (it.next()) |sym_loc| { | ||
| 1157 | const inner_sym = macho_file.getSymbolPtr(sym_loc); | ||
| 1158 | inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset( | ||
| 1159 | macho_file, | ||
| 1160 | atom_index, | ||
| 1161 | sym_loc.sym_index, | ||
| 1162 | ); | ||
| 1163 | inner_sym.n_sect = n_sect; | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | // If there is a section alias, update it now too | ||
| 1167 | if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| { | ||
| 1168 | const alias = macho_file.getSymbolPtr(sym_loc); | ||
| 1169 | alias.n_value = sym.n_value; | ||
| 1170 | alias.n_sect = n_sect; | ||
| 1171 | } | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | if (atom.next_index) |next_index| { | ||
| 1175 | atom_index = next_index; | ||
| 1176 | } else break; | ||
| 1177 | } | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | start = start_aligned + header.size; | ||
| 1181 | |||
| 1182 | if (!header.isZerofill()) { | ||
| 1183 | segment.filesize = start; | ||
| 1184 | } | ||
| 1185 | segment.vmsize = start; | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | const page_size = MachO.getPageSize(target.cpu.arch); | ||
| 1189 | segment.filesize = mem.alignForward(u64, segment.filesize, page_size); | ||
| 1190 | segment.vmsize = mem.alignForward(u64, segment.vmsize, page_size); | ||
| 1191 | } | ||
| 1192 | |||
| 1193 | const std = @import("std"); | ||
| 1194 | const build_options = @import("build_options"); | ||
| 1195 | const assert = std.debug.assert; | ||
| 1196 | const dwarf = std.dwarf; | ||
| 1197 | const fs = std.fs; | ||
| 1198 | const log = std.log.scoped(.link); | ||
| 1199 | const macho = std.macho; | ||
| 1200 | const math = std.math; | ||
| 1201 | const mem = std.mem; | ||
| 1202 | |||
| 1203 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 1204 | const calcUuid = @import("uuid.zig").calcUuid; | ||
| 1205 | const dead_strip = @import("dead_strip.zig"); | ||
| 1206 | const eh_frame = @import("eh_frame.zig"); | ||
| 1207 | const fat = @import("fat.zig"); | ||
| 1208 | const link = @import("../../link.zig"); | ||
| 1209 | const load_commands = @import("load_commands.zig"); | ||
| 1210 | const stubs = @import("stubs.zig"); | ||
| 1211 | const thunks = @import("thunks.zig"); | ||
| 1212 | const trace = @import("../../tracy.zig").trace; | ||
| 1213 | |||
| 1214 | const Allocator = mem.Allocator; | ||
| 1215 | const Archive = @import("Archive.zig"); | ||
| 1216 | const Atom = @import("Atom.zig"); | ||
| 1217 | const Cache = std.Build.Cache; | ||
| 1218 | const CodeSignature = @import("CodeSignature.zig"); | ||
| 1219 | const Compilation = @import("../../Compilation.zig"); | ||
| 1220 | const Dylib = @import("Dylib.zig"); | ||
| 1221 | const MachO = @import("../MachO.zig"); | ||
| 1222 | const Md5 = std.crypto.hash.Md5; | ||
| 1223 | const LibStub = @import("../tapi.zig").LibStub; | ||
| 1224 | const Object = @import("Object.zig"); | ||
| 1225 | const Platform = load_commands.Platform; | ||
| 1226 | const Section = MachO.Section; | ||
| 1227 | const SymbolWithLoc = MachO.SymbolWithLoc; | ||
| 1228 | const TableSection = @import("../table_section.zig").TableSection; | ||
| 1229 | const Trie = @import("Trie.zig"); | ||
| 1230 | const UnwindInfo = @import("UnwindInfo.zig"); | ||