Skip to content

Instantly share code, notes, and snippets.

@nornagon
Created October 11, 2022 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nornagon/b22e4c5af4b53705286b139cbffcbf19 to your computer and use it in GitHub Desktop.
Save nornagon/b22e4c5af4b53705286b139cbffcbf19 to your computer and use it in GitHub Desktop.
dyld dependency tree walker
#!/usr/bin/env node
import macho from 'macho'
import * as path from 'path'
import * as fs from 'fs'
const rootExe = path.resolve(process.argv[2])
function resolve(exe, rpath, lib) {
return path.resolve(lib.replace(/@rpath/, rpath).replace(/@loader_path/, path.dirname(exe)).replace(/@executable_path/, path.dirname(rootExe)))
}
function readLib(exe) {
// Use https://lapcatsoftware.com/articles/bigsur.html to extract the dyld
// cache
if (!fs.existsSync(exe))
return fs.readFileSync('/users/jeremy.rose/Desktop/libraries/' + exe)
return fs.readFileSync(exe)
}
const getReexportDylibName = c => /^.*?(?=\x00)/.exec(c.data.slice(16).toString('utf8'))[0]
const seen = new Set
function printLibs(exe, depth=0, labels=null) {
if (seen.has(exe)) return
seen.add(exe)
try {
const {cmds} = macho.parse(readLib(exe))
console.log(' '.repeat(depth) + exe + (labels ? ` [${labels.join(',')}]` : ''))
const rpath = cmds.find(c => c.type === 'rpath')?.name
const libs = []
for (const cmd of cmds) {
if (cmd.type === 'load_dylib')
libs.push({name: cmd.name})
else if (cmd.type === 'load_weak_dylib')
libs.push({name: cmd.name, labels: ['weak']})
else if (cmd.type === 'reexport_dylib')
libs.push({name: getReexportDylibName(cmd), labels: ['reexported']})
}
for (const lib of libs) {
printLibs(resolve(exe, rpath, lib.name), depth + 1, lib.labels)
}
} catch (e) {
console.log(' '.repeat(depth) + exe, `(failed to parse: ${e.message})`)
}
}
const deps = new Map
function walkLibs(exe) {
if (deps.has(exe)) return
try {
const {cmds} = macho.parse(readLib(exe))
const rpath = cmds.find(c => c.type === 'rpath')?.name
const libs = []
for (const cmd of cmds) {
if (cmd.type === 'load_dylib')
libs.push({name: cmd.name})
else if (cmd.type === 'load_weak_dylib')
libs.push({name: cmd.name, labels: ['weak']})
else if (cmd.type === 'reexport_dylib')
libs.push({name: getReexportDylibName(cmd), labels: ['reexport']})
}
deps.set(exe, libs)
for (const lib of libs) {
walkLibs(resolve(exe, rpath, lib.name))
}
} catch (e) {
deps.set(exe, [])
}
}
printLibs(rootExe)
/*
walkLibs(rootExe)
const ideps = new Map
for (const [k, v] of deps.entries())
for (const {name: d} of v) {
if (!ideps.has(d)) ideps.set(d, new Set)
ideps.get(d).add(k)
}
function reachableFrom(from, links) {
const q = []
const visited = new Set
visited.add(from)
q.push(from)
while (q.length) {
const e = q.shift()
for (const n of links(e)) {
if (!visited.has(n)) {
visited.add(n)
q.push(n)
}
}
}
return visited
}
const nodes = reachableFrom('/System/Library/Frameworks/WebKit.framework/Versions/A/WebKit', x => [...ideps.get(x) ?? []])
//printLibs(rootExe)
console.log('digraph {')
for (const k of deps.keys()) {
if (nodes.has(k))
console.log(`"${k}" [label="${path.basename(k)}"]`)
}
for (const [k, v] of deps.entries()) {
if (nodes.has(k))
for (const dep of v)
if (nodes.has(dep.name))
console.log(`"${k}" -> "${dep.name}" [label=" ${dep.labels?.join(', ') ?? ''}"]`)
}
console.log('}')
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment