Created
October 11, 2022 21:43
-
-
Save nornagon/b22e4c5af4b53705286b139cbffcbf19 to your computer and use it in GitHub Desktop.
dyld dependency tree walker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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