132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
"""
|
|
Utilities for reading and writing Mach-O headers
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from altgraph.Graph import Graph
|
|
from altgraph.ObjectGraph import ObjectGraph
|
|
|
|
from macholib.mach_o import *
|
|
from macholib.dyld import dyld_find
|
|
from macholib.MachO import MachO
|
|
from macholib.itergraphreport import itergraphreport
|
|
|
|
__all__ = ['MachOGraph']
|
|
|
|
try:
|
|
unicode
|
|
except NameError:
|
|
unicode = str
|
|
|
|
class MissingMachO(object):
|
|
def __init__(self, filename):
|
|
self.graphident = filename
|
|
self.headers = ()
|
|
|
|
def __repr__(self):
|
|
return '<%s graphident=%r>' % (type(self).__name__, self.graphident)
|
|
|
|
class MachOGraph(ObjectGraph):
|
|
"""
|
|
Graph data structure of Mach-O dependencies
|
|
"""
|
|
def __init__(self, debug=0, graph=None, env=None, executable_path=None):
|
|
super(MachOGraph, self).__init__(debug=debug, graph=graph)
|
|
self.env = env
|
|
self.trans_table = {}
|
|
self.executable_path = executable_path
|
|
|
|
def locate(self, filename, loader=None):
|
|
assert isinstance(filename, (str, unicode))
|
|
if filename.startswith('@loader_path/') and loader is not None:
|
|
fn = self.trans_table.get((loader.filename, filename))
|
|
if fn is None:
|
|
try:
|
|
fn = dyld_find(filename, env=self.env,
|
|
executable_path=self.executable_path,
|
|
loader=loader.filename)
|
|
self.trans_table[(loader.filename, filename)] = fn
|
|
except ValueError:
|
|
return None
|
|
|
|
else:
|
|
fn = self.trans_table.get(filename)
|
|
if fn is None:
|
|
try:
|
|
fn = dyld_find(filename, env=self.env,
|
|
executable_path=self.executable_path)
|
|
self.trans_table[filename] = fn
|
|
except ValueError:
|
|
return None
|
|
return fn
|
|
|
|
def findNode(self, name, loader=None):
|
|
assert isinstance(name, (str, unicode))
|
|
data = super(MachOGraph, self).findNode(name)
|
|
if data is not None:
|
|
return data
|
|
newname = self.locate(name, loader=loader)
|
|
if newname is not None and newname != name:
|
|
return self.findNode(newname)
|
|
return None
|
|
|
|
def run_file(self, pathname, caller=None):
|
|
assert isinstance(pathname, (str, unicode))
|
|
self.msgin(2, "run_file", pathname)
|
|
m = self.findNode(pathname, loader=caller)
|
|
if m is None:
|
|
if not os.path.exists(pathname):
|
|
raise ValueError('%r does not exist' % (pathname,))
|
|
m = self.createNode(MachO, pathname)
|
|
self.createReference(caller, m, edge_data='run_file')
|
|
self.scan_node(m)
|
|
self.msgout(2, '')
|
|
return m
|
|
|
|
def load_file(self, name, caller=None):
|
|
assert isinstance(name, (str, unicode))
|
|
self.msgin(2, "load_file", name)
|
|
m = self.findNode(name)
|
|
if m is None:
|
|
newname = self.locate(name, loader=caller)
|
|
if newname is not None and newname != name:
|
|
return self.load_file(newname, caller=caller)
|
|
if os.path.exists(name):
|
|
m = self.createNode(MachO, name)
|
|
self.scan_node(m)
|
|
else:
|
|
m = self.createNode(MissingMachO, name)
|
|
self.msgout(2, '')
|
|
return m
|
|
|
|
def scan_node(self, node):
|
|
self.msgin(2, 'scan_node', node)
|
|
for header in node.headers:
|
|
for idx, name, filename in header.walkRelocatables():
|
|
assert isinstance(name, (str, unicode))
|
|
assert isinstance(filename, (str, unicode))
|
|
m = self.load_file(filename, caller=node)
|
|
self.createReference(node, m, edge_data=name)
|
|
self.msgout(2, '', node)
|
|
|
|
def itergraphreport(self, name='G'):
|
|
nodes = map(self.graph.describe_node, self.graph.iterdfs(self))
|
|
describe_edge = self.graph.describe_edge
|
|
return itergraphreport(nodes, describe_edge, name=name)
|
|
|
|
def graphreport(self, fileobj=None):
|
|
if fileobj is None:
|
|
fileobj = sys.stdout
|
|
fileobj.writelines(self.itergraphreport())
|
|
|
|
def main(args):
|
|
g = MachOGraph()
|
|
for arg in args:
|
|
g.run_file(arg)
|
|
g.graphreport()
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:] or ['/bin/ls'])
|