├── .gitmodules ├── .gitignore ├── .envrc ├── shell.nix ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── src ├── fat.zig ├── Archive.zig ├── main.zig └── Object.zig ├── flake.nix └── flake.lock /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gyro 2 | zig-out 3 | zig-cache 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | # If we are a computer with nix-shell available, then use that to setup 2 | # the build environment with exactly what we need. 3 | if has nix; then 4 | use nix 5 | fi 6 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let 4 | flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat; 5 | in 6 | fetchTarball { 7 | url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz"; 8 | sha256 = flake-compat.locked.narHash; 9 | } 10 | ) 11 | {src = ./.;}) 12 | .shellNix 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build and test 8 | runs-on: ${{ matrix.os }}-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [macos, ubuntu, windows] 13 | 14 | steps: 15 | - if: matrix.os == 'windows' 16 | run: git config --global core.autocrlf false 17 | - uses: actions/checkout@v2 18 | with: 19 | submodules: true 20 | - uses: mlugg/setup-zig@v2 21 | with: 22 | version: master 23 | - run: zig fmt --check src 24 | - run: zig build install 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jakub Konka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zacho 2 | 3 | ...or Zig's Mach-O parser. This project started off as a dummy scratchpad for reinforcing my 4 | understanding of the Mach-O file format while I was working on the Zig's stage2 Mach-O linker 5 | (I still am working on it, in case anyone was asking). 6 | 7 | My current vision for `zacho` is for it to be a cross-platform version of `otool` and `pagestuff` 8 | macOS utilities. These seem to be very useful when battling the Darwin kernel and `dyld` when those 9 | refuse to load your hand-crafter binary, or you just like looking at Mach-O dissected output. 10 | 11 | ## Usage 12 | 13 | ``` 14 | Usage: zacho [options] file 15 | 16 | General options: 17 | -c, --code-signature Print the contents of code signature (if any) 18 | -d, --dyld-info Print the contents of dyld rebase and bind opcodes 19 | -e, --exports-trie Print export trie (if any) 20 | -h, --header Print the Mach-O header 21 | -i, --indirect-symbol-table Print the indirect symbol table 22 | -l, --load-commands Print load commands 23 | -r, --relocations Print relocation entries (if any) 24 | -s, --symbol-table Print the symbol table 25 | -u, --unwind-info Print the contents of (compact) unwind info section (if any) 26 | -v, --verbose Print more detailed info for each flag 27 | --archive-index Print archive index (if any) 28 | --string-table Print the string table 29 | --data-in-code Print data-in-code entries (if any) 30 | --hex-dump=[name] Dump section contents as bytes 31 | --string-dump=[name] Dump section contents as strings 32 | --verify-memory-layout Print virtual memory layout and verify there is no overlap 33 | --help Display this help and exit 34 | ``` 35 | 36 | ## Building from source 37 | 38 | Building from source requires nightly [Zig](https://ziglang.org/download/). 39 | 40 | ``` 41 | $ git clone https://github.com/kubkon/zacho.git 42 | $ zig build 43 | ``` 44 | 45 | Additionally, on macOS, you will need to provide Foundation and Security frameworks. 46 | -------------------------------------------------------------------------------- /src/fat.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | const builtin = @import("builtin"); 4 | const macho = std.macho; 5 | const mem = std.mem; 6 | const native_endian = builtin.target.cpu.arch.endian(); 7 | 8 | pub fn isFatLibrary(path: []const u8) !bool { 9 | const file = try std.fs.cwd().openFile(path, .{}); 10 | defer file.close(); 11 | const hdr = try readFatHeaderGeneric(macho.fat_header, file, 0); 12 | return hdr.magic == macho.FAT_MAGIC; 13 | } 14 | 15 | fn readFatHeaderGeneric(comptime Hdr: type, file: std.fs.File, offset: usize) !Hdr { 16 | var buffer: [@sizeOf(Hdr)]u8 = undefined; 17 | const nread = try file.preadAll(&buffer, offset); 18 | if (nread != buffer.len) return error.InputOutput; 19 | var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*; 20 | mem.byteSwapAllFields(Hdr, &hdr); 21 | return hdr; 22 | } 23 | 24 | pub const Arch = struct { 25 | tag: std.Target.Cpu.Arch, 26 | offset: u32, 27 | size: u32, 28 | }; 29 | 30 | pub fn parseArchs(file: std.fs.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { 31 | var count: usize = 0; 32 | var fat_arch_index: u32 = 0; 33 | while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) { 34 | const offset = @sizeOf(macho.fat_header) + @sizeOf(macho.fat_arch) * fat_arch_index; 35 | const fat_arch = try readFatHeaderGeneric(macho.fat_arch, file, offset); 36 | // If we come across an architecture that we do not know how to handle, that's 37 | // fine because we can keep looking for one that might match. 38 | const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) { 39 | macho.CPU_TYPE_ARM64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_ARM_ALL) .aarch64 else continue, 40 | macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, 41 | else => continue, 42 | }; 43 | out[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size }; 44 | count += 1; 45 | } 46 | 47 | return out[0..count]; 48 | } 49 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Zacho tool"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | zig.url = "github:mitchellh/zig-overlay"; 8 | zls.url = "github:zigtools/zls"; 9 | 10 | # Used for shell.nix 11 | flake-compat = { 12 | url = "github:edolstra/flake-compat"; 13 | flake = false; 14 | }; 15 | }; 16 | 17 | outputs = 18 | { 19 | self, 20 | nixpkgs, 21 | flake-utils, 22 | ... 23 | }@inputs: 24 | let 25 | overlays = [ 26 | # Other overlays 27 | (final: prev: { 28 | zigpkgs = inputs.zig.packages.${prev.system}; 29 | zlspkgs = inputs.zls.packages.${prev.system}; 30 | }) 31 | ]; 32 | 33 | # Our supported systems are the same supported systems as the Zig binaries 34 | systems = builtins.attrNames inputs.zig.packages; 35 | in 36 | flake-utils.lib.eachSystem systems ( 37 | system: 38 | let 39 | pkgs = import nixpkgs { inherit overlays system; }; 40 | in 41 | rec { 42 | commonInputs = with pkgs; [ zigpkgs.master ] ++ darwinInputs; 43 | 44 | darwinInputs = pkgs.lib.optionals pkgs.stdenv.isDarwin ( 45 | with pkgs; 46 | [ 47 | darwin.apple_sdk.frameworks.Security 48 | darwin.apple_sdk.frameworks.Foundation 49 | ] 50 | ); 51 | 52 | sysroot = pkgs.lib.optionalString pkgs.stdenv.isDarwin "--sysroot $SDKROOT"; 53 | 54 | packages.default = packages.zacho; 55 | packages.zacho = pkgs.stdenv.mkDerivation { 56 | name = "zacho"; 57 | version = "master"; 58 | src = ./.; 59 | nativeBuildInputs = commonInputs; 60 | dontConfigure = true; 61 | dontInstall = true; 62 | doCheck = false; 63 | buildPhase = '' 64 | mkdir -p .cache 65 | zig build install ${sysroot} -Doptimize=ReleaseSafe --prefix $out --cache-dir $(pwd)/.zig-cache --global-cache-dir $(pwd)/.cache 66 | ''; 67 | }; 68 | 69 | devShells.default = pkgs.mkShell { 70 | buildInputs = commonInputs ++ (with pkgs; [ zlspkgs.default ]); 71 | }; 72 | 73 | # For compatibility with older versions of the `nix` binary 74 | devShell = self.devShells.${system}.default; 75 | } 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1747046372, 7 | "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-compat_2": { 20 | "flake": false, 21 | "locked": { 22 | "lastModified": 1696426674, 23 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 24 | "owner": "edolstra", 25 | "repo": "flake-compat", 26 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "edolstra", 31 | "repo": "flake-compat", 32 | "type": "github" 33 | } 34 | }, 35 | "flake-compat_3": { 36 | "flake": false, 37 | "locked": { 38 | "lastModified": 1696426674, 39 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 40 | "owner": "edolstra", 41 | "repo": "flake-compat", 42 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 43 | "type": "github" 44 | }, 45 | "original": { 46 | "owner": "edolstra", 47 | "repo": "flake-compat", 48 | "type": "github" 49 | } 50 | }, 51 | "flake-utils": { 52 | "inputs": { 53 | "systems": "systems" 54 | }, 55 | "locked": { 56 | "lastModified": 1731533236, 57 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 58 | "owner": "numtide", 59 | "repo": "flake-utils", 60 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 61 | "type": "github" 62 | }, 63 | "original": { 64 | "owner": "numtide", 65 | "repo": "flake-utils", 66 | "type": "github" 67 | } 68 | }, 69 | "flake-utils_2": { 70 | "inputs": { 71 | "systems": "systems_2" 72 | }, 73 | "locked": { 74 | "lastModified": 1705309234, 75 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 76 | "owner": "numtide", 77 | "repo": "flake-utils", 78 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 79 | "type": "github" 80 | }, 81 | "original": { 82 | "owner": "numtide", 83 | "repo": "flake-utils", 84 | "type": "github" 85 | } 86 | }, 87 | "flake-utils_3": { 88 | "inputs": { 89 | "systems": "systems_3" 90 | }, 91 | "locked": { 92 | "lastModified": 1705309234, 93 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 94 | "owner": "numtide", 95 | "repo": "flake-utils", 96 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 97 | "type": "github" 98 | }, 99 | "original": { 100 | "owner": "numtide", 101 | "repo": "flake-utils", 102 | "type": "github" 103 | } 104 | }, 105 | "gitignore": { 106 | "inputs": { 107 | "nixpkgs": [ 108 | "zls", 109 | "nixpkgs" 110 | ] 111 | }, 112 | "locked": { 113 | "lastModified": 1709087332, 114 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 115 | "owner": "hercules-ci", 116 | "repo": "gitignore.nix", 117 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 118 | "type": "github" 119 | }, 120 | "original": { 121 | "owner": "hercules-ci", 122 | "repo": "gitignore.nix", 123 | "type": "github" 124 | } 125 | }, 126 | "nixpkgs": { 127 | "locked": { 128 | "lastModified": 1751274312, 129 | "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", 130 | "owner": "nixos", 131 | "repo": "nixpkgs", 132 | "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", 133 | "type": "github" 134 | }, 135 | "original": { 136 | "owner": "nixos", 137 | "ref": "nixos-24.11", 138 | "repo": "nixpkgs", 139 | "type": "github" 140 | } 141 | }, 142 | "nixpkgs_2": { 143 | "locked": { 144 | "lastModified": 1708161998, 145 | "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", 146 | "owner": "NixOS", 147 | "repo": "nixpkgs", 148 | "rev": "84d981bae8b5e783b3b548de505b22880559515f", 149 | "type": "github" 150 | }, 151 | "original": { 152 | "owner": "NixOS", 153 | "ref": "nixos-23.11", 154 | "repo": "nixpkgs", 155 | "type": "github" 156 | } 157 | }, 158 | "nixpkgs_3": { 159 | "locked": { 160 | "lastModified": 1753345091, 161 | "narHash": "sha256-CdX2Rtvp5I8HGu9swBmYuq+ILwRxpXdJwlpg8jvN4tU=", 162 | "owner": "NixOS", 163 | "repo": "nixpkgs", 164 | "rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9", 165 | "type": "github" 166 | }, 167 | "original": { 168 | "owner": "NixOS", 169 | "ref": "nixos-25.05", 170 | "repo": "nixpkgs", 171 | "type": "github" 172 | } 173 | }, 174 | "root": { 175 | "inputs": { 176 | "flake-compat": "flake-compat", 177 | "flake-utils": "flake-utils", 178 | "nixpkgs": "nixpkgs", 179 | "zig": "zig", 180 | "zls": "zls" 181 | } 182 | }, 183 | "systems": { 184 | "locked": { 185 | "lastModified": 1681028828, 186 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 187 | "owner": "nix-systems", 188 | "repo": "default", 189 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 190 | "type": "github" 191 | }, 192 | "original": { 193 | "owner": "nix-systems", 194 | "repo": "default", 195 | "type": "github" 196 | } 197 | }, 198 | "systems_2": { 199 | "locked": { 200 | "lastModified": 1681028828, 201 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 202 | "owner": "nix-systems", 203 | "repo": "default", 204 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 205 | "type": "github" 206 | }, 207 | "original": { 208 | "owner": "nix-systems", 209 | "repo": "default", 210 | "type": "github" 211 | } 212 | }, 213 | "systems_3": { 214 | "locked": { 215 | "lastModified": 1681028828, 216 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 217 | "owner": "nix-systems", 218 | "repo": "default", 219 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 220 | "type": "github" 221 | }, 222 | "original": { 223 | "owner": "nix-systems", 224 | "repo": "default", 225 | "type": "github" 226 | } 227 | }, 228 | "zig": { 229 | "inputs": { 230 | "flake-compat": "flake-compat_2", 231 | "flake-utils": "flake-utils_2", 232 | "nixpkgs": "nixpkgs_2" 233 | }, 234 | "locked": { 235 | "lastModified": 1753663230, 236 | "narHash": "sha256-24um6FSyprsjp7RLhFQKUasyg50NP04ykrTvOdwylkQ=", 237 | "owner": "mitchellh", 238 | "repo": "zig-overlay", 239 | "rev": "b680835e0e74de69f4a195dd25d8affd953d7b31", 240 | "type": "github" 241 | }, 242 | "original": { 243 | "owner": "mitchellh", 244 | "repo": "zig-overlay", 245 | "type": "github" 246 | } 247 | }, 248 | "zig-overlay": { 249 | "inputs": { 250 | "flake-compat": "flake-compat_3", 251 | "flake-utils": "flake-utils_3", 252 | "nixpkgs": [ 253 | "zls", 254 | "nixpkgs" 255 | ] 256 | }, 257 | "locked": { 258 | "lastModified": 1753445652, 259 | "narHash": "sha256-3ICoa7kfkLjDXUMBAcvc2py/8QKoY+cjqfKwO2g7CMY=", 260 | "owner": "mitchellh", 261 | "repo": "zig-overlay", 262 | "rev": "153f0fc99e985f32eb3f0f3a4aabbedd474d2df0", 263 | "type": "github" 264 | }, 265 | "original": { 266 | "owner": "mitchellh", 267 | "repo": "zig-overlay", 268 | "type": "github" 269 | } 270 | }, 271 | "zls": { 272 | "inputs": { 273 | "gitignore": "gitignore", 274 | "nixpkgs": "nixpkgs_3", 275 | "zig-overlay": "zig-overlay" 276 | }, 277 | "locked": { 278 | "lastModified": 1753492458, 279 | "narHash": "sha256-x3gf+ISc+MtQ03Nt6BZopMQFiWf3Wn1R179VbxshFoo=", 280 | "owner": "zigtools", 281 | "repo": "zls", 282 | "rev": "2765a6b7250b034272171b70a41db67d2700ed19", 283 | "type": "github" 284 | }, 285 | "original": { 286 | "owner": "zigtools", 287 | "repo": "zls", 288 | "type": "github" 289 | } 290 | } 291 | }, 292 | "root": "root", 293 | "version": 7 294 | } 295 | -------------------------------------------------------------------------------- /src/Archive.zig: -------------------------------------------------------------------------------- 1 | gpa: Allocator, 2 | data: []const u8, 3 | path: []const u8, 4 | 5 | objects: std.AutoArrayHashMapUnmanaged(u64, Object) = .{}, 6 | symtab: Symtab = .{}, 7 | 8 | verbose: bool = false, 9 | 10 | pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool { 11 | const file = try std.fs.cwd().openFile(path, .{}); 12 | defer file.close(); 13 | var buffer: [SARMAG]u8 = undefined; 14 | const offset = if (fat_arch) |arch| arch.offset else 0; 15 | const nread = try file.preadAll(&buffer, offset); 16 | if (nread != buffer.len) return error.InputOutput; 17 | const magic = buffer[0..SARMAG]; 18 | if (!mem.eql(u8, magic, ARMAG)) return false; 19 | return true; 20 | } 21 | 22 | pub fn deinit(self: *Archive) void { 23 | for (self.objects.values()) |*object| { 24 | object.deinit(); 25 | } 26 | self.objects.deinit(self.gpa); 27 | self.symtab.deinit(self.gpa); 28 | self.gpa.free(self.path); 29 | } 30 | 31 | pub fn parse(self: *Archive) !void { 32 | var stream = std.io.fixedBufferStream(self.data); 33 | const reader = stream.reader(); 34 | _ = try reader.readBytesNoEof(ARMAG.len); 35 | 36 | while (true) { 37 | if (stream.pos >= self.data.len) break; 38 | if (!mem.isAligned(stream.pos, 2)) stream.pos += 1; 39 | 40 | const pos = stream.pos; 41 | const hdr = try reader.readStruct(ar_hdr); 42 | 43 | if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) return error.InvalidHeaderDelimiter; 44 | 45 | var size = try hdr.size(); 46 | if (try hdr.nameLength()) |len| size -= len; 47 | defer { 48 | _ = stream.seekBy(size) catch {}; 49 | } 50 | 51 | const name = try hdr.getName(self.gpa, reader); 52 | if (mem.eql(u8, name, SYMDEF) or 53 | mem.eql(u8, name, SYMDEF64) or 54 | mem.eql(u8, name, SYMDEF_SORTED) or 55 | mem.eql(u8, name, SYMDEF64_SORTED)) 56 | { 57 | const is_64 = mem.eql(u8, name, SYMDEF64) or mem.eql(u8, name, SYMDEF64_SORTED); 58 | if (is_64) self.symtab.format = .p64; 59 | try self.symtab.parse(self.gpa, self.data[stream.pos..][0..size]); 60 | continue; 61 | } 62 | 63 | const gop = try self.objects.getOrPut(self.gpa, pos); 64 | assert(!gop.found_existing); 65 | const object = gop.value_ptr; 66 | object.* = Object{ 67 | .gpa = self.gpa, 68 | .path = name, 69 | .data = self.data[stream.pos..][0..size], 70 | .verbose = self.verbose, 71 | }; 72 | try object.parse(); 73 | } 74 | } 75 | 76 | pub fn printSymbolTable(self: Archive, writer: anytype) !void { 77 | if (self.symtab.entries.items.len == 0) { 78 | return writer.writeAll("no index found in archive\n"); 79 | } 80 | 81 | const size_in_symtab: usize = self.symtab.entries.items.len * 2 * self.symtab.ptrWidth(); 82 | try writer.print("Index of archive {s}: ({d} entries, 0x{x} bytes in the symbol table)\n", .{ 83 | self.path, 84 | self.symtab.entries.items.len, 85 | size_in_symtab, 86 | }); 87 | 88 | // Sort by file 89 | var by_file = std.AutoArrayHashMap(u64, std.ArrayList(usize)).init(self.gpa); 90 | defer { 91 | for (by_file.values()) |*list| { 92 | list.deinit(); 93 | } 94 | by_file.deinit(); 95 | } 96 | for (self.symtab.entries.items, 0..) |entry, i| { 97 | const gop = try by_file.getOrPut(entry.file); 98 | if (!gop.found_existing) { 99 | gop.value_ptr.* = std.ArrayList(usize).init(self.gpa); 100 | } 101 | try gop.value_ptr.append(i); 102 | } 103 | 104 | for (by_file.keys(), by_file.values()) |file, indexes| { 105 | const object = self.objects.get(file).?; 106 | try writer.print("Contents of binary {s}({s}) at offset 0x{x}\n", .{ self.path, object.path, file }); 107 | for (indexes.items) |index| { 108 | try writer.print(" {s}\n", .{self.symtab.entries.items[index].getName(&self.symtab)}); 109 | } 110 | } 111 | } 112 | 113 | const ar_hdr = extern struct { 114 | /// Member file name, sometimes / terminated. 115 | ar_name: [16]u8, 116 | 117 | /// File date, decimal seconds since Epoch. 118 | ar_date: [12]u8, 119 | 120 | /// User ID, in ASCII format. 121 | ar_uid: [6]u8, 122 | 123 | /// Group ID, in ASCII format. 124 | ar_gid: [6]u8, 125 | 126 | /// File mode, in ASCII octal. 127 | ar_mode: [8]u8, 128 | 129 | /// File size, in ASCII decimal. 130 | ar_size: [10]u8, 131 | 132 | /// Always contains ARFMAG. 133 | ar_fmag: [2]u8, 134 | 135 | fn date(self: ar_hdr) !u64 { 136 | const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); 137 | return std.fmt.parseInt(u64, value, 10); 138 | } 139 | 140 | fn size(self: ar_hdr) !u32 { 141 | const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)}); 142 | return std.fmt.parseInt(u32, value, 10); 143 | } 144 | 145 | fn name(self: *const ar_hdr) ?[]const u8 { 146 | const value = &self.ar_name; 147 | if (mem.startsWith(u8, value, "#1/")) return null; 148 | const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len; 149 | return value[0..sentinel]; 150 | } 151 | 152 | fn nameLength(self: ar_hdr) !?u32 { 153 | const value = &self.ar_name; 154 | if (!mem.startsWith(u8, value, "#1/")) return null; 155 | const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); 156 | return try std.fmt.parseInt(u32, trimmed, 10); 157 | } 158 | 159 | /// Caller owns the memory. 160 | fn getName(self: *const ar_hdr, gpa: Allocator, reader: anytype) ![]const u8 { 161 | if (self.name()) |n| return n; 162 | if (try self.nameLength()) |len| { 163 | const buf = try gpa.alloc(u8, len); 164 | defer gpa.free(buf); 165 | try reader.readNoEof(buf); 166 | const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len; 167 | return try gpa.dupe(u8, buf[0..actual_len]); 168 | } 169 | return error.MalformedArchive; 170 | } 171 | 172 | pub fn format( 173 | self: ar_hdr, 174 | comptime unused_fmt_string: []const u8, 175 | options: std.fmt.FormatOptions, 176 | writer: anytype, 177 | ) !void { 178 | _ = unused_fmt_string; 179 | _ = options; 180 | try writer.print("ar_name: {s} ({x})\n", .{ 181 | std.fmt.fmtSliceEscapeLower(&self.ar_name), 182 | std.fmt.fmtSliceHexLower(&self.ar_name), 183 | }); 184 | try writer.print("ar_date: {s} ({x})\n", .{ 185 | std.fmt.fmtSliceEscapeLower(&self.ar_date), 186 | std.fmt.fmtSliceHexLower(&self.ar_date), 187 | }); 188 | try writer.print("ar_uid: {s} ({x})\n", .{ 189 | std.fmt.fmtSliceEscapeLower(&self.ar_uid), 190 | std.fmt.fmtSliceHexLower(&self.ar_uid), 191 | }); 192 | try writer.print("ar_gid: {s} ({x})\n", .{ 193 | std.fmt.fmtSliceEscapeLower(&self.ar_gid), 194 | std.fmt.fmtSliceHexLower(&self.ar_gid), 195 | }); 196 | try writer.print("ar_mode: {s} ({x})\n", .{ 197 | std.fmt.fmtSliceEscapeLower(&self.ar_mode), 198 | std.fmt.fmtSliceHexLower(&self.ar_mode), 199 | }); 200 | try writer.print("ar_size: {s} ({x})\n", .{ 201 | std.fmt.fmtSliceEscapeLower(&self.ar_size), 202 | std.fmt.fmtSliceHexLower(&self.ar_size), 203 | }); 204 | try writer.print("ar_fmag: {s} ({x})\n", .{ 205 | std.fmt.fmtSliceEscapeLower(&self.ar_fmag), 206 | std.fmt.fmtSliceHexLower(&self.ar_fmag), 207 | }); 208 | } 209 | }; 210 | 211 | const Symtab = struct { 212 | entries: std.ArrayListUnmanaged(Entry) = .{}, 213 | strtab: []const u8 = &[0]u8{}, 214 | format: enum { p32, p64 } = .p32, 215 | 216 | fn deinit(ar: *Symtab, gpa: Allocator) void { 217 | ar.entries.deinit(gpa); 218 | } 219 | 220 | fn ptrWidth(ar: Symtab) usize { 221 | return switch (ar.format) { 222 | .p32 => @as(usize, 4), 223 | .p64 => 8, 224 | }; 225 | } 226 | 227 | fn parse(ar: *Symtab, gpa: Allocator, data: []const u8) !void { 228 | var stream = std.io.fixedBufferStream(data); 229 | const reader = stream.reader(); 230 | 231 | const size = try ar.readInt(reader); 232 | const num = @divExact(size, ar.ptrWidth() * 2); 233 | try ar.entries.ensureTotalCapacityPrecise(gpa, num); 234 | 235 | for (0..num) |_| { 236 | const name = try ar.readInt(reader); 237 | const file = try ar.readInt(reader); 238 | ar.entries.appendAssumeCapacity(.{ .name = name, .file = file }); 239 | } 240 | 241 | const strtab_off = size + ar.ptrWidth(); 242 | const strtab_len = try ar.readInt(reader); 243 | ar.strtab = data[strtab_off + ar.ptrWidth() ..][0..strtab_len]; 244 | } 245 | 246 | fn readInt(ar: Symtab, reader: anytype) !u64 { 247 | return switch (ar.format) { 248 | .p32 => @as(u64, @intCast(try reader.readInt(u32, .little))), 249 | .p64 => try reader.readInt(u64, .little), 250 | }; 251 | } 252 | 253 | fn getString(ar: *const Symtab, off: u64) [:0]const u8 { 254 | assert(off < ar.strtab.len); 255 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(ar.strtab.ptr + off)), 0); 256 | } 257 | 258 | const Entry = struct { 259 | /// Symbol name offset 260 | name: u64, 261 | /// Offset of the object file. 262 | file: u64, 263 | 264 | fn getName(entry: Entry, ctx: *const Symtab) [:0]const u8 { 265 | return ctx.getString(entry.name); 266 | } 267 | }; 268 | }; 269 | 270 | const ARMAG = "!\n"; 271 | const SARMAG: u4 = 8; 272 | const ARFMAG = "`\n"; 273 | const SYMDEF = "__.SYMDEF"; 274 | const SYMDEF64 = "__.SYMDEF_64"; 275 | const SYMDEF_SORTED = "__.SYMDEF SORTED"; 276 | const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED"; 277 | 278 | const assert = std.debug.assert; 279 | const fat = @import("fat.zig"); 280 | const mem = std.mem; 281 | const std = @import("std"); 282 | 283 | const Allocator = mem.Allocator; 284 | const Archive = @This(); 285 | const Object = @import("Object.zig"); 286 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const fat = @import("fat.zig"); 2 | const std = @import("std"); 3 | 4 | const Archive = @import("Archive.zig"); 5 | const Object = @import("Object.zig"); 6 | 7 | var allocator = std.heap.GeneralPurposeAllocator(.{}){}; 8 | const gpa = allocator.allocator(); 9 | 10 | const usage = 11 | \\Usage: zacho [options] file 12 | \\ 13 | \\General options: 14 | \\-c, --code-signature Print the contents of code signature (if any) 15 | \\-d, --dyld-info Print the contents of dyld rebase and bind opcodes 16 | \\-e, --exports-trie Print export trie (if any) 17 | \\-h, --header Print the Mach-O header 18 | \\-i, --indirect-symbol-table Print the indirect symbol table 19 | \\-l, --load-commands Print load commands 20 | \\-r, --relocations Print relocation entries (if any) 21 | \\-s, --symbol-table Print the symbol table 22 | \\-u, --unwind-info Print the contents of (compact) unwind info section (if any) 23 | \\-v, --verbose Print more detailed info for each flag 24 | \\--archive-index Print archive index (if any) 25 | \\--string-table Print the string table 26 | \\--data-in-code Print data-in-code entries (if any) 27 | \\--hex-dump=[name] Dump section contents as bytes 28 | \\--string-dump=[name] Dump section contents as strings 29 | \\--verify-memory-layout Print virtual memory layout and verify there is no overlap 30 | \\--help Display this help and exit 31 | \\ 32 | ; 33 | 34 | fn fatal(comptime format: []const u8, args: anytype) noreturn { 35 | std.debug.lockStdErr(); 36 | defer std.debug.unlockStdErr(); 37 | ret: { 38 | const msg = std.fmt.allocPrint(gpa, format ++ "\n", args) catch break :ret; 39 | std.fs.File.stderr().writeAll(msg) catch {}; 40 | } 41 | std.process.exit(1); 42 | } 43 | 44 | pub fn main() !void { 45 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); 46 | defer arena_allocator.deinit(); 47 | const arena = arena_allocator.allocator(); 48 | 49 | const all_args = try std.process.argsAlloc(arena); 50 | const args = all_args[1..]; 51 | 52 | if (args.len == 0) fatal(usage, .{}); 53 | 54 | var filename: ?[]const u8 = null; 55 | var opts: Options = .{}; 56 | 57 | var print_matrix: PrintMatrix = .{}; 58 | var sect_name: ?[]const u8 = null; 59 | 60 | var it = ArgsIterator{ .args = args }; 61 | var p = ArgsParser{ .it = &it }; 62 | while (p.hasMore()) { 63 | if (std.mem.startsWith(u8, p.next_arg, "-")) blk: { 64 | var i: usize = 1; 65 | var tmp = PrintMatrix{}; 66 | while (i < p.next_arg.len) : (i += 1) switch (p.next_arg[i]) { 67 | '-' => break :blk, 68 | 'c' => tmp.code_signature = true, 69 | 'd' => tmp.dyld_info = true, 70 | 'e' => tmp.exports_trie = true, 71 | 'h' => tmp.header = true, 72 | 'i' => tmp.indirect_symbol_table = true, 73 | 'l' => tmp.load_commands = true, 74 | 'r' => tmp.relocations = true, 75 | 's' => tmp.symbol_table = true, 76 | 'u' => tmp.unwind_info = true, 77 | 'v' => opts.verbose = true, 78 | else => break :blk, 79 | }; 80 | print_matrix.add(tmp); 81 | continue; 82 | } 83 | 84 | if (p.flag2("help")) { 85 | fatal(usage, .{}); 86 | } else if (p.flag2("code-signature")) { 87 | print_matrix.code_signature = true; 88 | } else if (p.flag2("dyld-info")) { 89 | print_matrix.dyld_info = true; 90 | } else if (p.flag2("exports-trie")) { 91 | print_matrix.exports_trie = true; 92 | } else if (p.flag2("header")) { 93 | print_matrix.header = true; 94 | } else if (p.flag2("load-commands")) { 95 | print_matrix.load_commands = true; 96 | } else if (p.flag2("relocations")) { 97 | print_matrix.relocations = true; 98 | } else if (p.flag2("symbol-table")) { 99 | print_matrix.symbol_table = true; 100 | } else if (p.flag2("indirect-symbol-table")) { 101 | print_matrix.indirect_symbol_table = true; 102 | } else if (p.flag2("unwind-info")) { 103 | print_matrix.unwind_info = true; 104 | } else if (p.flag2("string-table")) { 105 | print_matrix.string_table = true; 106 | } else if (p.flag2("archive-index")) { 107 | print_matrix.archive_index = true; 108 | } else if (p.flag2("data-in-code")) { 109 | print_matrix.data_in_code = true; 110 | } else if (p.arg2("hex-dump")) |name| { 111 | print_matrix.dump_hex = true; 112 | sect_name = name; 113 | } else if (p.arg2("string-dump")) |name| { 114 | print_matrix.dump_string = true; 115 | sect_name = name; 116 | } else if (p.flag2("verify-memory-layout")) { 117 | print_matrix.verify_memory_layout = true; 118 | } else if (p.flag2("verbose")) { 119 | opts.verbose = true; 120 | } else { 121 | if (std.mem.startsWith(u8, p.next_arg, "-")) fatal("unknown option: {s}", .{p.next_arg}); 122 | if (filename != null) fatal("too many positional arguments specified", .{}); 123 | filename = p.next_arg; 124 | } 125 | } 126 | 127 | const fname = filename orelse fatal("no input file specified", .{}); 128 | const file = std.fs.cwd().openFile(fname, .{}) catch |err| switch (err) { 129 | error.FileNotFound => fatal("file not found: {s}", .{fname}), 130 | else => |e| fatal("unexpected error occurred: {s}", .{@errorName(e)}), 131 | }; 132 | defer file.close(); 133 | const data = try file.readToEndAlloc(arena, std.math.maxInt(u32)); 134 | 135 | var buffer: [1024]u8 = undefined; 136 | var fw = std.fs.File.stdout().writer(&buffer); 137 | var stdout = &fw.interface; 138 | defer stdout.flush() catch fatal("could not write to stdout", .{}); 139 | if (print_matrix.isUnset()) fatal("no option specified", .{}); 140 | 141 | if (try fat.isFatLibrary(fname)) { 142 | fatal("TODO: handle fat (universal) files: {s} is a fat file", .{fname}); 143 | } else if (try Archive.isArchive(fname, null)) { 144 | var archive = Archive{ .gpa = gpa, .data = data, .path = try gpa.dupe(u8, fname), .verbose = opts.verbose }; 145 | defer archive.deinit(); 146 | try archive.parse(); 147 | if (print_matrix.archive_index) { 148 | try archive.printSymbolTable(stdout); 149 | } 150 | print_matrix.archive_index = false; 151 | if (!print_matrix.isUnset()) for (archive.objects.values()) |object| { 152 | try stdout.print("File: {s}({s})\n", .{ archive.path, object.path }); 153 | try printObject(object, print_matrix, sect_name, stdout); 154 | }; 155 | } else { 156 | var object = Object{ .gpa = gpa, .data = data, .path = try gpa.dupe(u8, fname), .verbose = opts.verbose }; 157 | defer object.deinit(); 158 | object.parse() catch |err| switch (err) { 159 | error.InvalidMagic => fatal("not a MachO file - invalid magic bytes", .{}), 160 | else => |e| return e, 161 | }; 162 | try printObject(object, print_matrix, sect_name, stdout); 163 | } 164 | } 165 | 166 | fn printObject(object: Object, print_matrix: PrintMatrix, sect_name: ?[]const u8, stdout: anytype) !void { 167 | if (print_matrix.header) { 168 | try object.printHeader(stdout); 169 | } 170 | if (print_matrix.load_commands) { 171 | try object.printLoadCommands(stdout); 172 | } 173 | if (print_matrix.dyld_info) { 174 | try object.printDyldInfo(stdout); 175 | } 176 | if (print_matrix.exports_trie) { 177 | try object.printExportsTrie(stdout); 178 | } 179 | if (print_matrix.unwind_info) { 180 | try object.printUnwindInfo(stdout); 181 | } 182 | if (print_matrix.data_in_code) { 183 | try object.printDataInCode(stdout); 184 | } 185 | if (print_matrix.code_signature) { 186 | try object.printCodeSignature(stdout); 187 | } 188 | if (print_matrix.verify_memory_layout) { 189 | try object.verifyMemoryLayout(stdout); 190 | } 191 | if (print_matrix.relocations) { 192 | try object.printRelocations(stdout); 193 | } 194 | if (print_matrix.symbol_table) { 195 | try object.printSymbolTable(stdout); 196 | } 197 | if (print_matrix.string_table) { 198 | try object.printStringTable(stdout); 199 | } 200 | if (print_matrix.indirect_symbol_table) { 201 | try object.printIndirectSymbolTable(stdout); 202 | } 203 | if (print_matrix.dump_string or print_matrix.dump_hex) { 204 | const sect = getSectionByName(object, sect_name.?) catch |err| switch (err) { 205 | error.InvalidSectionName => fatal("invalid section name: '{s}'", .{sect_name.?}), 206 | error.SectionNotFound => fatal("section not found: '{s}'", .{sect_name.?}), 207 | }; 208 | if (print_matrix.dump_string) { 209 | try object.dumpString(sect, stdout); 210 | } 211 | if (print_matrix.dump_hex) { 212 | try object.dumpHex(sect, stdout); 213 | } 214 | } 215 | } 216 | 217 | fn getSectionByName(object: Object, name: []const u8) !std.macho.section_64 { 218 | const index = std.mem.indexOfScalar(u8, name, ',') orelse return error.InvalidSectionName; 219 | if (index + 1 >= name.len) return error.InvalidSectionName; 220 | const seg_name = name[0..index]; 221 | const sect_name = name[index + 1 ..]; 222 | const sect = object.getSectionByName(seg_name, sect_name) orelse return error.SectionNotFound; 223 | return sect; 224 | } 225 | 226 | pub const Options = struct { 227 | verbose: bool = false, 228 | }; 229 | 230 | const PrintMatrix = packed struct { 231 | header: bool = false, 232 | load_commands: bool = false, 233 | dyld_info: bool = false, 234 | exports_trie: bool = false, 235 | unwind_info: bool = false, 236 | code_signature: bool = false, 237 | verify_memory_layout: bool = false, 238 | relocations: bool = false, 239 | symbol_table: bool = false, 240 | indirect_symbol_table: bool = false, 241 | data_in_code: bool = false, 242 | string_table: bool = false, 243 | archive_index: bool = false, 244 | dump_string: bool = false, 245 | dump_hex: bool = false, 246 | 247 | const Int = blk: { 248 | const bits = @typeInfo(@This()).@"struct".fields.len; 249 | break :blk @Type(.{ 250 | .int = .{ 251 | .signedness = .unsigned, 252 | .bits = bits, 253 | }, 254 | }); 255 | }; 256 | 257 | fn enableAll() @This() { 258 | return @as(@This(), @bitCast(~@as(Int, 0))); 259 | } 260 | 261 | fn isUnset(pm: @This()) bool { 262 | return @as(Int, @bitCast(pm)) == 0; 263 | } 264 | 265 | fn add(pm: *@This(), other: @This()) void { 266 | pm.* = @as(@This(), @bitCast(@as(Int, @bitCast(pm.*)) | @as(Int, @bitCast(other)))); 267 | } 268 | }; 269 | 270 | const ArgsIterator = struct { 271 | args: []const []const u8, 272 | i: usize = 0, 273 | 274 | fn next(it: *@This()) ?[]const u8 { 275 | if (it.i >= it.args.len) { 276 | return null; 277 | } 278 | defer it.i += 1; 279 | return it.args[it.i]; 280 | } 281 | 282 | fn nextOrFatal(it: *@This()) []const u8 { 283 | return it.next() orelse fatal("expected parameter after {s}", .{it.args[it.i - 1]}); 284 | } 285 | 286 | pub fn peek(it: *@This()) ?[]const u8 { 287 | const arg = it.next(); 288 | defer if (it.i > 0) { 289 | it.i -= 1; 290 | }; 291 | return arg; 292 | } 293 | }; 294 | 295 | const ArgsParser = struct { 296 | next_arg: []const u8 = undefined, 297 | it: *ArgsIterator, 298 | 299 | pub fn hasMore(p: *ArgsParser) bool { 300 | p.next_arg = p.it.next() orelse return false; 301 | return true; 302 | } 303 | 304 | pub fn flag1(p: *ArgsParser, comptime pat: []const u8) bool { 305 | return p.flagPrefix(pat, "-"); 306 | } 307 | 308 | pub fn flag2(p: *ArgsParser, comptime pat: []const u8) bool { 309 | return p.flagPrefix(pat, "--"); 310 | } 311 | 312 | fn flagPrefix(p: *ArgsParser, comptime pat: []const u8, comptime prefix: []const u8) bool { 313 | if (std.mem.startsWith(u8, p.next_arg, prefix)) { 314 | const actual_arg = p.next_arg[prefix.len..]; 315 | if (std.mem.eql(u8, actual_arg, pat)) { 316 | return true; 317 | } 318 | } 319 | return false; 320 | } 321 | 322 | pub fn arg1(p: *ArgsParser, comptime pat: []const u8) ?[]const u8 { 323 | return p.argPrefix(pat, "-"); 324 | } 325 | 326 | pub fn arg2(p: *ArgsParser, comptime pat: []const u8) ?[]const u8 { 327 | return p.argPrefix(pat, "--"); 328 | } 329 | 330 | fn argPrefix(p: *ArgsParser, comptime pat: []const u8, comptime prefix: []const u8) ?[]const u8 { 331 | if (std.mem.startsWith(u8, p.next_arg, prefix)) { 332 | const actual_arg = p.next_arg[prefix.len..]; 333 | if (std.mem.eql(u8, actual_arg, pat)) { 334 | if (p.it.peek()) |next| { 335 | if (std.mem.startsWith(u8, next, "-")) return null; 336 | } 337 | return p.it.nextOrFatal(); 338 | } 339 | if (std.mem.startsWith(u8, actual_arg, pat)) { 340 | return actual_arg[pat.len..]; 341 | } 342 | } 343 | return null; 344 | } 345 | }; 346 | -------------------------------------------------------------------------------- /src/Object.zig: -------------------------------------------------------------------------------- 1 | gpa: Allocator, 2 | data: []const u8, 3 | path: []const u8, 4 | 5 | arch: Arch = undefined, 6 | header: macho.mach_header_64 = undefined, 7 | segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{}, 8 | 9 | symtab: []align(1) const macho.nlist_64 = &[0]macho.nlist_64{}, 10 | sorted_symtab: std.ArrayListUnmanaged(SymbolAtIndex) = .{}, 11 | strtab: []const u8 = &[0]u8{}, 12 | symtab_lc: ?macho.symtab_command = null, 13 | dysymtab_lc: ?macho.dysymtab_command = null, 14 | 15 | dyld_info_only_lc: ?macho.dyld_info_command = null, 16 | dyld_exports_trie_lc: ?macho.linkedit_data_command = null, 17 | 18 | data_in_code_lc: ?macho.linkedit_data_command = null, 19 | 20 | verbose: bool = false, 21 | 22 | pub fn deinit(self: *Object) void { 23 | self.gpa.free(self.path); 24 | self.segments.deinit(self.gpa); 25 | self.sorted_symtab.deinit(self.gpa); 26 | } 27 | 28 | pub fn parse(self: *Object) !void { 29 | var stream = std.io.fixedBufferStream(self.data); 30 | const reader = stream.reader(); 31 | const header = try reader.readStruct(macho.mach_header_64); 32 | 33 | if (header.magic != macho.MH_MAGIC_64) return error.InvalidMagic; 34 | 35 | self.header = header; 36 | self.arch = switch (header.cputype) { 37 | macho.CPU_TYPE_ARM64 => .aarch64, 38 | macho.CPU_TYPE_X86_64 => .x86_64, 39 | else => .unknown, 40 | }; 41 | 42 | var it = self.getLoadCommandsIterator(); 43 | while (it.next()) |lc| switch (lc.cmd()) { 44 | .SEGMENT_64 => { 45 | const cmd = lc.cast(macho.segment_command_64).?; 46 | try self.segments.append(self.gpa, cmd); 47 | }, 48 | .SYMTAB => self.symtab_lc = lc.cast(macho.symtab_command).?, 49 | .DYSYMTAB => self.dysymtab_lc = lc.cast(macho.dysymtab_command).?, 50 | .DYLD_INFO_ONLY => self.dyld_info_only_lc = lc.cast(macho.dyld_info_command).?, 51 | .DYLD_EXPORTS_TRIE => self.dyld_exports_trie_lc = lc.cast(macho.linkedit_data_command).?, 52 | .DATA_IN_CODE => self.data_in_code_lc = lc.cast(macho.linkedit_data_command).?, 53 | else => {}, 54 | }; 55 | 56 | if (self.symtab_lc) |lc| { 57 | self.symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.data.ptr + lc.symoff))[0..lc.nsyms]; 58 | self.strtab = self.data[lc.stroff..][0..lc.strsize]; 59 | 60 | // Filter defined symbols, sort by address and then by seniority 61 | try self.sorted_symtab.ensureTotalCapacityPrecise(self.gpa, self.symtab.len); 62 | for (self.symtab, 0..) |sym, idx| { 63 | if (sym.stab() or !sym.sect()) continue; 64 | self.sorted_symtab.appendAssumeCapacity(.{ .index = @intCast(idx), .size = 0 }); 65 | } 66 | 67 | mem.sort(SymbolAtIndex, self.sorted_symtab.items, self, SymbolAtIndex.lessThan); 68 | 69 | // Remove duplicates 70 | var i: usize = 0; 71 | while (i < self.sorted_symtab.items.len) : (i += 1) { 72 | const start = i; 73 | const curr = self.sorted_symtab.items[start].getSymbol(self); 74 | 75 | while (i < self.sorted_symtab.items.len and 76 | self.sorted_symtab.items[i].getSymbol(self).n_sect == curr.n_sect and 77 | self.sorted_symtab.items[i].getSymbol(self).n_value == curr.n_value) : (i += 1) 78 | {} 79 | 80 | for (1..i - start) |_| { 81 | _ = self.sorted_symtab.orderedRemove(start + 1); 82 | } 83 | i = start; 84 | } 85 | 86 | // Estimate symbol sizes 87 | i = 0; 88 | while (i < self.sorted_symtab.items.len) : (i += 1) { 89 | const curr = self.sorted_symtab.items[i].getSymbol(self); 90 | const sect = self.getSectionByIndex(curr.n_sect); 91 | const end = if (i + 1 < self.sorted_symtab.items.len) 92 | self.sorted_symtab.items[i + 1].getSymbol(self).n_value 93 | else 94 | sect.addr + sect.size; 95 | const size = end - curr.n_value; 96 | self.sorted_symtab.items[i].size = size; 97 | } 98 | } 99 | } 100 | 101 | pub fn dumpString(self: Object, sect: macho.section_64, writer: anytype) !void { 102 | try writer.print("String dump of section '{s},{s}':\n", .{ sect.segName(), sect.sectName() }); 103 | const data = self.data[sect.offset..][0..sect.size]; 104 | var start: usize = 0; 105 | while (start < data.len) { 106 | try writer.print(" [{x: >6}] ", .{start}); 107 | var end = start; 108 | while (end < data.len - 1 and data[end] != 0) : (end += 1) {} 109 | if (data[end] != 0) { 110 | @panic("string not null terminated"); 111 | } 112 | end += 1; 113 | const string = data[start..end]; 114 | try writer.print("{f}\n", .{std.ascii.hexEscape(string, .lower)}); 115 | start = end; 116 | } 117 | } 118 | 119 | pub fn dumpHex(self: Object, sect: macho.section_64, writer: anytype) !void { 120 | try writer.print("Hex dump of section '{s},{s}':\n", .{ sect.segName(), sect.sectName() }); 121 | const data = self.data[sect.offset..][0..sect.size]; 122 | try fmtBlobHex(data, writer); 123 | } 124 | 125 | // Format as 4 hex columns and 1 ascii column. 126 | // xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 127 | fn fmtBlobHex(blob: []const u8, writer: anytype) !void { 128 | const step = 16; 129 | var hex_buf: [step]u8 = undefined; 130 | var str_buf: [step]u8 = undefined; 131 | var i: usize = 0; 132 | while (i < blob.len) : (i += step) { 133 | try writer.print(" 0x{x:0>8} ", .{i}); 134 | const end = if (blob[i..].len >= step) step else blob[i..].len; 135 | @memset(&hex_buf, 0); 136 | @memcpy(hex_buf[0..end], blob[i .. i + end]); 137 | var j: usize = 0; 138 | while (j < step) : (j += 4) { 139 | try writer.print("{f:<8} ", .{std.ascii.hexEscape(hex_buf[j .. j + 4], .lower)}); 140 | } 141 | _ = try std.fmt.bufPrint(&str_buf, "{s}", .{&hex_buf}); 142 | std.mem.replaceScalar(u8, &str_buf, 0, '.'); 143 | try writer.print("{f}\n", .{std.ascii.hexEscape(&str_buf, .lower)}); 144 | } 145 | } 146 | 147 | pub fn printHeader(self: Object, writer: anytype) !void { 148 | const header = self.header; 149 | 150 | const cputype = switch (header.cputype) { 151 | macho.CPU_TYPE_ARM64 => "ARM64", 152 | macho.CPU_TYPE_X86_64 => "X86_64", 153 | else => "Unknown", 154 | }; 155 | 156 | const cpusubtype = switch (header.cpusubtype) { 157 | macho.CPU_SUBTYPE_ARM_ALL => "ARM_ALL", 158 | macho.CPU_SUBTYPE_X86_64_ALL => "X86_64_ALL", 159 | else => "Unknown", 160 | }; 161 | 162 | const filetype = switch (header.filetype) { 163 | macho.MH_OBJECT => "MH_OBJECT", 164 | macho.MH_EXECUTE => "MH_EXECUTE", 165 | macho.MH_FVMLIB => "MH_FVMLIB", 166 | macho.MH_CORE => "MH_CORE", 167 | macho.MH_PRELOAD => "MH_PRELOAD", 168 | macho.MH_DYLIB => "MH_DYLIB", 169 | macho.MH_DYLINKER => "MH_DYLINKER", 170 | macho.MH_BUNDLE => "MH_BUNDLE", 171 | macho.MH_DYLIB_STUB => "MH_DYLIB_STUB", 172 | macho.MH_DSYM => "MH_DSYM", 173 | macho.MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE", 174 | else => "Unknown", 175 | }; 176 | 177 | const fmt = struct { 178 | pub fn fmt(comptime specifier: []const u8) []const u8 { 179 | return " {s: <25} {" ++ specifier ++ ": >15}\n"; 180 | } 181 | }; 182 | 183 | try writer.print("Header\n", .{}); 184 | try writer.print(fmt.fmt("x"), .{ "Magic number:", header.magic }); 185 | try writer.print(fmt.fmt("s"), .{ "CPU type:", cputype }); 186 | try writer.print(fmt.fmt("s"), .{ "CPU sub-type:", cpusubtype }); 187 | try writer.print(fmt.fmt("s"), .{ "File type:", filetype }); 188 | try writer.print(fmt.fmt("x"), .{ "Number of load commands:", header.ncmds }); 189 | try writer.print(fmt.fmt("x"), .{ "Size of load commands:", header.sizeofcmds }); 190 | try writer.print(fmt.fmt("x"), .{ "Flags:", header.flags }); 191 | 192 | if (header.flags > 0) { 193 | const flags_fmt = " {s: <37}\n"; 194 | 195 | if (header.flags & macho.MH_NOUNDEFS != 0) try writer.print(flags_fmt, .{"MH_NOUNDEFS"}); 196 | if (header.flags & macho.MH_INCRLINK != 0) try writer.print(flags_fmt, .{"MH_INCRLINK"}); 197 | if (header.flags & macho.MH_DYLDLINK != 0) try writer.print(flags_fmt, .{"MH_DYLDLINK"}); 198 | if (header.flags & macho.MH_BINDATLOAD != 0) try writer.print(flags_fmt, .{"MH_BINDATLOAD"}); 199 | if (header.flags & macho.MH_PREBOUND != 0) try writer.print(flags_fmt, .{"MH_PREBOUND"}); 200 | if (header.flags & macho.MH_SPLIT_SEGS != 0) try writer.print(flags_fmt, .{"MH_SPLIT_SEGS"}); 201 | if (header.flags & macho.MH_LAZY_INIT != 0) try writer.print(flags_fmt, .{"MH_LAZY_INIT"}); 202 | if (header.flags & macho.MH_TWOLEVEL != 0) try writer.print(flags_fmt, .{"MH_TWOLEVEL"}); 203 | if (header.flags & macho.MH_FORCE_FLAT != 0) try writer.print(flags_fmt, .{"MH_FORCE_FLAT"}); 204 | if (header.flags & macho.MH_NOMULTIDEFS != 0) try writer.print(flags_fmt, .{"MH_NOMULTIDEFS"}); 205 | if (header.flags & macho.MH_NOFIXPREBINDING != 0) try writer.print(flags_fmt, .{"MH_NOFIXPREBINDING"}); 206 | if (header.flags & macho.MH_PREBINDABLE != 0) try writer.print(flags_fmt, .{"MH_PREBINDABLE"}); 207 | if (header.flags & macho.MH_ALLMODSBOUND != 0) try writer.print(flags_fmt, .{"MH_ALLMODSBOUND"}); 208 | if (header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) try writer.print(flags_fmt, .{"MH_SUBSECTIONS_VIA_SYMBOLS"}); 209 | if (header.flags & macho.MH_CANONICAL != 0) try writer.print(flags_fmt, .{"MH_CANONICAL"}); 210 | if (header.flags & macho.MH_WEAK_DEFINES != 0) try writer.print(flags_fmt, .{"MH_WEAK_DEFINES"}); 211 | if (header.flags & macho.MH_BINDS_TO_WEAK != 0) try writer.print(flags_fmt, .{"MH_BINDS_TO_WEAK"}); 212 | if (header.flags & macho.MH_ALLOW_STACK_EXECUTION != 0) try writer.print(flags_fmt, .{"MH_ALLOW_STACK_EXECUTION"}); 213 | if (header.flags & macho.MH_ROOT_SAFE != 0) try writer.print(flags_fmt, .{"MH_ROOT_SAFE"}); 214 | if (header.flags & macho.MH_SETUID_SAFE != 0) try writer.print(flags_fmt, .{"MH_SETUID_SAFE"}); 215 | if (header.flags & macho.MH_NO_REEXPORTED_DYLIBS != 0) try writer.print(flags_fmt, .{"MH_NO_REEXPORTED_DYLIBS"}); 216 | if (header.flags & macho.MH_PIE != 0) try writer.print(flags_fmt, .{"MH_PIE"}); 217 | if (header.flags & macho.MH_DEAD_STRIPPABLE_DYLIB != 0) try writer.print(flags_fmt, .{"MH_DEAD_STRIPPABLE_DYLIB"}); 218 | if (header.flags & macho.MH_HAS_TLV_DESCRIPTORS != 0) try writer.print(flags_fmt, .{"MH_HAS_TLV_DESCRIPTORS"}); 219 | if (header.flags & macho.MH_NO_HEAP_EXECUTION != 0) try writer.print(flags_fmt, .{"MH_NO_HEAP_EXECUTION"}); 220 | if (header.flags & macho.MH_APP_EXTENSION_SAFE != 0) try writer.print(flags_fmt, .{"MH_APP_EXTENSION_SAFE"}); 221 | if (header.flags & macho.MH_NLIST_OUTOFSYNC_WITH_DYLDINFO != 0) try writer.print(flags_fmt, .{"MH_NLIST_OUTOFSYNC_WITH_DYLDINFO"}); 222 | } 223 | 224 | try writer.print(fmt.fmt("x"), .{ "Reserved:", header.reserved }); 225 | try writer.writeByte('\n'); 226 | } 227 | 228 | pub fn printLoadCommands(self: Object, writer: anytype) !void { 229 | const fmt = struct { 230 | pub fn fmt(comptime specifier: []const u8) []const u8 { 231 | return " {s: <20} {" ++ specifier ++ ": >30}\n"; 232 | } 233 | }; 234 | 235 | var it = self.getLoadCommandsIterator(); 236 | while (it.next()) |lc| { 237 | try writer.print("LOAD COMMAND {d}:\n", .{it.index - 1}); 238 | try printGenericLC(fmt, lc, writer); 239 | 240 | switch (lc.cmd()) { 241 | .SEGMENT_64 => try printSegmentLC(fmt, lc, writer), 242 | .DYLD_INFO_ONLY => try printDyldInfoOnlyLC(fmt, lc, writer), 243 | .UUID => try printUuidLC(fmt, lc, writer), 244 | .RPATH => try printRpathLC(fmt, lc, writer), 245 | .ID_DYLIB, 246 | .LOAD_DYLIB, 247 | .LOAD_WEAK_DYLIB, 248 | .LOAD_UPWARD_DYLIB, 249 | .REEXPORT_DYLIB, 250 | => try printDylibLC(fmt, lc, writer), 251 | .DATA_IN_CODE, 252 | .CODE_SIGNATURE, 253 | .FUNCTION_STARTS, 254 | .LINKER_OPTIMIZATION_HINT, 255 | .DYLIB_CODE_SIGN_DRS, 256 | .SEGMENT_SPLIT_INFO, 257 | => try printLinkeditDataLC(fmt, lc, writer), 258 | .SYMTAB => try printSymtabLC(fmt, lc, writer), 259 | .DYSYMTAB => try printDysymtabLC(fmt, lc, writer), 260 | .BUILD_VERSION => try printBuildVersionLC(fmt, lc, writer), 261 | .VERSION_MIN_MACOSX, 262 | .VERSION_MIN_IPHONEOS, 263 | .VERSION_MIN_WATCHOS, 264 | .VERSION_MIN_TVOS, 265 | => try printVersionMinLC(fmt, lc, writer), 266 | .ID_DYLINKER, 267 | .LOAD_DYLINKER, 268 | .DYLD_ENVIRONMENT, 269 | => try printDylinkerLC(fmt, lc, writer), 270 | .MAIN => try printEntryPointLC(fmt, lc, writer), 271 | .SOURCE_VERSION => try printSourceVersionLC(fmt, lc, writer), 272 | else => {}, 273 | } 274 | 275 | try writer.writeByte('\n'); 276 | } 277 | } 278 | 279 | fn printGenericLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 280 | try writer.print(f.fmt("s"), .{ "Command:", @tagName(lc.cmd()) }); 281 | try writer.print(f.fmt("x"), .{ "Command size:", lc.cmdsize() }); 282 | } 283 | 284 | fn printSourceVersionLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 285 | const cmd = lc.cast(macho.source_version_command).?; 286 | try writer.print(f.fmt("d"), .{ "Version:", cmd.version }); 287 | } 288 | 289 | fn printDylinkerLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 290 | const cmd = lc.cast(macho.dylinker_command).?; 291 | const data = lc.data[cmd.name..]; 292 | const name = mem.sliceTo(data, 0); 293 | try writer.print(f.fmt("s"), .{ "Dynamic linker:", name }); 294 | } 295 | 296 | fn printEntryPointLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 297 | const cmd = lc.cast(macho.entry_point_command).?; 298 | try writer.print(f.fmt("x"), .{ "Entry offset:", cmd.entryoff }); 299 | try writer.print(f.fmt("d"), .{ "Initial stack size:", cmd.stacksize }); 300 | } 301 | 302 | fn printSymtabLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 303 | const cmd = lc.cast(macho.symtab_command).?; 304 | try writer.print(f.fmt("x"), .{ "Symtab offset:", cmd.symoff }); 305 | try writer.print(f.fmt("d"), .{ "Number of symbols:", cmd.nsyms }); 306 | try writer.print(f.fmt("x"), .{ "Strtab offset:", cmd.stroff }); 307 | try writer.print(f.fmt("d"), .{ "Strtab size:", cmd.strsize }); 308 | } 309 | 310 | fn printDysymtabLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 311 | const cmd = lc.cast(macho.dysymtab_command).?; 312 | try writer.print(f.fmt("d"), .{ "Local syms index:", cmd.ilocalsym }); 313 | try writer.print(f.fmt("d"), .{ "Number of locals:", cmd.nlocalsym }); 314 | try writer.print(f.fmt("d"), .{ "Export syms index:", cmd.iextdefsym }); 315 | try writer.print(f.fmt("d"), .{ "Number of exports:", cmd.nextdefsym }); 316 | try writer.print(f.fmt("d"), .{ "Undef syms index:", cmd.iundefsym }); 317 | try writer.print(f.fmt("d"), .{ "Number of undefs:", cmd.nundefsym }); 318 | try writer.print(f.fmt("x"), .{ "ToC offset:", cmd.tocoff }); 319 | try writer.print(f.fmt("d"), .{ "ToC entries:", cmd.ntoc }); 320 | try writer.print(f.fmt("x"), .{ "Module tab offset:", cmd.modtaboff }); 321 | try writer.print(f.fmt("d"), .{ "Module tab entries:", cmd.nmodtab }); 322 | try writer.print(f.fmt("x"), .{ "Ref symtab offset:", cmd.extrefsymoff }); 323 | try writer.print(f.fmt("d"), .{ "Ref symtab entries:", cmd.nextrefsyms }); 324 | try writer.print(f.fmt("x"), .{ "Indsymtab offset:", cmd.indirectsymoff }); 325 | try writer.print(f.fmt("d"), .{ "Indsymtab entries:", cmd.nindirectsyms }); 326 | try writer.print(f.fmt("x"), .{ "Extrel offset:", cmd.extreloff }); 327 | try writer.print(f.fmt("d"), .{ "Extrel entries:", cmd.nextrel }); 328 | try writer.print(f.fmt("x"), .{ "Locrel offset:", cmd.locreloff }); 329 | try writer.print(f.fmt("d"), .{ "Locrel entries:", cmd.nlocrel }); 330 | } 331 | 332 | fn printBuildVersionLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 333 | const cmd = lc.cast(macho.build_version_command).?; 334 | try writer.print(f.fmt("s"), .{ "Platform:", @tagName(cmd.platform) }); 335 | try writer.print(f.fmt("d"), .{ "Min OS version:", cmd.minos }); 336 | try writer.print(f.fmt("d"), .{ "SDK version:", cmd.sdk }); 337 | try writer.print(f.fmt("d"), .{ "Number of tools:", cmd.ntools }); 338 | 339 | const tools = lc.getBuildVersionTools(); 340 | for (tools) |tool| { 341 | switch (tool.tool) { 342 | .CLANG, .SWIFT, .LD, .LLD, .ZIG => try writer.print(f.fmt("s"), .{ "Tool:", @tagName(tool.tool) }), 343 | else => try writer.print(f.fmt("x"), .{ "Tool:", @intFromEnum(tool.tool) }), 344 | } 345 | try writer.print(f.fmt("d"), .{ "Version:", tool.version }); 346 | } 347 | } 348 | 349 | fn printVersionMinLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 350 | const cmd = lc.cast(macho.version_min_command).?; 351 | try writer.print(f.fmt("d"), .{ "Version:", cmd.version }); 352 | try writer.print(f.fmt("d"), .{ "SDK version:", cmd.sdk }); 353 | } 354 | 355 | fn printRpathLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 356 | const rpath = lc.getRpathPathName(); 357 | try writer.print(f.fmt("s"), .{ "Path:", rpath }); 358 | } 359 | 360 | fn printUuidLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 361 | const cmd = lc.cast(macho.uuid_command).?; 362 | var buffer: [64]u8 = undefined; 363 | const encoded = std.base64.standard.Encoder.encode(&buffer, &cmd.uuid); 364 | try writer.print(f.fmt("s"), .{ "UUID:", encoded }); 365 | } 366 | 367 | fn printDylibLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 368 | const name = lc.getDylibPathName(); 369 | const cmd = lc.cast(macho.dylib_command).?; 370 | try writer.print(f.fmt("s"), .{ "Name:", name }); 371 | try writer.print(f.fmt("d"), .{ "Timestamp:", cmd.dylib.timestamp }); 372 | try writer.print(f.fmt("d"), .{ "Current version:", cmd.dylib.current_version }); 373 | try writer.print(f.fmt("d"), .{ "Compat version:", cmd.dylib.compatibility_version }); 374 | } 375 | 376 | fn printDyldInfoOnlyLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 377 | const cmd = lc.cast(macho.dyld_info_command).?; 378 | try writer.print(f.fmt("x"), .{ "Rebase offset:", cmd.rebase_off }); 379 | try writer.print(f.fmt("x"), .{ "Rebase size:", cmd.rebase_size }); 380 | try writer.print(f.fmt("x"), .{ "Binding offset:", cmd.bind_off }); 381 | try writer.print(f.fmt("x"), .{ "Binding size:", cmd.bind_size }); 382 | try writer.print(f.fmt("x"), .{ "Weak binding offset:", cmd.weak_bind_off }); 383 | try writer.print(f.fmt("x"), .{ "Weak binding offset:", cmd.weak_bind_size }); 384 | try writer.print(f.fmt("x"), .{ "Lazy binding size:", cmd.lazy_bind_off }); 385 | try writer.print(f.fmt("x"), .{ "Lazy binding size:", cmd.lazy_bind_size }); 386 | try writer.print(f.fmt("x"), .{ "Export offset:", cmd.export_off }); 387 | try writer.print(f.fmt("x"), .{ "Export size:", cmd.export_size }); 388 | } 389 | 390 | fn printLinkeditDataLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 391 | const cmd = lc.cast(macho.linkedit_data_command).?; 392 | try writer.print(f.fmt("x"), .{ "Data offset:", cmd.dataoff }); 393 | try writer.print(f.fmt("x"), .{ "Data size:", cmd.datasize }); 394 | } 395 | 396 | fn printSegmentLC(f: anytype, lc: macho.LoadCommandIterator.LoadCommand, writer: anytype) !void { 397 | const seg = lc.cast(macho.segment_command_64).?; 398 | try writer.print(f.fmt("s"), .{ "Segment name:", seg.segName() }); 399 | try writer.print(f.fmt("x"), .{ "VM address:", seg.vmaddr }); 400 | try writer.print(f.fmt("x"), .{ "VM size:", seg.vmsize }); 401 | try writer.print(f.fmt("x"), .{ "File offset:", seg.fileoff }); 402 | try writer.print(f.fmt("x"), .{ "File size:", seg.filesize }); 403 | 404 | const prot_fmt = " {s: <37}\n"; 405 | try writer.print(f.fmt("x"), .{ "Max VM protection:", seg.maxprot }); 406 | try printProtectionFlags(prot_fmt, seg.maxprot, writer); 407 | 408 | try writer.print(f.fmt("x"), .{ "Init VM protection:", seg.initprot }); 409 | try printProtectionFlags(prot_fmt, seg.initprot, writer); 410 | 411 | try writer.print(f.fmt("x"), .{ "Number of sections:", seg.nsects }); 412 | try writer.print(f.fmt("x"), .{ "Flags:", seg.flags }); 413 | 414 | if (seg.nsects > 0) { 415 | const sect_fmt = struct { 416 | pub fn fmt(comptime specifier: []const u8) []const u8 { 417 | return " {s: <20} {" ++ specifier ++ ": >28}\n"; 418 | } 419 | }; 420 | try writer.writeByte('\n'); 421 | for (lc.getSections()) |sect| { 422 | try writer.print(" SECTION HEADER:\n", .{}); 423 | try printSectionHeader(sect_fmt, sect, writer); 424 | } 425 | } 426 | } 427 | 428 | fn printProtectionFlags(comptime f: []const u8, flags: macho.vm_prot_t, writer: anytype) !void { 429 | if (flags == macho.PROT.NONE) try writer.print(f, .{"VM_PROT_NONE"}); 430 | if (flags & macho.PROT.READ != 0) try writer.print(f, .{"VM_PROT_READ"}); 431 | if (flags & macho.PROT.WRITE != 0) try writer.print(f, .{"VM_PROT_WRITE"}); 432 | if (flags & macho.PROT.EXEC != 0) try writer.print(f, .{"VM_PROT_EXEC"}); 433 | if (flags & macho.PROT.COPY != 0) try writer.print(f, .{"VM_PROT_COPY"}); 434 | } 435 | 436 | fn printSectionHeader(f: anytype, sect: macho.section_64, writer: anytype) !void { 437 | try writer.print(f.fmt("s"), .{ "Section name:", sect.sectName() }); 438 | try writer.print(f.fmt("s"), .{ "Segment name:", sect.segName() }); 439 | try writer.print(f.fmt("x"), .{ "Address:", sect.addr }); 440 | try writer.print(f.fmt("x"), .{ "Size:", sect.size }); 441 | try writer.print(f.fmt("x"), .{ "Offset:", sect.offset }); 442 | try writer.print(f.fmt("x"), .{ "Alignment:", std.math.powi(u32, 2, sect.@"align") catch unreachable }); 443 | try writer.print(f.fmt("x"), .{ "Relocs offset:", sect.reloff }); 444 | try writer.print(f.fmt("x"), .{ "Number of relocs:", sect.nreloc }); 445 | try writer.print(f.fmt("x"), .{ "Flags:", sect.flags }); 446 | 447 | const flag_fmt = " {s: <35}\n"; 448 | switch (sect.type()) { 449 | macho.S_REGULAR => try writer.print(flag_fmt, .{"S_REGULAR"}), 450 | macho.S_ZEROFILL => try writer.print(flag_fmt, .{"S_ZEROFILL"}), 451 | macho.S_CSTRING_LITERALS => try writer.print(flag_fmt, .{"S_CSTRING_LITERALS"}), 452 | macho.S_4BYTE_LITERALS => try writer.print(flag_fmt, .{"S_4BYTE_LITERALS"}), 453 | macho.S_8BYTE_LITERALS => try writer.print(flag_fmt, .{"S_8BYTE_LITERALS"}), 454 | macho.S_LITERAL_POINTERS => try writer.print(flag_fmt, .{"S_LITERAL_POINTERS"}), 455 | macho.S_NON_LAZY_SYMBOL_POINTERS => try writer.print(flag_fmt, .{"S_NON_LAZY_SYMBOL_POINTERS"}), 456 | macho.S_LAZY_SYMBOL_POINTERS => try writer.print(flag_fmt, .{"S_LAZY_SYMBOL_POINTERS"}), 457 | macho.S_SYMBOL_STUBS => try writer.print(flag_fmt, .{"S_SYMBOL_STUBS"}), 458 | macho.S_MOD_INIT_FUNC_POINTERS => try writer.print(flag_fmt, .{"S_MOD_INIT_FUNC_POINTERS"}), 459 | macho.S_MOD_TERM_FUNC_POINTERS => try writer.print(flag_fmt, .{"S_MOD_TERM_FUNC_POINTERS"}), 460 | macho.S_COALESCED => try writer.print(flag_fmt, .{"S_COALESCED"}), 461 | macho.S_GB_ZEROFILL => try writer.print(flag_fmt, .{"S_GB_ZEROFILL"}), 462 | macho.S_INTERPOSING => try writer.print(flag_fmt, .{"S_INTERPOSING"}), 463 | macho.S_16BYTE_LITERALS => try writer.print(flag_fmt, .{"S_16BYTE_LITERALS"}), 464 | macho.S_DTRACE_DOF => try writer.print(flag_fmt, .{"S_DTRACE_DOF"}), 465 | macho.S_THREAD_LOCAL_REGULAR => try writer.print(flag_fmt, .{"S_THREAD_LOCAL_REGULAR"}), 466 | macho.S_THREAD_LOCAL_ZEROFILL => try writer.print(flag_fmt, .{"S_THREAD_LOCAL_ZEROFILl"}), 467 | macho.S_THREAD_LOCAL_VARIABLE_POINTERS => try writer.print(flag_fmt, .{"S_THREAD_LOCAL_VARIABLE_POINTERS"}), 468 | macho.S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => try writer.print(flag_fmt, .{"S_THREAD_LOCAL_INIT_FUNCTION_POINTERS"}), 469 | macho.S_INIT_FUNC_OFFSETS => try writer.print(flag_fmt, .{"S_INIT_FUNC_OFFSETS"}), 470 | else => {}, 471 | } 472 | const attrs = sect.attrs(); 473 | if (attrs > 0) { 474 | if (attrs & macho.S_ATTR_DEBUG != 0) try writer.print(flag_fmt, .{"S_ATTR_DEBUG"}); 475 | if (attrs & macho.S_ATTR_PURE_INSTRUCTIONS != 0) try writer.print(flag_fmt, .{"S_ATTR_PURE_INSTRUCTIONS"}); 476 | if (attrs & macho.S_ATTR_NO_TOC != 0) try writer.print(flag_fmt, .{"S_ATTR_NO_TOC"}); 477 | if (attrs & macho.S_ATTR_STRIP_STATIC_SYMS != 0) try writer.print(flag_fmt, .{"S_ATTR_STRIP_STATIC_SYMS"}); 478 | if (attrs & macho.S_ATTR_NO_DEAD_STRIP != 0) try writer.print(flag_fmt, .{"S_ATTR_NO_DEAD_STRIP"}); 479 | if (attrs & macho.S_ATTR_LIVE_SUPPORT != 0) try writer.print(flag_fmt, .{"S_ATTR_LIVE_SUPPORT"}); 480 | if (attrs & macho.S_ATTR_SELF_MODIFYING_CODE != 0) try writer.print(flag_fmt, .{"S_ATTR_SELF_MODIFYING_CODE"}); 481 | if (attrs & macho.S_ATTR_SOME_INSTRUCTIONS != 0) try writer.print(flag_fmt, .{"S_ATTR_SOME_INSTRUCTIONS"}); 482 | if (attrs & macho.S_ATTR_EXT_RELOC != 0) try writer.print(flag_fmt, .{"S_ATTR_EXT_RELOC"}); 483 | if (attrs & macho.S_ATTR_LOC_RELOC != 0) try writer.print(flag_fmt, .{"S_ATTR_LOC_RELOC"}); 484 | } 485 | 486 | if (sect.type() == macho.S_SYMBOL_STUBS) { 487 | try writer.print(f.fmt("x"), .{ "Indirect sym index:", sect.reserved1 }); 488 | try writer.print(f.fmt("x"), .{ "Size of stubs:", sect.reserved2 }); 489 | } else if (sect.type() == macho.S_NON_LAZY_SYMBOL_POINTERS) { 490 | try writer.print(f.fmt("x"), .{ "Indirect sym index:", sect.reserved1 }); 491 | try writer.print(f.fmt("x"), .{ "Reserved 2:", sect.reserved2 }); 492 | } else if (sect.type() == macho.S_LAZY_SYMBOL_POINTERS) { 493 | try writer.print(f.fmt("x"), .{ "Indirect sym index:", sect.reserved1 }); 494 | try writer.print(f.fmt("x"), .{ "Reserved 2:", sect.reserved2 }); 495 | } else { 496 | try writer.print(f.fmt("x"), .{ "Reserved 1:", sect.reserved1 }); 497 | try writer.print(f.fmt("x"), .{ "Reserved 2:", sect.reserved2 }); 498 | } 499 | try writer.print(f.fmt("x"), .{ "Reserved 3:", sect.reserved3 }); 500 | } 501 | 502 | pub fn printDyldInfo(self: Object, writer: anytype) !void { 503 | const lc = self.dyld_info_only_lc orelse { 504 | return writer.writeAll("LC_DYLD_INFO_ONLY load command not found\n"); 505 | }; 506 | 507 | const rebase_data = self.data[lc.rebase_off..][0..lc.rebase_size]; 508 | if (rebase_data.len > 0) { 509 | try writer.writeAll("REBASE INFO:\n"); 510 | try self.printRebaseInfo(rebase_data, writer); 511 | } 512 | 513 | const bind_data = self.data[lc.bind_off..][0..lc.bind_size]; 514 | if (bind_data.len > 0) { 515 | try writer.writeAll("\nBIND INFO:\n"); 516 | try self.printBindInfo(bind_data, writer); 517 | } 518 | 519 | const weak_bind_data = self.data[lc.weak_bind_off..][0..lc.weak_bind_size]; 520 | if (weak_bind_data.len > 0) { 521 | try writer.writeAll("\nWEAK BIND INFO:\n"); 522 | try self.printBindInfo(weak_bind_data, writer); 523 | } 524 | 525 | const lazy_bind_data = self.data[lc.lazy_bind_off..][0..lc.lazy_bind_size]; 526 | if (lazy_bind_data.len > 0) { 527 | try writer.writeAll("\nLAZY BIND INFO:\n"); 528 | try self.printBindInfo(lazy_bind_data, writer); 529 | } 530 | } 531 | 532 | fn printRebaseInfo(self: Object, data: []const u8, writer: anytype) !void { 533 | var rebases = std.ArrayList(u64).init(self.gpa); 534 | defer rebases.deinit(); 535 | try self.parseRebaseInfo(data, &rebases, writer); 536 | mem.sort(u64, rebases.items, {}, std.sort.asc(u64)); 537 | for (rebases.items) |addr| { 538 | try writer.print("0x{x}\n", .{addr}); 539 | } 540 | } 541 | 542 | fn parseRebaseInfo(self: Object, data: []const u8, rebases: *std.ArrayList(u64), writer: anytype) !void { 543 | var stream = std.io.fixedBufferStream(data); 544 | var creader = std.io.countingReader(stream.reader()); 545 | const reader = creader.reader(); 546 | 547 | var segments = std.ArrayList(macho.segment_command_64).init(self.gpa); 548 | defer segments.deinit(); 549 | var it = self.getLoadCommandsIterator(); 550 | while (it.next()) |lc| switch (lc.cmd()) { 551 | .SEGMENT_64 => try segments.append(lc.cast(macho.segment_command_64).?), 552 | else => {}, 553 | }; 554 | 555 | const fmt_value = " {x: <8} {s: <50} {s: >20} ({x})\n"; 556 | 557 | var seg_id: ?u8 = null; 558 | var offset: u64 = 0; 559 | while (true) { 560 | const byte = reader.readByte() catch break; 561 | const opc = byte & macho.REBASE_OPCODE_MASK; 562 | const imm = byte & macho.REBASE_IMMEDIATE_MASK; 563 | switch (opc) { 564 | macho.REBASE_OPCODE_DONE => { 565 | if (self.verbose) { 566 | try writer.print(fmt_value, .{ byte, "REBASE_OPCODE_DONE", "", imm }); 567 | } 568 | break; 569 | }, 570 | macho.REBASE_OPCODE_SET_TYPE_IMM => { 571 | if (self.verbose) { 572 | const tt = switch (imm) { 573 | macho.REBASE_TYPE_POINTER => "REBASE_TYPE_POINTER", 574 | macho.REBASE_TYPE_TEXT_ABSOLUTE32 => "REBASE_TYPE_TEXT_ABSOLUTE32", 575 | macho.REBASE_TYPE_TEXT_PCREL32 => "REBASE_TYPE_TEXT_PCREL32", 576 | else => "UNKNOWN", 577 | }; 578 | try writer.print(fmt_value, .{ byte, "REBASE_OPCODE_SET_TYPE_IMM", tt, imm }); 579 | } 580 | }, 581 | macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { 582 | seg_id = imm; 583 | const start = creader.bytes_read; 584 | offset = try std.leb.readUleb128(u64, reader); 585 | const end = creader.bytes_read; 586 | 587 | if (self.verbose) { 588 | try writer.print(fmt_value, .{ byte, "REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", "segment", seg_id.? }); 589 | if (end - start > 1) { 590 | try writer.print(" {x:0<2}..{x:0<2} {s: <50} {s: >20} ({x})\n", .{ 591 | data[start], data[end - 1], "", "offset", offset, 592 | }); 593 | } else { 594 | try writer.print(" {x:0<2} {s: <56} {s: >20} ({x})\n", .{ data[start], "", "offset", offset }); 595 | } 596 | } 597 | }, 598 | macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED => { 599 | offset += imm * @sizeOf(u64); 600 | if (self.verbose) { 601 | try writer.print(fmt_value, .{ byte, "REBASE_OPCODE_ADD_ADDR_IMM_SCALED", "scale", imm }); 602 | } 603 | }, 604 | macho.REBASE_OPCODE_ADD_ADDR_ULEB => { 605 | const addend = try std.leb.readUleb128(u64, reader); 606 | offset += addend; 607 | if (self.verbose) { 608 | try writer.print(fmt_value, .{ byte, "REBASE_OPCODE_ADD_ADDR_ULEB", "addr", addend }); 609 | } 610 | }, 611 | macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB => { 612 | const addend = try std.leb.readUleb128(u64, reader); 613 | 614 | if (self.verbose) { 615 | // TODO clean up formatting 616 | try writer.print(fmt_value, .{ byte, "REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", "addr", addend }); 617 | } 618 | 619 | const seg = segments.items[seg_id.?]; 620 | const addr = seg.vmaddr + offset; 621 | if (addr > seg.vmaddr + seg.vmsize) { 622 | std.log.err("malformed rebase: address {x} outside of segment {s} ({d})!", .{ 623 | addr, 624 | seg.segName(), 625 | seg_id.?, 626 | }); 627 | continue; 628 | } 629 | try rebases.append(addr); 630 | offset += addend + @sizeOf(u64); 631 | }, 632 | macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES, 633 | macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES, 634 | macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB, 635 | => { 636 | var ntimes: u64 = 1; 637 | var skip: u64 = 0; 638 | switch (opc) { 639 | macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES => { 640 | ntimes = imm; 641 | if (self.verbose) { 642 | try writer.print(fmt_value, .{ 643 | byte, 644 | "REBASE_OPCODE_DO_REBASE_IMM_TIMES", 645 | "count", 646 | ntimes, 647 | }); 648 | } 649 | }, 650 | macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES => { 651 | ntimes = try std.leb.readUleb128(u64, reader); 652 | if (self.verbose) { 653 | try writer.print(fmt_value, .{ 654 | byte, 655 | "REBASE_OPCODE_DO_REBASE_ULEB_TIMES", 656 | "count", 657 | ntimes, 658 | }); 659 | } 660 | }, 661 | macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB => { 662 | ntimes = try std.leb.readUleb128(u64, reader); 663 | const start = creader.bytes_read; 664 | skip = try std.leb.readUleb128(u64, reader); 665 | if (self.verbose) { 666 | try writer.print(fmt_value, .{ 667 | byte, 668 | "REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB", 669 | "count", 670 | ntimes, 671 | }); 672 | try writer.print(" {x:0<2} {s: <56} {s: >20} ({x})\n", .{ 673 | data[start], 674 | "", 675 | "skip", 676 | skip, 677 | }); 678 | } 679 | }, 680 | else => unreachable, 681 | } 682 | const seg = segments.items[seg_id.?]; 683 | const base_addr = seg.vmaddr; 684 | var count: usize = 0; 685 | while (count < ntimes) : (count += 1) { 686 | const addr = base_addr + offset; 687 | if (addr > seg.vmaddr + seg.vmsize) { 688 | std.log.err("malformed rebase: address {x} outside of segment {s} ({d})!", .{ 689 | addr, 690 | seg.segName(), 691 | seg_id.?, 692 | }); 693 | continue; 694 | } 695 | try rebases.append(addr); 696 | offset += skip + @sizeOf(u64); 697 | } 698 | }, 699 | else => { 700 | std.log.err("unknown opcode: {x}", .{opc}); 701 | break; 702 | }, 703 | } 704 | } 705 | } 706 | 707 | fn printBindInfo(self: Object, data: []const u8, writer: anytype) !void { 708 | var bindings = std.ArrayList(Binding).init(self.gpa); 709 | defer bindings.deinit(); 710 | try self.parseBindInfo(data, &bindings, writer); 711 | mem.sort(Binding, bindings.items, {}, Binding.lessThan); 712 | for (bindings.items) |binding| { 713 | try writer.print("0x{x} [addend: {d}]", .{ binding.address, binding.addend }); 714 | try writer.writeAll(" ("); 715 | switch (binding.tag) { 716 | .self => try writer.writeAll("self"), 717 | .exe => try writer.writeAll("main executable"), 718 | .flat => try writer.writeAll("flat lookup"), 719 | .ord => { 720 | const dylib_name = self.getDylibNameByIndex(binding.ordinal); 721 | try writer.print("{s}", .{dylib_name}); 722 | }, 723 | } 724 | try writer.print(") {s}\n", .{binding.name}); 725 | } 726 | } 727 | 728 | const Binding = struct { 729 | address: u64, 730 | addend: i64, 731 | name: []const u8, 732 | tag: Tag, 733 | ordinal: u16, 734 | 735 | fn lessThan(ctx: void, lhs: Binding, rhs: Binding) bool { 736 | _ = ctx; 737 | return lhs.address < rhs.address; 738 | } 739 | 740 | const Tag = enum { 741 | ord, 742 | self, 743 | exe, 744 | flat, 745 | }; 746 | }; 747 | 748 | fn parseBindInfo(self: Object, data: []const u8, bindings: *std.ArrayList(Binding), writer: anytype) !void { 749 | var stream = std.io.fixedBufferStream(data); 750 | var creader = std.io.countingReader(stream.reader()); 751 | const reader = creader.reader(); 752 | 753 | var segments = std.ArrayList(macho.segment_command_64).init(self.gpa); 754 | defer segments.deinit(); 755 | var it = self.getLoadCommandsIterator(); 756 | while (it.next()) |lc| switch (lc.cmd()) { 757 | .SEGMENT_64 => try segments.append(lc.cast(macho.segment_command_64).?), 758 | else => {}, 759 | }; 760 | 761 | const fmt_value = " {x:0<2} {s: <50} {s: >20} ({x})\n"; 762 | 763 | var seg_id: ?u8 = null; 764 | var tag: Binding.Tag = .self; 765 | var ordinal: u16 = 0; 766 | var offset: u64 = 0; 767 | var addend: i64 = 0; 768 | 769 | var name_buf = std.ArrayList(u8).init(self.gpa); 770 | defer name_buf.deinit(); 771 | 772 | while (true) { 773 | const byte = reader.readByte() catch break; 774 | const opc = byte & macho.BIND_OPCODE_MASK; 775 | const imm = byte & macho.BIND_IMMEDIATE_MASK; 776 | switch (opc) { 777 | macho.BIND_OPCODE_DONE => { 778 | if (self.verbose) { 779 | try writer.print(fmt_value, .{ byte, "BIND_OPCODE_DONE", "", imm }); 780 | } 781 | }, 782 | macho.BIND_OPCODE_SET_TYPE_IMM => { 783 | if (self.verbose) { 784 | const tt = switch (imm) { 785 | macho.BIND_TYPE_POINTER => "BIND_TYPE_POINTER", 786 | macho.BIND_TYPE_TEXT_ABSOLUTE32 => "BIND_TYPE_TEXT_ABSOLUTE32", 787 | macho.BIND_TYPE_TEXT_PCREL32 => "BIND_TYPE_TEXT_PCREL32", 788 | else => "UNKNOWN", 789 | }; 790 | try writer.print(fmt_value, .{ byte, "BIND_OPCODE_SET_TYPE_IMM", tt, imm }); 791 | } 792 | }, 793 | macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { 794 | tag = .ord; 795 | ordinal = imm; 796 | if (self.verbose) { 797 | try writer.print(fmt_value, .{ 798 | byte, 799 | "BIND_OPCODE_SET_DYLIB_ORDINAL_IMM", 800 | "", 801 | imm, 802 | }); 803 | 804 | const dylib_name = self.getDylibNameByIndex(imm); 805 | try writer.print(" '{s}'\n", .{dylib_name}); 806 | } 807 | }, 808 | macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM => { 809 | if (self.verbose) { 810 | try writer.print(fmt_value, .{ 811 | byte, 812 | "BIND_OPCODE_SET_DYLIB_SPECIAL_IMM", 813 | "", 814 | imm, 815 | }); 816 | } 817 | switch (imm) { 818 | 0 => tag = .self, 819 | 0xf => tag = .exe, 820 | 0xe => tag = .flat, 821 | else => unreachable, 822 | } 823 | }, 824 | macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { 825 | seg_id = imm; 826 | const start = creader.bytes_read; 827 | offset = try std.leb.readUleb128(u64, reader); 828 | const end = creader.bytes_read; 829 | 830 | if (self.verbose) { 831 | try writer.print(fmt_value, .{ 832 | byte, 833 | "BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", 834 | "segment", 835 | seg_id.?, 836 | }); 837 | if (end - start > 1) { 838 | try writer.print(" {x:0<2}..{x:0<2} {s: <50} {s: >20} ({x})\n", .{ 839 | data[start], data[end - 1], "", "offset", offset, 840 | }); 841 | } else { 842 | try writer.print(" {x:0<2} {s: <56} {s: >20} ({x})\n", .{ 843 | data[start], 844 | "", 845 | "offset", 846 | offset, 847 | }); 848 | } 849 | } 850 | }, 851 | macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { 852 | name_buf.clearRetainingCapacity(); 853 | try reader.readUntilDelimiterArrayList(&name_buf, 0, std.math.maxInt(u32)); 854 | try name_buf.append(0); 855 | if (self.verbose) { 856 | const name = name_buf.items; 857 | try writer.print(fmt_value, .{ 858 | byte, 859 | "BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM", 860 | "flags", 861 | imm, 862 | }); 863 | try writer.print(" {x:0<2}..{x:0<2} {s: <50} {s: >20} ({d})\n", .{ 864 | name[0], 865 | name[name.len - 1], 866 | "", 867 | "string", 868 | name.len, 869 | }); 870 | try writer.print(" '{s}'\n", .{name}); 871 | } 872 | }, 873 | macho.BIND_OPCODE_SET_ADDEND_SLEB => { 874 | addend = try std.leb.readIleb128(i64, reader); 875 | if (self.verbose) { 876 | try writer.print(fmt_value, .{ 877 | byte, 878 | "BIND_OPCODE_SET_ADDEND_SLEB", 879 | "addend", 880 | addend, 881 | }); 882 | } 883 | }, 884 | macho.BIND_OPCODE_ADD_ADDR_ULEB => { 885 | const x = try std.leb.readUleb128(u64, reader); 886 | if (self.verbose) { 887 | try writer.print(fmt_value, .{ byte, "BIND_OPCODE_ADD_ADDR_ULEB", "addr", x }); 888 | } 889 | offset = @intCast(@as(i64, @intCast(offset)) + @as(i64, @bitCast(x))); 890 | }, 891 | macho.BIND_OPCODE_DO_BIND, 892 | macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB, 893 | macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED, 894 | macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB, 895 | => { 896 | var add_addr: u64 = 0; 897 | var count: u64 = 1; 898 | var skip: u64 = 0; 899 | 900 | switch (opc) { 901 | macho.BIND_OPCODE_DO_BIND => { 902 | if (self.verbose) { 903 | try writer.print(fmt_value, .{ byte, "BIND_OPCODE_DO_BIND", "", imm }); 904 | } 905 | }, 906 | macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => { 907 | add_addr = try std.leb.readUleb128(u64, reader); 908 | if (self.verbose) { 909 | try writer.print(fmt_value, .{ 910 | byte, 911 | "BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", 912 | "addr", 913 | add_addr, 914 | }); 915 | } 916 | }, 917 | macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED => { 918 | add_addr = imm * @sizeOf(u64); 919 | if (self.verbose) { 920 | try writer.print(fmt_value, .{ 921 | byte, 922 | "BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", 923 | "scale", 924 | imm, 925 | }); 926 | } 927 | }, 928 | macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => { 929 | count = try std.leb.readUleb128(u64, reader); 930 | const start = creader.bytes_read; 931 | skip = try std.leb.readUleb128(u64, reader); 932 | if (self.verbose) { 933 | try writer.print(fmt_value, .{ 934 | byte, 935 | "BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", 936 | "count", 937 | count, 938 | }); 939 | try writer.print(" {x:0<2} {s: <56} {s: >20} ({x})\n", .{ 940 | data[start], 941 | "", 942 | "skip", 943 | skip, 944 | }); 945 | } 946 | }, 947 | else => unreachable, 948 | } 949 | 950 | const seg = segments.items[seg_id.?]; 951 | var i: u64 = 0; 952 | while (i < count) : (i += 1) { 953 | const addr: u64 = @intCast(@as(i64, @intCast(seg.vmaddr + offset))); 954 | if (addr > seg.vmaddr + seg.vmsize) { 955 | std.log.err("malformed rebase: address {x} outside of segment {s} ({d})!", .{ 956 | addr, 957 | seg.segName(), 958 | seg_id.?, 959 | }); 960 | continue; 961 | } 962 | try bindings.append(.{ 963 | .address = addr, 964 | .addend = addend, 965 | .tag = tag, 966 | .ordinal = ordinal, 967 | .name = try self.gpa.dupe(u8, name_buf.items), 968 | }); 969 | 970 | offset += skip + @sizeOf(u64) + add_addr; 971 | } 972 | }, 973 | else => { 974 | std.log.err("unknown opcode: {x}", .{opc}); 975 | break; 976 | }, 977 | } 978 | } 979 | } 980 | 981 | pub fn printExportsTrie(self: Object, writer: anytype) !void { 982 | const maybe_data = if (self.dyld_info_only_lc) |lc| 983 | self.data[lc.export_off..][0..lc.export_size] 984 | else if (self.dyld_exports_trie_lc) |lc| 985 | self.data[lc.dataoff..][0..lc.datasize] 986 | else 987 | null; 988 | const data = maybe_data orelse 989 | return writer.print("LC_DYLD_INFO_ONLY or LC_DYLD_EXPORTS_TRIE load command not found\n", .{}); 990 | 991 | var arena = std.heap.ArenaAllocator.init(self.gpa); 992 | defer arena.deinit(); 993 | 994 | var exports = std.ArrayList(Export).init(self.gpa); 995 | defer exports.deinit(); 996 | 997 | var it = TrieIterator{ .data = data }; 998 | try parseTrieNode(arena.allocator(), &it, "", &exports, self.verbose, writer); 999 | 1000 | mem.sort(Export, exports.items, {}, Export.lessThan); 1001 | 1002 | const seg = self.getSegmentByName("__TEXT").?; 1003 | 1004 | if (self.verbose) try writer.writeByte('\n'); 1005 | try writer.writeAll("Exports:\n"); 1006 | for (exports.items) |exp| { 1007 | switch (exp.tag) { 1008 | .@"export" => { 1009 | const info = exp.data.@"export"; 1010 | if (info.kind != .regular or info.weak) { 1011 | try writer.writeByte('['); 1012 | } 1013 | switch (info.kind) { 1014 | .regular => {}, 1015 | .absolute => try writer.writeAll("ABS, "), 1016 | .tlv => try writer.writeAll("THREAD_LOCAL, "), 1017 | } 1018 | if (info.weak) try writer.writeAll("WEAK"); 1019 | if (info.kind != .regular or info.weak) { 1020 | try writer.writeAll("] "); 1021 | } 1022 | try writer.print("0x{x} ", .{seg.vmaddr + info.vmoffset}); 1023 | }, 1024 | else => {}, // TODO 1025 | } 1026 | 1027 | try writer.print("{s}\n", .{exp.name}); 1028 | } 1029 | } 1030 | 1031 | const TrieIterator = struct { 1032 | data: []const u8, 1033 | pos: usize = 0, 1034 | 1035 | fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) { 1036 | return std.io.fixedBufferStream(it.data[it.pos..]); 1037 | } 1038 | 1039 | fn readUleb128(it: *TrieIterator) !u64 { 1040 | var stream = it.getStream(); 1041 | var creader = std.io.countingReader(stream.reader()); 1042 | const reader = creader.reader(); 1043 | const value = try std.leb.readUleb128(u64, reader); 1044 | it.pos += creader.bytes_read; 1045 | return value; 1046 | } 1047 | 1048 | fn readString(it: *TrieIterator) ![:0]const u8 { 1049 | var stream = it.getStream(); 1050 | const reader = stream.reader(); 1051 | 1052 | var count: usize = 0; 1053 | while (true) : (count += 1) { 1054 | const byte = try reader.readByte(); 1055 | if (byte == 0) break; 1056 | } 1057 | 1058 | const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0]; 1059 | it.pos += count + 1; 1060 | return str; 1061 | } 1062 | 1063 | fn readByte(it: *TrieIterator) !u8 { 1064 | var stream = it.getStream(); 1065 | const value = try stream.reader().readByte(); 1066 | it.pos += 1; 1067 | return value; 1068 | } 1069 | }; 1070 | 1071 | const Export = struct { 1072 | name: []const u8, 1073 | tag: enum { @"export", reexport, stub_resolver }, 1074 | data: union { 1075 | @"export": struct { 1076 | kind: enum { regular, absolute, tlv }, 1077 | weak: bool = false, 1078 | vmoffset: u64, 1079 | }, 1080 | reexport: u64, 1081 | stub_resolver: struct { 1082 | stub_offset: u64, 1083 | resolver_offset: u64, 1084 | }, 1085 | }, 1086 | 1087 | inline fn rankByTag(self: Export) u3 { 1088 | return switch (self.tag) { 1089 | .@"export" => 1, 1090 | .reexport => 2, 1091 | .stub_resolver => 3, 1092 | }; 1093 | } 1094 | 1095 | fn lessThan(ctx: void, lhs: Export, rhs: Export) bool { 1096 | _ = ctx; 1097 | if (lhs.rankByTag() == rhs.rankByTag()) { 1098 | return switch (lhs.tag) { 1099 | .@"export" => lhs.data.@"export".vmoffset < rhs.data.@"export".vmoffset, 1100 | .reexport => lhs.data.reexport < rhs.data.reexport, 1101 | .stub_resolver => lhs.data.stub_resolver.stub_offset < rhs.data.stub_resolver.stub_offset, 1102 | }; 1103 | } 1104 | return lhs.rankByTag() < rhs.rankByTag(); 1105 | } 1106 | }; 1107 | 1108 | fn parseTrieNode( 1109 | arena: Allocator, 1110 | it: *TrieIterator, 1111 | prefix: []const u8, 1112 | exports: *std.ArrayList(Export), 1113 | verbose: bool, 1114 | writer: anytype, 1115 | ) !void { 1116 | const start = it.pos; 1117 | const size = try it.readUleb128(); 1118 | if (verbose) try writer.print("0x{x:0>8} size: {d}\n", .{ start, size }); 1119 | if (size > 0) { 1120 | const flags = try it.readUleb128(); 1121 | if (verbose) try writer.print("{s: >12}flags: 0x{x}\n", .{ "", flags }); 1122 | switch (flags) { 1123 | macho.EXPORT_SYMBOL_FLAGS_REEXPORT => { 1124 | const ord = try it.readUleb128(); 1125 | const name = try arena.dupe(u8, try it.readString()); 1126 | if (verbose) { 1127 | try writer.print("{s: >12}ordinal: {d}\n", .{ "", ord }); 1128 | try writer.print("{s: >12}install_name: {s}\n", .{ "", name }); 1129 | } 1130 | try exports.append(.{ 1131 | .name = if (name.len > 0) name else prefix, 1132 | .tag = .reexport, 1133 | .data = .{ .reexport = ord }, 1134 | }); 1135 | }, 1136 | macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER => { 1137 | const stub_offset = try it.readUleb128(); 1138 | const resolver_offset = try it.readUleb128(); 1139 | if (verbose) { 1140 | try writer.print("{s: >12}stub_offset: 0x{x}\n", .{ "", stub_offset }); 1141 | try writer.print("{s: >12}resolver_offset: 0x{x}\n", .{ "", resolver_offset }); 1142 | } 1143 | try exports.append(.{ 1144 | .name = prefix, 1145 | .tag = .stub_resolver, 1146 | .data = .{ .stub_resolver = .{ 1147 | .stub_offset = stub_offset, 1148 | .resolver_offset = resolver_offset, 1149 | } }, 1150 | }); 1151 | }, 1152 | else => { 1153 | const vmoff = try it.readUleb128(); 1154 | if (verbose) try writer.print("{s: >12}vmoffset: 0x{x}\n", .{ "", vmoff }); 1155 | try exports.append(.{ 1156 | .name = prefix, 1157 | .tag = .@"export", 1158 | .data = .{ .@"export" = .{ 1159 | .kind = switch (flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK) { 1160 | macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR => .regular, 1161 | macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE => .absolute, 1162 | macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL => .tlv, 1163 | else => unreachable, 1164 | }, 1165 | .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0, 1166 | .vmoffset = vmoff, 1167 | } }, 1168 | }); 1169 | }, 1170 | } 1171 | } 1172 | const nedges = try it.readByte(); 1173 | if (verbose) try writer.print("{s: >12}nedges: {d}\n", .{ "", nedges }); 1174 | 1175 | var edges: [256]struct { off: u64, label: [:0]const u8 } = undefined; 1176 | 1177 | for (0..nedges) |i| { 1178 | const label = try it.readString(); 1179 | const off = try it.readUleb128(); 1180 | if (verbose) try writer.print("{s: >12}label: {s}\n", .{ "", label }); 1181 | if (verbose) try writer.print("{s: >12}next: 0x{x}\n", .{ "", off }); 1182 | edges[i] = .{ .off = off, .label = label }; 1183 | } 1184 | 1185 | for (edges[0..nedges]) |edge| { 1186 | const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, edge.label }); 1187 | const curr = it.pos; 1188 | it.pos = edge.off; 1189 | if (verbose) try writer.writeByte('\n'); 1190 | try parseTrieNode(arena, it, prefix_label, exports, verbose, writer); 1191 | it.pos = curr; 1192 | } 1193 | } 1194 | 1195 | const UnwindInfoTargetNameAndAddend = struct { 1196 | tag: enum { symbol, section }, 1197 | name: u32, 1198 | addend: u64, 1199 | 1200 | fn getName(self: UnwindInfoTargetNameAndAddend, object: *const Object) []const u8 { 1201 | switch (self.tag) { 1202 | .symbol => return object.getString(self.name), 1203 | .section => return object.getSectionByIndex(@intCast(self.name)).sectName(), 1204 | } 1205 | } 1206 | }; 1207 | 1208 | fn getUnwindInfoTargetNameAndAddend( 1209 | self: *const Object, 1210 | rel: macho.relocation_info, 1211 | code: u64, 1212 | ) UnwindInfoTargetNameAndAddend { 1213 | if (rel.r_extern == 1) { 1214 | const sym = self.symtab[rel.r_symbolnum]; 1215 | return .{ 1216 | .tag = .symbol, 1217 | .name = sym.n_strx, 1218 | .addend = code, 1219 | }; 1220 | } 1221 | if (self.findSymbolByAddress(code)) |sym| { 1222 | return .{ 1223 | .tag = .symbol, 1224 | .name = sym.n_strx, 1225 | .addend = code - sym.n_value, 1226 | }; 1227 | } else { 1228 | const sect = self.getSectionByIndex(@intCast(rel.r_symbolnum)); 1229 | return .{ 1230 | .tag = .section, 1231 | .name = rel.r_symbolnum, 1232 | .addend = code - sect.addr, 1233 | }; 1234 | } 1235 | } 1236 | 1237 | pub fn printUnwindInfo(self: *const Object, writer: anytype) !void { 1238 | const is_obj = self.header.filetype == macho.MH_OBJECT; 1239 | 1240 | if (is_obj) { 1241 | const sect = self.getSectionByName("__LD", "__compact_unwind") orelse { 1242 | try writer.writeAll("No __LD,__compact_unwind section found.\n"); 1243 | return; 1244 | }; 1245 | 1246 | const data = self.data[sect.offset..][0..sect.size]; 1247 | if (data.len % @sizeOf(macho.compact_unwind_entry) != 0) { 1248 | try writer.print("Size of __LD,__compact_unwind section not integral of compact unwind info entry: {d} % {d} != 0", .{ 1249 | data.len, @sizeOf(macho.compact_unwind_entry), 1250 | }); 1251 | return error.MalformedCompactUnwindSection; 1252 | } 1253 | 1254 | const num_entries = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); 1255 | const entries = @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data))[0..num_entries]; 1256 | const relocs = relocs: { 1257 | const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(self.data.ptr + sect.reloff))[0..sect.nreloc]; 1258 | const out = try self.gpa.alloc(macho.relocation_info, relocs.len); 1259 | @memcpy(out, relocs); 1260 | break :relocs out; 1261 | }; 1262 | defer self.gpa.free(relocs); 1263 | 1264 | const sortFn = struct { 1265 | fn sortFn(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { 1266 | _ = ctx; 1267 | return lhs.r_address > rhs.r_address; 1268 | } 1269 | }.sortFn; 1270 | mem.sort(macho.relocation_info, relocs, {}, sortFn); 1271 | 1272 | try writer.writeAll("Contents of __LD,__compact_unwind section:\n"); 1273 | 1274 | for (entries, 0..) |entry, i| { 1275 | const base_offset = i * @sizeOf(macho.compact_unwind_entry); 1276 | const entry_relocs = filterRelocsByAddress(relocs, base_offset, @sizeOf(macho.compact_unwind_entry)); 1277 | const func = blk: { 1278 | const rel = filterRelocsByAddress(entry_relocs, base_offset, 8); 1279 | assert(rel.len == 1); 1280 | break :blk self.getUnwindInfoTargetNameAndAddend(rel[0], entry.rangeStart); 1281 | }; 1282 | const personality = blk: { 1283 | const rel = filterRelocsByAddress(entry_relocs, base_offset + 16, 8); 1284 | if (rel.len == 0) break :blk null; 1285 | assert(rel.len == 1); 1286 | break :blk self.getUnwindInfoTargetNameAndAddend(rel[0], entry.personalityFunction); 1287 | }; 1288 | const lsda = blk: { 1289 | const rel = filterRelocsByAddress(entry_relocs, base_offset + 24, 8); 1290 | if (rel.len == 0) break :blk null; 1291 | assert(rel.len == 1); 1292 | break :blk self.getUnwindInfoTargetNameAndAddend(rel[0], entry.lsda); 1293 | }; 1294 | 1295 | try writer.print(" Entry at offset 0x{x}:\n", .{i * @sizeOf(macho.compact_unwind_entry)}); 1296 | try writer.print(" {s: <22} 0x{x}", .{ "start:", entry.rangeStart }); 1297 | if (func.addend > 0) { 1298 | try writer.print(" + {x}", .{func.addend}); 1299 | } 1300 | try writer.print(" {s}\n", .{func.getName(self)}); 1301 | try writer.print(" {s: <22} 0x{x}\n", .{ "length:", entry.rangeLength }); 1302 | 1303 | if (!self.verbose) { 1304 | try writer.print(" {s: <22} 0x{x:0>8}\n", .{ "compact encoding:", entry.compactUnwindEncoding }); 1305 | } 1306 | 1307 | if (personality) |x| { 1308 | try writer.print(" {s: <22} 0x{x}", .{ "personality function:", entry.personalityFunction }); 1309 | if (x.addend > 0) { 1310 | try writer.print(" + {x}", .{x.addend}); 1311 | } 1312 | try writer.print(" {s}\n", .{x.getName(self)}); 1313 | } 1314 | 1315 | if (lsda) |x| { 1316 | try writer.print(" {s: <22} 0x{x}", .{ "LSDA:", entry.lsda }); 1317 | if (x.addend > 0) { 1318 | try writer.print(" + {x}", .{x.addend}); 1319 | } 1320 | try writer.print(" {s}\n", .{x.getName(self)}); 1321 | } 1322 | 1323 | if (self.verbose) { 1324 | try writer.print(" {s: <22}\n", .{"compact encoding:"}); 1325 | 1326 | switch (self.arch) { 1327 | .aarch64 => { 1328 | const enc = try UnwindEncodingArm64.fromU32(entry.compactUnwindEncoding); 1329 | try formatCompactUnwindEncodingArm64(enc, writer, .{ 1330 | .prefix = 12, 1331 | }); 1332 | }, 1333 | .x86_64 => { 1334 | const enc = try UnwindEncodingX86_64.fromU32(entry.compactUnwindEncoding); 1335 | try formatCompactUnwindEncodingX86_64(enc, writer, .{ 1336 | .prefix = 12, 1337 | }); 1338 | }, 1339 | else => unreachable, 1340 | } 1341 | } 1342 | } 1343 | } else { 1344 | const sect = self.getSectionByName("__TEXT", "__unwind_info") orelse { 1345 | try writer.writeAll("No __TEXT,__unwind_info section found.\n"); 1346 | return; 1347 | }; 1348 | 1349 | const data = self.data[sect.offset..][0..sect.size]; 1350 | const header = @as(*align(1) const macho.unwind_info_section_header, @ptrCast(data.ptr)).*; 1351 | 1352 | try writer.writeAll("Contents of __TEXT,__unwind_info section:\n"); 1353 | try writer.print(" {s: <25} {d}\n", .{ "Version:", header.version }); 1354 | try writer.print(" {s: <25} 0x{x}\n", .{ 1355 | "Common encodings offset:", 1356 | header.commonEncodingsArraySectionOffset, 1357 | }); 1358 | try writer.print(" {s: <25} {d}\n", .{ 1359 | "Common encodings count:", 1360 | header.commonEncodingsArrayCount, 1361 | }); 1362 | try writer.print(" {s: <25} 0x{x}\n", .{ 1363 | "Personalities offset:", 1364 | header.personalityArraySectionOffset, 1365 | }); 1366 | try writer.print(" {s: <25} {d}\n", .{ 1367 | "Personalities count:", 1368 | header.personalityArrayCount, 1369 | }); 1370 | try writer.print(" {s: <25} 0x{x}\n", .{ 1371 | "Indexes offset:", 1372 | header.indexSectionOffset, 1373 | }); 1374 | try writer.print(" {s: <25} {d}\n", .{ 1375 | "Indexes count:", 1376 | header.indexCount, 1377 | }); 1378 | 1379 | const common_encodings = @as( 1380 | [*]align(1) const macho.compact_unwind_encoding_t, 1381 | @ptrCast(data.ptr + header.commonEncodingsArraySectionOffset), 1382 | )[0..header.commonEncodingsArrayCount]; 1383 | 1384 | try writer.print("\n Common encodings: (count = {d})\n", .{common_encodings.len}); 1385 | 1386 | for (common_encodings, 0..) |raw, i| { 1387 | if (self.verbose) blk: { 1388 | try writer.print(" encoding[{d}]\n", .{i}); 1389 | switch (self.arch) { 1390 | .aarch64 => { 1391 | const enc = UnwindEncodingArm64.fromU32(raw) catch |err| switch (err) { 1392 | error.UnknownEncoding => if (raw == 0) { 1393 | try writer.writeAll(" none\n"); 1394 | break :blk; 1395 | } else return err, 1396 | }; 1397 | try formatCompactUnwindEncodingArm64(enc, writer, .{ 1398 | .prefix = 6, 1399 | }); 1400 | }, 1401 | .x86_64 => { 1402 | const enc = UnwindEncodingX86_64.fromU32(raw) catch |err| switch (err) { 1403 | error.UnknownEncoding => if (raw == 0) { 1404 | try writer.writeAll(" none\n"); 1405 | break :blk; 1406 | } else return err, 1407 | }; 1408 | try formatCompactUnwindEncodingX86_64(enc, writer, .{ 1409 | .prefix = 6, 1410 | }); 1411 | }, 1412 | else => unreachable, 1413 | } 1414 | } else { 1415 | try writer.print(" encoding[{d}]: 0x{x:0>8}\n", .{ i, raw }); 1416 | } 1417 | } 1418 | 1419 | const personalities = @as( 1420 | [*]align(1) const u32, 1421 | @ptrCast(data.ptr + header.personalityArraySectionOffset), 1422 | )[0..header.personalityArrayCount]; 1423 | 1424 | try writer.print("\n Personality functions: (count = {d})\n", .{personalities.len}); 1425 | 1426 | for (personalities, 0..) |personality, i| { 1427 | if (self.verbose) { 1428 | const seg = self.getSegmentByName("__TEXT").?; 1429 | const addr = seg.vmaddr + personality; 1430 | const target_sect = self.getSectionByAddress(addr).?; 1431 | assert(target_sect.flags == macho.S_NON_LAZY_SYMBOL_POINTERS); 1432 | const ptr = self.getGotPointerAtIndex(@divExact(addr - target_sect.addr, 8)); 1433 | if (self.findSymbolByAddress(ptr)) |sym| { 1434 | const name = self.getString(sym.n_strx); 1435 | try writer.print(" personality[{d}]: 0x{x} -> 0x{x} {s}\n", .{ i + 1, addr, ptr, name }); 1436 | } else { 1437 | // TODO we need to parse DYSYMTAB and figure out which import we are referring to 1438 | try writer.print(" personality[{d}]: 0x{x} -> 0x{x} {s}\n", .{ 1439 | i + 1, 1440 | addr, 1441 | ptr, 1442 | "(undefined)", 1443 | }); 1444 | } 1445 | } else { 1446 | try writer.print(" personality[{d}]: 0x{x:0>8}\n", .{ i + 1, personality }); 1447 | } 1448 | } 1449 | 1450 | const indexes = @as( 1451 | [*]align(1) const macho.unwind_info_section_header_index_entry, 1452 | @ptrCast(data.ptr + header.indexSectionOffset), 1453 | )[0..header.indexCount]; 1454 | 1455 | try writer.print("\n Top level indices: (count = {d})\n", .{indexes.len}); 1456 | for (indexes, 0..) |entry, i| { 1457 | if (self.verbose) { 1458 | const seg = self.getSegmentByName("__TEXT").?; 1459 | const name = if (self.findSymbolByAddress(seg.vmaddr + entry.functionOffset)) |sym| 1460 | self.getString(sym.n_strx) 1461 | else 1462 | "unknown"; 1463 | 1464 | try writer.print(" [{d}] {s}\n", .{ i, name }); 1465 | try writer.print(" {s: <20} 0x{x:0>16}\n", .{ 1466 | "Function address:", 1467 | seg.vmaddr + entry.functionOffset, 1468 | }); 1469 | try writer.print(" {s: <20} 0x{x:0>8}\n", .{ 1470 | "Second level pages:", 1471 | entry.secondLevelPagesSectionOffset, 1472 | }); 1473 | try writer.print(" {s: <20} 0x{x:0>8}\n", .{ 1474 | "LSDA index array:", 1475 | entry.lsdaIndexArraySectionOffset, 1476 | }); 1477 | } else { 1478 | try writer.print(" [{d}]: function offset=0x{x:0>8}, 2nd level page offset=0x{x:0>8}, LSDA offset=0x{x:0>8}\n", .{ 1479 | i, 1480 | entry.functionOffset, 1481 | entry.secondLevelPagesSectionOffset, 1482 | entry.lsdaIndexArraySectionOffset, 1483 | }); 1484 | } 1485 | } 1486 | 1487 | if (indexes.len == 0) return; 1488 | 1489 | const num_lsdas = @divExact( 1490 | indexes[indexes.len - 1].lsdaIndexArraySectionOffset - 1491 | indexes[0].lsdaIndexArraySectionOffset, 1492 | @sizeOf(macho.unwind_info_section_header_lsda_index_entry), 1493 | ); 1494 | const lsdas = @as( 1495 | [*]align(1) const macho.unwind_info_section_header_lsda_index_entry, 1496 | @ptrCast(data.ptr + indexes[0].lsdaIndexArraySectionOffset), 1497 | )[0..num_lsdas]; 1498 | 1499 | try writer.writeAll("\n LSDA descriptors:\n"); 1500 | for (lsdas, 0..) |lsda, i| { 1501 | if (self.verbose) { 1502 | const seg = self.getSegmentByName("__TEXT").?; 1503 | const func_name = if (self.findSymbolByAddress(seg.vmaddr + lsda.functionOffset)) |sym| 1504 | self.getString(sym.n_strx) 1505 | else 1506 | "unknown"; 1507 | const lsda_name = if (self.findSymbolByAddress(seg.vmaddr + lsda.lsdaOffset)) |sym| 1508 | self.getString(sym.n_strx) 1509 | else 1510 | "unknown"; 1511 | 1512 | try writer.print(" [{d}] {s}\n", .{ i, func_name }); 1513 | try writer.print(" {s: <20} 0x{x:0>16}\n", .{ 1514 | "Function address:", 1515 | seg.vmaddr + lsda.functionOffset, 1516 | }); 1517 | try writer.print(" {s: <20} 0x{x:0>16} {s}\n", .{ 1518 | "LSDA address:", 1519 | seg.vmaddr + lsda.lsdaOffset, 1520 | lsda_name, 1521 | }); 1522 | } else { 1523 | try writer.print(" [{d}]: function offset=0x{x:0>8}, LSDA offset=0x{x:0>8}\n", .{ 1524 | i, 1525 | lsda.functionOffset, 1526 | lsda.lsdaOffset, 1527 | }); 1528 | } 1529 | } 1530 | 1531 | try writer.writeAll("\n Second level indices:\n"); 1532 | for (indexes, 0..) |entry, i| { 1533 | const start_offset = entry.secondLevelPagesSectionOffset; 1534 | if (start_offset == 0) break; 1535 | 1536 | if (self.verbose) { 1537 | const seg = self.getSegmentByName("__TEXT").?; 1538 | const func_name = if (self.findSymbolByAddress(seg.vmaddr + entry.functionOffset)) |func_sym| 1539 | self.getString(func_sym.n_strx) 1540 | else 1541 | "unknown"; 1542 | try writer.print(" Second level index[{d}]: {s}\n", .{ i, func_name }); 1543 | try writer.print(" Offset in section: 0x{x:0>8}\n", .{entry.secondLevelPagesSectionOffset}); 1544 | try writer.print(" Function address: 0x{x:0>16}\n", .{seg.vmaddr + entry.functionOffset}); 1545 | } else { 1546 | try writer.print(" Second level index[{d}]: offset in section=0x{x:0>8}, base function offset=0x{x:0>8}\n", .{ 1547 | i, 1548 | entry.secondLevelPagesSectionOffset, 1549 | entry.functionOffset, 1550 | }); 1551 | } 1552 | 1553 | const kind = @as( 1554 | macho.UNWIND_SECOND_LEVEL, 1555 | @enumFromInt(@as(*align(1) const u32, @ptrCast(data.ptr + start_offset)).*), 1556 | ); 1557 | 1558 | switch (kind) { 1559 | .REGULAR => { 1560 | const page_header = @as( 1561 | *align(1) const macho.unwind_info_regular_second_level_page_header, 1562 | @ptrCast(data.ptr + start_offset), 1563 | ).*; 1564 | 1565 | var pos = start_offset + page_header.entryPageOffset; 1566 | var count: usize = 0; 1567 | while (count < page_header.entryCount) : (count += 1) { 1568 | const inner = @as( 1569 | *align(1) const macho.unwind_info_regular_second_level_entry, 1570 | @ptrCast(data.ptr + pos), 1571 | ).*; 1572 | 1573 | if (self.verbose) blk: { 1574 | const seg = self.getSegmentByName("__TEXT").?; 1575 | const func_name = if (self.findSymbolByAddress(seg.vmaddr + inner.functionOffset)) |sym| 1576 | self.getString(sym.n_strx) 1577 | else 1578 | "unknown"; 1579 | 1580 | try writer.print(" [{d}] {s}\n", .{ 1581 | count, 1582 | func_name, 1583 | }); 1584 | try writer.print(" Function address: 0x{x:0>16}\n", .{ 1585 | seg.vmaddr + inner.functionOffset, 1586 | }); 1587 | try writer.writeAll(" Encoding:\n"); 1588 | 1589 | switch (self.arch) { 1590 | .aarch64 => { 1591 | const enc = UnwindEncodingArm64.fromU32(inner.encoding) catch |err| switch (err) { 1592 | error.UnknownEncoding => if (inner.encoding == 0) { 1593 | try writer.writeAll(" none\n"); 1594 | break :blk; 1595 | } else return err, 1596 | }; 1597 | try formatCompactUnwindEncodingArm64(enc, writer, .{ 1598 | .prefix = 10, 1599 | }); 1600 | }, 1601 | .x86_64 => { 1602 | const enc = UnwindEncodingX86_64.fromU32(inner.encoding) catch |err| switch (err) { 1603 | error.UnknownEncoding => if (inner.encoding == 0) { 1604 | try writer.writeAll(" none\n"); 1605 | break :blk; 1606 | } else return err, 1607 | }; 1608 | try formatCompactUnwindEncodingX86_64(enc, writer, .{ 1609 | .prefix = 10, 1610 | }); 1611 | }, 1612 | else => unreachable, 1613 | } 1614 | } else { 1615 | try writer.print(" [{d}]: function offset=0x{x:0>8}, encoding=0x{x:0>8}\n", .{ 1616 | count, 1617 | inner.functionOffset, 1618 | inner.encoding, 1619 | }); 1620 | } 1621 | 1622 | pos += @sizeOf(macho.unwind_info_regular_second_level_entry); 1623 | } 1624 | }, 1625 | .COMPRESSED => { 1626 | const page_header = @as( 1627 | *align(1) const macho.unwind_info_compressed_second_level_page_header, 1628 | @ptrCast(data.ptr + start_offset), 1629 | ).*; 1630 | 1631 | var page_encodings = std.ArrayList(macho.compact_unwind_encoding_t).init(self.gpa); 1632 | defer page_encodings.deinit(); 1633 | 1634 | if (page_header.encodingsCount > 0) { 1635 | try page_encodings.ensureTotalCapacityPrecise(page_header.encodingsCount); 1636 | try writer.print(" Page encodings: (count = {d})\n", .{ 1637 | page_header.encodingsCount, 1638 | }); 1639 | 1640 | var pos = start_offset + page_header.encodingsPageOffset; 1641 | var count: usize = 0; 1642 | while (count < page_header.encodingsCount) : (count += 1) { 1643 | const raw = @as(*align(1) const macho.compact_unwind_encoding_t, @ptrCast(data.ptr + pos)).*; 1644 | 1645 | if (self.verbose) blk: { 1646 | try writer.print(" encoding[{d}]\n", .{count + common_encodings.len}); 1647 | switch (self.arch) { 1648 | .aarch64 => { 1649 | const enc = UnwindEncodingArm64.fromU32(raw) catch |err| switch (err) { 1650 | error.UnknownEncoding => if (raw == 0) { 1651 | try writer.writeAll(" none\n"); 1652 | break :blk; 1653 | } else return err, 1654 | }; 1655 | try formatCompactUnwindEncodingArm64(enc, writer, .{ 1656 | .prefix = 10, 1657 | }); 1658 | }, 1659 | .x86_64 => { 1660 | const enc = UnwindEncodingX86_64.fromU32(raw) catch |err| switch (err) { 1661 | error.UnknownEncoding => if (raw == 0) { 1662 | try writer.writeAll(" none\n"); 1663 | break :blk; 1664 | } else return err, 1665 | }; 1666 | try formatCompactUnwindEncodingX86_64(enc, writer, .{ 1667 | .prefix = 10, 1668 | }); 1669 | }, 1670 | else => unreachable, 1671 | } 1672 | } else { 1673 | try writer.print(" encoding[{d}]: 0x{x:0>8}\n", .{ 1674 | count + common_encodings.len, 1675 | raw, 1676 | }); 1677 | } 1678 | 1679 | page_encodings.appendAssumeCapacity(raw); 1680 | pos += @sizeOf(macho.compact_unwind_encoding_t); 1681 | } 1682 | } 1683 | 1684 | var pos = start_offset + page_header.entryPageOffset; 1685 | var count: usize = 0; 1686 | while (count < page_header.entryCount) : (count += 1) { 1687 | const inner = @as(*align(1) const u32, @ptrCast(data.ptr + pos)).*; 1688 | const func_offset = entry.functionOffset + (inner & 0xFFFFFF); 1689 | const id = inner >> 24; 1690 | const raw = if (id < common_encodings.len) 1691 | common_encodings[id] 1692 | else 1693 | page_encodings.items[id - common_encodings.len]; 1694 | 1695 | if (self.verbose) blk: { 1696 | const seg = self.getSegmentByName("__TEXT").?; 1697 | const func_name = if (self.findSymbolByAddress(seg.vmaddr + func_offset)) |func_sym| 1698 | self.getString(func_sym.n_strx) 1699 | else 1700 | "unknown"; 1701 | 1702 | try writer.print(" [{d}] {s}\n", .{ count, func_name }); 1703 | try writer.print(" Function address: 0x{x:0>16}\n", .{seg.vmaddr + func_offset}); 1704 | try writer.writeAll(" Encoding\n"); 1705 | 1706 | switch (self.arch) { 1707 | .aarch64 => { 1708 | const enc = UnwindEncodingArm64.fromU32(raw) catch |err| switch (err) { 1709 | error.UnknownEncoding => if (raw == 0) { 1710 | try writer.writeAll(" none\n"); 1711 | break :blk; 1712 | } else return err, 1713 | }; 1714 | try formatCompactUnwindEncodingArm64(enc, writer, .{ 1715 | .prefix = 10, 1716 | }); 1717 | }, 1718 | 1719 | .x86_64 => { 1720 | const enc = UnwindEncodingX86_64.fromU32(raw) catch |err| switch (err) { 1721 | error.UnknownEncoding => if (raw == 0) { 1722 | try writer.writeAll(" none\n"); 1723 | break :blk; 1724 | } else return err, 1725 | }; 1726 | try formatCompactUnwindEncodingX86_64(enc, writer, .{ 1727 | .prefix = 10, 1728 | }); 1729 | }, 1730 | else => unreachable, 1731 | } 1732 | } else { 1733 | try writer.print(" [{d}]: function offset=0x{x:0>8}, encoding[{d}]=0x{x:0>8}\n", .{ 1734 | count, 1735 | func_offset, 1736 | id, 1737 | raw, 1738 | }); 1739 | } 1740 | 1741 | pos += @sizeOf(u32); 1742 | } 1743 | }, 1744 | else => return error.UnhandledSecondLevelKind, 1745 | } 1746 | } 1747 | } 1748 | } 1749 | 1750 | fn formatCompactUnwindEncodingArm64(enc: UnwindEncodingArm64, writer: anytype, comptime opts: struct { 1751 | prefix: usize = 0, 1752 | }) !void { 1753 | const prefix: [opts.prefix]u8 = [_]u8{' '} ** opts.prefix; 1754 | try writer.print(prefix ++ "{s: <12} {}\n", .{ "start:", enc.start() }); 1755 | try writer.print(prefix ++ "{s: <12} {}\n", .{ "LSDA:", enc.hasLsda() }); 1756 | try writer.print(prefix ++ "{s: <12} {d}\n", .{ "personality:", enc.personalityIndex() }); 1757 | try writer.print(prefix ++ "{s: <12} {s}\n", .{ "mode:", @tagName(enc.mode()) }); 1758 | 1759 | switch (enc) { 1760 | .frameless => |frameless| { 1761 | try writer.print(prefix ++ "{s: <12} {d}\n", .{ 1762 | "stack size:", 1763 | frameless.stack_size * 16, 1764 | }); 1765 | }, 1766 | .frame => |frame| { 1767 | inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields) |field| { 1768 | try writer.print(prefix ++ "{s: <12} {}\n", .{ 1769 | field.name, 1770 | @field(frame.x_reg_pairs, field.name) == 0b1, 1771 | }); 1772 | } 1773 | 1774 | inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields) |field| { 1775 | try writer.print(prefix ++ "{s: <12} {}\n", .{ 1776 | field.name, 1777 | @field(frame.d_reg_pairs, field.name) == 0b1, 1778 | }); 1779 | } 1780 | }, 1781 | .dwarf => |dwarf| { 1782 | try writer.print(prefix ++ "{s: <12} 0x{x:0>8}\n", .{ 1783 | "FDE offset:", 1784 | dwarf.section_offset, 1785 | }); 1786 | }, 1787 | } 1788 | } 1789 | 1790 | fn formatCompactUnwindEncodingX86_64(enc: UnwindEncodingX86_64, writer: anytype, comptime opts: struct { 1791 | prefix: usize = 0, 1792 | }) !void { 1793 | const prefix: [opts.prefix]u8 = [_]u8{' '} ** opts.prefix; 1794 | try writer.print(prefix ++ "{s: <12} {}\n", .{ "start:", enc.start() }); 1795 | try writer.print(prefix ++ "{s: <12} {}\n", .{ "LSDA:", enc.hasLsda() }); 1796 | try writer.print(prefix ++ "{s: <12} {d}\n", .{ "personality:", enc.personalityIndex() }); 1797 | try writer.print(prefix ++ "{s: <12} {s}\n", .{ "mode:", @tagName(enc.mode()) }); 1798 | 1799 | switch (enc) { 1800 | .frameless => |frameless| { 1801 | inline for (@typeInfo(@TypeOf(frameless)).@"struct".fields) |field| { 1802 | try writer.print(prefix ++ "{s: <12} {x}\n", .{ 1803 | field.name, 1804 | @field(frameless, field.name), 1805 | }); 1806 | } 1807 | }, 1808 | .frame => |frame| { 1809 | inline for (@typeInfo(@TypeOf(frame)).@"struct".fields) |field| { 1810 | try writer.print(prefix ++ "{s: <12} {x}\n", .{ 1811 | field.name, 1812 | @field(frame, field.name), 1813 | }); 1814 | } 1815 | }, 1816 | .dwarf => |dwarf| { 1817 | try writer.print(prefix ++ "{s: <12} 0x{x:0>8}\n", .{ 1818 | "FDE offset:", 1819 | dwarf.section_offset, 1820 | }); 1821 | }, 1822 | } 1823 | } 1824 | 1825 | pub fn printCodeSignature(self: Object, writer: anytype) !void { 1826 | var it = self.getLoadCommandsIterator(); 1827 | while (it.next()) |lc| switch (lc.cmd()) { 1828 | .CODE_SIGNATURE => return self.formatCodeSignatureData(lc.cast(macho.linkedit_data_command).?, writer), 1829 | else => continue, 1830 | }; 1831 | return writer.print("LC_CODE_SIGNATURE load command not found\n", .{}); 1832 | } 1833 | 1834 | fn formatCodeSignatureData( 1835 | self: Object, 1836 | csig: macho.linkedit_data_command, 1837 | writer: anytype, 1838 | ) !void { 1839 | const start_pos = csig.dataoff; 1840 | const end_pos = csig.dataoff + csig.datasize; 1841 | 1842 | if (end_pos == start_pos) return; 1843 | 1844 | try writer.print("Code signature data:\n", .{}); 1845 | try writer.print("file = {{ {}, {} }}\n\n", .{ start_pos, end_pos }); 1846 | 1847 | var data = self.data[start_pos..end_pos]; 1848 | var ptr = data; 1849 | const magic = mem.readInt(u32, ptr[0..4], .big); 1850 | const length = mem.readInt(u32, ptr[4..8], .big); 1851 | const count = mem.readInt(u32, ptr[8..12], .big); 1852 | ptr = ptr[12..]; 1853 | 1854 | try writer.print("{{\n", .{}); 1855 | try writer.print(" Magic = 0x{x}\n", .{magic}); 1856 | try writer.print(" Length = {}\n", .{length}); 1857 | try writer.print(" Count = {}\n", .{count}); 1858 | try writer.print("}}\n", .{}); 1859 | 1860 | if (magic != macho.CSMAGIC_EMBEDDED_SIGNATURE) { 1861 | try writer.print("unknown signature type: 0x{x}\n", .{magic}); 1862 | try formatBinaryBlob(self.data[start_pos..end_pos], .{}, writer); 1863 | return; 1864 | } 1865 | 1866 | var blobs = std.ArrayList(macho.BlobIndex).init(self.gpa); 1867 | defer blobs.deinit(); 1868 | try blobs.ensureTotalCapacityPrecise(count); 1869 | 1870 | var i: usize = 0; 1871 | while (i < count) : (i += 1) { 1872 | const tt = mem.readInt(u32, ptr[0..4], .big); 1873 | const offset = mem.readInt(u32, ptr[4..8], .big); 1874 | try writer.print("{{\n Type: {s}(0x{x})\n Offset: {}\n}}\n", .{ fmtCsSlotConst(tt), tt, offset }); 1875 | blobs.appendAssumeCapacity(.{ 1876 | .type = tt, 1877 | .offset = offset, 1878 | }); 1879 | ptr = ptr[8..]; 1880 | } 1881 | 1882 | for (blobs.items) |blob| { 1883 | ptr = data[blob.offset..]; 1884 | const magic2 = mem.readInt(u32, ptr[0..4], .big); 1885 | const length2 = mem.readInt(u32, ptr[4..8], .big); 1886 | 1887 | try writer.print("{{\n", .{}); 1888 | try writer.print(" Magic: {s}(0x{x})\n", .{ fmtCsMagic(magic2), magic2 }); 1889 | try writer.print(" Length: {}\n", .{length2}); 1890 | 1891 | switch (magic2) { 1892 | macho.CSMAGIC_CODEDIRECTORY => { 1893 | const version = mem.readInt(u32, ptr[8..12], .big); 1894 | const flags = mem.readInt(u32, ptr[12..16], .big); 1895 | const hash_off = mem.readInt(u32, ptr[16..20], .big); 1896 | const ident_off = mem.readInt(u32, ptr[20..24], .big); 1897 | const n_special_slots = mem.readInt(u32, ptr[24..28], .big); 1898 | const n_code_slots = mem.readInt(u32, ptr[28..32], .big); 1899 | const code_limit = mem.readInt(u32, ptr[32..36], .big); 1900 | const hash_size = ptr[36]; 1901 | const page_size = std.math.pow(u16, 2, ptr[39]); 1902 | const team_off = mem.readInt(u32, ptr[48..52], .big); 1903 | 1904 | try writer.print(" Version: 0x{x}\n", .{version}); 1905 | try writer.print(" Flags: 0x{x}\n", .{flags}); 1906 | try writer.print(" Hash offset: {}\n", .{hash_off}); 1907 | try writer.print(" Ident offset: {}\n", .{ident_off}); 1908 | try writer.print(" Number of special slots: {}\n", .{n_special_slots}); 1909 | try writer.print(" Number of code slots: {}\n", .{n_code_slots}); 1910 | try writer.print(" Code limit: {}\n", .{code_limit}); 1911 | try writer.print(" Hash size: {}\n", .{hash_size}); 1912 | try writer.print(" Hash type: {}\n", .{ptr[37]}); 1913 | try writer.print(" Platform: {}\n", .{ptr[38]}); 1914 | try writer.print(" Page size: {}\n", .{ptr[39]}); 1915 | try writer.print(" Reserved: {}\n", .{mem.readInt(u32, ptr[40..44], .big)}); 1916 | 1917 | switch (version) { 1918 | 0x20400 => { 1919 | try writer.print(" Scatter offset: {}\n", .{mem.readInt(u32, ptr[44..48], .big)}); 1920 | try writer.print(" Team offset: {}\n", .{team_off}); 1921 | try writer.print(" Reserved: {}\n", .{mem.readInt(u32, ptr[52..56], .big)}); 1922 | try writer.print(" Code limit 64: {}\n", .{mem.readInt(u64, ptr[56..64], .big)}); 1923 | try writer.print(" Offset of executable segment: {}\n", .{mem.readInt(u64, ptr[64..72], .big)}); 1924 | try writer.print(" Limit of executable segment: {}\n", .{mem.readInt(u64, ptr[72..80], .big)}); 1925 | try writer.print(" Executable segment flags: 0x{x}\n", .{mem.readInt(u64, ptr[80..88], .big)}); 1926 | ptr = ptr[88..]; 1927 | }, 1928 | 0x20100 => { 1929 | try writer.print(" Scatter offset: {}\n", .{mem.readInt(u32, ptr[52..56], .big)}); 1930 | ptr = ptr[56..]; 1931 | }, 1932 | else => { 1933 | ptr = ptr[52..]; 1934 | }, 1935 | } 1936 | 1937 | const ident = mem.sliceTo(@as([*:0]const u8, @ptrCast(ptr)), 0); 1938 | try writer.print("\nIdent: {s}\n", .{ident}); 1939 | ptr = ptr[ident.len + 1 ..]; 1940 | 1941 | if (team_off > 0) { 1942 | assert(team_off - ident_off == ident.len + 1); 1943 | const team_ident = mem.sliceTo(@as([*:0]const u8, @ptrCast(ptr)), 0); 1944 | try writer.print("\nTeam ident: {s}\n", .{team_ident}); 1945 | ptr = ptr[team_ident.len + 1 ..]; 1946 | } 1947 | 1948 | var j: isize = n_special_slots; 1949 | while (j > 0) : (j -= 1) { 1950 | const hash = ptr[0..hash_size]; 1951 | try writer.print("\nSpecial slot for {s}:\n", .{ 1952 | fmtCsSlotConst(@as(u32, @intCast(if (j == 6) macho.CSSLOT_SIGNATURESLOT else j))), 1953 | }); 1954 | try formatBinaryBlob(hash, .{ 1955 | .prefix = " ", 1956 | .fmt_as_str = false, 1957 | }, writer); 1958 | ptr = ptr[hash_size..]; 1959 | } 1960 | 1961 | var k: usize = 0; 1962 | const base_addr: u64 = 0x100000000; 1963 | while (k < n_code_slots) : (k += 1) { 1964 | const hash = ptr[0..hash_size]; 1965 | try writer.print("\nCode slot (0x{x} - 0x{x}):\n", .{ 1966 | base_addr + k * page_size, 1967 | base_addr + (k + 1) * page_size, 1968 | }); 1969 | try formatBinaryBlob(hash, .{ 1970 | .prefix = " ", 1971 | .fmt_as_str = false, 1972 | }, writer); 1973 | ptr = ptr[hash_size..]; 1974 | } 1975 | }, 1976 | macho.CSMAGIC_REQUIREMENTS => { 1977 | const req_data = ptr[8..length2]; 1978 | var stream = std.io.fixedBufferStream(req_data); 1979 | var reader = stream.reader(); 1980 | 1981 | try writer.print(" Parsed data:\n", .{}); 1982 | 1983 | const req_count = try reader.readInt(u32, .big); 1984 | 1985 | var req_blobs = std.ArrayList(macho.BlobIndex).init(self.gpa); 1986 | defer req_blobs.deinit(); 1987 | try req_blobs.ensureTotalCapacityPrecise(req_count); 1988 | 1989 | var next_req: usize = 0; 1990 | while (next_req < req_count) : (next_req += 1) { 1991 | const tt = try reader.readInt(u32, .big); 1992 | const off = try reader.readInt(u32, .big); 1993 | try writer.print("\n {{\n Type: {s}(0x{x})\n Offset: {}\n }}\n", .{ 1994 | fmtCsSlotConst(tt), 1995 | tt, 1996 | off, 1997 | }); 1998 | req_blobs.appendAssumeCapacity(.{ 1999 | .type = tt, 2000 | .offset = off, 2001 | }); 2002 | } 2003 | 2004 | for (req_blobs.items) |req_blob| { 2005 | try stream.seekTo(req_blob.offset - 8); 2006 | const req_blob_magic = try reader.readInt(u32, .big); 2007 | const req_blob_len = try reader.readInt(u32, .big); 2008 | 2009 | try writer.writeAll("\n {\n"); 2010 | try writer.print(" Magic: {s}(0x{x})\n", .{ 2011 | fmtCsMagic(req_blob_magic), 2012 | req_blob_magic, 2013 | }); 2014 | try writer.print(" Length: {}\n", .{req_blob_len}); 2015 | 2016 | while (reader.context.pos < req_blob_len) { 2017 | const next = try reader.readInt(u32, .big); 2018 | const op = @as(ExprOp, @enumFromInt(next)); 2019 | 2020 | try writer.print(" {}", .{op}); 2021 | 2022 | switch (op) { 2023 | .op_false, 2024 | .op_true, 2025 | .op_and, 2026 | .op_or, 2027 | .op_not, 2028 | .op_apple_anchor, 2029 | .op_anchor_hash, 2030 | .op_info_key_value, 2031 | .op_trusted_cert, 2032 | .op_trusted_certs, 2033 | .op_apple_generic_anchor, 2034 | .op_entitlement_field, 2035 | .op_cert_policy, 2036 | .op_named_anchor, 2037 | .op_named_code, 2038 | .op_notarized, 2039 | .op_cert_field_date, 2040 | .op_legacy_dev_id, 2041 | => {}, 2042 | .op_ident => try fmtReqData(req_data, reader, writer), 2043 | .op_cert_generic => { 2044 | const slot = try reader.readInt(i32, .big); 2045 | switch (slot) { 2046 | LEAF_CERT => try writer.writeAll("\n leaf"), 2047 | ROOT_CERT => try writer.writeAll("\n root"), 2048 | else => try writer.print("\n slot {d}", .{slot}), 2049 | } 2050 | try fmtCssmData(req_data, reader, writer); 2051 | try fmtReqMatch(req_data, reader, writer); 2052 | }, 2053 | .op_cert_field => { 2054 | const slot = try reader.readInt(i32, .big); 2055 | switch (slot) { 2056 | LEAF_CERT => try writer.writeAll("\n leaf"), 2057 | ROOT_CERT => try writer.writeAll("\n root"), 2058 | else => try writer.print("\n slot {d}", .{slot}), 2059 | } 2060 | try fmtReqData(req_data, reader, writer); 2061 | try fmtReqMatch(req_data, reader, writer); 2062 | }, 2063 | .op_platform => { 2064 | const platform = try reader.readInt(i32, .big); 2065 | try writer.print("\n {f}", .{ 2066 | std.ascii.hexEscape(mem.asBytes(&platform), .lower), 2067 | }); 2068 | }, 2069 | else => { 2070 | if (next & EXPR_OP_GENERIC_FALSE != 0) { 2071 | try writer.writeAll("\n generic false"); 2072 | } else if (next & EXPR_OP_GENERIC_SKIP != 0) { 2073 | try writer.writeAll("\n generic skip"); 2074 | } else { 2075 | try writer.writeAll("\n unknown opcode"); 2076 | } 2077 | }, 2078 | } 2079 | 2080 | try writer.writeByte('\n'); 2081 | } 2082 | try writer.writeAll("\n }"); 2083 | } 2084 | 2085 | try writer.print("\n Raw data:\n", .{}); 2086 | try formatBinaryBlob(ptr[8..length2], .{ 2087 | .prefix = " ", 2088 | .fmt_as_str = true, 2089 | .escape_str = true, 2090 | }, writer); 2091 | }, 2092 | macho.CSMAGIC_BLOBWRAPPER => { 2093 | const signature = ptr[8..length2]; 2094 | 2095 | if (comptime builtin.target.os.tag.isDarwin()) { 2096 | const cd: []const u8 = blk: { 2097 | const cd_blob = blobs.items[0]; 2098 | const cd_header = data[cd_blob.offset..][0..8]; 2099 | const cd_length = mem.readInt(u32, cd_header[4..8], .big); 2100 | break :blk data[cd_blob.offset..][0..cd_length]; 2101 | }; 2102 | 2103 | const decoder = try CMSDecoder.create(); 2104 | defer decoder.release(); 2105 | try decoder.updateMessage(signature); 2106 | try decoder.setDetachedContent(cd); 2107 | try decoder.finalizeMessage(); 2108 | 2109 | const num_signers = try decoder.getNumSigners(); 2110 | try writer.print(" Number of signers: {d}\n", .{num_signers}); 2111 | 2112 | const status = try decoder.getSignerStatus(0); 2113 | try writer.print(" Signer status: {}\n", .{status}); 2114 | } else { 2115 | try writer.print("\n\n !! Validating signatures available only on macOS !! \n\n", .{}); 2116 | try writer.print(" Raw data:\n", .{}); 2117 | try formatBinaryBlob(signature, .{ 2118 | .prefix = " ", 2119 | .fmt_as_str = true, 2120 | .escape_str = true, 2121 | }, writer); 2122 | } 2123 | }, 2124 | else => { 2125 | try writer.print(" Raw data:\n", .{}); 2126 | try formatBinaryBlob(ptr[8..length2], .{ 2127 | .prefix = " ", 2128 | .fmt_as_str = true, 2129 | .escape_str = true, 2130 | }, writer); 2131 | }, 2132 | } 2133 | 2134 | try writer.print("}}\n", .{}); 2135 | } 2136 | } 2137 | 2138 | fn parseReqData(buf: []const u8, reader: anytype) ![]const u8 { 2139 | const len = try reader.readInt(u32, .big); 2140 | const pos = try reader.context.getPos(); 2141 | const data = buf[@as(usize, @intCast(pos))..][0..len]; 2142 | try reader.context.seekBy(@as(i64, @intCast(mem.alignForward(u32, len, @sizeOf(u32))))); 2143 | return data; 2144 | } 2145 | 2146 | fn fmtReqData(buf: []const u8, reader: anytype, writer: anytype) !void { 2147 | const data = try parseReqData(buf, reader); 2148 | try writer.print("\n {s}", .{data}); 2149 | } 2150 | 2151 | fn getOid(buf: []const u8, pos: *usize) usize { 2152 | var q: usize = 0; 2153 | while (true) { 2154 | q = q * 128 + (buf[pos.*] & ~@as(usize, 0x80)); 2155 | if (pos.* >= buf.len) break; 2156 | if (buf[pos.*] & 0x80 == 0) { 2157 | pos.* += 1; 2158 | break; 2159 | } 2160 | pos.* += 1; 2161 | } 2162 | return q; 2163 | } 2164 | 2165 | fn fmtCssmData(buf: []const u8, reader: anytype, writer: anytype) !void { 2166 | const data = try parseReqData(buf, reader); 2167 | 2168 | var pos: usize = 0; 2169 | 2170 | const oid1 = getOid(data, &pos); 2171 | const q1 = @min(@divFloor(oid1, 40), 2); 2172 | try writer.print("\n {d}.{d}", .{ q1, oid1 - @as(usize, q1) * 40 }); 2173 | 2174 | while (pos < data.len) { 2175 | const oid2 = getOid(data, &pos); 2176 | try writer.print(".{d}", .{oid2}); 2177 | } 2178 | 2179 | try writer.print(" ({f})", .{std.ascii.hexEscape(data, .lower)}); 2180 | } 2181 | 2182 | fn fmtReqTimestamp(buf: []const u8, reader: anytype, writer: anytype) !void { 2183 | _ = buf; 2184 | const ts = try reader.readInt(i64, .big); 2185 | try writer.print("\n {d}", .{ts}); 2186 | } 2187 | 2188 | fn fmtReqMatch(buf: []const u8, reader: anytype, writer: anytype) !void { 2189 | const match = @as(MatchOperation, @enumFromInt(try reader.readInt(u32, .big))); 2190 | try writer.print("\n {}", .{match}); 2191 | switch (match) { 2192 | .match_exists, .match_absent => {}, 2193 | .match_equal, 2194 | .match_contains, 2195 | .match_begins_with, 2196 | .match_ends_with, 2197 | .match_less_than, 2198 | .match_greater_equal, 2199 | .match_less_equal, 2200 | .match_greater_than, 2201 | => try fmtReqData(buf, reader, writer), 2202 | .match_on, 2203 | .match_before, 2204 | .match_after, 2205 | .match_on_or_before, 2206 | .match_on_or_after, 2207 | => try fmtReqTimestamp(buf, reader, writer), 2208 | else => try writer.writeAll("\n unknown opcode"), 2209 | } 2210 | } 2211 | 2212 | fn fmtCsSlotConst(raw: u32) []const u8 { 2213 | if (macho.CSSLOT_ALTERNATE_CODEDIRECTORIES <= raw and raw < macho.CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT) { 2214 | return "CSSLOT_ALTERNATE_CODEDIRECTORIES"; 2215 | } 2216 | return switch (raw) { 2217 | macho.CSSLOT_CODEDIRECTORY => "CSSLOT_CODEDIRECTORY", 2218 | macho.CSSLOT_INFOSLOT => "CSSLOT_INFOSLOT", 2219 | macho.CSSLOT_REQUIREMENTS => "CSSLOT_REQUIREMENTS", 2220 | macho.CSSLOT_RESOURCEDIR => "CSSLOT_RESOURCEDIR", 2221 | macho.CSSLOT_APPLICATION => "CSSLOT_APPLICATION", 2222 | macho.CSSLOT_ENTITLEMENTS => "CSSLOT_ENTITLEMENTS", 2223 | macho.CSSLOT_DER_ENTITLEMENTS => "CSSLOT_DER_ENTITLEMENTS", 2224 | macho.CSSLOT_SIGNATURESLOT => "CSSLOT_SIGNATURESLOT", 2225 | macho.CSSLOT_IDENTIFICATIONSLOT => "CSSLOT_IDENTIFICATIONSLOT", 2226 | else => "UNKNOWN", 2227 | }; 2228 | } 2229 | 2230 | fn fmtCsMagic(raw: u32) []const u8 { 2231 | const magic = switch (raw) { 2232 | macho.CSMAGIC_REQUIREMENT => "CSMAGIC_REQUIREMENT", 2233 | macho.CSMAGIC_REQUIREMENTS => "CSMAGIC_REQUIREMENTS", 2234 | macho.CSMAGIC_CODEDIRECTORY => "CSMAGIC_CODEDIRECTORY", 2235 | macho.CSMAGIC_BLOBWRAPPER => "CSMAGIC_BLOBWRAPPER", 2236 | macho.CSMAGIC_EMBEDDED_ENTITLEMENTS => "CSMAGIC_EMBEDDED_ENTITLEMENTS", 2237 | macho.CSMAGIC_EMBEDDED_DER_ENTITLEMENTS => "CSMAGIC_EMBEDDED_DER_ENTITLEMENTS", 2238 | else => "UNKNOWN", 2239 | }; 2240 | return magic; 2241 | } 2242 | 2243 | const FmtBinaryBlobOpts = struct { 2244 | prefix: ?[]const u8 = null, 2245 | fmt_as_str: bool = true, 2246 | escape_str: bool = false, 2247 | }; 2248 | 2249 | fn formatBinaryBlob(blob: []const u8, opts: FmtBinaryBlobOpts, writer: anytype) !void { 2250 | // Format as 16-by-16-by-8 with two left column in hex, and right in ascii: 2251 | // xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx xxxxxxxx 2252 | var i: usize = 0; 2253 | const step = 16; 2254 | const pp = opts.prefix orelse ""; 2255 | var tmp_buf: [step]u8 = undefined; 2256 | while (i < blob.len) : (i += step) { 2257 | const end = if (blob[i..].len >= step) step else blob[i..].len; 2258 | const padding = step - blob[i .. i + end].len; 2259 | if (padding > 0) { 2260 | @memset(tmp_buf[0..], 0); 2261 | } 2262 | @memcpy(&tmp_buf, blob[i .. i + end]); 2263 | try writer.print("{s}{x:<016} {x:<016}", .{ 2264 | pp, tmp_buf[0 .. step / 2], tmp_buf[step / 2 .. step], 2265 | }); 2266 | if (opts.fmt_as_str) { 2267 | if (opts.escape_str) { 2268 | try writer.print(" {f}", .{std.ascii.hexEscape(tmp_buf[0..step], .lower)}); 2269 | } else { 2270 | try writer.print(" {s}", .{tmp_buf[0..step]}); 2271 | } 2272 | } 2273 | try writer.writeByte('\n'); 2274 | } 2275 | } 2276 | 2277 | const ExprOp = enum(u32) { 2278 | op_false, 2279 | op_true, 2280 | op_ident, 2281 | op_apple_anchor, 2282 | op_anchor_hash, 2283 | op_info_key_value, 2284 | op_and, 2285 | op_or, 2286 | op_cd_hash, 2287 | op_not, 2288 | op_info_key_field, 2289 | op_cert_field, 2290 | op_trusted_cert, 2291 | op_trusted_certs, 2292 | op_cert_generic, 2293 | op_apple_generic_anchor, 2294 | op_entitlement_field, 2295 | op_cert_policy, 2296 | op_named_anchor, 2297 | op_named_code, 2298 | op_platform, 2299 | op_notarized, 2300 | op_cert_field_date, 2301 | op_legacy_dev_id, 2302 | _, 2303 | }; 2304 | 2305 | const MatchOperation = enum(u32) { 2306 | match_exists, 2307 | match_equal, 2308 | match_contains, 2309 | match_begins_with, 2310 | match_ends_with, 2311 | match_less_than, 2312 | match_greater_than, 2313 | match_less_equal, 2314 | match_greater_equal, 2315 | match_on, 2316 | match_before, 2317 | match_after, 2318 | match_on_or_before, 2319 | match_on_or_after, 2320 | match_absent, 2321 | _, 2322 | }; 2323 | 2324 | pub const EXPR_OP_FLAG_MASK: u32 = 0xff; 2325 | pub const EXPR_OP_GENERIC_FALSE: u32 = 0x80; 2326 | pub const EXPR_OP_GENERIC_SKIP: u32 = 0x40; 2327 | 2328 | pub const LEAF_CERT = 0; 2329 | pub const ROOT_CERT = -1; 2330 | 2331 | pub fn verifyMemoryLayout(self: Object, writer: anytype) !void { 2332 | var segments = std.ArrayList(macho.segment_command_64).init(self.gpa); 2333 | defer segments.deinit(); 2334 | 2335 | var sections = std.AutoHashMap(u8, std.ArrayList(macho.section_64)).init(self.gpa); 2336 | defer { 2337 | var it = sections.valueIterator(); 2338 | while (it.next()) |value_ptr| { 2339 | value_ptr.deinit(); 2340 | } 2341 | sections.deinit(); 2342 | } 2343 | 2344 | var sorted_by_address = std.ArrayList(u8).init(self.gpa); 2345 | defer sorted_by_address.deinit(); 2346 | 2347 | var sorted_by_offset = std.ArrayList(u8).init(self.gpa); 2348 | defer sorted_by_offset.deinit(); 2349 | 2350 | var it = self.getLoadCommandsIterator(); 2351 | while (it.next()) |lc| switch (lc.cmd()) { 2352 | .SEGMENT_64 => { 2353 | const seg = lc.cast(macho.segment_command_64).?; 2354 | const seg_id = @as(u8, @intCast(segments.items.len)); 2355 | try segments.append(seg); 2356 | 2357 | const headers = lc.getSections(); 2358 | if (headers.len > 0) { 2359 | const gop = try sections.getOrPut(seg_id); 2360 | if (!gop.found_existing) { 2361 | gop.value_ptr.* = std.ArrayList(macho.section_64).init(self.gpa); 2362 | } 2363 | try gop.value_ptr.ensureUnusedCapacity(headers.len); 2364 | gop.value_ptr.appendUnalignedSliceAssumeCapacity(headers); 2365 | } 2366 | 2367 | for (sorted_by_address.items, 0..) |other_id, i| { 2368 | const other_seg = segments.items[other_id]; 2369 | if (seg.vmaddr < other_seg.vmaddr) { 2370 | try sorted_by_address.insert(i, seg_id); 2371 | break; 2372 | } 2373 | } else try sorted_by_address.append(seg_id); 2374 | 2375 | for (sorted_by_offset.items, 0..) |other_id, i| { 2376 | const other_seg = segments.items[other_id]; 2377 | if (seg.fileoff < other_seg.fileoff) { 2378 | try sorted_by_offset.insert(i, seg_id); 2379 | break; 2380 | } 2381 | } else try sorted_by_offset.append(seg_id); 2382 | }, 2383 | else => continue, 2384 | }; 2385 | 2386 | if (segments.items.len == 0) { 2387 | try writer.writeAll("\nNo segments found.\n"); 2388 | return; 2389 | } 2390 | 2391 | try writer.writeAll("\nMEMORY LAYOUT:\n"); 2392 | 2393 | var i: u8 = 0; 2394 | while (i < sorted_by_address.items.len) : (i += 1) { 2395 | const seg_id = sorted_by_address.items[i]; 2396 | const seg = segments.items[seg_id]; 2397 | try writer.print(" {s: >20} ---------- {x}\n", .{ seg.segName(), seg.vmaddr }); 2398 | try writer.print(" {s: >20} |\n", .{""}); 2399 | 2400 | if (sections.get(seg_id)) |headers| { 2401 | try writer.writeByte('\n'); 2402 | for (headers.items, 0..) |header, header_id| { 2403 | try writer.print(" {s: >20} -------- {x}\n", .{ header.sectName(), header.addr }); 2404 | try writer.print(" {s: >20} |\n", .{""}); 2405 | try writer.print(" {s: >20} -------- {x}\n", .{ "", header.addr + header.size }); 2406 | if (header_id < headers.items.len - 1) { 2407 | const next_header = headers.items[header_id + 1]; 2408 | if (next_header.addr < header.addr + header.size) { 2409 | try writer.writeAll(" CURRENT SECTION OVERLAPS THE NEXT ONE\n"); 2410 | } 2411 | } 2412 | } 2413 | try writer.writeByte('\n'); 2414 | } else { 2415 | try writer.print(" {s: >20} |\n", .{""}); 2416 | } 2417 | 2418 | try writer.print(" {s: >20} |\n", .{""}); 2419 | try writer.print(" {s: >20} ---------- {x}\n", .{ "", seg.vmaddr + seg.vmsize }); 2420 | if (i < sorted_by_address.items.len - 1) { 2421 | const next_seg_id = sorted_by_address.items[i + 1]; 2422 | const next_seg = segments.items[next_seg_id]; 2423 | if (next_seg.vmaddr < seg.vmaddr + seg.vmsize) { 2424 | try writer.writeAll(" CURRENT SEGMENT OVERLAPS THE NEXT ONE\n"); 2425 | } 2426 | } 2427 | try writer.writeByte('\n'); 2428 | } 2429 | 2430 | try writer.writeAll("\nIN-FILE LAYOUT:\n"); 2431 | 2432 | i = 0; 2433 | while (i < sorted_by_offset.items.len) : (i += 1) { 2434 | const seg_id = sorted_by_offset.items[i]; 2435 | const seg = segments.items[seg_id]; 2436 | try writer.print(" {s: >20} ---------- {x}\n", .{ seg.segName(), seg.fileoff }); 2437 | try writer.print(" {s: >20} |\n", .{""}); 2438 | 2439 | if (sections.get(seg_id)) |headers| { 2440 | try writer.writeByte('\n'); 2441 | for (headers.items, 0..) |header, header_id| { 2442 | if (header.isZerofill()) continue; 2443 | try writer.print(" {s: >20} -------- {x}\n", .{ header.sectName(), header.offset }); 2444 | try writer.print(" {s: >20} |\n", .{""}); 2445 | try writer.print(" {s: >20} -------- {x}\n", .{ "", header.offset + header.size }); 2446 | if (header_id < headers.items.len - 1) { 2447 | const next_header = headers.items[header_id + 1]; 2448 | if (next_header.offset < header.offset + header.size and !next_header.isZerofill()) { 2449 | try writer.writeAll(" CURRENT SECTION OVERLAPS THE NEXT ONE\n"); 2450 | } 2451 | } 2452 | } 2453 | try writer.writeByte('\n'); 2454 | } else { 2455 | try writer.print(" {s: >20} |\n", .{""}); 2456 | } 2457 | 2458 | try writer.print(" {s: >20} |\n", .{""}); 2459 | try writer.print(" {s: >20} ---------- {x}\n", .{ "", seg.fileoff + seg.filesize }); 2460 | if (i < sorted_by_offset.items.len - 1) { 2461 | const next_seg_id = sorted_by_offset.items[i + 1]; 2462 | const next_seg = segments.items[next_seg_id]; 2463 | if (next_seg.fileoff < seg.fileoff + seg.filesize and next_seg.filesize > 0) { 2464 | try writer.writeAll(" CURRENT SEGMENT OVERLAPS THE NEXT ONE\n"); 2465 | } 2466 | } 2467 | try writer.writeByte('\n'); 2468 | } 2469 | } 2470 | 2471 | pub fn printRelocations(self: Object, writer: anytype) !void { 2472 | var has_relocs = false; 2473 | var it = self.getLoadCommandsIterator(); 2474 | while (it.next()) |lc| switch (lc.cmd()) { 2475 | .SEGMENT_64 => { 2476 | for (lc.getSections()) |sect| { 2477 | const code = self.data[sect.offset..][0..sect.size]; 2478 | const relocs = relocs: { 2479 | const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(self.data.ptr + sect.reloff))[0..sect.nreloc]; 2480 | const out = try self.gpa.alloc(macho.relocation_info, relocs.len); 2481 | @memcpy(out, relocs); 2482 | break :relocs out; 2483 | }; 2484 | defer self.gpa.free(relocs); 2485 | 2486 | if (relocs.len == 0) continue; 2487 | 2488 | const sortFn = struct { 2489 | fn sortFn(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { 2490 | _ = ctx; 2491 | return lhs.r_address > rhs.r_address; 2492 | } 2493 | }.sortFn; 2494 | mem.sort(macho.relocation_info, relocs, {}, sortFn); 2495 | 2496 | try writer.print("Relocation information ({s},{s}) {d} entries:\n", .{ 2497 | sect.segName(), 2498 | sect.sectName(), 2499 | relocs.len, 2500 | }); 2501 | try writer.writeAll(" address pcrel length extern type scattered symbolnum/value\n"); 2502 | for (relocs) |rel| { 2503 | try writer.print(" {x:0>8}", .{@as(u32, @intCast(rel.r_address))}); 2504 | try writer.print(" {s: <5}", .{if (rel.r_pcrel == 0) "false" else "true"}); 2505 | try writer.print(" {s: <6}", .{switch (rel.r_length) { 2506 | 0 => "byte", 2507 | 1 => "short", 2508 | 2 => "long", 2509 | 3 => "quad", 2510 | }}); 2511 | try writer.print(" {s: <6}", .{if (rel.r_extern == 0) "false" else "true"}); 2512 | try writer.print(" {f}", .{fmtRelocType(rel.r_type, self.arch, 8)}); 2513 | try writer.print(" {s: <9}", .{"false"}); 2514 | 2515 | if (isArm64Addend(rel.r_type, self.arch)) { 2516 | try writer.print(" 0x{x}", .{rel.r_symbolnum}); 2517 | } else { 2518 | if (rel.r_extern == 0) { 2519 | const target = self.getSectionByIndex(@intCast(rel.r_symbolnum)); 2520 | try writer.print(" {d} ({s},{s})", .{ rel.r_symbolnum, target.segName(), target.sectName() }); 2521 | } else { 2522 | const target = self.symtab[rel.r_symbolnum]; 2523 | try writer.print(" {s}", .{self.getString(target.n_strx)}); 2524 | } 2525 | 2526 | if (hasAddendInCode(rel.r_type, self.arch)) { 2527 | const rel_offset = @as(usize, @intCast(rel.r_address)); 2528 | var addend = switch (rel.r_length) { 2529 | 0 => code[rel_offset], 2530 | 1 => mem.readInt(i16, code[rel_offset..][0..2], .little), 2531 | 2 => mem.readInt(i32, code[rel_offset..][0..4], .little), 2532 | 3 => mem.readInt(i64, code[rel_offset..][0..8], .little), 2533 | }; 2534 | 2535 | if (rel.r_extern == 0) { 2536 | const target = self.getSectionByIndex(@intCast(rel.r_symbolnum)); 2537 | if (rel.r_pcrel == 1) { 2538 | addend = @as(i64, @intCast(sect.addr)) + rel.r_address + addend + 4; 2539 | } 2540 | if (addend < target.addr or addend > target.addr + target.size) { 2541 | try writer.writeAll(" ADDEND OVERFLOWS TARGET SECTION"); 2542 | } else { 2543 | addend -= @intCast(target.addr); 2544 | } 2545 | } 2546 | try writer.print(" + 0x{x}", .{addend}); 2547 | } 2548 | } 2549 | 2550 | try writer.writeByte('\n'); 2551 | } 2552 | has_relocs = true; 2553 | } 2554 | }, 2555 | else => {}, 2556 | }; 2557 | 2558 | if (!has_relocs) { 2559 | try writer.writeAll("No relocation entries found.\n"); 2560 | } 2561 | } 2562 | 2563 | fn isArm64Addend(r_type: u8, arch: Arch) bool { 2564 | return switch (arch) { 2565 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(r_type))) { 2566 | .ARM64_RELOC_ADDEND => true, 2567 | else => false, 2568 | }, 2569 | else => false, 2570 | }; 2571 | } 2572 | 2573 | fn hasAddendInCode(r_type: u8, arch: Arch) bool { 2574 | return switch (arch) { 2575 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(r_type))) { 2576 | .ARM64_RELOC_UNSIGNED => true, 2577 | else => false, 2578 | }, 2579 | .x86_64 => true, 2580 | else => false, 2581 | }; 2582 | } 2583 | 2584 | fn fmtRelocType(r_type: u8, arch: Arch, width: usize) std.fmt.Formatter(FmtRelocTypeCtx, formatRelocType) { 2585 | return .{ .data = .{ 2586 | .r_type = r_type, 2587 | .arch = arch, 2588 | .width = width, 2589 | } }; 2590 | } 2591 | 2592 | const FmtRelocTypeCtx = struct { 2593 | r_type: u8, 2594 | arch: Arch, 2595 | width: usize, 2596 | }; 2597 | 2598 | fn formatRelocType(ctx: FmtRelocTypeCtx, writer: *std.Io.Writer) !void { 2599 | const len = switch (ctx.arch) { 2600 | .aarch64 => blk: { 2601 | const r_type = switch (@as(macho.reloc_type_arm64, @enumFromInt(ctx.r_type))) { 2602 | .ARM64_RELOC_UNSIGNED => "UNSIGND", 2603 | .ARM64_RELOC_SUBTRACTOR => "SUB", 2604 | .ARM64_RELOC_BRANCH26 => "BR26", 2605 | .ARM64_RELOC_PAGE21 => "PAGE21", 2606 | .ARM64_RELOC_PAGEOFF12 => "PAGOF12", 2607 | .ARM64_RELOC_GOT_LOAD_PAGE21 => "GOTLDP", 2608 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => "GOTLDPOF", 2609 | .ARM64_RELOC_POINTER_TO_GOT => "PTRGOT", 2610 | .ARM64_RELOC_TLVP_LOAD_PAGE21 => "TLVLDP", 2611 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => "TLVLDPOF", 2612 | .ARM64_RELOC_ADDEND => "ADDEND", 2613 | }; 2614 | try writer.print("{s}", .{r_type}); 2615 | break :blk r_type.len; 2616 | }, 2617 | .x86_64 => blk: { 2618 | const r_type = switch (@as(macho.reloc_type_x86_64, @enumFromInt(ctx.r_type))) { 2619 | .X86_64_RELOC_UNSIGNED => "UNSIGND", 2620 | .X86_64_RELOC_SUBTRACTOR => "SUB", 2621 | .X86_64_RELOC_SIGNED => "SIGNED", 2622 | .X86_64_RELOC_SIGNED_1 => "SIGNED1", 2623 | .X86_64_RELOC_SIGNED_2 => "SIGNED2", 2624 | .X86_64_RELOC_SIGNED_4 => "SIGNED4", 2625 | .X86_64_RELOC_BRANCH => "BR", 2626 | .X86_64_RELOC_GOT_LOAD => "GOTLD", 2627 | .X86_64_RELOC_GOT => "GOT", 2628 | .X86_64_RELOC_TLV => "TLV", 2629 | }; 2630 | try writer.print("{s}", .{r_type}); 2631 | break :blk r_type.len; 2632 | }, 2633 | .unknown => unreachable, 2634 | }; 2635 | if (ctx.width > len) { 2636 | const padding = ctx.width - len; 2637 | const slice = try writer.writableSlice(padding); 2638 | @memset(slice, ' '); 2639 | } 2640 | } 2641 | 2642 | pub fn printSymbolTable(self: Object, writer: anytype) !void { 2643 | if (self.symtab_lc == null) { 2644 | try writer.writeAll("\nNo symbol table found in the object file.\n"); 2645 | return; 2646 | } 2647 | 2648 | try writer.writeAll("\nSymbol table:\n"); 2649 | 2650 | for (self.symtab) |sym| { 2651 | const sym_name = self.getString(sym.n_strx); 2652 | 2653 | if (sym.stab()) { 2654 | const tt = switch (sym.n_type) { 2655 | macho.N_SO => "SO", 2656 | macho.N_OSO => "OSO", 2657 | macho.N_BNSYM => "BNSYM", 2658 | macho.N_ENSYM => "ENSYM", 2659 | macho.N_FUN => "FUN", 2660 | macho.N_GSYM => "GSYM", 2661 | macho.N_STSYM => "STSYM", 2662 | else => "TODO", 2663 | }; 2664 | try writer.print(" 0x{x:0>16}", .{sym.n_value}); 2665 | if (sym.n_sect > 0) { 2666 | const sect = self.getSectionByIndex(sym.n_sect); 2667 | try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() }); 2668 | } 2669 | try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name }); 2670 | } else if (sym.sect()) { 2671 | const sect = self.getSectionByIndex(sym.n_sect); 2672 | try writer.print(" 0x{x:0>16} ({s},{s})", .{ 2673 | sym.n_value, 2674 | sect.segName(), 2675 | sect.sectName(), 2676 | }); 2677 | 2678 | if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]"); 2679 | if (sym.weakDef()) try writer.writeAll(" weak"); 2680 | if (sym.weakRef()) try writer.writeAll(" weak-ref"); 2681 | 2682 | if (sym.ext()) { 2683 | if (sym.pext()) try writer.writeAll(" private"); 2684 | try writer.writeAll(" external"); 2685 | } else { 2686 | try writer.writeAll(" non-external"); 2687 | if (sym.pext()) try writer.writeAll(" (was private external)"); 2688 | } 2689 | 2690 | try writer.print(" {s}\n", .{sym_name}); 2691 | } else if (sym.tentative()) { 2692 | const alignment = (sym.n_desc >> 8) & 0x0F; 2693 | try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment }); 2694 | 2695 | if (sym.ext()) { 2696 | try writer.writeAll(" external"); 2697 | } else { 2698 | try writer.writeAll(" non-external"); 2699 | } 2700 | 2701 | try writer.print(" {s}\n", .{sym_name}); 2702 | } else { 2703 | try writer.print(" {s: >16} (undefined)", .{" "}); 2704 | 2705 | if (sym.weakRef()) try writer.writeAll(" weak-ref"); 2706 | if (sym.ext()) try writer.writeAll(" external"); 2707 | 2708 | try writer.print(" {s}", .{sym_name}); 2709 | 2710 | const ord = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER); 2711 | switch (ord) { 2712 | macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP => try writer.writeAll(" (from flat lookup)"), 2713 | macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE => try writer.writeAll(" (from main executable)"), 2714 | macho.BIND_SPECIAL_DYLIB_SELF => try writer.writeAll(" (from self)"), 2715 | else => { 2716 | const dylib = self.getDylibByIndex(@as(u16, @intCast(ord))); 2717 | const full_path = dylib.getDylibPathName(); 2718 | const leaf_path = std.fs.path.basename(full_path); 2719 | var name = leaf_path; 2720 | while (true) { 2721 | const ext = std.fs.path.extension(name); 2722 | if (ext.len == 0) break; 2723 | name = name[0 .. name.len - ext.len]; 2724 | } 2725 | try writer.print(" (from {s})", .{name}); 2726 | }, 2727 | } 2728 | 2729 | try writer.writeByte('\n'); 2730 | } 2731 | } 2732 | } 2733 | 2734 | pub fn printStringTable(self: Object, writer: anytype) !void { 2735 | if (self.symtab_lc == null or self.symtab_lc.?.strsize == 0) { 2736 | try writer.writeAll("\nNo string table found in the object file.\n"); 2737 | return; 2738 | } 2739 | try writer.writeAll("\nString table:\n"); 2740 | 2741 | var strings = std.ArrayList(struct { pos: usize, str: []const u8 }).init(self.gpa); 2742 | defer strings.deinit(); 2743 | 2744 | var pos: usize = 0; 2745 | while (pos < self.strtab.len) { 2746 | const str = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + pos)), 0); 2747 | try strings.append(.{ .pos = pos, .str = str }); 2748 | pos += str.len + 1; 2749 | } 2750 | 2751 | for (strings.items) |str| { 2752 | try writer.print("{d}: {s}\n", .{ str.pos, str.str }); 2753 | } 2754 | } 2755 | 2756 | pub fn printIndirectSymbolTable(self: Object, writer: anytype) !void { 2757 | if (self.dysymtab_lc == null or self.dysymtab_lc.?.nindirectsyms == 0) { 2758 | try writer.writeAll("\nNo indirect symbol table found in the object file.\n"); 2759 | return; 2760 | } 2761 | try writer.writeAll("\nIndirect symbol table:\n"); 2762 | 2763 | var sects = std.ArrayList(macho.section_64).init(self.gpa); 2764 | defer sects.deinit(); 2765 | try sects.ensureUnusedCapacity(3); 2766 | 2767 | if (self.getSectionByName("__TEXT", "__stubs")) |sect| sects.appendAssumeCapacity(sect); 2768 | if (self.getSectionByName("__DATA_CONST", "__got")) |sect| sects.appendAssumeCapacity(sect); 2769 | if (self.getSectionByName("__DATA", "__la_symbol_ptr")) |sect| sects.appendAssumeCapacity(sect); 2770 | 2771 | const sortFn = struct { 2772 | fn sortFn(ctx: void, lhs: macho.section_64, rhs: macho.section_64) bool { 2773 | _ = ctx; 2774 | return lhs.reserved1 < rhs.reserved1; 2775 | } 2776 | }.sortFn; 2777 | mem.sort(macho.section_64, sects.items, {}, sortFn); 2778 | 2779 | const lc = self.dysymtab_lc.?; 2780 | const indsymtab = @as([*]align(1) const u32, @ptrCast(self.data.ptr + lc.indirectsymoff))[0..lc.nindirectsyms]; 2781 | 2782 | var i: usize = 0; 2783 | while (i < sects.items.len) : (i += 1) { 2784 | const sect = sects.items[i]; 2785 | const start = sect.reserved1; 2786 | const end = if (i + 1 >= sects.items.len) indsymtab.len else sects.items[i + 1].reserved1; 2787 | const entry_size = blk: { 2788 | if (mem.eql(u8, sect.sectName(), "__stubs")) break :blk sect.reserved2; 2789 | break :blk @sizeOf(u64); 2790 | }; 2791 | 2792 | try writer.print("Indirect symbols for ({s},{s}) {d} entries\n", .{ sect.segName(), sect.sectName(), end - start }); 2793 | for (indsymtab[start..end], 0..) |index, j| { 2794 | const sym = self.symtab[index]; 2795 | const addr = sect.addr + entry_size * j; 2796 | try writer.print("0x{x} {d} {s}\n", .{ addr, index, self.getString(sym.n_strx) }); 2797 | } 2798 | } 2799 | } 2800 | 2801 | pub fn printDataInCode(self: Object, writer: anytype) !void { 2802 | const lc = self.data_in_code_lc orelse { 2803 | try writer.writeAll("\nNo data-in-code entries found in the object file.\n"); 2804 | return; 2805 | }; 2806 | try writer.writeAll("\nData-in-code entries:\n"); 2807 | try writer.writeAll(" offset length kind\n"); 2808 | 2809 | const dice = dice: { 2810 | const raw = self.data[lc.dataoff..][0..lc.datasize]; 2811 | const nentries = @divExact(lc.datasize, @sizeOf(macho.data_in_code_entry)); 2812 | break :dice @as([*]align(1) const macho.data_in_code_entry, @ptrCast(raw.ptr))[0..nentries]; 2813 | }; 2814 | 2815 | for (dice) |entry| { 2816 | const kind = switch (entry.kind) { 2817 | else => "UNKNOWN", 2818 | 1 => "DATA", 2819 | 2 => "JUMP_TABLE8", 2820 | 3 => "JUMP_TABLE16", 2821 | 4 => "JUMP_TABLE32", 2822 | 5 => "ABS_JUMP_TABLE32", 2823 | }; 2824 | try writer.print("{x:0>8} {d: >6} {s}", .{ entry.offset, entry.length, kind }); 2825 | 2826 | if (self.verbose) { 2827 | const seg = if (self.header.filetype == macho.MH_EXECUTE) 2828 | self.getSegmentByName("__TEXT").? 2829 | else 2830 | self.segments.items[0]; 2831 | const name = if (self.findSymbolByAddress(seg.vmaddr + entry.offset)) |sym| 2832 | self.getString(sym.n_strx) 2833 | else 2834 | "INVALID TARGET OFFSET"; 2835 | try writer.print(" {s}", .{name}); 2836 | } 2837 | 2838 | try writer.writeByte('\n'); 2839 | } 2840 | } 2841 | 2842 | fn getLoadCommandsIterator(self: Object) macho.LoadCommandIterator { 2843 | const data = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds]; 2844 | return .{ 2845 | .ncmds = self.header.ncmds, 2846 | .buffer = data, 2847 | }; 2848 | } 2849 | 2850 | fn findSymbolByAddress(self: *const Object, addr: u64) ?macho.nlist_64 { 2851 | for (self.sorted_symtab.items) |idx| { 2852 | const sym = idx.getSymbol(self); 2853 | if (sym.n_value <= addr and addr < sym.n_value + idx.size) return sym; 2854 | } 2855 | return null; 2856 | } 2857 | 2858 | fn getString(self: *const Object, off: u32) []const u8 { 2859 | assert(off < self.strtab.len); 2860 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0); 2861 | } 2862 | 2863 | pub fn getSectionByName(self: Object, segname: []const u8, sectname: []const u8) ?macho.section_64 { 2864 | var it = self.getLoadCommandsIterator(); 2865 | while (it.next()) |lc| switch (lc.cmd()) { 2866 | .SEGMENT_64 => { 2867 | const sections = lc.getSections(); 2868 | for (sections) |sect| { 2869 | if (mem.eql(u8, segname, sect.segName()) and mem.eql(u8, sectname, sect.sectName())) 2870 | return sect; 2871 | } 2872 | }, 2873 | else => {}, 2874 | }; 2875 | return null; 2876 | } 2877 | 2878 | fn getSectionByAddress(self: Object, addr: u64) ?macho.section_64 { 2879 | var it = self.getLoadCommandsIterator(); 2880 | const lc = while (it.next()) |lc| switch (lc.cmd()) { 2881 | .SEGMENT_64 => { 2882 | const seg = lc.cast(macho.segment_command_64).?; 2883 | if (seg.vmaddr <= addr and addr < seg.vmaddr + seg.vmsize) { 2884 | break lc; 2885 | } 2886 | }, 2887 | else => continue, 2888 | } else return null; 2889 | const sect = for (lc.getSections()) |sect| { 2890 | if (sect.addr <= addr and addr < sect.addr + sect.size) { 2891 | break sect; 2892 | } 2893 | } else return null; 2894 | return sect; 2895 | } 2896 | 2897 | fn getSectionByIndex(self: Object, index: u8) macho.section_64 { 2898 | var count: u8 = 1; 2899 | var it = self.getLoadCommandsIterator(); 2900 | while (it.next()) |lc| switch (lc.cmd()) { 2901 | .SEGMENT_64 => { 2902 | const sects = lc.getSections(); 2903 | if (index > count + sects.len) { 2904 | count += @as(u8, @intCast(sects.len)); 2905 | continue; 2906 | } 2907 | 2908 | for (sects) |sect| { 2909 | if (index == count) return sect; 2910 | count += 1; 2911 | } 2912 | }, 2913 | else => {}, 2914 | } else unreachable; 2915 | } 2916 | 2917 | fn getGotPointerAtIndex(self: Object, index: usize) u64 { 2918 | const sect = self.getSectionByName("__DATA_CONST", "__got").?; 2919 | const data = self.data[sect.offset..][0..sect.size]; 2920 | const ptr = @as(*align(1) const u64, @ptrCast(data[index * 8 ..])).*; 2921 | 2922 | const mask = 0xFFFF000000000000; // TODO I guessed the value of the mask, so verify! 2923 | switch ((mask & ptr) >> 48) { 2924 | 0x0 => { 2925 | // Old-style GOT with actual pointer values 2926 | return ptr; 2927 | }, 2928 | 0x10 => { 2929 | // indirect local 2930 | const offset = 0xFFFFFFFFFFFF & ptr; 2931 | const seg = self.getSegmentByName("__TEXT").?; 2932 | return seg.vmaddr + offset; 2933 | }, 2934 | else => { 2935 | // TODO parse opcodes 2936 | return 0x0; 2937 | }, 2938 | } 2939 | } 2940 | 2941 | fn getSegmentByName(self: Object, segname: []const u8) ?macho.segment_command_64 { 2942 | for (self.segments.items) |seg| { 2943 | if (mem.eql(u8, segname, seg.segName())) { 2944 | return seg; 2945 | } 2946 | } 2947 | return null; 2948 | } 2949 | 2950 | fn getSegmentByAddress(self: Object, addr: u64) ?macho.segment_command_64 { 2951 | var it = self.getLoadCommandsIterator(); 2952 | while (it.next()) |lc| switch (lc.cmd()) { 2953 | .SEGMENT_64 => { 2954 | const seg = lc.cast(macho.segment_command_64).?; 2955 | if (seg.vmaddr <= addr and addr < seg.vmaddr + seg.vmsize) { 2956 | return seg; 2957 | } 2958 | }, 2959 | else => continue, 2960 | } else return null; 2961 | } 2962 | 2963 | fn sliceContentsByAddress(self: Object, addr: u64, size: u64) ?[]const u8 { 2964 | var it = self.getLoadCommandsIterator(); 2965 | const lc = while (it.next()) |lc| switch (lc.cmd()) { 2966 | .SEGMENT_64 => { 2967 | const seg = lc.cast(macho.segment_command_64).?; 2968 | if (seg.vmaddr <= addr and addr < seg.vmaddr + seg.vmsize) { 2969 | break lc; 2970 | } 2971 | }, 2972 | else => continue, 2973 | } else return null; 2974 | const sect = for (lc.getSections()) |sect| { 2975 | if (sect.addr <= addr and addr < sect.addr + sect.size) { 2976 | break sect; 2977 | } 2978 | } else return null; 2979 | const offset = addr - sect.addr + sect.offset; 2980 | assert(offset + size < sect.offset + sect.size); 2981 | return self.data[offset..][0..size]; 2982 | } 2983 | 2984 | fn getDylibByIndex(self: Object, index: u16) macho.LoadCommandIterator.LoadCommand { 2985 | var count: u16 = 1; 2986 | var it = self.getLoadCommandsIterator(); 2987 | while (it.next()) |lc| switch (lc.cmd()) { 2988 | .LOAD_DYLIB, 2989 | .LOAD_WEAK_DYLIB, 2990 | .LOAD_UPWARD_DYLIB, 2991 | .REEXPORT_DYLIB, 2992 | => { 2993 | if (count == index) return lc; 2994 | count += 1; 2995 | }, 2996 | else => {}, 2997 | } else unreachable; 2998 | } 2999 | 3000 | fn getDylibNameByIndex(self: Object, index: u16) []const u8 { 3001 | const dylib = self.getDylibByIndex(index); 3002 | const full_path = dylib.getDylibPathName(); 3003 | const leaf_path = std.fs.path.basename(full_path); 3004 | var name = leaf_path; 3005 | while (true) { 3006 | const ext = std.fs.path.extension(name); 3007 | if (ext.len == 0) break; 3008 | name = name[0 .. name.len - ext.len]; 3009 | } 3010 | return name; 3011 | } 3012 | 3013 | fn filterRelocsByAddress( 3014 | relocs: []align(1) const macho.relocation_info, 3015 | address: u64, 3016 | size: u64, 3017 | ) []align(1) const macho.relocation_info { 3018 | var i: usize = 0; 3019 | const start = while (i < relocs.len) : (i += 1) { 3020 | const rel = relocs[i]; 3021 | if (rel.r_address < address + size) break i; 3022 | } else relocs.len; 3023 | 3024 | i = start; 3025 | const end = while (i < relocs.len) : (i += 1) { 3026 | const rel = relocs[i]; 3027 | if (rel.r_address < address) break i; 3028 | } else relocs.len; 3029 | 3030 | return relocs[start..end]; 3031 | } 3032 | 3033 | const UnwindEncodingArm64 = union(enum) { 3034 | frame: Frame, 3035 | frameless: Frameless, 3036 | dwarf: Dwarf, 3037 | 3038 | const Frame = packed struct { 3039 | x_reg_pairs: packed struct { 3040 | x19_x20: u1, 3041 | x21_x22: u1, 3042 | x23_x24: u1, 3043 | x25_x26: u1, 3044 | x27_x28: u1, 3045 | }, 3046 | d_reg_pairs: packed struct { 3047 | d8_d9: u1, 3048 | d10_d11: u1, 3049 | d12_d13: u1, 3050 | d14_d15: u1, 3051 | }, 3052 | unused: u15, 3053 | mode: Mode = .frame, 3054 | personality_index: u2, 3055 | has_lsda: u1, 3056 | start: u1, 3057 | }; 3058 | 3059 | const Frameless = packed struct { 3060 | unused: u12 = 0, 3061 | stack_size: u12, 3062 | mode: Mode = .frameless, 3063 | personality_index: u2, 3064 | has_lsda: u1, 3065 | start: u1, 3066 | }; 3067 | 3068 | const Dwarf = packed struct { 3069 | section_offset: u24, 3070 | mode: Mode = .dwarf, 3071 | personality_index: u2, 3072 | has_lsda: u1, 3073 | start: u1, 3074 | }; 3075 | 3076 | const Mode = enum(u4) { 3077 | frameless = 0x2, 3078 | dwarf = 0x3, 3079 | frame = 0x4, 3080 | _, 3081 | }; 3082 | 3083 | const mode_mask: u32 = 0x0F000000; 3084 | 3085 | fn fromU32(enc: u32) !UnwindEncodingArm64 { 3086 | const m = (enc & mode_mask) >> 24; 3087 | return switch (@as(Mode, @enumFromInt(m))) { 3088 | .frame => .{ .frame = @as(Frame, @bitCast(enc)) }, 3089 | .frameless => .{ .frameless = @as(Frameless, @bitCast(enc)) }, 3090 | .dwarf => .{ .dwarf = @as(Dwarf, @bitCast(enc)) }, 3091 | else => return error.UnknownEncoding, 3092 | }; 3093 | } 3094 | 3095 | fn toU32(enc: UnwindEncodingArm64) u32 { 3096 | return switch (enc) { 3097 | inline else => |x| @as(u32, @bitCast(x)), 3098 | }; 3099 | } 3100 | 3101 | fn start(enc: UnwindEncodingArm64) bool { 3102 | return switch (enc) { 3103 | inline else => |x| x.start == 0b1, 3104 | }; 3105 | } 3106 | 3107 | fn hasLsda(enc: UnwindEncodingArm64) bool { 3108 | return switch (enc) { 3109 | inline else => |x| x.has_lsda == 0b1, 3110 | }; 3111 | } 3112 | 3113 | fn personalityIndex(enc: UnwindEncodingArm64) u2 { 3114 | return switch (enc) { 3115 | inline else => |x| x.personality_index, 3116 | }; 3117 | } 3118 | 3119 | fn mode(enc: UnwindEncodingArm64) Mode { 3120 | return switch (enc) { 3121 | inline else => |x| x.mode, 3122 | }; 3123 | } 3124 | }; 3125 | 3126 | pub const UnwindEncodingX86_64 = union(enum) { 3127 | frame: Frame, 3128 | frameless: Frameless, 3129 | dwarf: Dwarf, 3130 | 3131 | pub const Frame = packed struct { 3132 | frame_registers: u15, 3133 | unused: u1 = 0, 3134 | frame_offset: u8, 3135 | mode: Mode = .ebp_frame, 3136 | personality_index: u2, 3137 | has_lsda: u1, 3138 | start: u1, 3139 | }; 3140 | 3141 | pub const Frameless = packed struct { 3142 | stack_reg_permutation: u10, 3143 | stack_reg_count: u3, 3144 | stack_adjust: u3, 3145 | stack_size: u8, 3146 | mode: Mode, 3147 | personality_index: u2, 3148 | has_lsda: u1, 3149 | start: u1, 3150 | }; 3151 | 3152 | pub const Dwarf = packed struct { 3153 | section_offset: u24, 3154 | mode: Mode = .dwarf, 3155 | personality_index: u2, 3156 | has_lsda: u1, 3157 | start: u1, 3158 | }; 3159 | 3160 | pub const Mode = enum(u4) { 3161 | ebp_frame = 0x1, 3162 | stack_immd = 0x2, 3163 | stack_ind = 0x3, 3164 | dwarf = 0x4, 3165 | _, 3166 | }; 3167 | 3168 | pub const mode_mask: u32 = 0x0F000000; 3169 | 3170 | pub fn fromU32(enc: u32) !UnwindEncodingX86_64 { 3171 | const m = (enc & mode_mask) >> 24; 3172 | return switch (@as(Mode, @enumFromInt(m))) { 3173 | .ebp_frame => .{ .frame = @as(Frame, @bitCast(enc)) }, 3174 | .stack_immd, .stack_ind => .{ .frameless = @as(Frameless, @bitCast(enc)) }, 3175 | .dwarf => .{ .dwarf = @as(Dwarf, @bitCast(enc)) }, 3176 | else => return error.UnknownEncoding, 3177 | }; 3178 | } 3179 | 3180 | pub fn toU32(enc: UnwindEncodingX86_64) u32 { 3181 | return switch (enc) { 3182 | inline else => |x| @as(u32, @bitCast(x)), 3183 | }; 3184 | } 3185 | 3186 | pub fn start(enc: UnwindEncodingX86_64) bool { 3187 | return switch (enc) { 3188 | inline else => |x| x.start == 0b1, 3189 | }; 3190 | } 3191 | 3192 | pub fn hasLsda(enc: UnwindEncodingX86_64) bool { 3193 | return switch (enc) { 3194 | inline else => |x| x.has_lsda == 0b1, 3195 | }; 3196 | } 3197 | 3198 | pub fn personalityIndex(enc: UnwindEncodingX86_64) u2 { 3199 | return switch (enc) { 3200 | inline else => |x| x.personality_index, 3201 | }; 3202 | } 3203 | 3204 | pub fn mode(enc: UnwindEncodingX86_64) Mode { 3205 | return switch (enc) { 3206 | inline else => |x| x.mode, 3207 | }; 3208 | } 3209 | }; 3210 | 3211 | const SymbolAtIndex = struct { 3212 | index: u32, 3213 | size: u64, 3214 | 3215 | const Context = *const Object; 3216 | 3217 | fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 { 3218 | return ctx.symtab[self.index]; 3219 | } 3220 | 3221 | fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 { 3222 | const off = self.getSymbol(ctx).n_strx; 3223 | return ctx.getString(off); 3224 | } 3225 | 3226 | fn getSymbolSeniority(self: SymbolAtIndex, ctx: Context) u2 { 3227 | const sym = self.getSymbol(ctx); 3228 | if (sym.ext()) return 1; 3229 | const sym_name = self.getSymbolName(ctx); 3230 | if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3; 3231 | return 2; 3232 | } 3233 | 3234 | fn lessThan(ctx: Context, lhs_index: SymbolAtIndex, rhs_index: SymbolAtIndex) bool { 3235 | const lhs = lhs_index.getSymbol(ctx); 3236 | const rhs = rhs_index.getSymbol(ctx); 3237 | if (lhs.n_value == rhs.n_value) { 3238 | if (lhs.n_sect == rhs.n_sect) { 3239 | const lhs_senior = lhs_index.getSymbolSeniority(ctx); 3240 | const rhs_senior = rhs_index.getSymbolSeniority(ctx); 3241 | if (lhs_senior == rhs_senior) { 3242 | return lessThanByNStrx(ctx, lhs_index, rhs_index); 3243 | } else return lhs_senior < rhs_senior; 3244 | } else return lhs.n_sect < rhs.n_sect; 3245 | } else return lhs.n_value < rhs.n_value; 3246 | } 3247 | 3248 | fn lessThanByNStrx(ctx: Context, lhs: SymbolAtIndex, rhs: SymbolAtIndex) bool { 3249 | return lhs.getSymbol(ctx).n_strx < rhs.getSymbol(ctx).n_strx; 3250 | } 3251 | }; 3252 | 3253 | const Arch = enum { 3254 | aarch64, 3255 | x86_64, 3256 | unknown, 3257 | }; 3258 | 3259 | const Object = @This(); 3260 | 3261 | const std = @import("std"); 3262 | const builtin = @import("builtin"); 3263 | const assert = std.debug.assert; 3264 | const fs = std.fs; 3265 | const io = std.io; 3266 | const mem = std.mem; 3267 | const macho = std.macho; 3268 | 3269 | const Allocator = std.mem.Allocator; 3270 | const ZigKit = @import("ZigKit"); 3271 | const CMSDecoder = ZigKit.Security.CMSDecoder; 3272 | --------------------------------------------------------------------------------