| 1 | //! `afs-ld --dump <path>` — otool-style summary of a Mach-O object. |
| 2 | //! |
| 3 | //! Sprint 1 prints the `mach_header_64` + every load command in `header.ncmds`. |
| 4 | //! Section bodies, symbols, strings, and relocations become visible as Sprint 2 |
| 5 | //! and later sprints bring them into the reader's model. |
| 6 | |
| 7 | use std::io::{self, Write}; |
| 8 | use std::path::Path; |
| 9 | |
| 10 | use crate::archive::{Archive, Flavor, SpecialMember}; |
| 11 | use crate::input::ObjectFile; |
| 12 | use crate::macho::constants::*; |
| 13 | use crate::macho::dylib::{DylibFile, DylibLoadKind}; |
| 14 | use crate::macho::exports::ExportKind; |
| 15 | use crate::macho::reader::{ |
| 16 | BuildVersionCmd, DyldInfoCmd, DylibCmd, DysymtabCmd, LinkEditDataCmd, LoadCommand, |
| 17 | MachHeader64, RpathCmd, Section64Header, Segment64, SymtabCmd, |
| 18 | }; |
| 19 | use crate::macho::tbd::parse_tbd; |
| 20 | use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind}; |
| 21 | use crate::section::InputSection; |
| 22 | use crate::symbol::{InputSymbol, SymKind}; |
| 23 | |
| 24 | pub fn dump_archive_file(path: &Path) -> io::Result<()> { |
| 25 | let bytes = std::fs::read(path)?; |
| 26 | let ar = Archive::open(path, &bytes) |
| 27 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; |
| 28 | let out = io::stdout(); |
| 29 | let mut h = out.lock(); |
| 30 | let flavor = match ar.flavor { |
| 31 | Flavor::Bsd => "BSD", |
| 32 | Flavor::Sysv => "SysV", |
| 33 | Flavor::GnuThin => "GNU-thin", |
| 34 | }; |
| 35 | writeln!(h, "{}:", path.display())?; |
| 36 | writeln!( |
| 37 | h, |
| 38 | "archive: flavor={flavor} members={} symbols={}", |
| 39 | ar.members().len(), |
| 40 | ar.symbol_index().map(|i| i.len()).unwrap_or(0), |
| 41 | )?; |
| 42 | writeln!(h, "Members:")?; |
| 43 | for (i, m) in ar.members().iter().enumerate() { |
| 44 | let kind = match m.special { |
| 45 | SpecialMember::None => "obj", |
| 46 | SpecialMember::BsdSymIndex => "bsd-symindex", |
| 47 | SpecialMember::SysvSymIndex => "sysv-symindex", |
| 48 | SpecialMember::SysvLongNames => "sysv-longnames", |
| 49 | }; |
| 50 | writeln!( |
| 51 | h, |
| 52 | " [{i}] @0x{:x} {kind:<16} {} ({} bytes)", |
| 53 | m.header_offset, |
| 54 | m.name, |
| 55 | m.body.len() |
| 56 | )?; |
| 57 | } |
| 58 | if let Some(idx) = ar.symbol_index() { |
| 59 | writeln!(h, "Symbols ({}):", idx.len())?; |
| 60 | for (i, e) in idx.entries.iter().enumerate().take(16) { |
| 61 | writeln!( |
| 62 | h, |
| 63 | " [{i}] {} -> member@0x{:x}", |
| 64 | e.name, e.member_header_offset |
| 65 | )?; |
| 66 | } |
| 67 | if idx.len() > 16 { |
| 68 | writeln!(h, " ... ({} more)", idx.len() - 16)?; |
| 69 | } |
| 70 | } |
| 71 | Ok(()) |
| 72 | } |
| 73 | |
| 74 | pub fn dump_tbd_file(path: &Path) -> io::Result<()> { |
| 75 | let src = std::fs::read_to_string(path)?; |
| 76 | let docs = |
| 77 | parse_tbd(&src).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; |
| 78 | let out = io::stdout(); |
| 79 | let mut h = out.lock(); |
| 80 | writeln!(h, "{}:", path.display())?; |
| 81 | writeln!(h, "tbd: documents={}", docs.len())?; |
| 82 | for (i, tbd) in docs.iter().enumerate() { |
| 83 | let targets: Vec<String> = tbd.targets.iter().map(|t| t.as_string()).collect(); |
| 84 | writeln!( |
| 85 | h, |
| 86 | " [{i}] install_name={:?} targets=[{}] current={} compat={}", |
| 87 | tbd.install_name, |
| 88 | targets.join(", "), |
| 89 | tbd.current_version.as_deref().unwrap_or("-"), |
| 90 | tbd.compatibility_version.as_deref().unwrap_or("-") |
| 91 | )?; |
| 92 | if !tbd.reexported_libraries.is_empty() { |
| 93 | let total: usize = tbd.reexported_libraries.iter().map(|s| s.value.len()).sum(); |
| 94 | writeln!(h, " reexported-libraries: {total}")?; |
| 95 | } |
| 96 | let export_syms: usize = tbd.exports.iter().map(|s| s.value.total()).sum(); |
| 97 | if export_syms > 0 { |
| 98 | writeln!(h, " exports: {export_syms} symbols total")?; |
| 99 | } |
| 100 | } |
| 101 | Ok(()) |
| 102 | } |
| 103 | |
| 104 | pub fn dump_dylib_file(path: &Path) -> io::Result<()> { |
| 105 | let bytes = std::fs::read(path)?; |
| 106 | let dy = DylibFile::parse(path, &bytes) |
| 107 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; |
| 108 | let out = io::stdout(); |
| 109 | let mut h = out.lock(); |
| 110 | writeln!(h, "{}:", path.display())?; |
| 111 | writeln!( |
| 112 | h, |
| 113 | "dylib: install_name={:?} current={} compat={}", |
| 114 | dy.install_name, |
| 115 | version_str(dy.current_version), |
| 116 | version_str(dy.compatibility_version) |
| 117 | )?; |
| 118 | writeln!(h, "Dependencies ({}):", dy.dependencies.len())?; |
| 119 | for d in &dy.dependencies { |
| 120 | let kind = match d.kind { |
| 121 | DylibLoadKind::Normal => "normal", |
| 122 | DylibLoadKind::Weak => "weak", |
| 123 | DylibLoadKind::Reexport => "reexport", |
| 124 | DylibLoadKind::Upward => "upward", |
| 125 | }; |
| 126 | writeln!( |
| 127 | h, |
| 128 | " [{}] {kind:<8} {} current={} compat={}", |
| 129 | d.ordinal, |
| 130 | d.install_name, |
| 131 | version_str(d.current_version), |
| 132 | version_str(d.compatibility_version) |
| 133 | )?; |
| 134 | } |
| 135 | if !dy.rpaths.is_empty() { |
| 136 | writeln!(h, "Rpaths ({}):", dy.rpaths.len())?; |
| 137 | for (i, p) in dy.rpaths.iter().enumerate() { |
| 138 | writeln!(h, " [{i}] {p}")?; |
| 139 | } |
| 140 | } |
| 141 | let entries = dy |
| 142 | .exports |
| 143 | .entries() |
| 144 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; |
| 145 | writeln!(h, "Exports ({}):", entries.len())?; |
| 146 | for (i, e) in entries.iter().enumerate().take(32) { |
| 147 | let rhs = match &e.kind { |
| 148 | ExportKind::Regular { address } => format!("addr=0x{address:x}"), |
| 149 | ExportKind::ThreadLocal { address } => format!("tlv addr=0x{address:x}"), |
| 150 | ExportKind::Absolute { address } => format!("abs=0x{address:x}"), |
| 151 | ExportKind::Reexport { |
| 152 | ordinal, |
| 153 | imported_name, |
| 154 | } => { |
| 155 | if imported_name.is_empty() { |
| 156 | format!("reexport from dylib#{ordinal}") |
| 157 | } else { |
| 158 | format!("reexport from dylib#{ordinal} as {imported_name}") |
| 159 | } |
| 160 | } |
| 161 | ExportKind::StubAndResolver { stub, resolver } => { |
| 162 | format!("stub=0x{stub:x} resolver=0x{resolver:x}") |
| 163 | } |
| 164 | }; |
| 165 | writeln!(h, " [{i}] {:<40} {rhs}", e.name)?; |
| 166 | } |
| 167 | if entries.len() > 32 { |
| 168 | writeln!(h, " ... ({} more)", entries.len() - 32)?; |
| 169 | } |
| 170 | Ok(()) |
| 171 | } |
| 172 | |
| 173 | pub fn dump_file(path: &Path) -> io::Result<()> { |
| 174 | let bytes = std::fs::read(path)?; |
| 175 | let obj = ObjectFile::parse(path, &bytes) |
| 176 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; |
| 177 | let out = io::stdout(); |
| 178 | let mut h = out.lock(); |
| 179 | writeln!(h, "{}:", path.display())?; |
| 180 | write_header(&mut h, &obj.header)?; |
| 181 | for (i, cmd) in obj.commands.iter().enumerate() { |
| 182 | write_command(&mut h, i, cmd)?; |
| 183 | } |
| 184 | write_sections(&mut h, &obj.sections)?; |
| 185 | write_symbols(&mut h, &obj)?; |
| 186 | Ok(()) |
| 187 | } |
| 188 | |
| 189 | fn write_header(w: &mut impl Write, hdr: &MachHeader64) -> io::Result<()> { |
| 190 | writeln!( |
| 191 | w, |
| 192 | "mach_header_64: {} {} ncmds={} sizeofcmds={} flags=0x{:08x}", |
| 193 | cpu_name(hdr.cputype), |
| 194 | filetype_name(hdr.filetype), |
| 195 | hdr.ncmds, |
| 196 | hdr.sizeofcmds, |
| 197 | hdr.flags |
| 198 | ) |
| 199 | } |
| 200 | |
| 201 | fn write_command(w: &mut impl Write, idx: usize, cmd: &LoadCommand) -> io::Result<()> { |
| 202 | writeln!( |
| 203 | w, |
| 204 | "Load command {}: {} cmdsize={}", |
| 205 | idx, |
| 206 | cmd_name(cmd.cmd()), |
| 207 | cmd.cmdsize() |
| 208 | )?; |
| 209 | match cmd { |
| 210 | LoadCommand::Segment64(s) => write_segment64(w, s), |
| 211 | LoadCommand::Symtab(s) => write_symtab(w, s), |
| 212 | LoadCommand::Dysymtab(d) => write_dysymtab(w, d), |
| 213 | LoadCommand::BuildVersion(b) => write_build_version(w, b), |
| 214 | LoadCommand::LinkerOptimizationHint(l) => write_linkedit_data(w, l, "LOH"), |
| 215 | LoadCommand::Dylib(d) => write_dylib(w, d), |
| 216 | LoadCommand::Rpath(r) => write_rpath(w, r), |
| 217 | LoadCommand::DyldInfoOnly(d) => write_dyld_info(w, d), |
| 218 | LoadCommand::DyldExportsTrie(l) => write_linkedit_data(w, l, "EXPORTS_TRIE"), |
| 219 | LoadCommand::DyldChainedFixups(l) => write_linkedit_data(w, l, "CHAINED_FIXUPS"), |
| 220 | LoadCommand::Raw { cmd, data, .. } => { |
| 221 | writeln!(w, " (raw — cmd=0x{:x}, payload {} bytes)", cmd, data.len()) |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | fn write_dylib(w: &mut impl Write, d: &DylibCmd) -> io::Result<()> { |
| 227 | writeln!( |
| 228 | w, |
| 229 | " name={:?} timestamp={} current={} compat={}", |
| 230 | d.name, |
| 231 | d.timestamp, |
| 232 | version_str(d.current_version), |
| 233 | version_str(d.compatibility_version) |
| 234 | ) |
| 235 | } |
| 236 | |
| 237 | fn write_rpath(w: &mut impl Write, r: &RpathCmd) -> io::Result<()> { |
| 238 | writeln!(w, " path={:?}", r.path) |
| 239 | } |
| 240 | |
| 241 | fn write_dyld_info(w: &mut impl Write, d: &DyldInfoCmd) -> io::Result<()> { |
| 242 | writeln!( |
| 243 | w, |
| 244 | " rebase=@{}..+{} bind=@{}..+{} weak_bind=@{}..+{} lazy_bind=@{}..+{} export=@{}..+{}", |
| 245 | d.rebase_off, |
| 246 | d.rebase_size, |
| 247 | d.bind_off, |
| 248 | d.bind_size, |
| 249 | d.weak_bind_off, |
| 250 | d.weak_bind_size, |
| 251 | d.lazy_bind_off, |
| 252 | d.lazy_bind_size, |
| 253 | d.export_off, |
| 254 | d.export_size |
| 255 | ) |
| 256 | } |
| 257 | |
| 258 | fn write_segment64(w: &mut impl Write, s: &Segment64) -> io::Result<()> { |
| 259 | writeln!( |
| 260 | w, |
| 261 | " segname={:<16} vmaddr=0x{:x} vmsize=0x{:x} fileoff={} filesize={} maxprot={} initprot={} nsects={} flags=0x{:x}", |
| 262 | format!("\"{}\"", s.segname_str()), |
| 263 | s.vmaddr, |
| 264 | s.vmsize, |
| 265 | s.fileoff, |
| 266 | s.filesize, |
| 267 | prot_str(s.maxprot), |
| 268 | prot_str(s.initprot), |
| 269 | s.sections.len(), |
| 270 | s.flags |
| 271 | )?; |
| 272 | for (i, sec) in s.sections.iter().enumerate() { |
| 273 | write_section(w, i, sec)?; |
| 274 | } |
| 275 | Ok(()) |
| 276 | } |
| 277 | |
| 278 | fn write_section(w: &mut impl Write, idx: usize, s: &Section64Header) -> io::Result<()> { |
| 279 | writeln!( |
| 280 | w, |
| 281 | " Section {}: {},{} addr=0x{:x} size=0x{:x} offset={} align=2^{} reloff={} nreloc={} flags=0x{:08x}", |
| 282 | idx, |
| 283 | s.segname_str(), |
| 284 | s.sectname_str(), |
| 285 | s.addr, |
| 286 | s.size, |
| 287 | s.offset, |
| 288 | s.align, |
| 289 | s.reloff, |
| 290 | s.nreloc, |
| 291 | s.flags |
| 292 | ) |
| 293 | } |
| 294 | |
| 295 | fn write_symtab(w: &mut impl Write, s: &SymtabCmd) -> io::Result<()> { |
| 296 | writeln!( |
| 297 | w, |
| 298 | " symoff={} nsyms={} stroff={} strsize={}", |
| 299 | s.symoff, s.nsyms, s.stroff, s.strsize |
| 300 | ) |
| 301 | } |
| 302 | |
| 303 | fn write_dysymtab(w: &mut impl Write, d: &DysymtabCmd) -> io::Result<()> { |
| 304 | writeln!( |
| 305 | w, |
| 306 | " ilocalsym={} nlocalsym={} iextdefsym={} nextdefsym={} iundefsym={} nundefsym={} indirectsymoff={} nindirectsyms={}", |
| 307 | d.ilocalsym, |
| 308 | d.nlocalsym, |
| 309 | d.iextdefsym, |
| 310 | d.nextdefsym, |
| 311 | d.iundefsym, |
| 312 | d.nundefsym, |
| 313 | d.indirectsymoff, |
| 314 | d.nindirectsyms |
| 315 | ) |
| 316 | } |
| 317 | |
| 318 | fn write_build_version(w: &mut impl Write, b: &BuildVersionCmd) -> io::Result<()> { |
| 319 | writeln!( |
| 320 | w, |
| 321 | " platform={} minos={} sdk={} ntools={}", |
| 322 | platform_name(b.platform), |
| 323 | version_str(b.minos), |
| 324 | version_str(b.sdk), |
| 325 | b.tools.len() |
| 326 | )?; |
| 327 | for t in &b.tools { |
| 328 | writeln!(w, " tool={} version={}", t.tool, version_str(t.version))?; |
| 329 | } |
| 330 | Ok(()) |
| 331 | } |
| 332 | |
| 333 | fn write_linkedit_data(w: &mut impl Write, l: &LinkEditDataCmd, kind: &str) -> io::Result<()> { |
| 334 | writeln!(w, " {kind} dataoff={} datasize={}", l.dataoff, l.datasize) |
| 335 | } |
| 336 | |
| 337 | fn write_sections(w: &mut impl Write, secs: &[InputSection]) -> io::Result<()> { |
| 338 | if secs.is_empty() { |
| 339 | return Ok(()); |
| 340 | } |
| 341 | writeln!(w, "Sections ({}):", secs.len())?; |
| 342 | for (i, s) in secs.iter().enumerate() { |
| 343 | writeln!( |
| 344 | w, |
| 345 | " [{i}] {},{:<16} {:?} addr=0x{:x} size=0x{:x} align=2^{} offset={} flags=0x{:08x}", |
| 346 | s.segname, s.sectname, s.kind, s.addr, s.size, s.align_pow2, s.offset, s.flags |
| 347 | )?; |
| 348 | if !s.data.is_empty() { |
| 349 | writeln!(w, " data: {}", hex_preview(&s.data, 16))?; |
| 350 | } |
| 351 | if s.nreloc > 0 { |
| 352 | writeln!(w, " relocs ({}):", s.nreloc)?; |
| 353 | match parse_raw_relocs(&s.raw_relocs, 0, s.nreloc).and_then(|raws| parse_relocs(&raws)) |
| 354 | { |
| 355 | Ok(fused) => { |
| 356 | for (ri, r) in fused.iter().enumerate() { |
| 357 | writeln!(w, " [{ri}] {}", describe_reloc(r))?; |
| 358 | } |
| 359 | } |
| 360 | Err(e) => writeln!(w, " <parse error: {e}>")?, |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | Ok(()) |
| 365 | } |
| 366 | |
| 367 | fn write_symbols(w: &mut impl Write, obj: &ObjectFile) -> io::Result<()> { |
| 368 | if obj.symbols.is_empty() { |
| 369 | return Ok(()); |
| 370 | } |
| 371 | writeln!(w, "Symbols ({}):", obj.symbols.len())?; |
| 372 | for (i, sym) in obj.symbols.iter().enumerate() { |
| 373 | let name = obj.symbol_name(sym).unwrap_or("<unresolved>"); |
| 374 | writeln!(w, " [{i}] {:<32} {}", name, describe_symbol(sym))?; |
| 375 | } |
| 376 | Ok(()) |
| 377 | } |
| 378 | |
| 379 | fn describe_symbol(sym: &InputSymbol) -> String { |
| 380 | if let Some(stab) = sym.stab_kind() { |
| 381 | return format!( |
| 382 | "STAB kind=0x{stab:02x} sect={} value=0x{:x}", |
| 383 | sym.sect_idx(), |
| 384 | sym.value() |
| 385 | ); |
| 386 | } |
| 387 | let mut parts: Vec<String> = Vec::new(); |
| 388 | parts.push( |
| 389 | match sym.kind() { |
| 390 | SymKind::Undef => "UNDF", |
| 391 | SymKind::Abs => "ABS", |
| 392 | SymKind::Sect => "SECT", |
| 393 | SymKind::Indirect => "INDR", |
| 394 | } |
| 395 | .to_string(), |
| 396 | ); |
| 397 | if sym.is_ext() { |
| 398 | parts.push("ext".into()); |
| 399 | } |
| 400 | if sym.is_private_ext() { |
| 401 | parts.push("pext".into()); |
| 402 | } |
| 403 | if sym.weak_def() { |
| 404 | parts.push("weak_def".into()); |
| 405 | } |
| 406 | if sym.weak_ref() { |
| 407 | parts.push("weak_ref".into()); |
| 408 | } |
| 409 | if sym.no_dead_strip() { |
| 410 | parts.push("no_dead_strip".into()); |
| 411 | } |
| 412 | match sym.kind() { |
| 413 | SymKind::Sect => parts.push(format!("sect={} value=0x{:x}", sym.sect_idx(), sym.value())), |
| 414 | SymKind::Abs => parts.push(format!("value=0x{:x}", sym.value())), |
| 415 | SymKind::Undef => { |
| 416 | if let Some(sz) = sym.common_size() { |
| 417 | let a = sym.common_align_pow2().unwrap_or(0); |
| 418 | parts.push(format!("common size={sz} align=2^{a}")); |
| 419 | } else if let Some(ord) = sym.library_ordinal() { |
| 420 | parts.push(format!("ord={ord}")); |
| 421 | } |
| 422 | } |
| 423 | SymKind::Indirect => { |
| 424 | parts.push(format!("-> strx={}", sym.value() as u32)); |
| 425 | } |
| 426 | } |
| 427 | parts.join(" ") |
| 428 | } |
| 429 | |
| 430 | fn describe_reloc(r: &Reloc) -> String { |
| 431 | let kind = match r.kind { |
| 432 | RelocKind::Unsigned => "Unsigned", |
| 433 | RelocKind::Branch26 => "Branch26", |
| 434 | RelocKind::Page21 => "Page21", |
| 435 | RelocKind::PageOff12 => "PageOff12", |
| 436 | RelocKind::GotLoadPage21 => "GotLoadPage21", |
| 437 | RelocKind::GotLoadPageOff12 => "GotLoadPageOff12", |
| 438 | RelocKind::PointerToGot => "PointerToGot", |
| 439 | RelocKind::TlvpLoadPage21 => "TlvpLoadPage21", |
| 440 | RelocKind::TlvpLoadPageOff12 => "TlvpLoadPageOff12", |
| 441 | RelocKind::Subtractor => "Subtractor", |
| 442 | }; |
| 443 | let width = r.length.byte_width(); |
| 444 | let mut parts = vec![ |
| 445 | format!("offset=0x{:x}", r.offset), |
| 446 | kind.to_string(), |
| 447 | format!("len={width}"), |
| 448 | ]; |
| 449 | if r.pcrel { |
| 450 | parts.push("pcrel".into()); |
| 451 | } |
| 452 | parts.push(match r.referent { |
| 453 | Referent::Symbol(i) => format!("sym={i}"), |
| 454 | Referent::Section(i) => format!("sect={i}"), |
| 455 | }); |
| 456 | if let Some(sub) = r.subtrahend { |
| 457 | parts.push(match sub { |
| 458 | Referent::Symbol(i) => format!("- sym={i}"), |
| 459 | Referent::Section(i) => format!("- sect={i}"), |
| 460 | }); |
| 461 | } |
| 462 | if r.addend != 0 { |
| 463 | parts.push(format!("+ 0x{:x}", r.addend)); |
| 464 | } |
| 465 | parts.join(" ") |
| 466 | } |
| 467 | |
| 468 | fn hex_preview(bytes: &[u8], max: usize) -> String { |
| 469 | let shown = bytes.iter().take(max); |
| 470 | let hex: Vec<String> = shown.map(|b| format!("{b:02x}")).collect(); |
| 471 | if bytes.len() > max { |
| 472 | format!("{} ... ({} more)", hex.join(" "), bytes.len() - max) |
| 473 | } else { |
| 474 | hex.join(" ") |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | // ---- pretty-printers ------------------------------------------------------ |
| 479 | |
| 480 | fn cpu_name(ct: u32) -> &'static str { |
| 481 | match ct { |
| 482 | CPU_TYPE_ARM64 => "arm64", |
| 483 | _ => "??", |
| 484 | } |
| 485 | } |
| 486 | |
| 487 | fn filetype_name(ft: u32) -> &'static str { |
| 488 | match ft { |
| 489 | MH_OBJECT => "MH_OBJECT", |
| 490 | MH_EXECUTE => "MH_EXECUTE", |
| 491 | MH_DYLIB => "MH_DYLIB", |
| 492 | _ => "??", |
| 493 | } |
| 494 | } |
| 495 | |
| 496 | fn cmd_name(cmd: u32) -> String { |
| 497 | match cmd { |
| 498 | LC_SEGMENT_64 => "LC_SEGMENT_64".into(), |
| 499 | LC_SYMTAB => "LC_SYMTAB".into(), |
| 500 | LC_DYSYMTAB => "LC_DYSYMTAB".into(), |
| 501 | LC_BUILD_VERSION => "LC_BUILD_VERSION".into(), |
| 502 | LC_LINKER_OPTIMIZATION_HINT => "LC_LINKER_OPTIMIZATION_HINT".into(), |
| 503 | LC_UUID => "LC_UUID".into(), |
| 504 | LC_MAIN => "LC_MAIN".into(), |
| 505 | LC_LOAD_DYLIB => "LC_LOAD_DYLIB".into(), |
| 506 | LC_LOAD_DYLINKER => "LC_LOAD_DYLINKER".into(), |
| 507 | LC_LOAD_WEAK_DYLIB => "LC_LOAD_WEAK_DYLIB".into(), |
| 508 | LC_ID_DYLIB => "LC_ID_DYLIB".into(), |
| 509 | LC_REEXPORT_DYLIB => "LC_REEXPORT_DYLIB".into(), |
| 510 | LC_RPATH => "LC_RPATH".into(), |
| 511 | LC_CODE_SIGNATURE => "LC_CODE_SIGNATURE".into(), |
| 512 | LC_FUNCTION_STARTS => "LC_FUNCTION_STARTS".into(), |
| 513 | LC_DATA_IN_CODE => "LC_DATA_IN_CODE".into(), |
| 514 | LC_SOURCE_VERSION => "LC_SOURCE_VERSION".into(), |
| 515 | LC_DYLD_INFO_ONLY => "LC_DYLD_INFO_ONLY".into(), |
| 516 | LC_DYLD_CHAINED_FIXUPS => "LC_DYLD_CHAINED_FIXUPS".into(), |
| 517 | LC_DYLD_EXPORTS_TRIE => "LC_DYLD_EXPORTS_TRIE".into(), |
| 518 | _ => format!("LC_0x{cmd:x}"), |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | fn platform_name(p: u32) -> &'static str { |
| 523 | match p { |
| 524 | PLATFORM_MACOS => "MACOS", |
| 525 | PLATFORM_IOS => "IOS", |
| 526 | _ => "UNKNOWN", |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | /// X.Y.Z packed 0xXXXXYYZZ → "X.Y.Z". |
| 531 | fn version_str(v: u32) -> String { |
| 532 | let x = (v >> 16) & 0xffff; |
| 533 | let y = (v >> 8) & 0xff; |
| 534 | let z = v & 0xff; |
| 535 | format!("{x}.{y}.{z}") |
| 536 | } |
| 537 | |
| 538 | fn prot_str(p: u32) -> String { |
| 539 | let r = if p & 1 != 0 { 'r' } else { '-' }; |
| 540 | let w = if p & 2 != 0 { 'w' } else { '-' }; |
| 541 | let x = if p & 4 != 0 { 'x' } else { '-' }; |
| 542 | format!("{r}{w}{x}") |
| 543 | } |
| 544 | |
| 545 | #[cfg(test)] |
| 546 | mod tests { |
| 547 | use super::*; |
| 548 | |
| 549 | #[test] |
| 550 | fn version_str_examples() { |
| 551 | assert_eq!(version_str(0x000B_0000), "11.0.0"); |
| 552 | assert_eq!(version_str(0x000E_0200), "14.2.0"); |
| 553 | assert_eq!(version_str(0x000E_0203), "14.2.3"); |
| 554 | } |
| 555 | |
| 556 | #[test] |
| 557 | fn prot_str_examples() { |
| 558 | assert_eq!(prot_str(0), "---"); |
| 559 | assert_eq!(prot_str(7), "rwx"); |
| 560 | assert_eq!(prot_str(5), "r-x"); |
| 561 | } |
| 562 | } |
| 563 |