macholib-1.7/0000755000076500000240000000000012365134503013543 5ustar ronaldstaff00000000000000macholib-1.7/doc/0000755000076500000240000000000012365134503014310 5ustar ronaldstaff00000000000000macholib-1.7/doc/changelog.rst0000644000076500000240000001426112364212074016774 0ustar ronaldstaff00000000000000Release history =============== macholib 1.7 ------------ * Added support for ARM64, LC_ENCRYPTION_INFO_64 and LC_LINKER_OPTION Patch by Matthias Ringwald. * Load commands now have a "describe" method that returns more information about the command. Patch by David Dorsey. * The MAGIC value in the header was always represented in the native byte order, instead of as the value read from the binary. Patch by David Dorsey. * Added various new constants to "macholib.mach_o". Patch by David Dorsey. macholib 1.6.1 -------------- * ? macholib 1.6 ------------ * Add support for '@loader_path' link command in macholib.dyld: - Added function ``macholib.dyld.dyld_loader_search`` - This function is used by ``macholib.dyld.dyld_find``, and that function now has an new (optional) argument with the path to the loader. * Also add support for '@loader_path' to macholib.MachoGraph, using the newly added '@loader_path' support in the dyld module. Due to this suppport the *macho_standalone* tool can now rewrite binaries that contain an '@loader_path' load command. macholib 1.5.2 -------------- * Issue #93: Show the name of the affected file in the exception message for Mach-O headers that are too large to relocate. macholib 1.5.1 -------------- * There were no 'classifiers' in the package metadata due to a bug in setup.py. macholib 1.5 -------------- macholib 1.5 is a minor feature release * No longer use 2to3 to provide Python 3 support As a side-effect of this macholib no longer supports Python 2.5 and earlier. * Adds suppport for some new macho load commands * Fix for py3k problem in macho_standalone.py Patch by Guanqun Lu. * Fix for some issues in macho_dump.py Patch by Nam Nguyen * Issue #10: Fix for LC_DATA_IN_CODE linker commands, without this fix py2app cannot build application bundles when the source binaries have been compiled with Xcode 4.5. * Issue #6: Fix for LC_ENCRYPTION_INFO linker commands * Use the mach header information to print the cpu type of a binary, instead of trying to deduce that from pointer width and endianness. Changed the code because of issue #6, in which a user tries to dump a iOS binary which results in bogus output in the previous releases. * The mapping ``macholib.macho_dump.ARCH_MAP`` is undocumented and no longer used by macholib itself. It will be removed in the next release. * The command-line tools ``macho_find``, ``macho_dump`` and ``macho_standalone`` are deprecated. Use "python -mmacholib" instead. That is:: $ python -mmacholib dump /usr/bin/grep $ python -mmacholib find ~ $ python -mmacholib standalone myapp.app This makes it clearer which version of the tools are used. macholib 1.4.3 -------------- macholib 1.4.3 is a minor feature release * Added strings for 'x86_64' and 'ppc64' to macholib.mach_o.CPU_TYPE_NAMES. * macho_find and macho_dump were broken in the 1.4.2 release * added 'macholib.util.NOT_SYSTEM_FILES', a list of files that aren't system path's even though they are located in system locations. Needed to work around a bug in PySide (see issue #32 in the py2app tracker) macholib 1.4.2 -------------- macholib 1.4.2 is a minor bugfix release * The support for new load commands that was added in 1.4.1 contained a typo that caused problems on OSX 10.7 (Lion). macholib 1.4.1 -------------- macholib 1.4.1 is a minor feature release Features: - Add support for a number of new MachO load commands that were added during the lifetime of OSX 10.6: ``LC_LOAD_UPWARD_DYLIB``, ``LC_VERSION_MIN_MACOSX``, ``LC_VERSION_MIN_IPHONEOS`` and ``LC_FUNCTION_STARTS``. macholib 1.4 ------------- macholib 1.4 is a feature release Features: - Documentation is now generated using `sphinx `_ and can be viewed at . - The repository has moved to bitbucket - There now is a testsuite - Private functionality inside modules was renamed to a name starting with an underscore. .. note:: if this change affects your code you are relying on undefined implementation features, please stop using private functions. - The basic packable types in ``macholib.ptypes`` were renamed to better represent the corresponding C type. The table below lists the old an new names (the old names are still available, but are deprecated and will be removed in a future release). +--------------+--------------+ | **Old name** | **New name** | +==============+==============+ | p_byte | p_int8 | +--------------+--------------+ | p_ubyte | p_uint8 | +--------------+--------------+ | p_short | p_int16 | +--------------+--------------+ | p_ushort | p_uint16 | +--------------+--------------+ | p_int | p_int32 | +--------------+--------------+ | p_uint | p_uint32 | +--------------+--------------+ | p_long | p_int32 | +--------------+--------------+ | p_ulong | p_uint32 | +--------------+--------------+ | p_longlong | p_int64 | +--------------+--------------+ | p_ulonglong | p_uint64 | +--------------+--------------+ ``Macholib.ptypes.p_ptr`` is no longer present as it had an unclear definition and isn't actually used in the codebase. Bug fixes: - The semantics of ``dyld.dyld_default_search`` were changed a bit, it now first searches the framework path (if appropriate) and then the linker path, irrespective of the value of the ``DYLD_FALLBACK*`` environment variables. Previous versions would change the search order when those variables was set, which is odd and doesn't correspond with the documented behaviour of the system dyld. - It is once again possible to install using python2.5 - The source distribution includes all files, this was broken due to the switch to mercurial (which confused setuptools) macholib 1.3 ------------ macholib 1.3 is a feature release. Features: - Experimental Python 3.x support This version contains lightly tested support for Python 3. macholib 1.2.2 -------------- macholib 1.2.2 is a bugfix release. Bug fixes: - Macholib should work better with 64-bit code (patch by Marc-Antoine Parent) macholib-1.7/doc/conf.py0000644000076500000240000002112611557222520015610 0ustar ronaldstaff00000000000000# -*- coding: utf-8 -*- # # macholib documentation build configuration file, created by # sphinx-quickstart on Tue Sep 28 22:23:35 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os def get_version(): fn = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'setup.cfg') for ln in open(fn): if ln.startswith('version'): version = ln.split('=')[-1].strip() return version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'macholib' copyright = u'2010-2011, Ronald Oussoren' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = get_version() # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'macholibdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'macholib.tex', u'macholib Documentation', u'Ronald Oussoren', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'macholib', u'macholib Documentation', [u'Ronald Oussoren'], 1) ] # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'macholib' epub_author = u'Ronald Oussoren' epub_publisher = u'Ronald Oussoren' epub_copyright = u'2010, Ronald Oussoren' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('http://docs.python.org/', None), 'altgraph': ('http://packages.python.org/altgraph', None), } macholib-1.7/doc/dyld.rst0000644000076500000240000001136612272472611016007 0ustar ronaldstaff00000000000000:mod:`macholib.dyld` --- Dyld emulation ======================================= .. module:: macholib.dyld :synopsis: Emulation of functonality of the dynamic linker This module defines a number of functions that can be used to emulate the functionality of the dynamic linker (``dyld``) w.r.t. looking for library files and framworks. .. function:: dyld_image_suffix([env]) Looks up the suffix to append to shared library and framework names and returns this value when found. Returns ``None`` when no suffix should be appended. The *env* argument is a dictionary, which defaults to :data:`os.environ`. See the description of ``DYLD_IMAGE_SUFFIX`` in the manual page for dyld(1) for more information. .. function:: dydl_framework_path([env]) Returns a user-specified framework search path, or an empty list when only the default search path should be used. The *env* argument is a dictionary, which defaults to :data:`os.environ`. See the description of ``DYLD_FRAMEWORK_PATH`` in the manual page for dyld(1) for more information. .. function:: dyld_library_path([env]) Returns a user-specified library search path, or an empty list when only the default search path should be used. The *env* argument is a dictionary, which defaults to :data:`os.environ`. See the description of ``DYLD_LIBRARY_PATH`` in the manual page for dyld(1) for more information. .. function:: dyld_fallback_framework_path([env]) Return a user specified list of of directories where to look for frameworks that aren't in their install path, or an empty list when the default fallback path should be used. The *env* argument is a dictionary, which defaults to :data:`os.environ`. See the description of ``DYLD_FALLBACK_FRAMEWORK_PATH`` in the manual page for dyld(1) for more information. .. function:: dyld_fallback_library_path([env]) Return a user specified list of of directories where to look for libraries that aren't in their install path, or an empty list when the default fallback path should be used. The *env* argument is a dictionary, which defaults to :data:`os.environ`. See the description of ``DYLD_FALLBACK_LIBRARY_PATH`` in the manual page for dyld(1) for more information. .. function:: dyld_image_suffix_search(iterator[, env]) Yields all items in *iterator*, and prepents names with the image suffix to those items when the suffix is specified. The *env* argument is a dictionary, which defaults to :data:`os.environ`. .. function:: dyld_override_search(name[, env]) If *name* is a framework name yield filesystem paths relative to the entries in the framework search path. Always yield the filesystem paths relative to the entries in the library search path. The *env* argument is a dictionary, which defaults to :data:`os.environ`. .. function:: dyld_executable_path_search(name, executable_path) If *name* is a path starting with ``@executable_path/`` yield the path relative to the specified *executable_path*. If *executable_path* is None nothing is yielded. .. function:: dyld_loader_search(name, loader_path) If *name* is a path starting with ``@loader_path/`` yield the path relative to the specified *loader_path*. If *loader_path* is None nothing is yielded. .. versionadded: 1.6 .. function:: dyld_default_search(name[, env]) Yield the filesystem locations to look for a dynamic library or framework using the default locations used by the system dynamic linker. This function will look in ``~/Library/Frameworks`` for frameworks, even though the system dynamic linker doesn't. The *env* argument is a dictionary, which defaults to :data:`os.environ`. .. function:: dyld_find(name[, executable_path[, env [, loader]]]) Returns the path of the requested dynamic library, raises :exc:`ValueError` when the library cannot be found. This function searches for the library in the same locations and de system dynamic linker. The *executable_path* should be the filesystem path of the executable to which the library is linked (either directly or indirectly). The *env* argument is a dictionary, which defaults to :data:`os.environ`. The *loader_path* argument is an optional filesystem path for the object file (binary of shared library) that references *name*. .. versionchanged:: 1.6 Added the *loader_path* argument. .. function:: framework_find(fn[, executable_path[, env]]) Find a framework using the same semantics as the system dynamic linker, but will accept looser names than the system linker. This function will return a correct result for input values like: * Python * Python.framework * Python.framework/Versions/Current macholib-1.7/doc/dylib.rst0000644000076500000240000000161011537027121016140 0ustar ronaldstaff00000000000000:mod:`macholib.dylib` --- Generic dylib path manipulation ========================================================= .. module:: macholib.dylib :synopsis: Generic dylib path manipulation This module defines a function :func:`dylib_info` that can extract useful information from the name of a dynamic library. .. function:: dylib_info(filename) A dylib name can take one of the following four forms: * ``Location/Name.SomeVersion_Suffix.dylib`` * ``Location/Name.SomeVersion.dylib`` * ``Location/Name_Suffix.dylib`` * ``Location/Name.dylib`` Returns None if not found or a mapping equivalent to:: dict( location='Location', name='Name.SomeVersion_Suffix.dylib', shortname='Name', version='SomeVersion', suffix='Suffix', ) .. note:: *SomeVersion* and *Suffix* are optional and my be ``None`` if not present. macholib-1.7/doc/framework.rst0000644000076500000240000000202111537027061017032 0ustar ronaldstaff00000000000000:mod:`macholib.framework` --- Generic framework path manipulation ========================================================================== .. module:: macholib.framework :synopsis: Generic framework path manipulation This module defines a function :func:`framework_info` that can extract useful information from the name of a dynamic library in a framework. .. function:: framework_info(filename) A framework name can take one of the following four forms: * ``Location/Name.framework/Versions/SomeVersion/Name_Suffix`` * ``Location/Name.framework/Versions/SomeVersion/Name`` * ``Location/Name.framework/Name_Suffix`` * ``Location/Name.framework/Name`` Returns ``None`` if not found, or a mapping equivalent to:: dict( location='Location', name='Name.framework/Versions/SomeVersion/Name_Suffix', shortname='Name', version='SomeVersion', suffix='Suffix', ) .. note:: *SomeVersion* and *Suffix* are optional and may be None if not present. macholib-1.7/doc/index.rst0000644000076500000240000000250211620562303016144 0ustar ronaldstaff00000000000000Macholib - Analyze and edit Mach-O headers ========================================== macholib can be used to analyze and edit Mach-O headers, the executable format used by Mac OS X. It's typically used as a dependency analysis tool, and also to rewrite dylib references in Mach-O headers to be ``@executable_path`` relative. Though this tool targets a platform specific file format, it is pure python code that is platform and endian independent. General documentation --------------------- .. toctree:: :maxdepth: 1 changelog license scripts Reference Guide --------------- .. toctree:: :maxdepth: 1 MachO MachoOGraph MachoOStandalone SymbolTable dyld dylib framework macho_o ptypes Online Resources ---------------- * `Sourcecode repository on bitbucket `_ * `The issue tracker `_ * `Mac OS X ABI Mach-O File Format Reference at Apple `_ Contributors ------------ Macholib was written by Bob Ippolito and is currently maintained by Ronald Oussoren . Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` macholib-1.7/doc/license.rst0000644000076500000240000000172412272472611016472 0ustar ronaldstaff00000000000000License ======= Copyright (c) Bob Ippolito Parts are copyright (c) 2010-2014 Ronald Oussoren MIT License ........... Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. macholib-1.7/doc/MachO.rst0000644000076500000240000000122411537022751016031 0ustar ronaldstaff00000000000000:mod:`macholib.MachO` --- Utilities for reading and writing Mach-O headers ========================================================================== .. module:: macholib.MachO :synopsis: Utilities for reading and writing Mach-O headers This module defines a class :class:`Macho`, which enables reading and writing the Mach-O header of an executable file or dynamic library on MacOS X. .. class:: MachO(filename) Creates a MachO object by reading the Mach-O headers from *filename*. The *filename* should refer to an existing file in Mach-O format, and can refer to fat (universal) binaries. .. note:: more information will be added later macholib-1.7/doc/macho_o.rst0000644000076500000240000000114211537236740016453 0ustar ronaldstaff00000000000000:mod:`macholib.mach_o` --- Low-level definitions ================================================ .. module:: macholib.mach_o :synopsis: Low-level definitions of elements in a Mach-O file This module defines constants and packable structure types that correspond to elements of a Mach-O file. The names of classes and constants is the same as those in the Mach-O header files and `Apple's documentation `_. This document therefore doesn't explictly document the names in this module. macholib-1.7/doc/MachoOGraph.rst0000644000076500000240000000071111537023711017167 0ustar ronaldstaff00000000000000:mod:`macholib.MachoGraph` --- Graph data structure of Mach-O dependencies =============================================================================== .. module:: macholib.MachOGraph :synopsis: Graph data structure of Mach-O dependencies This module defines the class :class:`MachOGraph` which represents the direct and indirect dependencies of one or more Mach-O files on other (library) files. .. class:: MachOGraph(...) To be discussed. macholib-1.7/doc/MachoOStandalone.rst0000644000076500000240000000105711537024221020217 0ustar ronaldstaff00000000000000:mod:`macholib.MachOStandalone` --- Create standalone application bundles ========================================================================== .. module:: macholib.MachOStandalone :synopsis: Create standalone application bundles This module defines class :class:`MachOStandalone` which locates all Mach-O files in a directory (assumed to be the root of an application or plugin bundle) and then copies all non-system dependencies for the located files into the bundle. .. class:: MachOStandalone(base[, dest[, graph[, env[, executable_path]]]]) macholib-1.7/doc/Makefile0000644000076500000240000001076611450447307015765 0ustar ronaldstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/macholib.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/macholib.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/macholib" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/macholib" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." macholib-1.7/doc/ptypes.rst0000644000076500000240000001003611537020643016365 0ustar ronaldstaff00000000000000:mod:`macholib.ptypes` --- Packable types ========================================= .. module:: macholib.ptypes :synopsis: Serializable types The module :mod:`macholib.ptypes` defines types that can be serialized into byte arrays, both for basic types and structured types (C ``struct`` values). Utility functions ----------------- .. function:: sizeof(value) Returns the size in bytes of an object when packed, raises :exc:`ValueError` for inappropriate values. .. function:: pypackable(name, pytype, format) Returns a packable type that is a subclass of the Python type *pytype*. The value is converted to and from the packed format using the struct *format*. Packable types -------------- .. class:: BasePackable All packable types are a subclass of :class:`BasePackable`, which defines the basic interface but is itself an abstract base class. .. data:: _endian_ The byteorder of a packed value. This will be ``"<"` for little endian values and ``">"`` for big-endian ones. .. note:: the endianness option is a public value to be able to support both big- and little-endian file formats. The name suggests that this attribute is private, this is partically for historical reasons and partially to avoid conflicts with field names in C structs. .. method:: from_mmap(mmap, ptr, \**kw) This class method constructs the value from a subview of a :class:`mmap.mmap` object. It uses bytes starting at offset *ptr* and reads just enough bytes to read the entire object. .. method:: from_fileobj(fp, \**kw) This class method constructs the value by reading just enough bytes from a file-like object. .. note:: The file must be opened in binary mode, that is read calls should return byte-strings and not unicode-strings. .. method:: from_str(value, \**kw) This class method construct the value by using the struct module to parse the given bytes. .. note:: contrary to what the name suggests the argument to this method is a byte-string, not a unicode-string. .. method:: from_tuple(fp, \**kw) This class method constructs the object from a tuple with all fields. .. method:: to_str() Returns a byte representation of the value. .. note:: there is no default implementation for this method .. method:: to_fileobj(fp) Write a byte representation of the value to the given file-like object. The file should be opened in binary mode. .. method:: to_mmap(mmap, ptr) Write the byte representation of the value to a :class:`mmap.mmap` object, starting at offset *ptr*. .. class:: Structure(...) .. data:: _fields_ This class attribute is a list that contains the fields of the structure in the right order. Every item of this list is a tuple with 2 arguments: the first element is the name of the field, and the second the packable type for the field. Every subclass of :class:`Structure` must define *_fields_* to be usefull, and the value of *_fields_* should not be changed after class construction. Basic packables --------------- Other than the core functionality this module defines a number of :func:`pypackable` types that correspond to useful basic C types. .. class:: p_char([value]) A byte string of length 1 .. class:: p_int8 An 8-bit signed integer .. class:: p_uint8 An 8-bit unsigned integer .. class:: p_int16 An 16-bit signed integer .. class:: p_uint16 An 16-bit unsigned integer .. class:: p_int32 An 32-bit signed integer .. class:: p_uint32 An 32-bit unsigned integer .. class:: p_int64 An 64-bit signed integer .. class:: p_uint64 An 64-bit unsigned integer .. class:: p_float An floating point value of type ``float`` .. class:: p_double An floating point value of type ``double`` .. note:: the module exports a number of other types with names starting with ``p_``, such as ``p_int``. Those types are deprecated and should not be used. macholib-1.7/doc/scripts.rst0000644000076500000240000000132612033016511016521 0ustar ronaldstaff00000000000000Command-line tools ================== python -m macholib find ----------------------- Usage:: $ python -mmacholib find dir... Print the paths of all MachO binaries in the specified directories. python -m macholib standalone ----------------------------- Usage:: $ python -m macholib standalone appbundle... Convert one or more application bundles into standalone bundles. That is, copy all non-system shared libraries and frameworks used by the bundle into the bundle and rewrite load commands. python -mmacholib dump ---------------------- Usage:: $ python -mmacholib dump dir... Prints information about all architectures in a Mach-O file as well as all libraries it links to. macholib-1.7/doc/SymbolTable.rst0000644000076500000240000000170411537026537017270 0ustar ronaldstaff00000000000000:mod:`macholib.SymbolTable` --- Class to read the symbol table from a Mach-O header =================================================================================== .. module:: macholib.SymbolTable :synopsis: Class to read the symbol table from a Mach-O header This module is deprecated because it is not by the author and likely contains bugs. It also does not work for 64-bit binaries. .. class:: SymbolTable(macho[, openfile]) Reads the SymbolTable for the given Mach-O object. The option argument *openfile* specifies the function to use to open the file, defaulting to the builtin :func:`open` function. .. warning:: As far as we know this class is not used by any user of the modulegraph package, and the code has not been updated after the initial implementation. The end result of this is that the code does not support 64-bit code at all and likely doesn't work properly for 32-bit code as well. macholib-1.7/macholib/0000755000076500000240000000000012365134503015321 5ustar ronaldstaff00000000000000macholib-1.7/macholib/__init__.py0000644000076500000240000000037312033014413017422 0ustar ronaldstaff00000000000000""" Enough Mach-O to make your head spin. See the relevant header files in /usr/include/mach-o And also Apple's documentation. """ from __future__ import print_function import pkg_resources __version__ = pkg_resources.require('macholib')[0].version macholib-1.7/macholib/__main__.py0000644000076500000240000000342612272472611017422 0ustar ronaldstaff00000000000000from __future__ import print_function, absolute_import import os, sys from macholib.util import is_platform_file from macholib import macho_dump from macholib import macho_standalone gCommand = None def check_file(fp, path, callback): if not os.path.exists(path): print('%s: %s: No such file or directory' % (gCommand, path), file=sys.stderr) return 1 try: is_plat = is_platform_file(path) except IOError as msg: print('%s: %s: %s' % (gCommand, path, msg), file=sys.stderr) return 1 else: if is_plat: callback(fp, path) return 0 def walk_tree(callback, paths): args = sys.argv[1:] err = 0 for base in paths: if os.path.isdir(base): for root, dirs, files in os.walk(base): for fn in files: err |= check_file( sys.stdout, os.path.join(root, fn), callback) else: err |= check_file(sys.stdout, base, callback) return err def print_usage(fp): print("Usage:", file=sys.stderr) print(" python -mmacholib dump FILE ...", file=fp) print(" python -mmacholib find DIR ...", file=fp) print(" python -mmacholib standalone DIR ...", file=fp) def main(): global gCommand if len(sys.argv) < 3: print_usage(sys.stderr) sys.exit(1) gCommand = sys.argv[1] if gCommand == 'dump': walk_tree(macho_dump.print_file, sys.argv[2:]) elif gCommand == 'find': walk_tree(lambda fp, path: print(path, file=fp), sys.argv[2:]) elif gCommand == 'standalone': for dn in sys.argv[2:]: macho_standalone.standaloneApp(dn) else: print_usage(sys.stderr) sys.exit(1) if __name__ == "__main__": main() macholib-1.7/macholib/_cmdline.py0000644000076500000240000000211611750574152017452 0ustar ronaldstaff00000000000000""" Internal helpers for basic commandline tools """ from __future__ import print_function, absolute_import import os import sys from macholib.util import is_platform_file def check_file(fp, path, callback): if not os.path.exists(path): print('%s: %s: No such file or directory' % (sys.argv[0], path), file=sys.stderr) return 1 try: is_plat = is_platform_file(path) except IOError as msg: print('%s: %s: %s' % (sys.argv[0], path, msg), file=sys.stderr) return 1 else: if is_plat: callback(fp, path) return 0 def main(callback): args = sys.argv[1:] name = os.path.basename(sys.argv[0]) err = 0 if not args: print("Usage: %s filename..."%(name,), file=sys.stderr) return 1 for base in args: if os.path.isdir(base): for root, dirs, files in os.walk(base): for fn in files: err |= check_file(sys.stdout, os.path.join(root, fn), callback) else: err |= check_file(sys.stdout, base, callback) return err macholib-1.7/macholib/dyld.py0000644000076500000240000001262012272472611016632 0ustar ronaldstaff00000000000000""" dyld emulation """ from itertools import chain import os, sys from macholib.framework import framework_info from macholib.dylib import dylib_info __all__ = [ 'dyld_find', 'framework_find', 'framework_info', 'dylib_info', ] # These are the defaults as per man dyld(1) # _DEFAULT_FRAMEWORK_FALLBACK = [ os.path.expanduser("~/Library/Frameworks"), "/Library/Frameworks", "/Network/Library/Frameworks", "/System/Library/Frameworks", ] _DEFAULT_LIBRARY_FALLBACK = [ os.path.expanduser("~/lib"), "/usr/local/lib", "/lib", "/usr/lib", ] # XXX: Is this function still needed? if sys.version_info[0] == 2: def _ensure_utf8(s): """Not all of PyObjC and Python understand unicode paths very well yet""" if isinstance(s, unicode): return s.encode('utf8') return s else: def _ensure_utf8(s): if s is not None and not isinstance(s, str): raise ValueError(s) return s def _dyld_env(env, var): if env is None: env = os.environ rval = env.get(var) if rval is None or rval == '': return [] return rval.split(':') def dyld_image_suffix(env=None): if env is None: env = os.environ return env.get('DYLD_IMAGE_SUFFIX') def dyld_framework_path(env=None): return _dyld_env(env, 'DYLD_FRAMEWORK_PATH') def dyld_library_path(env=None): return _dyld_env(env, 'DYLD_LIBRARY_PATH') def dyld_fallback_framework_path(env=None): return _dyld_env(env, 'DYLD_FALLBACK_FRAMEWORK_PATH') def dyld_fallback_library_path(env=None): return _dyld_env(env, 'DYLD_FALLBACK_LIBRARY_PATH') def dyld_image_suffix_search(iterator, env=None): """For a potential path iterator, add DYLD_IMAGE_SUFFIX semantics""" suffix = dyld_image_suffix(env) if suffix is None: return iterator def _inject(iterator=iterator, suffix=suffix): for path in iterator: if path.endswith('.dylib'): yield path[:-len('.dylib')] + suffix + '.dylib' else: yield path + suffix yield path return _inject() def dyld_override_search(name, env=None): # If DYLD_FRAMEWORK_PATH is set and this dylib_name is a # framework name, use the first file that exists in the framework # path if any. If there is none go on to search the DYLD_LIBRARY_PATH # if any. framework = framework_info(name) if framework is not None: for path in dyld_framework_path(env): yield os.path.join(path, framework['name']) # If DYLD_LIBRARY_PATH is set then use the first file that exists # in the path. If none use the original name. for path in dyld_library_path(env): yield os.path.join(path, os.path.basename(name)) def dyld_executable_path_search(name, executable_path=None): # If we haven't done any searching and found a library and the # dylib_name starts with "@executable_path/" then construct the # library name. if name.startswith('@executable_path/') and executable_path is not None: yield os.path.join(executable_path, name[len('@executable_path/'):]) def dyld_loader_search(name, loader_path=None): # If we haven't done any searching and found a library and the # dylib_name starts with "@loader_path/" then construct the # library name. if name.startswith('@loader_path/') and loader_path is not None: yield os.path.join(loader_path, name[len('@loader_path/'):]) def dyld_default_search(name, env=None): yield name framework = framework_info(name) if framework is not None: fallback_framework_path = dyld_fallback_framework_path(env) if fallback_framework_path: for path in fallback_framework_path: yield os.path.join(path, framework['name']) else: for path in _DEFAULT_FRAMEWORK_FALLBACK: yield os.path.join(path, framework['name']) fallback_library_path = dyld_fallback_library_path(env) if fallback_library_path: for path in fallback_library_path: yield os.path.join(path, os.path.basename(name)) else: for path in _DEFAULT_LIBRARY_FALLBACK: yield os.path.join(path, os.path.basename(name)) def dyld_find(name, executable_path=None, env=None, loader_path=None): """ Find a library or framework using dyld semantics """ name = _ensure_utf8(name) executable_path = _ensure_utf8(executable_path) for path in dyld_image_suffix_search(chain( dyld_override_search(name, env), dyld_executable_path_search(name, executable_path), dyld_loader_search(name, loader_path), dyld_default_search(name, env), ), env): if os.path.isfile(path): return path raise ValueError("dylib %s could not be found" % (name,)) def framework_find(fn, executable_path=None, env=None): """ Find a framework using dyld semantics in a very loose manner. Will take input such as: Python Python.framework Python.framework/Versions/Current """ try: return dyld_find(fn, executable_path=executable_path, env=env) except ValueError: pass fmwk_index = fn.rfind('.framework') if fmwk_index == -1: fmwk_index = len(fn) fn += '.framework' fn = os.path.join(fn, os.path.basename(fn[:fmwk_index])) return dyld_find(fn, executable_path=executable_path, env=env) macholib-1.7/macholib/dylib.py0000644000076500000240000000170211537025736017005 0ustar ronaldstaff00000000000000""" Generic dylib path manipulation """ import re __all__ = ['dylib_info'] _DYLIB_RE = re.compile(r"""(?x) (?P^.*)(?:^|/) (?P (?P\w+?) (?:\.(?P[^._]+))? (?:_(?P[^._]+))? \.dylib$ ) """) def dylib_info(filename): """ A dylib name can take one of the following four forms: Location/Name.SomeVersion_Suffix.dylib Location/Name.SomeVersion.dylib Location/Name_Suffix.dylib Location/Name.dylib returns None if not found or a mapping equivalent to: dict( location='Location', name='Name.SomeVersion_Suffix.dylib', shortname='Name', version='SomeVersion', suffix='Suffix', ) Note that SomeVersion and Suffix are optional and may be None if not present. """ is_dylib = _DYLIB_RE.match(filename) if not is_dylib: return None return is_dylib.groupdict() macholib-1.7/macholib/framework.py0000644000076500000240000000213611537122522017670 0ustar ronaldstaff00000000000000""" Generic framework path manipulation """ import re __all__ = ['framework_info'] _STRICT_FRAMEWORK_RE = re.compile(r"""(?x) (?P^.*)(?:^|/) (?P (?P[-_A-Za-z0-9]+).framework/ (?:Versions/(?P[^/]+)/)? (?P=shortname) (?:_(?P[^_]+))? )$ """) def framework_info(filename): """ A framework name can take one of the following four forms: Location/Name.framework/Versions/SomeVersion/Name_Suffix Location/Name.framework/Versions/SomeVersion/Name Location/Name.framework/Name_Suffix Location/Name.framework/Name returns None if not found, or a mapping equivalent to: dict( location='Location', name='Name.framework/Versions/SomeVersion/Name_Suffix', shortname='Name', version='SomeVersion', suffix='Suffix', ) Note that SomeVersion and Suffix are optional and may be None if not present """ is_framework = _STRICT_FRAMEWORK_RE.match(filename) if not is_framework: return None return is_framework.groupdict() macholib-1.7/macholib/itergraphreport.py0000644000076500000240000000361111750573543021125 0ustar ronaldstaff00000000000000""" Utilities for creating dot output from a MachOGraph XXX: need to rewrite this based on altgraph.Dot """ from collections import deque try: from itertools import imap except ImportError: imap = map __all__ = ['itergraphreport'] def itergraphreport(nodes, describe_edge, name='G'): edges = deque() nodetoident = {} mainedges = set() def nodevisitor(node, data, outgoing, incoming): return {'label': str(node)} def edgevisitor(edge, data, head, tail): return {} yield 'digraph %s {\n' % (name,) attr = dict(rankdir='LR', concentrate='true') cpatt = '%s="%s"' for item in attr.iteritems(): yield '\t%s;\n' % (cpatt % item,) # find all packages (subgraphs) for (node, data, outgoing, incoming) in nodes: nodetoident[node] = getattr(data, 'identifier', node) # create sets for subgraph, write out descriptions for (node, data, outgoing, incoming) in nodes: # update edges for edge in imap(describe_edge, outgoing): edges.append(edge) # describe node yield '\t"%s" [%s];\n' % ( node, ','.join([ (cpatt % item) for item in nodevisitor(node, data, outgoing, incoming).iteritems() ]), ) graph = [] while edges: edge, data, head, tail = edges.popleft() if data in ('run_file', 'load_dylib'): graph.append((edge, data, head, tail)) def do_graph(edges, tabs): edgestr = tabs + '"%s" -> "%s" [%s];\n' # describe edge for (edge, data, head, tail) in edges: attribs = edgevisitor(edge, data, head, tail) yield edgestr % ( head, tail, ','.join([(cpatt % item) for item in attribs.iteritems()]), ) for s in do_graph(graph, '\t'): yield s yield '}\n' macholib-1.7/macholib/mach_o.py0000644000076500000240000011544212364212074017127 0ustar ronaldstaff00000000000000""" Other than changing the load commands in such a way that they do not contain the load command itself, this is largely a by-hand conversion of the C headers. Hopefully everything in here should be at least as obvious as the C headers, and you should be using the C headers as a real reference because the documentation didn't come along for the ride. Doing much of anything with the symbol tables or segments is really not covered at this point. See /usr/include/mach-o and friends. """ import time from macholib.ptypes import * _CPU_ARCH_ABI64 = 0x01000000 CPU_TYPE_NAMES = { -1: 'ANY', 1: 'VAX', 6: 'MC680x0', 7: 'i386', _CPU_ARCH_ABI64 | 7: 'x86_64', 8: 'MIPS', 10: 'MC98000', 11: 'HPPA', 12: 'ARM', _CPU_ARCH_ABI64 | 12: 'ARM64', 13: 'MC88000', 14: 'SPARC', 15: 'i860', 16: 'Alpha', 18: 'PowerPC', _CPU_ARCH_ABI64 | 18: 'PowerPC64', } INTEL64_SUBTYPE = { 3 : "CPU_SUBTYPE_X86_64_ALL", 4 : "CPU_SUBTYPE_X86_ARCH1" } #define CPU_SUBTYPE_INTEL(f, m) ((cpu_subtype_t) (f) + ((m) << 4)) INTEL_SUBTYPE = { 0 : "CPU_SUBTYPE_INTEL_MODEL_ALL", 1 : "CPU_THREADTYPE_INTEL_HTT", 3 : "CPU_SUBTYPE_I386_ALL", 4 : "CPU_SUBTYPE_486", 5 : "CPU_SUBTYPE_586", 8 : "CPU_SUBTYPE_PENTIUM_3", 9 : "CPU_SUBTYPE_PENTIUM_M", 10 : "CPU_SUBTYPE_PENTIUM_4", 11 : "CPU_SUBTYPE_ITANIUM", 12 : "CPU_SUBTYPE_XEON", 34 : "CPU_SUBTYPE_XEON_MP", 42 : "CPU_SUBTYPE_PENTIUM_4_M", 43 : "CPU_SUBTYPE_ITANIUM_2", 38 : "CPU_SUBTYPE_PENTPRO", 40 : "CPU_SUBTYPE_PENTIUM_3_M", 52 : "CPU_SUBTYPE_PENTIUM_3_XEON", 102 : "CPU_SUBTYPE_PENTII_M3", 132 : "CPU_SUBTYPE_486SX", 166 : "CPU_SUBTYPE_PENTII_M5", 199 : "CPU_SUBTYPE_CELERON", 231 : "CPU_SUBTYPE_CELERON_MOBILE" } MC680_SUBTYPE = { 1 : "CPU_SUBTYPE_MC680x0_ALL", 2 : "CPU_SUBTYPE_MC68040", 3 : "CPU_SUBTYPE_MC68030_ONLY" } MIPS_SUBTYPE = { 0 : "CPU_SUBTYPE_MIPS_ALL", 1 : "CPU_SUBTYPE_MIPS_R2300", 2 : "CPU_SUBTYPE_MIPS_R2600", 3 : "CPU_SUBTYPE_MIPS_R2800", 4 : "CPU_SUBTYPE_MIPS_R2000a", 5 : "CPU_SUBTYPE_MIPS_R2000", 6 : "CPU_SUBTYPE_MIPS_R3000a", 7 : "CPU_SUBTYPE_MIPS_R3000" } MC98000_SUBTYPE = { 0 : "CPU_SUBTYPE_MC98000_ALL", 1 : "CPU_SUBTYPE_MC98601" } HPPA_SUBTYPE = { 0 : "CPU_SUBTYPE_HPPA_7100", 1 : "CPU_SUBTYPE_HPPA_7100LC" } MC88_SUBTYPE = { 0 : "CPU_SUBTYPE_MC88000_ALL", 1 : "CPU_SUBTYPE_MC88100", 2 : "CPU_SUBTYPE_MC88110" } SPARC_SUBTYPE = { 0 : "CPU_SUBTYPE_SPARC_ALL" } I860_SUBTYPE = { 0 : "CPU_SUBTYPE_I860_ALL", 1 : "CPU_SUBTYPE_I860_860" } POWERPC_SUBTYPE = { 0 : "CPU_SUBTYPE_POWERPC_ALL", 1 : "CPU_SUBTYPE_POWERPC_601", 2 : "CPU_SUBTYPE_POWERPC_602", 3 : "CPU_SUBTYPE_POWERPC_603", 4 : "CPU_SUBTYPE_POWERPC_603e", 5 : "CPU_SUBTYPE_POWERPC_603ev", 6 : "CPU_SUBTYPE_POWERPC_604", 7 : "CPU_SUBTYPE_POWERPC_604e", 8 : "CPU_SUBTYPE_POWERPC_620", 9 : "CPU_SUBTYPE_POWERPC_750", 10 : "CPU_SUBTYPE_POWERPC_7400", 11 : "CPU_SUBTYPE_POWERPC_7450", 100 : "CPU_SUBTYPE_POWERPC_970" } ARM_SUBTYPE = { 0 : "CPU_SUBTYPE_ARM_ALL12", 5 : "CPU_SUBTYPE_ARM_V4T", 6 : "CPU_SUBTYPE_ARM_V6", 7 : "CPU_SUBTYPE_ARM_V5TEJ", 8 : "CPU_SUBTYPE_ARM_XSCALE", 9 : "CPU_SUBTYPE_ARM_V7", 10 : "CPU_SUBTYPE_ARM_V7F", 12 : "CPU_SUBTYPE_ARM_V7K" } VAX_SUBTYPE = { 0 : "CPU_SUBTYPE_VAX_ALL", 1 : "CPU_SUBTYPE_VAX780", 2 : "CPU_SUBTYPE_VAX785", 3 : "CPU_SUBTYPE_VAX750", 4 : "CPU_SUBTYPE_VAX730", 5 : "CPU_SUBTYPE_UVAXI", 6 : "CPU_SUBTYPE_UVAXII", 7 : "CPU_SUBTYPE_VAX8200", 8 : "CPU_SUBTYPE_VAX8500", 9 : "CPU_SUBTYPE_VAX8600", 10 : "CPU_SUBTYPE_VAX8650", 11 : "CPU_SUBTYPE_VAX8800", 12 : "CPU_SUBTYPE_UVAXIII", } def get_cpu_subtype(cpu_type, cpu_subtype): st = cpu_subtype & 0x0fffffff if cpu_type == 1: subtype = VAX_SUBTYPE.get(st, st) elif cpu_type == 6: subtype = MC680_SUBTYPE.get(st, st) elif cpu_type == 7: subtype = INTEL_SUBTYPE.get(st, st) elif cpu_type == 7 | _CPU_ARCH_ABI64: subtype = INTEL64_SUBTYPE.get(st, st) elif cpu_type == 8: subtype = MIPS_SUBTYPE.get(st, st) elif cpu_type == 10: subtype = MC98000_SUBTYPE.get(st, st) elif cpu_type == 11: subtype = HPPA_SUBTYPE.get(st, st) elif cpu_type == 12: subtype = ARM_SUBTYPE.get(st, st) elif cpu_type == 13: subtype = MC88_SUBTYPE.get(st, st) elif cpu_type == 14: subtype = SPARC_SUBTYPE.get(st, st) elif cpu_type == 15: subtype = I860_SUBTYPE.get(st, st) elif cpu_type == 16: subtype = MIPS_SUBTYPE.get(st, st) elif cpu_type == 18: subtype = POWERPC_SUBTYPE.get(st, st) elif cpu_type == 18 | _CPU_ARCH_ABI64: subtype = POWERPC_SUBTYPE.get(st, st) else: subtype = str(st) return subtype _MH_EXECUTE_SYM = "__mh_execute_header" MH_EXECUTE_SYM = "_mh_execute_header" _MH_BUNDLE_SYM = "__mh_bundle_header" MH_BUNDLE_SYM = "_mh_bundle_header" _MH_DYLIB_SYM = "__mh_dylib_header" MH_DYLIB_SYM = "_mh_dylib_header" _MH_DYLINKER_SYM = "__mh_dylinker_header" MH_DYLINKER_SYM = "_mh_dylinker_header" ( MH_OBJECT, MH_EXECUTE, MH_FVMLIB, MH_CORE, MH_PRELOAD, MH_DYLIB, MH_DYLINKER, MH_BUNDLE, MH_DYLIB_STUB, MH_DSYM ) = range(0x1, 0xb) ( MH_NOUNDEFS, MH_INCRLINK, MH_DYLDLINK, MH_BINDATLOAD, MH_PREBOUND, MH_SPLIT_SEGS, MH_LAZY_INIT, MH_TWOLEVEL, MH_FORCE_FLAT, MH_NOMULTIDEFS, MH_NOFIXPREBINDING, MH_PREBINDABLE, MH_ALLMODSBOUND, MH_SUBSECTIONS_VIA_SYMBOLS, MH_CANONICAL, MH_WEAK_DEFINES, MH_BINDS_TO_WEAK, MH_ALLOW_STACK_EXECUTION, MH_ROOT_SAFE, MH_SETUID_SAFE, MH_NO_REEXPORTED_DYLIBS, MH_PIE, MH_DEAD_STRIPPABLE_DYLIB, MH_HAS_TLV_DESCRIPTORS, MH_NO_HEAP_EXECUTION ) = map((1).__lshift__, range(25)) MH_MAGIC = 0xfeedface MH_CIGAM = 0xcefaedfe MH_MAGIC_64 = 0xfeedfacf MH_CIGAM_64 = 0xcffaedfe integer_t = p_int32 cpu_type_t = integer_t cpu_subtype_t = p_uint32 MH_FILETYPE_NAMES = { MH_OBJECT: 'relocatable object', MH_EXECUTE: 'demand paged executable', MH_FVMLIB: 'fixed vm shared library', MH_CORE: 'core', MH_PRELOAD: 'preloaded executable', MH_DYLIB: 'dynamically bound shared library', MH_DYLINKER: 'dynamic link editor', MH_BUNDLE: 'dynamically bound bundle', MH_DYLIB_STUB: 'shared library stub for static linking', MH_DSYM: 'symbol information', } MH_FILETYPE_SHORTNAMES = { MH_OBJECT: 'object', MH_EXECUTE: 'execute', MH_FVMLIB: 'fvmlib', MH_CORE: 'core', MH_PRELOAD: 'preload', MH_DYLIB: 'dylib', MH_DYLINKER: 'dylinker', MH_BUNDLE: 'bundle', MH_DYLIB_STUB: 'dylib_stub', MH_DSYM: 'dsym', } MH_FLAGS_NAMES = { MH_NOUNDEFS: 'MH_NOUNDEFS', MH_INCRLINK: 'MH_INCRLINK', MH_DYLDLINK: 'MH_DYLDLINK', MH_BINDATLOAD: 'MH_BINDATLOAD', MH_PREBOUND: 'MH_PREBOUND', MH_SPLIT_SEGS: 'MH_SPLIT_SEGS', MH_LAZY_INIT: 'MH_LAZY_INIT', MH_TWOLEVEL: 'MH_TWOLEVEL', MH_FORCE_FLAT: 'MH_FORCE_FLAT', MH_NOMULTIDEFS: 'MH_NOMULTIDEFS', MH_NOFIXPREBINDING: 'MH_NOFIXPREBINDING', MH_PREBINDABLE: 'MH_PREBINDABLE', MH_ALLMODSBOUND: 'MH_ALLMODSBOUND', MH_SUBSECTIONS_VIA_SYMBOLS: 'MH_SUBSECTIONS_VIA_SYMBOLS', MH_CANONICAL: 'MH_CANONICAL', MH_WEAK_DEFINES: 'MH_WEAK_DEFINES', MH_BINDS_TO_WEAK: 'MH_BINDS_TO_WEAK', MH_ALLOW_STACK_EXECUTION: 'MH_ALLOW_STACK_EXECUTION', MH_ROOT_SAFE: 'MH_ROOT_SAFE', MH_SETUID_SAFE: 'MH_SETUID_SAFE', MH_NO_REEXPORTED_DYLIBS: 'MH_NO_REEXPORTED_DYLIBS', MH_PIE: 'MH_PIE', MH_DEAD_STRIPPABLE_DYLIB: 'MH_DEAD_STRIPPABLE_DYLIB', MH_HAS_TLV_DESCRIPTORS: 'MH_HAS_TLV_DESCRIPTORS', MH_NO_HEAP_EXECUTION: 'MH_NO_HEAP_EXECUTION', } MH_FLAGS_DESCRIPTIONS = { MH_NOUNDEFS: 'no undefined references', MH_INCRLINK: 'output of an incremental link', MH_DYLDLINK: 'input for the dynamic linker', MH_BINDATLOAD: 'undefined references bound dynamically when loaded', MH_PREBOUND: 'dynamic undefined references prebound', MH_SPLIT_SEGS: 'split read-only and read-write segments', MH_LAZY_INIT: '(obsolete)', MH_TWOLEVEL: 'using two-level name space bindings', MH_FORCE_FLAT: 'forcing all imagges to use flat name space bindings', MH_NOMULTIDEFS: 'umbrella guarantees no multiple definitions', MH_NOFIXPREBINDING: 'do not notify prebinding agent about this executable', MH_PREBINDABLE: 'the binary is not prebound but can have its prebinding redone', MH_ALLMODSBOUND: 'indicates that this binary binds to all two-level namespace modules of its dependent libraries', MH_SUBSECTIONS_VIA_SYMBOLS: 'safe to divide up the sections into sub-sections via symbols for dead code stripping', MH_CANONICAL: 'the binary has been canonicalized via the unprebind operation', MH_WEAK_DEFINES: 'the final linked image contains external weak symbols', MH_BINDS_TO_WEAK: 'the final linked image uses weak symbols', MH_ALLOW_STACK_EXECUTION: 'all stacks in the task will be given stack execution privilege', MH_ROOT_SAFE: 'the binary declares it is safe for use in processes with uid zero', MH_SETUID_SAFE: 'the binary declares it is safe for use in processes when issetugid() is true', MH_NO_REEXPORTED_DYLIBS: 'the static linker does not need to examine dependent dylibs to see if any are re-exported', MH_PIE: 'the OS will load the main executable at a random address', MH_DEAD_STRIPPABLE_DYLIB: 'the static linker will automatically not create a LC_LOAD_DYLIB load command to the dylib if no symbols are being referenced from the dylib', MH_HAS_TLV_DESCRIPTORS: 'contains a section of type S_THREAD_LOCAL_VARIABLES', MH_NO_HEAP_EXECUTION: 'the OS will run the main executable with a non-executable heap even on platforms that don\'t require it', } class mach_version_helper(Structure): _fields_ = ( ('major', p_ushort), ('minor', p_uint8), ('rev', p_uint8), ) def __str__(self): return '%s.%s.%s' % (self.major, self.minor, self.rev) class mach_timestamp_helper(p_uint32): def __str__(self): return time.ctime(self) def read_struct(f, s, **kw): return s.from_fileobj(f, **kw) class mach_header(Structure): _fields_ = ( ('magic', p_uint32), ('cputype', cpu_type_t), ('cpusubtype', cpu_subtype_t), ('filetype', p_uint32), ('ncmds', p_uint32), ('sizeofcmds', p_uint32), ('flags', p_uint32), ) def _describe(self): bit = 1 flags = self.flags dflags = [] while flags and bit < (1<<32): if flags & bit: dflags.append({'name': MH_FLAGS_NAMES.get(bit, str(bit)), 'description': MH_FLAGS_DESCRIPTIONS.get(bit, str(bit))}) flags = flags ^ bit bit <<= 1 return ( ('magic', int(self.magic)), ('cputype_string', CPU_TYPE_NAMES.get(self.cputype, self.cputype)), ('cputype', int(self.cputype)), ('cpusubtype_string', get_cpu_subtype(self.cputype, self.cpusubtype)), ('cpusubtype', int(self.cpusubtype)), ('filetype_string', MH_FILETYPE_NAMES.get(self.filetype, self.filetype)), ('filetype', int(self.filetype)), ('ncmds', self.ncmds), ('sizeofcmds', self.sizeofcmds), ('flags', dflags), ('raw_flags', int(self.flags)) ) class mach_header_64(mach_header): _fields_ = mach_header._fields_ + (('reserved', p_uint32),) class load_command(Structure): _fields_ = ( ('cmd', p_uint32), ('cmdsize', p_uint32), ) def get_cmd_name(self): return LC_NAMES.get(self.cmd, self.cmd) LC_REQ_DYLD = 0x80000000 ( LC_SEGMENT, LC_SYMTAB, LC_SYMSEG, LC_THREAD, LC_UNIXTHREAD, LC_LOADFVMLIB, LC_IDFVMLIB, LC_IDENT, LC_FVMFILE, LC_PREPAGE, LC_DYSYMTAB, LC_LOAD_DYLIB, LC_ID_DYLIB, LC_LOAD_DYLINKER, LC_ID_DYLINKER, LC_PREBOUND_DYLIB, LC_ROUTINES, LC_SUB_FRAMEWORK, LC_SUB_UMBRELLA, LC_SUB_CLIENT, LC_SUB_LIBRARY, LC_TWOLEVEL_HINTS, LC_PREBIND_CKSUM ) = range(0x1, 0x18) LC_LOAD_WEAK_DYLIB = LC_REQ_DYLD | 0x18 LC_SEGMENT_64 = 0x19 LC_ROUTINES_64 = 0x1a LC_UUID = 0x1b LC_RPATH = (0x1c | LC_REQ_DYLD) LC_CODE_SIGNATURE = 0x1d LC_CODE_SEGMENT_SPLIT_INFO = 0x1e LC_REEXPORT_DYLIB = 0x1f | LC_REQ_DYLD LC_LAZY_LOAD_DYLIB = 0x20 LC_ENCRYPTION_INFO = 0x21 LC_DYLD_INFO = 0x22 LC_DYLD_INFO_ONLY = 0x22 | LC_REQ_DYLD LC_LOAD_UPWARD_DYLIB = 0x23 | LC_REQ_DYLD LC_VERSION_MIN_MACOSX = 0x24 LC_VERSION_MIN_IPHONEOS = 0x25 LC_FUNCTION_STARTS = 0x26 LC_DYLD_ENVIRONMENT = 0x27 LC_MAIN = 0x28 | LC_REQ_DYLD LC_DATA_IN_CODE = 0x29 LC_SOURCE_VERSION = 0x2a LC_DYLIB_CODE_SIGN_DRS = 0x2b LC_ENCRYPTION_INFO_64 = 0x2c LC_LINKER_OPTION = 0x2d # this is really a union.. but whatever class lc_str(p_uint32): pass p_str16 = pypackable('p_str16', bytes, '16s') vm_prot_t = p_int32 class segment_command(Structure): _fields_ = ( ('segname', p_str16), ('vmaddr', p_uint32), ('vmsize', p_uint32), ('fileoff', p_uint32), ('filesize', p_uint32), ('maxprot', vm_prot_t), ('initprot', vm_prot_t), ('nsects', p_uint32), # read the section structures ? ('flags', p_uint32), ) def describe(self): segname = self.segname s = {} s['segname'] = self.segname.rstrip('\x00') s['vmaddr'] = int(self.vmaddr) s['vmsize'] = int(self.vmsize) s['fileoff'] = int(self.fileoff) s['filesize'] = int(self.filesize) s['initprot'] = self.get_initial_virtual_memory_protections() s['initprot_raw'] = int(self.initprot) s['maxprot'] = self.get_max_virtual_memory_protections() s['maxprot_raw'] = int(self.maxprot) s['nsects'] = int(self.nsects) s['flags'] = self.flags return s def get_initial_virtual_memory_protections(self): vm = [] if self.initprot == 0: vm.append("VM_PROT_NONE") if self.initprot & 1: vm.append("VM_PROT_READ") if self.initprot & 2: vm.append("VM_PROT_WRITE") if self.initprot & 4: vm.append("VM_PROT_EXECUTE") return vm def get_max_virtual_memory_protections(self): vm = [] if self.maxprot == 0: vm.append("VM_PROT_NONE") if self.maxprot & 1: vm.append("VM_PROT_READ") if self.maxprot & 2: vm.append("VM_PROT_WRITE") if self.maxprot & 4: vm.append("VM_PROT_EXECUTE") return vm class segment_command_64(Structure): _fields_ = ( ('segname', p_str16), ('vmaddr', p_uint64), ('vmsize', p_uint64), ('fileoff', p_uint64), ('filesize', p_uint64), ('maxprot', vm_prot_t), ('initprot', vm_prot_t), ('nsects', p_uint32), # read the section structures ? ('flags', p_uint32), ) def describe(self): s = {} s['segname'] = self.segname.rstrip('\x00') s['vmaddr'] = int(self.vmaddr) s['vmsize'] = int(self.vmsize) s['fileoff'] = int(self.fileoff) s['filesize'] = int(self.filesize) s['initprot'] = self.get_initial_virtual_memory_protections() s['initprot_raw'] = int(self.initprot) s['maxprot'] = self.get_max_virtual_memory_protections() s['maxprot_raw'] = int(self.maxprot) s['nsects'] = int(self.nsects) s['flags'] = self.flags return s def get_initial_virtual_memory_protections(self): vm = [] if self.initprot == 0: vm.append("VM_PROT_NONE") if self.initprot & 1: vm.append("VM_PROT_READ") if self.initprot & 2: vm.append("VM_PROT_WRITE") if self.initprot & 4: vm.append("VM_PROT_EXECUTE") return vm def get_max_virtual_memory_protections(self): vm = [] if self.maxprot == 0: vm.append("VM_PROT_NONE") if self.maxprot & 1: vm.append("VM_PROT_READ") if self.maxprot & 2: vm.append("VM_PROT_WRITE") if self.maxprot & 4: vm.append("VM_PROT_EXECUTE") return vm SG_HIGHVM = 0x1 SG_FVMLIB = 0x2 SG_NORELOC = 0x4 class section(Structure): _fields_ = ( ('sectname', p_str16), ('segname', p_str16), ('addr', p_uint32), ('size', p_uint32), ('offset', p_uint32), ('align', p_uint32), ('reloff', p_uint32), ('nreloc', p_uint32), ('flags', p_uint32), ('reserved1', p_uint32), ('reserved2', p_uint32), ) def describe(self): s = {} s['sectname'] = self.sectname.rstrip('\x00') s['segname'] = self.segname.rstrip('\x00') s['addr'] = int(self.addr) s['size'] = int(self.size) s['offset'] = int(self.offset) s['align'] = int(self.align) s['reloff'] = int(self.reloff) s['nreloc'] = int(self.nreloc) f = {} f['type'] = FLAG_SECTION_TYPES[int(self.flags) & 0xff] f['attributes'] = [] for k in FLAG_SECTION_ATTRIBUTES: if k & self.flags: f['attributes'].append(FLAG_SECTION_ATTRIBUTES[k]) if not f['attributes']: del f['attributes'] s['flags'] = f s['reserved1'] = int(self.reserved1) s['reserved2'] = int(self.reserved2) return s def add_section_data(self, data): self.section_data = data class section_64(Structure): _fields_ = ( ('sectname', p_str16), ('segname', p_str16), ('addr', p_uint64), ('size', p_uint64), ('offset', p_uint32), ('align', p_uint32), ('reloff', p_uint32), ('nreloc', p_uint32), ('flags', p_uint32), ('reserved1', p_uint32), ('reserved2', p_uint32), ('reserved3', p_uint32), ) def describe(self): s = {} s['sectname'] = self.sectname.rstrip('\x00') s['segname'] = self.segname.rstrip('\x00') s['addr'] = int(self.addr) s['size'] = int(self.size) s['offset'] = int(self.offset) s['align'] = int(self.align) s['reloff'] = int(self.reloff) s['nreloc'] = int(self.nreloc) f = {} f['type'] = FLAG_SECTION_TYPES[int(self.flags) & 0xff] f['attributes'] = [] for k in FLAG_SECTION_ATTRIBUTES: if k & self.flags: f['attributes'].append(FLAG_SECTION_ATTRIBUTES[k]) if not f['attributes']: del f['attributes'] s['flags'] = f s['reserved1'] = int(self.reserved1) s['reserved2'] = int(self.reserved2) s['reserved3'] = int(self.reserved3) return s def add_section_data(self, data): self.section_data = data SECTION_TYPE = 0xff SECTION_ATTRIBUTES = 0xffffff00 S_REGULAR = 0x0 S_ZEROFILL = 0x1 S_CSTRING_LITERALS = 0x2 S_4BYTE_LITERALS = 0x3 S_8BYTE_LITERALS = 0x4 S_LITERAL_POINTERS = 0x5 S_NON_LAZY_SYMBOL_POINTERS = 0x6 S_LAZY_SYMBOL_POINTERS = 0x7 S_SYMBOL_STUBS = 0x8 S_MOD_INIT_FUNC_POINTERS = 0x9 S_MOD_TERM_FUNC_POINTERS = 0xa S_COALESCED = 0xb FLAG_SECTION_TYPES = { 0x0 : "S_REGULAR", 0x1 : "S_ZEROFILL", 0x2 : "S_CSTRING_LITERALS", 0x3 : "S_4BYTE_LITERALS", 0x4 : "S_8BYTE_LITERALS", 0x5 : "S_LITERAL_POINTERS", 0x6 : "S_NON_LAZY_SYMBOL_POINTERS", 0x7 : "S_LAZY_SYMBOL_POINTERS", 0x8 : "S_SYMBOL_STUBS", 0x9 : "S_MOD_INIT_FUNC_POINTERS", 0xa : "S_MOD_TERM_FUNC_POINTERS", 0xb : "S_COALESCED", 0xc : "S_GB_ZEROFILL", 0xd : "S_INTERPOSING", 0xe : "S_16BYTE_LITERALS", 0xf : "S_DTRACE_DOF", 0x10 : "S_LAZY_DYLIB_SYMBOL_POINTERS", 0x11 : "S_THREAD_LOCAL_REGULAR", 0x12 : "S_THREAD_LOCAL_ZEROFILL", 0x13 : "S_THREAD_LOCAL_VARIABLES", 0x14 : "S_THREAD_LOCAL_VARIABLE_POINTERS", 0x15 : "S_THREAD_LOCAL_INIT_FUNCTION_POINTERS" } FLAG_SECTION_ATTRIBUTES = { 0x80000000 : "S_ATTR_PURE_INSTRUCTIONS", 0x40000000 : "S_ATTR_NO_TOC", 0x20000000 : "S_ATTR_STRIP_STATIC_SYMS", 0x10000000 : "S_ATTR_NO_DEAD_STRIP", 0x08000000 : "S_ATTR_LIVE_SUPPORT", 0x04000000 : "S_ATTR_SELF_MODIFYING_CODE", 0x02000000 : "S_ATTR_DEBUG", 0x00000400 : "S_ATTR_SOME_INSTRUCTIONS", 0x00000200 : "S_ATTR_EXT_RELOC", 0x00000100 : "S_ATTR_LOC_RELOC" } SECTION_ATTRIBUTES_USR = 0xff000000 S_ATTR_PURE_INSTRUCTIONS = 0x80000000 S_ATTR_NO_TOC = 0x40000000 S_ATTR_STRIP_STATIC_SYMS = 0x20000000 SECTION_ATTRIBUTES_SYS = 0x00ffff00 S_ATTR_SOME_INSTRUCTIONS = 0x00000400 S_ATTR_EXT_RELOC = 0x00000200 S_ATTR_LOC_RELOC = 0x00000100 SEG_PAGEZERO = "__PAGEZERO" SEG_TEXT = "__TEXT" SECT_TEXT = "__text" SECT_FVMLIB_INIT0 = "__fvmlib_init0" SECT_FVMLIB_INIT1 = "__fvmlib_init1" SEG_DATA = "__DATA" SECT_DATA = "__data" SECT_BSS = "__bss" SECT_COMMON = "__common" SEG_OBJC = "__OBJC" SECT_OBJC_SYMBOLS = "__symbol_table" SECT_OBJC_MODULES = "__module_info" SECT_OBJC_STRINGS = "__selector_strs" SECT_OBJC_REFS = "__selector_refs" SEG_ICON = "__ICON" SECT_ICON_HEADER = "__header" SECT_ICON_TIFF = "__tiff" SEG_LINKEDIT = "__LINKEDIT" SEG_UNIXSTACK = "__UNIXSTACK" # # I really should remove all these _command classes because they # are no different. I decided to keep the load commands separate, # so classes like fvmlib and fvmlib_command are equivalent. # class fvmlib(Structure): _fields_ = ( ('name', lc_str), ('minor_version', mach_version_helper), ('header_addr', p_uint32), ) class fvmlib_command(Structure): _fields_ = fvmlib._fields_ def describe(self): s = {} s['header_addr'] = int(self.header_addr) return s class dylib(Structure): _fields_ = ( ('name', lc_str), ('timestamp', mach_timestamp_helper), ('current_version', mach_version_helper), ('compatibility_version', mach_version_helper), ) # merged dylib structure class dylib_command(Structure): _fields_ = dylib._fields_ def describe(self): s = {} s['timestamp'] = str(self.timestamp) s['current_version'] = str(self.current_version) s['compatibility_version'] = str(self.compatibility_version) return s class sub_framework_command(Structure): _fields_ = ( ('umbrella', lc_str), ) def describe(self): return {} class sub_client_command(Structure): _fields_ = ( ('client', lc_str), ) def describe(self): return {} class sub_umbrella_command(Structure): _fields_ = ( ('sub_umbrella', lc_str), ) def describe(self): return {} class sub_library_command(Structure): _fields_ = ( ('sub_library', lc_str), ) def describe(self): return {} class prebound_dylib_command(Structure): _fields_ = ( ('name', lc_str), ('nmodules', p_uint32), ('linked_modules', lc_str), ) def describe(self): return {'nmodules': int(self.nmodules)} class dylinker_command(Structure): _fields_ = ( ('name', lc_str), ) def describe(self): return {} class thread_command(Structure): _fields_ = ( ) def describe(self): return {} class entry_point_command(Structure): _fields_ = ( ('entryoff', p_uint64), ('stacksize', p_uint64), ) def describe(self): s = {} s['entryoff'] = int(self.entryoff) s['stacksize'] = int(self.stacksize) return s class routines_command(Structure): _fields_ = ( ('init_address', p_uint32), ('init_module', p_uint32), ('reserved1', p_uint32), ('reserved2', p_uint32), ('reserved3', p_uint32), ('reserved4', p_uint32), ('reserved5', p_uint32), ('reserved6', p_uint32), ) def describe(self): s = {} s['init_address'] = int(self.init_address) s['init_module'] = int(self.init_module) s['reserved1'] = int(self.reserved1) s['reserved2'] = int(self.reserved2) s['reserved3'] = int(self.reserved3) s['reserved4'] = int(self.reserved4) s['reserved5'] = int(self.reserved5) s['reserved6'] = int(self.reserved6) return s class routines_command_64(Structure): _fields_ = ( ('init_address', p_uint64), ('init_module', p_uint64), ('reserved1', p_uint64), ('reserved2', p_uint64), ('reserved3', p_uint64), ('reserved4', p_uint64), ('reserved5', p_uint64), ('reserved6', p_uint64), ) def describe(self): s = {} s['init_address'] = int(self.init_address) s['init_module'] = int(self.init_module) s['reserved1'] = int(self.reserved1) s['reserved2'] = int(self.reserved2) s['reserved3'] = int(self.reserved3) s['reserved4'] = int(self.reserved4) s['reserved5'] = int(self.reserved5) s['reserved6'] = int(self.reserved6) return s class symtab_command(Structure): _fields_ = ( ('symoff', p_uint32), ('nsyms', p_uint32), ('stroff', p_uint32), ('strsize', p_uint32), ) def describe(self): s = {} s['symoff'] = int(self.symoff) s['nsyms'] = int(self.nsyms) s['stroff'] = int(self.stroff) s['strsize'] = int(self.strsize) return s class dysymtab_command(Structure): _fields_ = ( ('ilocalsym', p_uint32), ('nlocalsym', p_uint32), ('iextdefsym', p_uint32), ('nextdefsym', p_uint32), ('iundefsym', p_uint32), ('nundefsym', p_uint32), ('tocoff', p_uint32), ('ntoc', p_uint32), ('modtaboff', p_uint32), ('nmodtab', p_uint32), ('extrefsymoff', p_uint32), ('nextrefsyms', p_uint32), ('indirectsymoff', p_uint32), ('nindirectsyms', p_uint32), ('extreloff', p_uint32), ('nextrel', p_uint32), ('locreloff', p_uint32), ('nlocrel', p_uint32), ) def describe(self): dys = {} dys['ilocalsym'] = int(self.ilocalsym) dys['nlocalsym'] = int(self.nlocalsym) dys['iextdefsym'] = int(self.iextdefsym) dys['nextdefsym'] = int(self.nextdefsym) dys['iundefsym'] = int(self.iundefsym) dys['nundefsym'] = int(self.nundefsym) dys['tocoff'] = int(self.tocoff) dys['ntoc'] = int(self.ntoc) dys['modtaboff'] = int(self.modtaboff) dys['nmodtab'] = int(self.nmodtab) dys['extrefsymoff'] = int(self.extrefsymoff) dys['nextrefsyms'] = int(self.nextrefsyms) dys['indirectsymoff'] = int(self.indirectsymoff) dys['nindirectsyms'] = int(self.nindirectsyms) dys['extreloff'] = int(self.extreloff) dys['nextrel'] = int(self.nextrel) dys['locreloff'] = int(self.locreloff) dys['nlocrel'] = int(self.nlocrel) return dys INDIRECT_SYMBOL_LOCAL = 0x80000000 INDIRECT_SYMBOL_ABS = 0x40000000 class dylib_table_of_contents(Structure): _fields_ = ( ('symbol_index', p_uint32), ('module_index', p_uint32), ) class dylib_module(Structure): _fields_ = ( ('module_name', p_uint32), ('iextdefsym', p_uint32), ('nextdefsym', p_uint32), ('irefsym', p_uint32), ('nrefsym', p_uint32), ('ilocalsym', p_uint32), ('nlocalsym', p_uint32), ('iextrel', p_uint32), ('nextrel', p_uint32), ('iinit_iterm', p_uint32), ('ninit_nterm', p_uint32), ('objc_module_info_addr', p_uint32), ('objc_module_info_size', p_uint32), ) class dylib_module_64(Structure): _fields_ = ( ('module_name', p_uint32), ('iextdefsym', p_uint32), ('nextdefsym', p_uint32), ('irefsym', p_uint32), ('nrefsym', p_uint32), ('ilocalsym', p_uint32), ('nlocalsym', p_uint32), ('iextrel', p_uint32), ('nextrel', p_uint32), ('iinit_iterm', p_uint32), ('ninit_nterm', p_uint32), ('objc_module_info_size', p_uint32), ('objc_module_info_addr', p_uint64), ) class dylib_reference(Structure): _fields_ = ( # XXX - ick, fix ('isym_flags', p_uint32), #('isym', p_uint8 * 3), #('flags', p_uint8), ) class twolevel_hints_command(Structure): _fields_ = ( ('offset', p_uint32), ('nhints', p_uint32), ) def describe(self): s = {} s['offset'] = int(self.offset) s['nhints'] = int(self.nhints) return s class twolevel_hint(Structure): _fields_ = ( # XXX - ick, fix ('isub_image_itoc', p_uint32), #('isub_image', p_uint8), #('itoc', p_uint8 * 3), ) class prebind_cksum_command(Structure): _fields_ = ( ('cksum', p_uint32), ) def describe(self): return {'cksum': int(self.cksum)} class symseg_command(Structure): _fields_ = ( ('offset', p_uint32), ('size', p_uint32), ) def describe(self): s = {} s['offset'] = int(self.offset) s['size'] = int(self.size) class ident_command(Structure): _fields_ = ( ) def describe(self): return {} class fvmfile_command(Structure): _fields_ = ( ('name', lc_str), ('header_addr', p_uint32), ) def describe(self): return {'header_addr': int(self.header_addr)} class uuid_command (Structure): _fields_ = ( ('uuid', p_str16), ) def describe(self): return {'uuid': self.uuid.rstrip('\x00')} class rpath_command (Structure): _fields_ = ( ('path', lc_str), ) def describe(self): return {} class linkedit_data_command (Structure): _fields_ = ( ('dataoff', p_uint32), ('datasize', p_uint32), ) def describe(self): s = {} s['dataoff'] = int(self.dataoff) s['datasize'] = int(self.datasize) return s class version_min_command (Structure): _fields_ = ( ('version', p_uint32), # X.Y.Z is encoded in nibbles xxxx.yy.zz ('reserved', p_uint32), ) def describe(self): v = int(self.version) v3 = v & 0xFF v = v >> 8 v2 = v & 0xFF v = v >> 8 v1 = v & 0xFFFF return {'version': str(int(v1)) + "." + str(int(v2)) + "." + str(int(v3))} class source_version_command (Structure): _fields_ = ( ('version', p_uint64), ) def describe(self): v = int(self.version) a = v >> 40 b = (v >> 30) & 0x3ff c = (v >> 20) & 0x3ff d = (v >> 10) & 0x3ff e = v & 0x3ff r = str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+'.'+str(e) return {'version': r} class encryption_info_command (Structure): _fields_ = ( ('cryptoff', p_uint32), ('cryptsize', p_uint32), ('cryptid', p_uint32), ) def describe(self): s = {} s['cryptoff'] = int(self.cryptoff) s['cryptsize'] = int(self.cryptsize) s['cryptid'] = int(self.cryptid) return s class encryption_info_command_64 (Structure): _fields_ = ( ('cryptoff', p_uint32), ('cryptsize', p_uint32), ('cryptid', p_uint32), ('pad', p_uint32), ) def describe(self): s = {} s['cryptoff'] = int(self.cryptoff) s['cryptsize'] = int(self.cryptsize) s['cryptid'] = int(self.cryptid) s['pad'] = int(self.pad) return s class dyld_info_command (Structure): _fields_ = ( ('rebase_off', p_uint32), ('rebase_size', p_uint32), ('bind_off', p_uint32), ('bind_size', p_uint32), ('weak_bind_off', p_uint32), ('weak_bind_size', p_uint32), ('lazy_bind_off', p_uint32), ('lazy_bind_size', p_uint32), ('export_off', p_uint32), ('export_size', p_uint32), ) def describe(self): dyld = {} dyld['rebase_off'] = int(self.rebase_off) dyld['rebase_size'] = int(self.rebase_size) dyld['bind_off'] = int(self.bind_off) dyld['bind_size'] = int(self.bind_size) dyld['weak_bind_off'] = int(self.weak_bind_off) dyld['weak_bind_size'] = int(self.weak_bind_size) dyld['lazy_bind_off'] = int(self.lazy_bind_off) dyld['lazy_bind_size'] = int(self.lazy_bind_size) dyld['export_off'] = int(self.export_off) dyld['export_size'] = int(self.export_size) return dyld class linker_option_command (Structure): _fields_ = ( ('count', p_uint32), ) def describe(self): return {'count': int(self.count)} LC_REGISTRY = { LC_SEGMENT: segment_command, LC_IDFVMLIB: fvmlib_command, LC_LOADFVMLIB: fvmlib_command, LC_ID_DYLIB: dylib_command, LC_LOAD_DYLIB: dylib_command, LC_LOAD_WEAK_DYLIB: dylib_command, LC_SUB_FRAMEWORK: sub_framework_command, LC_SUB_CLIENT: sub_client_command, LC_SUB_UMBRELLA: sub_umbrella_command, LC_SUB_LIBRARY: sub_library_command, LC_PREBOUND_DYLIB: prebound_dylib_command, LC_ID_DYLINKER: dylinker_command, LC_LOAD_DYLINKER: dylinker_command, LC_THREAD: thread_command, LC_UNIXTHREAD: thread_command, LC_ROUTINES: routines_command, LC_SYMTAB: symtab_command, LC_DYSYMTAB: dysymtab_command, LC_TWOLEVEL_HINTS: twolevel_hints_command, LC_PREBIND_CKSUM: prebind_cksum_command, LC_SYMSEG: symseg_command, LC_IDENT: ident_command, LC_FVMFILE: fvmfile_command, LC_SEGMENT_64: segment_command_64, LC_ROUTINES_64: routines_command_64, LC_UUID: uuid_command, LC_RPATH: rpath_command, LC_CODE_SIGNATURE: linkedit_data_command, LC_CODE_SEGMENT_SPLIT_INFO: linkedit_data_command, LC_REEXPORT_DYLIB: dylib_command, LC_LAZY_LOAD_DYLIB: dylib_command, LC_ENCRYPTION_INFO: encryption_info_command, LC_DYLD_INFO: dyld_info_command, LC_DYLD_INFO_ONLY: dyld_info_command, LC_LOAD_UPWARD_DYLIB: dylib_command, LC_VERSION_MIN_MACOSX: version_min_command, LC_VERSION_MIN_IPHONEOS: version_min_command, LC_FUNCTION_STARTS: linkedit_data_command, LC_DYLD_ENVIRONMENT: dylinker_command, LC_MAIN: entry_point_command, LC_DATA_IN_CODE: linkedit_data_command, LC_SOURCE_VERSION: source_version_command, LC_DYLIB_CODE_SIGN_DRS: linkedit_data_command, LC_ENCRYPTION_INFO_64: encryption_info_command_64, LC_LINKER_OPTION: linker_option_command, } LC_NAMES = { LC_SEGMENT: 'LC_SEGMENT', LC_IDFVMLIB: 'LC_IDFVMLIB', LC_LOADFVMLIB: 'LC_LOADFVMLIB', LC_ID_DYLIB: 'LC_ID_DYLIB', LC_LOAD_DYLIB: 'LC_LOAD_DYLIB', LC_LOAD_WEAK_DYLIB: 'LC_LOAD_WEAK_DYLIB', LC_SUB_FRAMEWORK: 'LC_SUB_FRAMEWORK', LC_SUB_CLIENT: 'LC_SUB_CLIENT', LC_SUB_UMBRELLA: 'LC_SUB_UMBRELLA', LC_SUB_LIBRARY: 'LC_SUB_LIBRARY', LC_PREBOUND_DYLIB: 'LC_PREBOUND_DYLIB', LC_ID_DYLINKER: 'LC_ID_DYLINKER', LC_LOAD_DYLINKER: 'LC_LOAD_DYLINKER', LC_THREAD: 'LC_THREAD', LC_UNIXTHREAD: 'LC_UNIXTHREAD', LC_ROUTINES: 'LC_ROUTINES', LC_SYMTAB: 'LC_SYMTAB', LC_DYSYMTAB: 'LC_DYSYMTAB', LC_TWOLEVEL_HINTS: 'LC_TWOLEVEL_HINTS', LC_PREBIND_CKSUM: 'LC_PREBIND_CKSUM', LC_SYMSEG: 'LC_SYMSEG', LC_IDENT: 'LC_IDENT', LC_FVMFILE: 'LC_FVMFILE', LC_SEGMENT_64: 'LC_SEGMENT_64', LC_ROUTINES_64: 'LC_ROUTINES_64', LC_UUID: 'LC_UUID', LC_RPATH: 'LC_RPATH', LC_CODE_SIGNATURE: 'LC_CODE_SIGNATURE', LC_CODE_SEGMENT_SPLIT_INFO: 'LC_CODE_SEGMENT_SPLIT_INFO', LC_REEXPORT_DYLIB: 'LC_REEXPORT_DYLIB', LC_LAZY_LOAD_DYLIB: 'LC_LAZY_LOAD_DYLIB', LC_ENCRYPTION_INFO: 'LC_ENCRYPTION_INFO', LC_DYLD_INFO: 'LC_DYLD_INFO', LC_DYLD_INFO_ONLY: 'LC_DYLD_INFO_ONLY', LC_LOAD_UPWARD_DYLIB: 'LC_LOAD_UPWARD_DYLIB', LC_VERSION_MIN_MACOSX: 'LC_VERSION_MIN_MACOSX', LC_VERSION_MIN_IPHONEOS: 'LC_VERSION_MIN_IPHONEOS', LC_FUNCTION_STARTS: 'LC_FUNCTION_STARTS', LC_DYLD_ENVIRONMENT: 'LC_DYLD_ENVIRONMENT', LC_MAIN: 'LC_MAIN', LC_DATA_IN_CODE: 'LC_DATA_IN_CODE', LC_SOURCE_VERSION: 'LC_SOURCE_VERSION', LC_DYLIB_CODE_SIGN_DRS: 'LC_DYLIB_CODE_SIGN_DRS', } #this is another union. class n_un(p_int32): pass class nlist(Structure): _fields_ = ( ('n_un', n_un), ('n_type', p_uint8), ('n_sect', p_uint8), ('n_desc', p_short), ('n_value', p_uint32), ) class nlist_64(Structure): _fields_ = [ ('n_un', n_un), ('n_type', p_uint8), ('n_sect', p_uint8), ('n_desc', p_short), ('n_value', p_int64), ] N_STAB = 0xe0 N_PEXT = 0x10 N_TYPE = 0x0e N_EXT = 0x01 N_UNDF = 0x0 N_ABS = 0x2 N_SECT = 0xe N_PBUD = 0xc N_INDR = 0xa NO_SECT = 0 MAX_SECT = 255 REFERENCE_TYPE = 0xf REFERENCE_FLAG_UNDEFINED_NON_LAZY = 0 REFERENCE_FLAG_UNDEFINED_LAZY = 1 REFERENCE_FLAG_DEFINED = 2 REFERENCE_FLAG_PRIVATE_DEFINED = 3 REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY = 4 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5 REFERENCED_DYNAMICALLY = 0x0010 def GET_LIBRARY_ORDINAL(n_desc): return (((n_desc) >> 8) & 0xff) def SET_LIBRARY_ORDINAL(n_desc, ordinal): return (((n_desc) & 0x00ff) | (((ordinal & 0xff) << 8))) SELF_LIBRARY_ORDINAL = 0x0 MAX_LIBRARY_ORDINAL = 0xfd DYNAMIC_LOOKUP_ORDINAL = 0xfe EXECUTABLE_ORDINAL = 0xff N_DESC_DISCARDED = 0x0020 N_WEAK_REF = 0x0040 N_WEAK_DEF = 0x0080 # /usr/include/mach-o/fat.h FAT_MAGIC = 0xcafebabe class fat_header(Structure): _fields_ = ( ('magic', p_uint32), ('nfat_arch', p_uint32), ) class fat_arch(Structure): _fields_ = ( ('cputype', cpu_type_t), ('cpusubtype', cpu_subtype_t), ('offset', p_uint32), ('size', p_uint32), ('align', p_uint32), ) macholib-1.7/macholib/MachO.py0000644000076500000240000003346212364212514016670 0ustar ronaldstaff00000000000000""" Utilities for reading and writing Mach-O headers """ from __future__ import print_function import sys import struct from macholib.mach_o import * from macholib.dyld import dyld_find, framework_info from macholib.util import fileview try: from macholib.compat import bytes except ImportError: pass try: unicode except NameError: unicode = str __all__ = ['MachO'] _RELOCATABLE = set(( # relocatable commands that should be used for dependency walking LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, LC_PREBOUND_DYLIB, LC_REEXPORT_DYLIB, )) _RELOCATABLE_NAMES = { LC_LOAD_DYLIB: 'load_dylib', LC_LOAD_WEAK_DYLIB: 'load_weak_dylib', LC_PREBOUND_DYLIB: 'prebound_dylib', LC_REEXPORT_DYLIB: 'reexport_dylib', } def _shouldRelocateCommand(cmd): """ Should this command id be investigated for relocation? """ return cmd in _RELOCATABLE class MachO(object): """ Provides reading/writing the Mach-O header of a specific existing file """ # filename - the original filename of this mach-o # sizediff - the current deviation from the initial mach-o size # header - the mach-o header # commands - a list of (load_command, somecommand, data) # data is either a str, or a list of segment structures # total_size - the current mach-o header size (including header) # low_offset - essentially, the maximum mach-o header size # id_cmd - the index of my id command, or None def __init__(self, filename): # supports the ObjectGraph protocol self.graphident = filename self.filename = filename # initialized by load self.fat = None self.headers = [] with open(filename, 'rb') as fp: self.load(fp) def __repr__(self): return "" % (self.filename,) def load(self, fh): assert fh.tell() == 0 header = struct.unpack('>I', fh.read(4))[0] fh.seek(0) if header == FAT_MAGIC: self.load_fat(fh) else: fh.seek(0, 2) size = fh.tell() fh.seek(0) self.load_header(fh, 0, size) def load_fat(self, fh): self.fat = fat_header.from_fileobj(fh) archs = [fat_arch.from_fileobj(fh) for i in range(self.fat.nfat_arch)] for arch in archs: self.load_header(fh, arch.offset, arch.size) def rewriteLoadCommands(self, *args, **kw): changed = False for header in self.headers: if header.rewriteLoadCommands(*args, **kw): changed = True return changed def load_header(self, fh, offset, size): fh.seek(offset) header = struct.unpack('>I', fh.read(4))[0] fh.seek(offset) if header == MH_MAGIC: magic, hdr, endian = MH_MAGIC, mach_header, '>' elif header == MH_CIGAM: magic, hdr, endian = MH_CIGAM, mach_header, '<' elif header == MH_MAGIC_64: magic, hdr, endian = MH_MAGIC_64, mach_header_64, '>' elif header == MH_CIGAM_64: magic, hdr, endian = MH_CIGAM_64, mach_header_64, '<' else: raise ValueError("Unknown Mach-O header: 0x%08x in %r" % ( header, fh)) hdr = MachOHeader(self, fh, offset, size, magic, hdr, endian) self.headers.append(hdr) def write(self, f): for header in self.headers: header.write(f) class MachOHeader(object): """ Provides reading/writing the Mach-O header of a specific existing file """ # filename - the original filename of this mach-o # sizediff - the current deviation from the initial mach-o size # header - the mach-o header # commands - a list of (load_command, somecommand, data) # data is either a str, or a list of segment structures # total_size - the current mach-o header size (including header) # low_offset - essentially, the maximum mach-o header size # id_cmd - the index of my id command, or None def __init__(self, parent, fh, offset, size, magic, hdr, endian): self.MH_MAGIC = magic self.mach_header = hdr # These are all initialized by self.load() self.parent = parent self.offset = offset self.size = size self.endian = endian self.header = None self.commands = None self.id_cmd = None self.sizediff = None self.total_size = None self.low_offset = None self.filetype = None self.headers = [] self.load(fh) def __repr__(self): return "<%s filename=%r offset=%d size=%d endian=%r>" % ( type(self).__name__, self.parent.filename, self.offset, self.size, self.endian) def load(self, fh): fh = fileview(fh, self.offset, self.size) fh.seek(0) self.sizediff = 0 kw = {'_endian_': self.endian} header = self.mach_header.from_fileobj(fh, **kw) self.header = header #if header.magic != self.MH_MAGIC: # raise ValueError("header has magic %08x, expecting %08x" % ( # header.magic, self.MH_MAGIC)) cmd = self.commands = [] self.filetype = self.get_filetype_shortname(header.filetype) read_bytes = 0 low_offset = sys.maxsize for i in range(header.ncmds): # read the load command cmd_load = load_command.from_fileobj(fh, **kw) # read the specific command klass = LC_REGISTRY.get(cmd_load.cmd, None) if klass is None: raise ValueError("Unknown load command: %d" % (cmd_load.cmd,)) cmd_cmd = klass.from_fileobj(fh, **kw) if cmd_load.cmd == LC_ID_DYLIB: # remember where this command was if self.id_cmd is not None: raise ValueError("This dylib already has an id") self.id_cmd = i if cmd_load.cmd in (LC_SEGMENT, LC_SEGMENT_64): # for segment commands, read the list of segments segs = [] # assert that the size makes sense if cmd_load.cmd == LC_SEGMENT: section_cls = section else: # LC_SEGMENT_64 section_cls = section_64 expected_size = ( sizeof(klass) + sizeof(load_command) + (sizeof(section_cls) * cmd_cmd.nsects) ) if cmd_load.cmdsize != expected_size: raise ValueError("Segment size mismatch") # this is a zero block or something # so the beginning is wherever the fileoff of this command is if cmd_cmd.nsects == 0: if cmd_cmd.filesize != 0: low_offset = min(low_offset, cmd_cmd.fileoff) else: # this one has multiple segments for j in range(cmd_cmd.nsects): # read the segment seg = section_cls.from_fileobj(fh, **kw) # if the segment has a size and is not zero filled # then its beginning is the offset of this segment not_zerofill = ((seg.flags & S_ZEROFILL) != S_ZEROFILL) if seg.offset > 0 and seg.size > 0 and not_zerofill: low_offset = min(low_offset, seg.offset) if not_zerofill: c = fh.tell() fh.seek(seg.offset) sd = fh.read(seg.size) seg.add_section_data(sd) fh.seek(c) segs.append(seg) # data is a list of segments cmd_data = segs # XXX: Disabled for now because writing back doesn't work #elif cmd_load.cmd == LC_CODE_SIGNATURE: # c = fh.tell() # fh.seek(cmd_cmd.dataoff) # cmd_data = fh.read(cmd_cmd.datasize) # fh.seek(c) #elif cmd_load.cmd == LC_SYMTAB: # c = fh.tell() # fh.seek(cmd_cmd.stroff) # cmd_data = fh.read(cmd_cmd.strsize) # fh.seek(c) else: # data is a raw str data_size = ( cmd_load.cmdsize - sizeof(klass) - sizeof(load_command) ) cmd_data = fh.read(data_size) cmd.append((cmd_load, cmd_cmd, cmd_data)) read_bytes += cmd_load.cmdsize # make sure the header made sense if read_bytes != header.sizeofcmds: raise ValueError("Read %d bytes, header reports %d bytes" % ( read_bytes, header.sizeofcmds)) self.total_size = sizeof(self.mach_header) + read_bytes self.low_offset = low_offset # this header overwrites a segment, what the heck? if self.total_size > low_offset: raise ValueError("total_size > low_offset (%d > %d)" % ( self.total_size, low_offset)) def walkRelocatables(self, shouldRelocateCommand=_shouldRelocateCommand): """ for all relocatable commands yield (command_index, command_name, filename) """ for (idx, (lc, cmd, data)) in enumerate(self.commands): if shouldRelocateCommand(lc.cmd): name = _RELOCATABLE_NAMES[lc.cmd] ofs = cmd.name - sizeof(lc.__class__) - sizeof(cmd.__class__) yield idx, name, data[ofs:data.find(b'\x00', ofs)].decode( sys.getfilesystemencoding()) def rewriteInstallNameCommand(self, loadcmd): """Rewrite the load command of this dylib""" if self.id_cmd is not None: self.rewriteDataForCommand(self.id_cmd, loadcmd) return True return False def changedHeaderSizeBy(self, bytes): self.sizediff += bytes if (self.total_size + self.sizediff) > self.low_offset: print("WARNING: Mach-O header in %r may be too large to relocate"%(self.parent.filename,)) def rewriteLoadCommands(self, changefunc): """ Rewrite the load commands based upon a change dictionary """ data = changefunc(self.parent.filename) changed = False if data is not None: if self.rewriteInstallNameCommand( data.encode(sys.getfilesystemencoding())): changed = True for idx, name, filename in self.walkRelocatables(): data = changefunc(filename) if data is not None: if self.rewriteDataForCommand(idx, data.encode( sys.getfilesystemencoding())): changed = True return changed def rewriteDataForCommand(self, idx, data): lc, cmd, old_data = self.commands[idx] hdrsize = sizeof(lc.__class__) + sizeof(cmd.__class__) align = struct.calcsize('L') data = data + (b'\x00' * (align - (len(data) % align))) newsize = hdrsize + len(data) self.commands[idx] = (lc, cmd, data) self.changedHeaderSizeBy(newsize - lc.cmdsize) lc.cmdsize, cmd.name = newsize, hdrsize return True def synchronize_size(self): if (self.total_size + self.sizediff) > self.low_offset: raise ValueError("New Mach-O header is too large to relocate in %r"%(self.parent.filename,)) self.header.sizeofcmds += self.sizediff self.total_size = sizeof(self.mach_header) + self.header.sizeofcmds self.sizediff = 0 def write(self, fileobj): fileobj = fileview(fileobj, self.offset, self.size) fileobj.seek(0) # serialize all the mach-o commands self.synchronize_size() self.header.to_fileobj(fileobj) for lc, cmd, data in self.commands: lc.to_fileobj(fileobj) cmd.to_fileobj(fileobj) if sys.version_info[0] == 2: if isinstance(data, unicode): fileobj.write(data.encode(sys.getfilesystemencoding())) elif isinstance(data, (bytes, str)): fileobj.write(data) else: # segments.. for obj in data: obj.to_fileobj(fileobj) else: if isinstance(data, str): fileobj.write(data.encode(sys.getfilesystemencoding())) elif isinstance(data, bytes): fileobj.write(data) else: # segments.. for obj in data: obj.to_fileobj(fileobj) # zero out the unused space, doubt this is strictly necessary # and is generally probably already the case fileobj.write(b'\x00' * (self.low_offset - fileobj.tell())) def getSymbolTableCommand(self): for lc, cmd, data in self.commands: if lc.cmd == LC_SYMTAB: return cmd return None def getDynamicSymbolTableCommand(self): for lc, cmd, data in self.commands: if lc.cmd == LC_DYSYMTAB: return cmd return None def get_filetype_shortname(self, filetype): if filetype in MH_FILETYPE_SHORTNAMES: return MH_FILETYPE_SHORTNAMES[filetype] else: return 'unknown' def main(fn): m = MachO(fn) seen = set() for header in m.headers: for idx, name, other in header.walkRelocatables(): if other not in seen: seen.add(other) print('\t' + name + ": " + other) if __name__ == '__main__': import sys files = sys.argv[1:] or ['/bin/ls'] for fn in files: print(fn) main(fn) macholib-1.7/macholib/macho_dump.py0000644000076500000240000000226412272472611020015 0ustar ronaldstaff00000000000000#!/usr/bin/env python from __future__ import print_function import os import sys from macholib._cmdline import main as _main from macholib.MachO import MachO from macholib.mach_o import * ARCH_MAP={ ('<', '64-bit'): 'x86_64', ('<', '32-bit'): 'i386', ('>', '64-bit'): 'ppc64', ('>', '32-bit'): 'ppc', } def print_file(fp, path): print(path, file=fp) m = MachO(path) for header in m.headers: seen = set() if header.MH_MAGIC == MH_MAGIC_64: sz = '64-bit' else: sz = '32-bit' arch = CPU_TYPE_NAMES.get(header.header.cputype, header.header.cputype) print(' [%s endian=%r size=%r arch=%r]' % (header.__class__.__name__, header.endian, sz, arch), file=fp) for idx, name, other in header.walkRelocatables(): if other not in seen: seen.add(other) print('\t' + other, file=fp) print('', file=fp) def main(): print("WARNING: 'macho_dump' is deprecated, use 'python -mmacholib dump' instead") _main(print_file) if __name__ == '__main__': try: sys.exit(main()) except KeyboardInterrupt: pass macholib-1.7/macholib/macho_find.py0000644000076500000240000000056612033016223017757 0ustar ronaldstaff00000000000000#!/usr/bin/env python from __future__ import print_function from macholib._cmdline import main as _main def print_file(fp, path): print(path, file=fp) def main(): print("WARNING: 'macho_find' is deprecated, use 'python -mmacholib dump' instead") _main(print_file) if __name__ == '__main__': try: main() except KeyboardInterrupt: pass macholib-1.7/macholib/macho_standalone.py0000644000076500000240000000133712037465621021202 0ustar ronaldstaff00000000000000#!/usr/bin/env python import os import sys from macholib.MachOStandalone import MachOStandalone from macholib.util import strip_files def standaloneApp(path): if not (os.path.isdir(path) and os.path.exists( os.path.join(path, 'Contents'))): print('%s: %s does not look like an app bundle' % (sys.argv[0], path)) sys.exit(1) files = MachOStandalone(path).run() strip_files(files) def main(): print("WARNING: 'macho_standalone' is deprecated, use 'python -mmacholib standalone' instead") if not sys.argv[1:]: raise SystemExit('usage: %s [appbundle ...]' % (sys.argv[0],)) for fn in sys.argv[1:]: standaloneApp(fn) if __name__ == '__main__': main() macholib-1.7/macholib/MachOGraph.py0000644000076500000240000001045212272472611017650 0ustar ronaldstaff00000000000000""" 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']) macholib-1.7/macholib/MachOStandalone.py0000644000076500000240000001202612272472611020676 0ustar ronaldstaff00000000000000import os from macholib.MachOGraph import MachOGraph, MissingMachO from macholib.util import iter_platform_files, in_system_path, mergecopy, \ mergetree, flipwritable, has_filename_filter from macholib.dyld import framework_info from collections import deque class ExcludedMachO(MissingMachO): pass class FilteredMachOGraph(MachOGraph): def __init__(self, delegate, *args, **kwargs): super(FilteredMachOGraph, self).__init__(*args, **kwargs) self.delegate = delegate def createNode(self, cls, name): cls = self.delegate.getClass(name, cls) res = super(FilteredMachOGraph, self).createNode(cls, name) return res def locate(self, filename, loader=None): newname = super(FilteredMachOGraph, self).locate(filename, loader) if newname is None: return None return self.delegate.locate(newname) class MachOStandalone(object): def __init__(self, base, dest=None, graph=None, env=None, executable_path=None): self.base = os.path.join(os.path.abspath(base), '') if dest is None: dest = os.path.join(self.base, 'Contents', 'Frameworks') self.dest = dest self.mm = FilteredMachOGraph(self, graph=graph, env=env, executable_path=executable_path) self.changemap = {} self.excludes = [] self.pending = deque() def getClass(self, name, cls): if in_system_path(name): return ExcludedMachO for base in self.excludes: if name.startswith(base): return ExcludedMachO return cls def locate(self, filename): if in_system_path(filename): return filename if filename.startswith(self.base): return filename for base in self.excludes: if filename.startswith(base): return filename if filename in self.changemap: return self.changemap[filename] info = framework_info(filename) if info is None: res = self.copy_dylib(filename) self.changemap[filename] = res return res else: res = self.copy_framework(info) self.changemap[filename] = res return res def copy_dylib(self, filename): # When the filename is a symlink use the basename of the target of the link # as the name in standalone bundle. This avoids problems when two libraries # link to the same dylib but using different symlinks. if os.path.islink(filename): dest = os.path.join(self.dest, os.path.basename(os.path.realpath(filename))) else: dest = os.path.join(self.dest, os.path.basename(filename)) if not os.path.exists(dest): self.mergecopy(filename, dest) return dest def mergecopy(self, src, dest): return mergecopy(src, dest) def mergetree(self, src, dest): return mergetree(src, dest) def copy_framework(self, info): dest = os.path.join(self.dest, info['shortname'] + '.framework') destfn = os.path.join(self.dest, info['name']) src = os.path.join(info['location'], info['shortname'] + '.framework') if not os.path.exists(dest): self.mergetree(src, dest) self.pending.append((destfn, iter_platform_files(dest))) return destfn def run(self, platfiles=None, contents=None): mm = self.mm if contents is None: contents = '@executable_path/..' if platfiles is None: platfiles = iter_platform_files(self.base) for fn in platfiles: mm.run_file(fn) while self.pending: fmwk, files = self.pending.popleft() ref = mm.findNode(fmwk) for fn in files: mm.run_file(fn, caller=ref) changemap = {} skipcontents = os.path.join(os.path.dirname(self.dest), '') machfiles = [] for node in mm.flatten(has_filename_filter): machfiles.append(node) dest = os.path.join(contents, node.filename[len(skipcontents):]) changemap[node.filename] = dest def changefunc(path): res = mm.locate(path) return changemap.get(res) for node in machfiles: fn = mm.locate(node.filename) if fn is None: continue rewroteAny = False for header in node.headers: if node.rewriteLoadCommands(changefunc): rewroteAny = True if rewroteAny: old_mode = flipwritable(fn) try: with open(fn, 'rb+') as f: for header in node.headers: f.seek(0) node.write(f) f.seek(0, 2) f.flush() finally: flipwritable(fn, old_mode) allfiles = [mm.locate(node.filename) for node in machfiles] return set(filter(None, allfiles)) macholib-1.7/macholib/ptypes.py0000644000076500000240000001710211750576543017233 0ustar ronaldstaff00000000000000""" This module defines packable types, that is types than can be easily converted to a binary format as used in MachO headers. """ import struct import sys try: from itertools import izip, imap except ImportError: izip, imap = zip, map from itertools import chain, starmap import warnings __all__ = """ sizeof BasePackable Structure pypackable p_char p_byte p_ubyte p_short p_ushort p_int p_uint p_long p_ulong p_longlong p_ulonglong p_int8 p_uint8 p_int16 p_uint16 p_int32 p_uint32 p_int64 p_uint64 p_float p_double """.split() def sizeof(s): """ Return the size of an object when packed """ if hasattr(s, '_size_'): return s._size_ elif isinstance(s, bytes): return len(s) raise ValueError(s) class MetaPackable(type): """ Fixed size struct.unpack-able types use from_tuple as their designated initializer """ def from_mmap(cls, mm, ptr, **kw): return cls.from_str(mm[ptr:ptr+cls._size_], **kw) def from_fileobj(cls, f, **kw): return cls.from_str(f.read(cls._size_), **kw) def from_str(cls, s, **kw): endian = kw.get('_endian_', cls._endian_) return cls.from_tuple(struct.unpack(endian + cls._format_, s), **kw) def from_tuple(cls, tpl, **kw): return cls(tpl[0], **kw) class BasePackable(object): _endian_ = '>' def to_str(self): raise NotImplementedError def to_fileobj(self, f): f.write(self.to_str()) def to_mmap(self, mm, ptr): mm[ptr:ptr+self._size_] = self.to_str() # This defines a class with a custom metaclass, we'd normally # use "class Packable(BasePackable, metaclass=MetaPackage)", # but that syntax is not valid in Python 2 (and likewise the # python 2 syntax is not valid in Python 3) def _make(): def to_str(self): cls = type(self) endian = getattr(self, '_endian_', cls._endian_) return struct.pack(endian + cls._format_, self) return MetaPackable("Packable", (BasePackable,), {'to_str': to_str}) Packable = _make() del _make def pypackable(name, pytype, format): """ Create a "mix-in" class with a python type and a Packable with the given struct format """ size, items = _formatinfo(format) return type(Packable)(name, (pytype, Packable), { '_format_': format, '_size_': size, '_items_': items, }) def _formatinfo(format): """ Calculate the size and number of items in a struct format. """ size = struct.calcsize(format) return size, len(struct.unpack(format, b'\x00' * size)) class MetaStructure(MetaPackable): """ The metaclass of Structure objects that does all the magic. Since we can assume that all Structures have a fixed size, we can do a bunch of calculations up front and pack or unpack the whole thing in one struct call. """ def __new__(cls, clsname, bases, dct): fields = dct['_fields_'] names = [] types = [] structmarks = [] format = '' items = 0 size = 0 def struct_property(name, typ): def _get(self): return self._objects_[name] def _set(self, obj): if type(obj) is not typ: obj = typ(obj) self._objects_[name] = obj return property(_get, _set, typ.__name__) for name, typ in fields: dct[name] = struct_property(name, typ) names.append(name) types.append(typ) format += typ._format_ size += typ._size_ if (typ._items_ > 1): structmarks.append((items, typ._items_, typ)) items += typ._items_ dct['_structmarks_'] = structmarks dct['_names_'] = names dct['_types_'] = types dct['_size_'] = size dct['_items_'] = items dct['_format_'] = format return super(MetaStructure, cls).__new__(cls, clsname, bases, dct) def from_tuple(cls, tpl, **kw): values = [] current = 0 for begin, length, typ in cls._structmarks_: if begin > current: values.extend(tpl[current:begin]) current = begin + length values.append(typ.from_tuple(tpl[begin:current], **kw)) values.extend(tpl[current:]) return cls(*values, **kw) # See metaclass discussion earlier in this file def _make(): class_dict={} class_dict['_fields_'] = () def as_method(function): class_dict[function.__name__] = function @as_method def __init__(self, *args, **kwargs): if len(args) == 1 and not kwargs and type(args[0]) is type(self): kwargs = args[0]._objects_ args = () self._objects_ = {} iargs = chain(izip(self._names_, args), kwargs.items()) for key, value in iargs: if key not in self._names_ and key != "_endian_": raise TypeError setattr(self, key, value) for key, typ in izip(self._names_, self._types_): if key not in self._objects_: self._objects_[key] = typ() @as_method def _get_packables(self): for obj in imap(self._objects_.__getitem__, self._names_): if obj._items_ == 1: yield obj else: for obj in obj._get_packables(): yield obj @as_method def to_str(self): return struct.pack(self._endian_ + self._format_, *self._get_packables()) @as_method def __cmp__(self, other): if type(other) is not type(self): raise TypeError('Cannot compare objects of type %r to objects of type %r' % (type(other), type(self))) if sys.version_info[0] == 2: _cmp = cmp else: def _cmp(a, b): if a < b: return -1 elif a > b: return 1 elif a == b: return 0 else: raise TypeError() for cmpval in starmap(_cmp, izip(self._get_packables(), other._get_packables())): if cmpval != 0: return cmpval return 0 @as_method def __eq__(self, other): r = self.__cmp__(other) return r == 0 @as_method def __ne__(self, other): r = self.__cmp__(other) return r != 0 @as_method def __lt__(self, other): r = self.__cmp__(other) return r < 0 @as_method def __le__(self, other): r = self.__cmp__(other) return r <= 0 @as_method def __gt__(self, other): r = self.__cmp__(other) return r > 0 @as_method def __ge__(self, other): r = self.__cmp__(other) return r >= 0 return MetaStructure("Structure", (BasePackable,), class_dict) Structure = _make() del _make try: long except NameError: long = int # export common packables with predictable names p_char = pypackable('p_char', bytes, 'c') p_int8 = pypackable('p_int8', int, 'b') p_uint8 = pypackable('p_uint8', int, 'B') p_int16 = pypackable('p_int16', int, 'h') p_uint16 = pypackable('p_uint16', int, 'H') p_int32 = pypackable('p_int32', int, 'i') p_uint32 = pypackable('p_uint32', long, 'I') p_int64 = pypackable('p_int64', long, 'q') p_uint64 = pypackable('p_uint64', long, 'Q') p_float = pypackable('p_float', float, 'f') p_double = pypackable('p_double', float, 'd') # Deprecated names, need trick to emit deprecation warning. p_byte = p_int8 p_ubyte = p_uint8 p_short = p_int16 p_ushort = p_uint16 p_int = p_long = p_int32 p_uint = p_ulong = p_uint32 p_longlong = p_int64 p_ulonglong = p_uint64 macholib-1.7/macholib/SymbolTable.py0000644000076500000240000000624011777256464020133 0ustar ronaldstaff00000000000000""" Class to read the symbol table from a Mach-O header """ from macholib.mach_o import * __all__ = ['SymbolTable'] # XXX: Does not support 64-bit, probably broken anyway class SymbolTable(object): def __init__(self, macho, openfile=None): if openfile is None: openfile = open self.macho = macho.headers[0] self.symtab = macho.getSymbolTableCommand() self.dysymtab = macho.getDynamicSymbolTableCommand() fh = openfile(self.macho.filename, 'rb') try: if self.symtab is not None: self.readSymbolTable(fh) if self.dysymtab is not None: self.readDynamicSymbolTable(fh) finally: fh.close() def readSymbolTable(self, fh): cmd = self.symtab fh.seek(cmd.stroff) strtab = fh.read(cmd.strsize) fh.seek(cmd.symoff) nlists = [] for i in xrange(cmd.nsyms): cmd = nlist.from_fileobj(fh) if cmd.n_un == 0: nlists.append((cmd, '')) else: nlists.append((cmd, strtab[cmd.n_un:strtab.find(b'\x00', cmd.n_un)])) self.nlists = nlists def readDynamicSymbolTable(self, fh): cmd = self.dysymtab nlists = self.nlists self.localsyms = nlists[cmd.ilocalsym:cmd.ilocalsym+cmd.nlocalsym] self.extdefsyms = nlists[cmd.iextdefsym:cmd.iextdefsym+cmd.nextdefsym] self.undefsyms = nlists[cmd.iundefsym:cmd.iundefsym+cmd.nundefsym] #if cmd.tocoff == 0: # self.toc = None #else: # self.toc = self.readtoc(fh, cmd.tocoff, cmd.ntoc) #if cmd.modtaboff == 0: # self.modtab = None #else: # self.modtab = self.readmodtab(fh, cmd.modtaboff, cmd.nmodtab) if cmd.extrefsymoff == 0: self.extrefsym = None else: self.extrefsym = self.readsym(fh, cmd.extrefsymoff, cmd.nextrefsyms) #if cmd.indirectsymoff == 0: # self.indirectsym = None #else: # self.indirectsym = self.readsym(fh, cmd.indirectsymoff, cmd.nindirectsyms) #if cmd.extreloff == 0: # self.extrel = None #else: # self.extrel = self.readrel(fh, cmd.extreloff, cmd.nextrel) #if cmd.locreloff == 0: # self.locrel = None #else: # self.locrel = self.readrel(fh, cmd.locreloff, cmd.nlocrel) def readtoc(self, fh, off, n): #print 'toc', off, n fh.seek(off) return [dylib_table_of_contents.from_fileobj(fh) for i in xrange(n)] def readmodtab(self, fh, off, n): #print 'modtab', off, n fh.seek(off) return [dylib_module.from_fileobj(fh) for i in xrange(n)] def readsym(self, fh, off, n): #print 'sym', off, n fh.seek(off) refs = [] for i in xrange(n): ref = dylib_reference.from_fileobj(fh) isym, flags = divmod(ref.isym_flags, 256) refs.append((self.nlists[isym], flags)) return refs def readrel(self, fh, off, n): #print 'rel', off, n fh.seek(off) return [relocation_info.from_fileobj(fh) for i in xrange(n)] macholib-1.7/macholib/util.py0000644000076500000240000001562512272472611016663 0ustar ronaldstaff00000000000000import os import sys import stat import operator import struct import shutil #from modulegraph.util import * from macholib import mach_o MAGIC = [ struct.pack('!L', getattr(mach_o, 'MH_' + _)) for _ in ['MAGIC', 'CIGAM', 'MAGIC_64', 'CIGAM_64'] ] FAT_MAGIC_BYTES = struct.pack('!L', mach_o.FAT_MAGIC) MAGIC_LEN = 4 STRIPCMD = ['/usr/bin/strip', '-x', '-S', '-'] try: unicode except NameError: unicode = str def fsencoding(s, encoding=sys.getfilesystemencoding()): """ Ensure the given argument is in filesystem encoding (not unicode) """ if isinstance(s, unicode): s = s.encode(encoding) return s def move(src, dst): """ move that ensures filesystem encoding of paths """ shutil.move(fsencoding(src), fsencoding(dst)) def copy2(src, dst): """ copy2 that ensures filesystem encoding of paths """ shutil.copy2(fsencoding(src), fsencoding(dst)) def flipwritable(fn, mode=None): """ Flip the writability of a file and return the old mode. Returns None if the file is already writable. """ if os.access(fn, os.W_OK): return None old_mode = os.stat(fn).st_mode os.chmod(fn, stat.S_IWRITE | old_mode) return old_mode class fileview(object): """ A proxy for file-like objects that exposes a given view of a file """ def __init__(self, fileobj, start, size): self._fileobj = fileobj self._start = start self._end = start + size def __repr__(self): return '' % ( self._start, self._end, self._fileobj) def tell(self): return self._fileobj.tell() - self._start def _checkwindow(self, seekto, op): if not (self._start <= seekto <= self._end): raise IOError("%s to offset %d is outside window [%d, %d]" % ( op, seekto, self._start, self._end)) def seek(self, offset, whence=0): seekto = offset if whence == 0: seekto += self._start elif whence == 1: seekto += self._fileobj.tell() elif whence == 2: seekto += self._end else: raise IOError("Invalid whence argument to seek: %r" % (whence,)) self._checkwindow(seekto, 'seek') self._fileobj.seek(seekto) def write(self, bytes): here = self._fileobj.tell() self._checkwindow(here, 'write') self._checkwindow(here + len(bytes), 'write') self._fileobj.write(bytes) def read(self, size=sys.maxsize): if size < 0: raise ValueError("Invalid size %s while reading from %s", size, self._fileobj) here = self._fileobj.tell() self._checkwindow(here, 'read') bytes = min(size, self._end - here) return self._fileobj.read(bytes) def mergecopy(src, dest): """ copy2, but only if the destination isn't up to date """ if os.path.exists(dest) and os.stat(dest).st_mtime >= os.stat(src).st_mtime: return copy2(src, dest) def mergetree(src, dst, condition=None, copyfn=mergecopy, srcbase=None): """ Recursively merge a directory tree using mergecopy(). """ src = fsencoding(src) dst = fsencoding(dst) if srcbase is None: srcbase = src names = map(fsencoding, os.listdir(src)) try: os.makedirs(dst) except OSError: pass errors = [] for name in names: srcname = os.path.join(src, name) dstname = os.path.join(dst, name) if condition is not None and not condition(srcname): continue try: if os.path.islink(srcname): # XXX: This is naive at best, should check srcbase(?) realsrc = os.readlink(srcname) os.symlink(realsrc, dstname) elif os.path.isdir(srcname): mergetree(srcname, dstname, condition=condition, copyfn=copyfn, srcbase=srcbase) else: copyfn(srcname, dstname) except (IOError, os.error) as why: errors.append((srcname, dstname, why)) if errors: raise IOError(errors) def sdk_normalize(filename): """ Normalize a path to strip out the SDK portion, normally so that it can be decided whether it is in a system path or not. """ if filename.startswith('/Developer/SDKs/'): pathcomp = filename.split('/') del pathcomp[1:4] filename = '/'.join(pathcomp) return filename NOT_SYSTEM_FILES=[] def in_system_path(filename): """ Return True if the file is in a system path """ fn = sdk_normalize(os.path.realpath(filename)) if fn.startswith('/usr/local/'): return False elif fn.startswith('/System/') or fn.startswith('/usr/'): if fn in NOT_SYSTEM_FILES: return False return True else: return False def has_filename_filter(module): """ Return False if the module does not have a filename attribute """ return getattr(module, 'filename', None) is not None def get_magic(): """ Get a list of valid Mach-O header signatures, not including the fat header """ return MAGIC def is_platform_file(path): """ Return True if the file is Mach-O """ if not os.path.exists(path) or os.path.islink(path): return False # If the header is fat, we need to read into the first arch with open(path, 'rb') as fileobj: bytes = fileobj.read(MAGIC_LEN) if bytes == FAT_MAGIC_BYTES: # Read in the fat header fileobj.seek(0) header = mach_o.fat_header.from_fileobj(fileobj, _endian_='>') if header.nfat_arch < 1: return False # Read in the first fat arch header arch = mach_o.fat_arch.from_fileobj(fileobj, _endian_='>') fileobj.seek(arch.offset) # Read magic off the first header bytes = fileobj.read(MAGIC_LEN) for magic in MAGIC: if bytes == magic: return True return False def iter_platform_files(dst): """ Walk a directory and yield each full path that is a Mach-O file """ for root, dirs, files in os.walk(dst): for fn in files: fn = os.path.join(root, fn) if is_platform_file(fn): yield fn def strip_files(files, argv_max=(256 * 1024)): """ Strip a list of files """ tostrip = [(fn, flipwritable(fn)) for fn in files] while tostrip: cmd = list(STRIPCMD) flips = [] pathlen = sum([len(s) + 1 for s in cmd]) while pathlen < argv_max: if not tostrip: break added, flip = tostrip.pop() pathlen += len(added) + 1 cmd.append(added) flips.append((added, flip)) else: cmd.pop() tostrip.append(flips.pop()) os.spawnv(os.P_WAIT, cmd[0], cmd) for args in flips: flipwritable(*args) macholib-1.7/macholib_tests/0000755000076500000240000000000012365134503016543 5ustar ronaldstaff00000000000000macholib-1.7/macholib_tests/__init__.py0000644000076500000240000000003711537476235020667 0ustar ronaldstaff00000000000000""" macholib_tests package """ macholib-1.7/macholib_tests/binaries/0000755000076500000240000000000012365134503020337 5ustar ronaldstaff00000000000000macholib-1.7/macholib_tests/binaries/src/0000755000076500000240000000000012365134503021126 5ustar ronaldstaff00000000000000macholib-1.7/macholib_tests/binaries/src/build.py0000644000076500000240000000061011537236537022606 0ustar ronaldstaff00000000000000#!/usr/bin/env python import os, sys class Builder (object): def __init__(self, args): self.output_dir = args[1] def run(self): for nm in dir(type(self)): if nm.startswith('build_'): getattr(self, nm)() def build_executable(self): print "Building plain executable" pass builder = Builder(sys.argv) builder.run() macholib-1.7/macholib_tests/test_command_line.py0000644000076500000240000001063512272472611022610 0ustar ronaldstaff00000000000000from macholib import macho_dump from macholib import macho_find from macholib import _cmdline from macholib import util import sys import shutil import os if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest try: from StringIO import StringIO except ImportError: from io import StringIO class TestCmdLine (unittest.TestCase): # This test is no longer valid: def no_test_main_is_shared(self): self.assertTrue(macho_dump.main is _cmdline.main) self.assertTrue(macho_find.main is _cmdline.main) def test_check_file(self): record = [] def record_cb(fp, path): record.append((fp, path)) self.assertEqual(_cmdline.check_file(sys.stdout, '/bin/sh', record_cb), 0) self.assertEqual(record, [(sys.stdout, '/bin/sh')]) saved_stderr = sys.stderr saved_argv = sys.argv try: sys.stderr = StringIO() sys.argv = ['macho_test'] record[:] = [] self.assertEqual(_cmdline.check_file(sys.stdout, '/bin/no-shell', record_cb), 1) self.assertEqual(record, []) self.assertEqual(sys.stderr.getvalue(), "macho_test: /bin/no-shell: No such file or directory\n") self.assertEqual(record, []) shutil.copy('/bin/sh', 'test.exec') os.chmod('test.exec', 0) sys.stderr = StringIO() self.assertEqual(_cmdline.check_file(sys.stdout, 'test.exec', record_cb), 1) self.assertEqual(record, []) self.assertEqual(sys.stderr.getvalue(), "macho_test: test.exec: [Errno 13] Permission denied: 'test.exec'\n") self.assertEqual(record, []) finally: sys.stderr = sys.stderr sys.argv = saved_argv if os.path.exists('test.exec'): os.unlink('test.exec') def test_shared_main(self): saved_stderr = sys.stderr saved_argv = sys.argv try: sys.stderr = StringIO() sys.argv = ['macho_tool'] self.assertEqual(_cmdline.main(lambda *args: None), 1) self.assertEqual(sys.stderr.getvalue(), 'Usage: macho_tool filename...\n') names = [] def record_names(fp, name): self.assertEqual(fp, sys.stdout) names.append(name) sys.stderr = StringIO() sys.argv = ['macho_tool', '/bin/sh'] self.assertEqual(_cmdline.main(record_names), 0) self.assertEqual(sys.stderr.getvalue(), '') self.assertEqual(names, ['/bin/sh']) names = [] sys.stderr = StringIO() sys.argv = ['macho_tool', '/bin/sh', '/bin/ls'] self.assertEqual(_cmdline.main(record_names), 0) self.assertEqual(sys.stderr.getvalue(), '') self.assertEqual(names, ['/bin/sh', '/bin/ls']) names = [] sys.stderr = StringIO() sys.argv = ['macho_tool', '/bin'] self.assertEqual(_cmdline.main(record_names), 0) self.assertEqual(sys.stderr.getvalue(), '') names.sort() dn = '/bin' real_names = [ os.path.join(dn, fn) for fn in os.listdir(dn) if util.is_platform_file(os.path.join(dn, fn)) ] real_names.sort() self.assertEqual(names, real_names) finally: sys.stderr = saved_stderr sys.argv = saved_argv def test_macho_find(self): fp = StringIO() macho_find.print_file(fp, "file1") macho_find.print_file(fp, "file2") self.assertEqual(fp.getvalue(), "file1\nfile2\n") def test_macho_dump(self): fp = StringIO() macho_dump.print_file(fp, "/bin/sh") lines = fp.getvalue().splitlines() self.assertEqual(lines[0], "/bin/sh") self.assertTrue(len(lines) > 3) self.assertEqual(lines[-1], '') del lines[-1] idx = 1 while idx < len(lines): self.assertTrue(lines[idx].startswith(' [MachOHeader endian')) idx+=1 lc = 0 while idx < len(lines): if not lines[idx].startswith('\t'): break lc +=1 self.assertTrue(os.path.exists(lines[idx].lstrip())) idx += 1 self.assertTrue(lc > 1) if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_dyld.py0000644000076500000240000004526012272472611021121 0ustar ronaldstaff00000000000000from macholib import dyld import sys import os import functools if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class DyldPatcher (object): def __init__(self): self.calls = [] self.patched = {} def clear_calls(self): self.calls = [] def cleanup(self): for name in self.patched: setattr(dyld, name, self.patched[name]) def log_calls(self, name): if name in self.patched: return self.patched[name] = getattr(dyld, name) @functools.wraps(self.patched[name]) def wrapper(*args, **kwds): self.calls.append((name, args, kwds)) return self.patched[name](*args, **kwds) setattr(dyld, name, wrapper) class TestDyld (unittest.TestCase): if not hasattr(unittest.TestCase, 'assertIsInstance'): def assertIsInstance(self, value, types, message=None): self.assertTrue(isinstance(value, types), message or "%r is not an instance of %r"%(value, types)) def setUp(self): self._environ = os.environ os.environ = dict([(k, os.environ[k]) for k in os.environ if 'DYLD' not in k]) self._dyld_env = dyld._dyld_env self._dyld_image_suffix = dyld.dyld_image_suffix def tearDown(self): dyld._dyld_env = self._dyld_env dyld.dyld_image_suffix = self._dyld_image_suffix os.environ = self._environ if sys.version_info[0] == 2: def test_ensure_utf8(self): self.assertEqual(dyld._ensure_utf8("hello"), "hello") self.assertEqual(dyld._ensure_utf8("hello".decode('utf-8')), "hello") self.assertEqual(dyld._ensure_utf8(None), None) else: def test_ensure_utf8(self): self.assertEqual(dyld._ensure_utf8("hello"), "hello") self.assertEqual(dyld._ensure_utf8(None), None) self.assertRaises(ValueError, dyld._ensure_utf8, b"hello") def test__dyld_env(self): new = os.environ self.assertEqual(dyld._dyld_env(None, 'DYLD_FOO'), []) self.assertEqual(dyld._dyld_env({'DYLD_FOO':'bar'}, 'DYLD_FOO'), ['bar']) self.assertEqual(dyld._dyld_env({'DYLD_FOO':'bar:baz'}, 'DYLD_FOO'), ['bar', 'baz']) self.assertEqual(dyld._dyld_env({}, 'DYLD_FOO'), []) self.assertEqual(dyld._dyld_env({'DYLD_FOO':''}, 'DYLD_FOO'), []) os.environ['DYLD_FOO'] = 'foobar' self.assertEqual(dyld._dyld_env(None, 'DYLD_FOO'), ['foobar']) os.environ['DYLD_FOO'] = 'foobar:nowhere' self.assertEqual(dyld._dyld_env(None, 'DYLD_FOO'), ['foobar', 'nowhere']) self.assertEqual(dyld._dyld_env({'DYLD_FOO':'bar'}, 'DYLD_FOO'), ['bar']) self.assertEqual(dyld._dyld_env({}, 'DYLD_FOO'), []) self.assertEqual(dyld.dyld_image_suffix(), None) self.assertEqual(dyld.dyld_image_suffix(None), None) self.assertEqual(dyld.dyld_image_suffix({'DYLD_IMAGE_SUFFIX':'bar'}), 'bar') os.environ['DYLD_IMAGE_SUFFIX'] = 'foobar' self.assertEqual(dyld.dyld_image_suffix(), 'foobar') self.assertEqual(dyld.dyld_image_suffix(None), 'foobar') def test_dyld_helpers(self): record = [] def fake__dyld_env(env, key): record.append((env, key)) return ['hello'] dyld._dyld_env = fake__dyld_env self.assertEqual(dyld.dyld_framework_path(), ['hello']) self.assertEqual(dyld.dyld_framework_path({}), ['hello']) self.assertEqual(dyld.dyld_library_path(), ['hello']) self.assertEqual(dyld.dyld_library_path({}), ['hello']) self.assertEqual(dyld.dyld_fallback_framework_path(), ['hello']) self.assertEqual(dyld.dyld_fallback_framework_path({}), ['hello']) self.assertEqual(dyld.dyld_fallback_library_path(), ['hello']) self.assertEqual(dyld.dyld_fallback_library_path({}), ['hello']) self.assertEqual(record, [ (None, 'DYLD_FRAMEWORK_PATH'), ({}, 'DYLD_FRAMEWORK_PATH'), (None, 'DYLD_LIBRARY_PATH'), ({}, 'DYLD_LIBRARY_PATH'), (None, 'DYLD_FALLBACK_FRAMEWORK_PATH'), ({}, 'DYLD_FALLBACK_FRAMEWORK_PATH'), (None, 'DYLD_FALLBACK_LIBRARY_PATH'), ({}, 'DYLD_FALLBACK_LIBRARY_PATH'), ]) def test_dyld_suffix_search(self): envs = [object()] def fake_suffix(env): envs[0] = env return None dyld.dyld_image_suffix = fake_suffix iterator = [ '/usr/lib/foo', '/usr/lib/foo.dylib', ] result = dyld.dyld_image_suffix_search(iter(iterator)) self.assertEqual(list(result), iterator) self.assertEqual(envs[0], None) result = dyld.dyld_image_suffix_search(iter(iterator), {}) self.assertEqual(list(result), iterator) self.assertEqual(envs[0], {}) envs = [object()] def fake_suffix(env): envs[0] = env return '_profile' dyld.dyld_image_suffix = fake_suffix iterator = [ '/usr/lib/foo', '/usr/lib/foo.dylib', ] result = dyld.dyld_image_suffix_search(iter(iterator)) self.assertEqual(list(result), [ '/usr/lib/foo_profile', '/usr/lib/foo', '/usr/lib/foo_profile.dylib', '/usr/lib/foo.dylib', ]) self.assertEqual(envs[0], None) result = dyld.dyld_image_suffix_search(iter(iterator), {}) self.assertEqual(list(result), [ '/usr/lib/foo_profile', '/usr/lib/foo', '/usr/lib/foo_profile.dylib', '/usr/lib/foo.dylib', ]) self.assertEqual(envs[0], {}) def test_override_search(self): os.environ['DYLD_FRAMEWORK_PATH'] = '' os.environ['DYLD_LIBRARY_PATH'] = '' self.assertEqual( list(dyld.dyld_override_search("foo.dyld", None)), []) self.assertEqual( list(dyld.dyld_override_search("/usr/lib/libfoo.dyld", None)), []) self.assertEqual( list(dyld.dyld_override_search("/Library/Frameworks/Python.framework/Versions/Current/Python", None)), []) os.environ['DYLD_FRAMEWORK_PATH'] = '/Foo/Frameworks:/Bar/Frameworks' os.environ['DYLD_LIBRARY_PATH'] = '' self.assertEqual( list(dyld.dyld_override_search("foo.dyld", None)), []) self.assertEqual( list(dyld.dyld_override_search("/usr/lib/libfoo.dyld", None)), []) self.assertEqual( list(dyld.dyld_override_search("/Library/Frameworks/Python.framework/Versions/Current/Python", None)), [ '/Foo/Frameworks/Python.framework/Versions/Current/Python', '/Bar/Frameworks/Python.framework/Versions/Current/Python', ]) os.environ['DYLD_FRAMEWORK_PATH'] = '' os.environ['DYLD_LIBRARY_PATH'] = '/local/lib:/remote/lib' self.assertEqual( list(dyld.dyld_override_search("foo.dyld", None)), [ '/local/lib/foo.dyld', '/remote/lib/foo.dyld', ]) self.assertEqual( list(dyld.dyld_override_search("/usr/lib/libfoo.dyld", None)), [ '/local/lib/libfoo.dyld', '/remote/lib/libfoo.dyld', ]) self.assertEqual( list(dyld.dyld_override_search("/Library/Frameworks/Python.framework/Versions/Current/Python", None)), [ '/local/lib/Python', '/remote/lib/Python', ]) os.environ['DYLD_FRAMEWORK_PATH'] = '/Foo/Frameworks:/Bar/Frameworks' os.environ['DYLD_LIBRARY_PATH'] = '/local/lib:/remote/lib' self.assertEqual( list(dyld.dyld_override_search("foo.dyld", None)), [ '/local/lib/foo.dyld', '/remote/lib/foo.dyld', ]) self.assertEqual( list(dyld.dyld_override_search("/usr/lib/libfoo.dyld", None)), [ '/local/lib/libfoo.dyld', '/remote/lib/libfoo.dyld', ]) self.assertEqual( list(dyld.dyld_override_search("/Library/Frameworks/Python.framework/Versions/Current/Python", None)), [ '/Foo/Frameworks/Python.framework/Versions/Current/Python', '/Bar/Frameworks/Python.framework/Versions/Current/Python', '/local/lib/Python', '/remote/lib/Python', ]) def test_executable_path_search(self): self.assertEqual(list(dyld.dyld_executable_path_search("/usr/lib/foo.dyld", "/usr/bin")), []) self.assertEqual( list(dyld.dyld_executable_path_search("@executable_path/foo.dyld", "/usr/bin")), ['/usr/bin/foo.dyld']) self.assertEqual( list(dyld.dyld_executable_path_search("@executable_path/../../lib/foo.dyld", "/usr/bin")), ['/usr/bin/../../lib/foo.dyld']) def test_default_search(self): self.assertEqual( list(dyld.dyld_default_search('/usr/lib/mylib.dylib', None)), [ '/usr/lib/mylib.dylib', os.path.join(os.path.expanduser('~/lib'), 'mylib.dylib'), '/usr/local/lib/mylib.dylib', '/lib/mylib.dylib', '/usr/lib/mylib.dylib', ]) self.assertEqual( list(dyld.dyld_default_search('/Library/Frameworks/Python.framework/Versions/2.7/Python', None)), [ '/Library/Frameworks/Python.framework/Versions/2.7/Python', os.path.join(os.path.expanduser('~/Library/Frameworks'), 'Python.framework/Versions/2.7/Python'), '/Library/Frameworks/Python.framework/Versions/2.7/Python', '/Network/Library/Frameworks/Python.framework/Versions/2.7/Python', '/System/Library/Frameworks/Python.framework/Versions/2.7/Python', os.path.join(os.path.expanduser('~/lib'), 'Python'), '/usr/local/lib/Python', '/lib/Python', '/usr/lib/Python', ]) os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = '/local/lib:/network/lib' os.environ['DYLD_FALLBACK_FRAMEWORK_PATH'] = '' self.assertEqual( list(dyld.dyld_default_search('/usr/lib/mylib.dylib', None)), [ '/usr/lib/mylib.dylib', '/local/lib/mylib.dylib', '/network/lib/mylib.dylib', ]) self.assertEqual( list(dyld.dyld_default_search('/Library/Frameworks/Python.framework/Versions/2.7/Python', None)), [ '/Library/Frameworks/Python.framework/Versions/2.7/Python', os.path.join(os.path.expanduser('~/Library/Frameworks'), 'Python.framework/Versions/2.7/Python'), '/Library/Frameworks/Python.framework/Versions/2.7/Python', '/Network/Library/Frameworks/Python.framework/Versions/2.7/Python', '/System/Library/Frameworks/Python.framework/Versions/2.7/Python', '/local/lib/Python', '/network/lib/Python', ]) os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = '' os.environ['DYLD_FALLBACK_FRAMEWORK_PATH'] = '/MyFrameworks:/OtherFrameworks' self.assertEqual( list(dyld.dyld_default_search('/usr/lib/mylib.dylib', None)), [ '/usr/lib/mylib.dylib', os.path.join(os.path.expanduser('~/lib'), 'mylib.dylib'), '/usr/local/lib/mylib.dylib', '/lib/mylib.dylib', '/usr/lib/mylib.dylib', ]) self.assertEqual( list(dyld.dyld_default_search('/Library/Frameworks/Python.framework/Versions/2.7/Python', None)), [ '/Library/Frameworks/Python.framework/Versions/2.7/Python', '/MyFrameworks/Python.framework/Versions/2.7/Python', '/OtherFrameworks/Python.framework/Versions/2.7/Python', os.path.join(os.path.expanduser('~/lib'), 'Python'), '/usr/local/lib/Python', '/lib/Python', '/usr/lib/Python', ]) os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = '/local/lib:/network/lib' os.environ['DYLD_FALLBACK_FRAMEWORK_PATH'] = '/MyFrameworks:/OtherFrameworks' self.assertEqual( list(dyld.dyld_default_search('/usr/lib/mylib.dylib', None)), [ '/usr/lib/mylib.dylib', '/local/lib/mylib.dylib', '/network/lib/mylib.dylib', ]) self.assertEqual( list(dyld.dyld_default_search('/Library/Frameworks/Python.framework/Versions/2.7/Python', None)), [ '/Library/Frameworks/Python.framework/Versions/2.7/Python', '/MyFrameworks/Python.framework/Versions/2.7/Python', '/OtherFrameworks/Python.framework/Versions/2.7/Python', '/local/lib/Python', '/network/lib/Python', ]) def test_dyld_find(self): result = dyld.dyld_find('/usr/lib/libSystem.dylib') self.assertEqual(result, '/usr/lib/libSystem.dylib') self.assertIsInstance(result, str) # bytes on 2.x, unicode on 3.x result = dyld.dyld_find(b'/usr/lib/libSystem.dylib'.decode('ascii')) self.assertEqual(result, '/usr/lib/libSystem.dylib') self.assertIsInstance(result, str) # bytes on 2.x, unicode on 3.x patcher = DyldPatcher() try: patcher.log_calls('dyld_image_suffix_search') patcher.log_calls('dyld_override_search') patcher.log_calls('dyld_executable_path_search') patcher.log_calls('dyld_default_search') result = dyld.dyld_find('/usr/lib/libSystem.dylib') self.assertEqual(patcher.calls[:-1], [ ('dyld_override_search', ('/usr/lib/libSystem.dylib', None), {}), ('dyld_executable_path_search', ('/usr/lib/libSystem.dylib', None), {}), ('dyld_default_search', ('/usr/lib/libSystem.dylib', None), {}), ]) self.assertEqual(patcher.calls[-1][0], 'dyld_image_suffix_search') patcher.clear_calls() result = dyld.dyld_find('/usr/lib/libSystem.dylib', env=None) self.assertEqual(patcher.calls[:-1], [ ('dyld_override_search', ('/usr/lib/libSystem.dylib', None), {}), ('dyld_executable_path_search', ('/usr/lib/libSystem.dylib', None), {}), ('dyld_default_search', ('/usr/lib/libSystem.dylib', None), {}), ]) self.assertEqual(patcher.calls[-1][0], 'dyld_image_suffix_search') patcher.clear_calls() result = dyld.dyld_find('/usr/lib/libSystem.dylib', env={}) self.assertEqual(patcher.calls[:-1], [ ('dyld_override_search', ('/usr/lib/libSystem.dylib', {}), {}), ('dyld_executable_path_search', ('/usr/lib/libSystem.dylib', None), {}), ('dyld_default_search', ('/usr/lib/libSystem.dylib', {}), {}), ]) self.assertEqual(patcher.calls[-1][0], 'dyld_image_suffix_search') patcher.clear_calls() result = dyld.dyld_find('/usr/lib/libSystem.dylib', executable_path="/opt/py2app/bin", env={}) self.assertEqual(patcher.calls[:-1], [ ('dyld_override_search', ('/usr/lib/libSystem.dylib', {}), {}), ('dyld_executable_path_search', ('/usr/lib/libSystem.dylib', "/opt/py2app/bin"), {}), ('dyld_default_search', ('/usr/lib/libSystem.dylib', {}), {}), ]) self.assertEqual(patcher.calls[-1][0], 'dyld_image_suffix_search') patcher.clear_calls() finally: patcher.cleanup() def test_framework_find(self): result = dyld.framework_find('/System/Library/Frameworks/Cocoa.framework/Versions/Current/Cocoa') self.assertEqual(result, '/System/Library/Frameworks/Cocoa.framework/Versions/Current/Cocoa') self.assertIsInstance(result, str) # bytes on 2.x, unicode on 3.x result = dyld.framework_find(b'/System/Library/Frameworks/Cocoa.framework/Versions/Current/Cocoa'.decode('latin1')) self.assertEqual(result, '/System/Library/Frameworks/Cocoa.framework/Versions/Current/Cocoa') self.assertIsInstance(result, str) # bytes on 2.x, unicode on 3.x result = dyld.framework_find('Cocoa.framework') self.assertEqual(result, '/System/Library/Frameworks/Cocoa.framework/Cocoa') self.assertIsInstance(result, str) # bytes on 2.x, unicode on 3.x result = dyld.framework_find('Cocoa') self.assertEqual(result, '/System/Library/Frameworks/Cocoa.framework/Cocoa') self.assertIsInstance(result, str) # bytes on 2.x, unicode on 3.x patcher = DyldPatcher() try: patcher.log_calls('dyld_find') result = dyld.framework_find('/System/Library/Frameworks/Cocoa.framework/Versions/Current/Cocoa') self.assertEqual(patcher.calls, [ ('dyld_find', ('/System/Library/Frameworks/Cocoa.framework/Versions/Current/Cocoa',), {'env':None, 'executable_path': None}), ]) patcher.clear_calls() result = dyld.framework_find('Cocoa') self.assertEqual(patcher.calls, [ ('dyld_find', ('Cocoa',), {'env':None, 'executable_path': None}), ('dyld_find', ('Cocoa.framework/Cocoa',), {'env':None, 'executable_path': None}), ]) patcher.clear_calls() result = dyld.framework_find('Cocoa', '/my/sw/bin', {}) self.assertEqual(patcher.calls, [ ('dyld_find', ('Cocoa',), {'env':{}, 'executable_path': '/my/sw/bin'}), ('dyld_find', ('Cocoa.framework/Cocoa',), {'env':{}, 'executable_path': '/my/sw/bin'}), ]) patcher.clear_calls() finally: patcher.cleanup() class TestTrivialDyld (unittest.TestCase): # Tests ported from the implementation file def testBasic(self): self.assertEqual(dyld.dyld_find('libSystem.dylib'), '/usr/lib/libSystem.dylib') self.assertEqual(dyld.dyld_find('System.framework/System'), '/System/Library/Frameworks/System.framework/System') if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_dylib.py0000644000076500000240000000236712272472611021271 0ustar ronaldstaff00000000000000from macholib import dylib import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest def d(location=None, name=None, shortname=None, version=None, suffix=None): return dict( location=location, name=name, shortname=shortname, version=version, suffix=suffix ) class TestDylib (unittest.TestCase): def testInvalid(self): self.assertTrue(dylib.dylib_info('completely/invalid') is None) self.assertTrue(dylib.dylib_info('completely/invalid_debug') is None) def testUnversioned(self): self.assertEqual(dylib.dylib_info('P/Foo.dylib'), d('P', 'Foo.dylib', 'Foo')) self.assertEqual(dylib.dylib_info('P/Foo_debug.dylib'), d('P', 'Foo_debug.dylib', 'Foo', suffix='debug')) def testVersioned(self): self.assertEqual(dylib.dylib_info('P/Foo.A.dylib'), d('P', 'Foo.A.dylib', 'Foo', 'A')) self.assertEqual(dylib.dylib_info('P/Foo_debug.A.dylib'), d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A')) self.assertEqual(dylib.dylib_info('P/Foo.A_debug.dylib'), d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug')) if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_framework.py0000644000076500000240000000715312041231522022145 0ustar ronaldstaff00000000000000from macholib import framework import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestFramework (unittest.TestCase): def test_framework(self): self.assertEqual( framework.framework_info('Location/Name.framework/Versions/SomeVersion/Name_Suffix'), dict( location='Location', name='Name.framework/Versions/SomeVersion/Name_Suffix', shortname='Name', version='SomeVersion', suffix='Suffix', )) self.assertEqual( framework.framework_info('Location/Name.framework/Versions/SomeVersion/Name'), dict( location='Location', name='Name.framework/Versions/SomeVersion/Name', shortname='Name', version='SomeVersion', suffix=None, )) self.assertEqual( framework.framework_info('Location/Name.framework/Name_Suffix'), dict( location='Location', name='Name.framework/Name_Suffix', shortname='Name', version=None, suffix='Suffix', )) self.assertEqual( framework.framework_info('Location/Name.framework/Name'), dict( location='Location', name='Name.framework/Name', shortname='Name', version=None, suffix=None )) self.assertEqual( framework.framework_info('Location/Name.framework.disabled/Name'), None ) self.assertEqual( framework.framework_info('Location/Name.framework/Versions/A/B/Name'), None ) self.assertEqual( framework.framework_info('Location/Name.framework/Versions/A'), None ) self.assertEqual( framework.framework_info('Location/Name.framework/Versions/A/Name/_debug'), None ) def test_interal_tests(self): # Ported over from the source file def d(location=None, name=None, shortname=None, version=None, suffix=None): return dict( location=location, name=name, shortname=shortname, version=version, suffix=suffix ) self.assertEqual(framework.framework_info('completely/invalid'), None) self.assertEqual(framework.framework_info('completely/invalid/_debug'), None) self.assertEqual(framework.framework_info('P/F.framework'), None) self.assertEqual(framework.framework_info('P/F.framework/_debug'), None) self.assertEqual(framework.framework_info('P/F.framework/F'), d('P', 'F.framework/F', 'F')) self.assertEqual(framework.framework_info('P/F.framework/F_debug'), d('P', 'F.framework/F_debug', 'F', suffix='debug')) self.assertEqual(framework.framework_info('P/F.framework/Versions'), None) self.assertEqual(framework.framework_info('P/F.framework/Versions/A'), None) self.assertEqual(framework.framework_info('P/F.framework/Versions/A/F'), d('P', 'F.framework/Versions/A/F', 'F', 'A')) self.assertEqual(framework.framework_info('P/F.framework/Versions/A/F_debug'), d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug')) if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_itergraphreport.py0000644000076500000240000000051712041231524023370 0ustar ronaldstaff00000000000000from macholib import itergraphreport import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestIterGraphReport (unittest.TestCase): @unittest.expectedFailure def test_missing(self): self.fail("tests are missing") if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_mach_o.py0000644000076500000240000000072612041231531021375 0ustar ronaldstaff00000000000000from macholib import mach_o import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestMachO (unittest.TestCase): # This module is just a set of struct definitions, # not sure how to test those without replicating # the code. # # The definitions will get exercised by the # other tests, therefore testing is ignored # for now. pass if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_MachO.py0000644000076500000240000000047312041231501021132 0ustar ronaldstaff00000000000000from macholib import MachO import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestMachO (unittest.TestCase): @unittest.expectedFailure def test_missing(self): self.fail("tests are missing") if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_MachOGraph.py0000644000076500000240000000050512041231504022113 0ustar ronaldstaff00000000000000from macholib import MachOGraph import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestMachOGraph (unittest.TestCase): @unittest.expectedFailure def test_missing(self): self.fail("tests are missing") if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_MachOStandalone.py0000644000076500000240000000051712041231506023147 0ustar ronaldstaff00000000000000from macholib import MachOStandalone import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestMachOStandalone (unittest.TestCase): @unittest.expectedFailure def test_missing(self): self.fail("tests are missing") if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_ptypes.py0000644000076500000240000001605712272472611021513 0ustar ronaldstaff00000000000000from macholib import ptypes import unittest import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest try: from io import BytesIO except ImportError: from cStringIO import StringIO as BytesIO import mmap try: long except NameError: long = int class TestPTypes (unittest.TestCase): if not hasattr(unittest.TestCase, 'assertIsSubclass'): def assertIsSubclass(self, class1, class2, message=None): self.assertTrue(issubclass(class1, class2), message or "%r is not a subclass of %r"%(class1, class2)) if not hasattr(unittest.TestCase, 'assertIsInstance'): def assertIsInstance(self, value, types, message=None): self.assertTrue(isinstance(value, types), message or "%r is not an instance of %r"%(value, types)) def test_sizeof(self): self.assertEqual(ptypes.sizeof(b"foobar"), 6) self.assertRaises(ValueError, ptypes.sizeof, []) self.assertRaises(ValueError, ptypes.sizeof, {}) self.assertRaises(ValueError, ptypes.sizeof, b"foo".decode('ascii')) class M (object): pass m = M() m._size_ = 42 self.assertEqual(ptypes.sizeof(m), 42) def verifyType(self, ptype, size, pytype, values): self.assertEqual(ptypes.sizeof(ptype), size) self.assertIsSubclass(ptype, pytype) for v in values: pv = ptype(v) packed = pv.to_str() self.assertIsInstance(packed, bytes) self.assertEqual(len(packed), size) unp = ptype.from_str(packed) self.assertIsInstance(unp, ptype) self.assertEqual(unp, pv) fp = BytesIO(packed) unp = ptype.from_fileobj(fp) fp.close() self.assertIsInstance(unp, ptype) self.assertEqual(unp, pv) fp = BytesIO() pv.to_fileobj(fp) data = fp.getvalue() fp.close() self.assertEqual(data, packed) mm = mmap.mmap(-1, size+20) mm[:] = b'\x00' * (size+20) pv.to_mmap(mm, 10) self.assertEqual(ptype.from_mmap(mm, 10), pv) self.assertEqual(mm[:], (b'\x00'*10) + packed + (b'\x00'*10)) self.assertEqual(ptype.from_tuple((v,)), pv) def test_basic_types(self): self.verifyType(ptypes.p_char, 1, bytes, [b'a', b'b']) self.verifyType(ptypes.p_int8, 1, int, [1, 42, -4]) self.verifyType(ptypes.p_uint8, 1, int, [1, 42, 253]) self.verifyType(ptypes.p_int16, 2, int, [1, 400, -10, -5000]) self.verifyType(ptypes.p_uint16, 2, int, [1, 400, 65000]) self.verifyType(ptypes.p_int32, 4, int, [1, 400, 2**24, -10, -5000, -2**24]) self.verifyType(ptypes.p_uint32, 4, long, [1, 400, 2*31+5, 65000]) self.verifyType(ptypes.p_int64, 8, long, [1, 400, 2**43, -10, -5000, -2**43]) self.verifyType(ptypes.p_uint64, 8, long, [1, 400, 2*63+5, 65000]) self.verifyType(ptypes.p_float, 4, float, [1.0, 42.5]) self.verifyType(ptypes.p_double, 8, float, [1.0, 42.5]) def test_basic_types_deprecated(self): self.verifyType(ptypes.p_byte, 1, int, [1, 42, -4]) self.verifyType(ptypes.p_ubyte, 1, int, [1, 42, 253]) self.verifyType(ptypes.p_short, 2, int, [1, 400, -10, -5000]) self.verifyType(ptypes.p_ushort, 2, int, [1, 400, 65000]) self.verifyType(ptypes.p_int, 4, int, [1, 400, 2**24, -10, -5000, -2**24]) self.verifyType(ptypes.p_uint, 4, long, [1, 400, 2*31+5, 65000]) self.verifyType(ptypes.p_long, 4, int, [1, 400, 2**24, -10, -5000, -2**24]) self.verifyType(ptypes.p_ulong, 4, long, [1, 400, 2*31+5, 65000]) self.verifyType(ptypes.p_longlong, 8, long, [1, 400, 2**43, -10, -5000, -2**43]) self.verifyType(ptypes.p_ulonglong, 8, long, [1, 400, 2*63+5, 65000]) class TestPTypesPrivate (unittest.TestCase): # These are tests for functions that aren't part of the public # API. def test_formatinfo(self): self.assertEqual(ptypes._formatinfo(">b"), (1, 1)) self.assertEqual(ptypes._formatinfo(">h"), (2, 1)) self.assertEqual(ptypes._formatinfo(">HhL"), (8, 3)) self.assertEqual(ptypes._formatinfo("<': kw = dict(_endian_=endian) MYSTRUCTURE = b'\x00\x11\x22\x33\xFF' for fn, args in [ ('from_str', (MYSTRUCTURE,)), ('from_mmap', (MYSTRUCTURE, 0)), ('from_fileobj', (BytesIO(MYSTRUCTURE),)), ]: myStructure = getattr(MyStructure, fn)(*args, **kw) if endian == '>': self.assertEqual(myStructure.foo, 0x00112233) else: self.assertEqual( myStructure.foo, 0x33221100) self.assertEqual(myStructure.bar, 0xFF) self.assertEqual(myStructure.to_str(), MYSTRUCTURE) MYFUNSTRUCTURE = b'!' + MYSTRUCTURE for fn, args in [ ('from_str', (MYFUNSTRUCTURE,)), ('from_mmap', (MYFUNSTRUCTURE, 0)), ('from_fileobj', (BytesIO(MYFUNSTRUCTURE),)), ]: myFunStructure = getattr(MyFunStructure, fn)(*args, **kw) self.assertEqual(myFunStructure.mystruct, myStructure) self.assertEqual(myFunStructure.fun, b'!', (myFunStructure.fun, b'!')) self.assertEqual(myFunStructure.to_str(), MYFUNSTRUCTURE) sio = BytesIO() myFunStructure.to_fileobj(sio) self.assertEqual(sio.getvalue(), MYFUNSTRUCTURE) mm = mmap.mmap(-1, ptypes.sizeof(MyFunStructure) * 2) mm[:] = b'\x00' * (ptypes.sizeof(MyFunStructure) * 2) myFunStructure.to_mmap(mm, 0) self.assertEqual(MyFunStructure.from_mmap(mm, 0, **kw), myFunStructure) self.assertEqual(mm[:ptypes.sizeof(MyFunStructure)], MYFUNSTRUCTURE) self.assertEqual(mm[ptypes.sizeof(MyFunStructure):], b'\x00' * ptypes.sizeof(MyFunStructure)) myFunStructure.to_mmap(mm, ptypes.sizeof(MyFunStructure)) self.assertEqual(mm[:], MYFUNSTRUCTURE + MYFUNSTRUCTURE) self.assertEqual(MyFunStructure.from_mmap(mm, ptypes.sizeof(MyFunStructure), **kw), myFunStructure) if __name__ == "__main__": unittest.main() macholib-1.7/macholib_tests/test_SymbolTable.py0000644000076500000240000000050712041231510022356 0ustar ronaldstaff00000000000000from macholib import SymbolTable import sys if sys.version_info[:2] <= (2,6): import unittest2 as unittest else: import unittest class TestSymbolTable (unittest.TestCase): @unittest.expectedFailure def test_missing(self): self.fail("tests are missing") if __name__ == "__main__": unittest.main() macholib-1.7/MANIFEST.in0000644000076500000240000000024711535367361015314 0ustar ronaldstaff00000000000000include *.txt MANIFEST.in *.py graft doc graft doc/_static graft doc/_templates graft macholib_tests global-exclude .DS_Store global-exclude *.pyc global-exclude *.so macholib-1.7/PKG-INFO0000644000076500000240000002256412365134503014651 0ustar ronaldstaff00000000000000Metadata-Version: 1.1 Name: macholib Version: 1.7 Summary: Mach-O header analysis and editing Home-page: http://bitbucket.org/ronaldoussoren/macholib Author: Ronald Oussoren Author-email: ronaldoussoren@mac.com License: MIT Download-URL: http://pypi.python.org/pypi/macholib Description: macholib can be used to analyze and edit Mach-O headers, the executable format used by Mac OS X. It's typically used as a dependency analysis tool, and also to rewrite dylib references in Mach-O headers to be @executable_path relative. Though this tool targets a platform specific file format, it is pure python code that is platform and endian independent. Release history =============== macholib 1.7 ------------ * Added support for ARM64, LC_ENCRYPTION_INFO_64 and LC_LINKER_OPTION Patch by Matthias Ringwald. * Load commands now have a "describe" method that returns more information about the command. Patch by David Dorsey. * The MAGIC value in the header was always represented in the native byte order, instead of as the value read from the binary. Patch by David Dorsey. * Added various new constants to "macholib.mach_o". Patch by David Dorsey. macholib 1.6.1 -------------- * ? macholib 1.6 ------------ * Add support for '@loader_path' link command in macholib.dyld: - Added function ``macholib.dyld.dyld_loader_search`` - This function is used by ``macholib.dyld.dyld_find``, and that function now has an new (optional) argument with the path to the loader. * Also add support for '@loader_path' to macholib.MachoGraph, using the newly added '@loader_path' support in the dyld module. Due to this suppport the *macho_standalone* tool can now rewrite binaries that contain an '@loader_path' load command. macholib 1.5.2 -------------- * Issue #93: Show the name of the affected file in the exception message for Mach-O headers that are too large to relocate. macholib 1.5.1 -------------- * There were no 'classifiers' in the package metadata due to a bug in setup.py. macholib 1.5 -------------- macholib 1.5 is a minor feature release * No longer use 2to3 to provide Python 3 support As a side-effect of this macholib no longer supports Python 2.5 and earlier. * Adds suppport for some new macho load commands * Fix for py3k problem in macho_standalone.py Patch by Guanqun Lu. * Fix for some issues in macho_dump.py Patch by Nam Nguyen * Issue #10: Fix for LC_DATA_IN_CODE linker commands, without this fix py2app cannot build application bundles when the source binaries have been compiled with Xcode 4.5. * Issue #6: Fix for LC_ENCRYPTION_INFO linker commands * Use the mach header information to print the cpu type of a binary, instead of trying to deduce that from pointer width and endianness. Changed the code because of issue #6, in which a user tries to dump a iOS binary which results in bogus output in the previous releases. * The mapping ``macholib.macho_dump.ARCH_MAP`` is undocumented and no longer used by macholib itself. It will be removed in the next release. * The command-line tools ``macho_find``, ``macho_dump`` and ``macho_standalone`` are deprecated. Use "python -mmacholib" instead. That is:: $ python -mmacholib dump /usr/bin/grep $ python -mmacholib find ~ $ python -mmacholib standalone myapp.app This makes it clearer which version of the tools are used. macholib 1.4.3 -------------- macholib 1.4.3 is a minor feature release * Added strings for 'x86_64' and 'ppc64' to macholib.mach_o.CPU_TYPE_NAMES. * macho_find and macho_dump were broken in the 1.4.2 release * added 'macholib.util.NOT_SYSTEM_FILES', a list of files that aren't system path's even though they are located in system locations. Needed to work around a bug in PySide (see issue #32 in the py2app tracker) macholib 1.4.2 -------------- macholib 1.4.2 is a minor bugfix release * The support for new load commands that was added in 1.4.1 contained a typo that caused problems on OSX 10.7 (Lion). macholib 1.4.1 -------------- macholib 1.4.1 is a minor feature release Features: - Add support for a number of new MachO load commands that were added during the lifetime of OSX 10.6: ``LC_LOAD_UPWARD_DYLIB``, ``LC_VERSION_MIN_MACOSX``, ``LC_VERSION_MIN_IPHONEOS`` and ``LC_FUNCTION_STARTS``. macholib 1.4 ------------- macholib 1.4 is a feature release Features: - Documentation is now generated using `sphinx `_ and can be viewed at . - The repository has moved to bitbucket - There now is a testsuite - Private functionality inside modules was renamed to a name starting with an underscore. .. note:: if this change affects your code you are relying on undefined implementation features, please stop using private functions. - The basic packable types in ``macholib.ptypes`` were renamed to better represent the corresponding C type. The table below lists the old an new names (the old names are still available, but are deprecated and will be removed in a future release). +--------------+--------------+ | **Old name** | **New name** | +==============+==============+ | p_byte | p_int8 | +--------------+--------------+ | p_ubyte | p_uint8 | +--------------+--------------+ | p_short | p_int16 | +--------------+--------------+ | p_ushort | p_uint16 | +--------------+--------------+ | p_int | p_int32 | +--------------+--------------+ | p_uint | p_uint32 | +--------------+--------------+ | p_long | p_int32 | +--------------+--------------+ | p_ulong | p_uint32 | +--------------+--------------+ | p_longlong | p_int64 | +--------------+--------------+ | p_ulonglong | p_uint64 | +--------------+--------------+ ``Macholib.ptypes.p_ptr`` is no longer present as it had an unclear definition and isn't actually used in the codebase. Bug fixes: - The semantics of ``dyld.dyld_default_search`` were changed a bit, it now first searches the framework path (if appropriate) and then the linker path, irrespective of the value of the ``DYLD_FALLBACK*`` environment variables. Previous versions would change the search order when those variables was set, which is odd and doesn't correspond with the documented behaviour of the system dyld. - It is once again possible to install using python2.5 - The source distribution includes all files, this was broken due to the switch to mercurial (which confused setuptools) macholib 1.3 ------------ macholib 1.3 is a feature release. Features: - Experimental Python 3.x support This version contains lightly tested support for Python 3. macholib 1.2.2 -------------- macholib 1.2.2 is a bugfix release. Bug fixes: - Macholib should work better with 64-bit code (patch by Marc-Antoine Parent) Keywords: Mach-O,,dyld Platform: any Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Build Tools macholib-1.7/README.txt0000644000076500000240000000055011537030324015235 0ustar ronaldstaff00000000000000macholib can be used to analyze and edit Mach-O headers, the executable format used by Mac OS X. It's typically used as a dependency analysis tool, and also to rewrite dylib references in Mach-O headers to be @executable_path relative. Though this tool targets a platform specific file format, it is pure python code that is platform and endian independent. macholib-1.7/setup.cfg0000644000076500000240000000222512365134503015365 0ustar ronaldstaff00000000000000[metadata] name = macholib version = 1.7 description = Mach-O header analysis and editing long_description_file = README.txt doc/changelog.rst classifiers = Intended Audience :: Developers License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Operating System :: MacOS :: MacOS X Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Build Tools author = Ronald Oussoren author_email = ronaldoussoren@mac.com maintainer = Ronald Oussoren maintainer_email = ronaldoussoren@mac.com url = http://bitbucket.org/ronaldoussoren/macholib download_url = http://pypi.python.org/pypi/macholib packages = macholib license = MIT platforms = any requires-dist = altgraph (>=0.12) zip-safe = yes console_scripts = macho_find = macholib.macho_find:main macho_standalone = macholib.macho_standalone:main macho_dump = macholib.macho_dump:main keywords = Mach-O, dyld [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 macholib-1.7/setup.py0000644000076500000240000006057712363731500015272 0ustar ronaldstaff00000000000000""" Shared setup file for simple python packages. Uses a setup.cfg that is the same as the distutils2 project, unless noted otherwise. It exists for two reasons: 1) This makes it easier to reuse setup.py code between my own projects 2) Easier migration to distutils2 when that catches on. Additional functionality: * Section metadata: requires-test: Same as 'tests_require' option for setuptools. """ import sys import os import re import platform from fnmatch import fnmatch import os import sys import time import tempfile import tarfile try: import urllib.request as urllib except ImportError: import urllib from distutils import log try: from hashlib import md5 except ImportError: from md5 import md5 if sys.version_info[0] == 2: from ConfigParser import RawConfigParser, NoOptionError, NoSectionError else: from configparser import RawConfigParser, NoOptionError, NoSectionError ROOTDIR = os.path.dirname(os.path.abspath(__file__)) # # # # Parsing the setup.cfg and converting it to something that can be # used by setuptools.setup() # # # def eval_marker(value): """ Evaluate an distutils2 environment marker. This code is unsafe when used with hostile setup.cfg files, but that's not a problem for our own files. """ value = value.strip() class M: def __init__(self, **kwds): for k, v in kwds.items(): setattr(self, k, v) variables = { 'python_version': '%d.%d'%(sys.version_info[0], sys.version_info[1]), 'python_full_version': sys.version.split()[0], 'os': M( name=os.name, ), 'sys': M( platform=sys.platform, ), 'platform': M( version=platform.version(), machine=platform.machine(), ), } return bool(eval(value, variables, variables)) return True def _opt_value(cfg, into, section, key, transform = None): try: v = cfg.get(section, key) if transform != _as_lines and ';' in v: v, marker = v.rsplit(';', 1) if not eval_marker(marker): return v = v.strip() if v: if transform: into[key] = transform(v.strip()) else: into[key] = v.strip() except (NoOptionError, NoSectionError): pass def _as_bool(value): if value.lower() in ('y', 'yes', 'on'): return True elif value.lower() in ('n', 'no', 'off'): return False elif value.isdigit(): return bool(int(value)) else: raise ValueError(value) def _as_list(value): return value.split() def _as_lines(value): result = [] for v in value.splitlines(): if ';' in v: v, marker = v.rsplit(';', 1) if not eval_marker(marker): continue v = v.strip() if v: result.append(v) else: result.append(v) return result def _map_requirement(value): m = re.search(r'(\S+)\s*(?:\((.*)\))?', value) name = m.group(1) version = m.group(2) if version is None: return name else: mapped = [] for v in version.split(','): v = v.strip() if v[0].isdigit(): # Checks for a specific version prefix m = v.rsplit('.', 1) mapped.append('>=%s,<%s.%s'%( v, m[0], int(m[1])+1)) else: mapped.append(v) return '%s %s'%(name, ','.join(mapped),) def _as_requires(value): requires = [] for req in value.splitlines(): if ';' in req: req, marker = v.rsplit(';', 1) if not eval_marker(marker): continue req = req.strip() if not req: continue requires.append(_map_requirement(req)) return requires def parse_setup_cfg(): cfg = RawConfigParser() r = cfg.read([os.path.join(ROOTDIR, 'setup.cfg')]) if len(r) != 1: print("Cannot read 'setup.cfg'") sys.exit(1) metadata = dict( name = cfg.get('metadata', 'name'), version = cfg.get('metadata', 'version'), description = cfg.get('metadata', 'description'), ) _opt_value(cfg, metadata, 'metadata', 'license') _opt_value(cfg, metadata, 'metadata', 'maintainer') _opt_value(cfg, metadata, 'metadata', 'maintainer_email') _opt_value(cfg, metadata, 'metadata', 'author') _opt_value(cfg, metadata, 'metadata', 'author_email') _opt_value(cfg, metadata, 'metadata', 'url') _opt_value(cfg, metadata, 'metadata', 'download_url') _opt_value(cfg, metadata, 'metadata', 'classifiers', _as_lines) _opt_value(cfg, metadata, 'metadata', 'platforms', _as_list) _opt_value(cfg, metadata, 'metadata', 'packages', _as_list) _opt_value(cfg, metadata, 'metadata', 'keywords', _as_list) try: v = cfg.get('metadata', 'requires-dist') except (NoOptionError, NoSectionError): pass else: requires = _as_requires(v) if requires: metadata['install_requires'] = requires try: v = cfg.get('metadata', 'requires-test') except (NoOptionError, NoSectionError): pass else: requires = _as_requires(v) if requires: metadata['tests_require'] = requires try: v = cfg.get('metadata', 'long_description_file') except (NoOptionError, NoSectionError): pass else: parts = [] for nm in v.split(): fp = open(nm, 'rU') parts.append(fp.read()) fp.close() metadata['long_description'] = '\n\n'.join(parts) try: v = cfg.get('metadata', 'zip-safe') except (NoOptionError, NoSectionError): pass else: metadata['zip_safe'] = _as_bool(v) try: v = cfg.get('metadata', 'console_scripts') except (NoOptionError, NoSectionError): pass else: if 'entry_points' not in metadata: metadata['entry_points'] = {} metadata['entry_points']['console_scripts'] = v.splitlines() if sys.version_info[:2] <= (2,6): try: metadata['tests_require'] += ", unittest2" except KeyError: metadata['tests_require'] = "unittest2" return metadata # # # # Bootstrapping setuptools/distribute, based on # a heavily modified version of distribute_setup.py # # # SETUPTOOLS_PACKAGE='setuptools' try: import subprocess def _python_cmd(*args): args = (sys.executable,) + args return subprocess.call(args) == 0 except ImportError: def _python_cmd(*args): args = (sys.executable,) + args new_args = [] for a in args: new_args.append(a.replace("'", "'\"'\"'")) os.system(' '.join(new_args)) == 0 try: import json def get_pypi_src_download(package): url = 'https://pypi.python.org/pypi/%s/json'%(package,) fp = urllib.urlopen(url) try: try: data = fp.read() finally: fp.close() except urllib.error: raise RuntimeError("Cannot determine download link for %s"%(package,)) pkgdata = json.loads(data.decode('utf-8')) if 'urls' not in pkgdata: raise RuntimeError("Cannot determine download link for %s"%(package,)) for info in pkgdata['urls']: if info['packagetype'] == 'sdist' and info['url'].endswith('tar.gz'): return (info.get('md5_digest'), info['url']) raise RuntimeError("Cannot determine downlink link for %s"%(package,)) except ImportError: # Python 2.5 compatibility, no JSON in stdlib but luckily JSON syntax is # simular enough to Python's syntax to be able to abuse the Python compiler import _ast as ast def get_pypi_src_download(package): url = 'https://pypi.python.org/pypi/%s/json'%(package,) fp = urllib.urlopen(url) try: try: data = fp.read() finally: fp.close() except urllib.error: raise RuntimeError("Cannot determine download link for %s"%(package,)) a = compile(data, '-', 'eval', ast.PyCF_ONLY_AST) if not isinstance(a, ast.Expression): raise RuntimeError("Cannot determine download link for %s"%(package,)) a = a.body if not isinstance(a, ast.Dict): raise RuntimeError("Cannot determine download link for %s"%(package,)) for k, v in zip(a.keys, a.values): if not isinstance(k, ast.Str): raise RuntimeError("Cannot determine download link for %s"%(package,)) k = k.s if k == 'urls': a = v break else: raise RuntimeError("PyPI JSON for %s doesn't contain URLs section"%(package,)) if not isinstance(a, ast.List): raise RuntimeError("Cannot determine download link for %s"%(package,)) for info in v.elts: if not isinstance(info, ast.Dict): raise RuntimeError("Cannot determine download link for %s"%(package,)) url = None packagetype = None chksum = None for k, v in zip(info.keys, info.values): if not isinstance(k, ast.Str): raise RuntimeError("Cannot determine download link for %s"%(package,)) if k.s == 'url': if not isinstance(v, ast.Str): raise RuntimeError("Cannot determine download link for %s"%(package,)) url = v.s elif k.s == 'packagetype': if not isinstance(v, ast.Str): raise RuntimeError("Cannot determine download link for %s"%(package,)) packagetype = v.s elif k.s == 'md5_digest': if not isinstance(v, ast.Str): raise RuntimeError("Cannot determine download link for %s"%(package,)) chksum = v.s if url is not None and packagetype == 'sdist' and url.endswith('.tar.gz'): return (chksum, url) raise RuntimeError("Cannot determine download link for %s"%(package,)) def _build_egg(egg, tarball, to_dir): # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) tar = tarfile.open(tarball) _extractall(tar) tar.close() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) # building an egg log.warn('Building a %s egg in %s', egg, to_dir) _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) finally: os.chdir(old_wd) # returning the result log.warn(egg) if not os.path.exists(egg): raise IOError('Could not build the egg.') def _do_download(to_dir, packagename=SETUPTOOLS_PACKAGE): tarball = download_setuptools(packagename, to_dir) version = tarball.split('-')[-1][:-7] egg = os.path.join(to_dir, '%s-%s-py%d.%d.egg' % (packagename, version, sys.version_info[0], sys.version_info[1])) if not os.path.exists(egg): _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) import setuptools setuptools.bootstrap_install_from = egg def use_setuptools(): # making sure we use the absolute path return _do_download(os.path.abspath(os.curdir)) def download_setuptools(packagename, to_dir): # making sure we use the absolute path to_dir = os.path.abspath(to_dir) try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen chksum, url = get_pypi_src_download(packagename) tgz_name = os.path.basename(url) saveto = os.path.join(to_dir, tgz_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: log.warn("Downloading %s", url) src = urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = src.read() if chksum is not None: data_sum = md5(data).hexdigest() if data_sum != chksum: raise RuntimeError("Downloading %s failed: corrupt checksum"%(url,)) dst = open(saveto, "wb") dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def _extractall(self, path=".", members=None): """Extract all members from the archive to the current working directory and set owner, modification time and permissions on directories afterwards. `path' specifies a different directory to extract to. `members' is optional and must be a subset of the list returned by getmembers(). """ import copy import operator from tarfile import ExtractError directories = [] if members is None: members = self for tarinfo in members: if tarinfo.isdir(): # Extract directories with a safe mode. directories.append(tarinfo) tarinfo = copy.copy(tarinfo) tarinfo.mode = 448 # decimal for oct 0700 self.extract(tarinfo, path) # Reverse sort directories. if sys.version_info < (2, 4): def sorter(dir1, dir2): return cmp(dir1.name, dir2.name) directories.sort(sorter) directories.reverse() else: directories.sort(key=operator.attrgetter('name'), reverse=True) # Set correct owner, mtime and filemode on directories. for tarinfo in directories: dirpath = os.path.join(path, tarinfo.name) try: self.chown(tarinfo, dirpath) self.utime(tarinfo, dirpath) self.chmod(tarinfo, dirpath) except ExtractError: e = sys.exc_info()[1] if self.errorlevel > 1: raise else: self._dbg(1, "tarfile: %s" % e) # # # # Definitions of custom commands # # # try: import setuptools except ImportError: use_setuptools() from setuptools import setup try: from distutils.core import PyPIRCCommand except ImportError: PyPIRCCommand = None # Ancient python version from distutils.core import Command from distutils.errors import DistutilsError from distutils import log if PyPIRCCommand is None: class upload_docs (Command): description = "upload sphinx documentation" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): raise DistutilsError("not supported on this version of python") else: class upload_docs (PyPIRCCommand): description = "upload sphinx documentation" user_options = PyPIRCCommand.user_options def initialize_options(self): PyPIRCCommand.initialize_options(self) self.username = '' self.password = '' def finalize_options(self): PyPIRCCommand.finalize_options(self) config = self._read_pypirc() if config != {}: self.username = config['username'] self.password = config['password'] def run(self): import subprocess import shutil import zipfile import os import urllib import StringIO from base64 import standard_b64encode import httplib import urlparse # Extract the package name from distutils metadata meta = self.distribution.metadata name = meta.get_name() # Run sphinx if os.path.exists('doc/_build'): shutil.rmtree('doc/_build') os.mkdir('doc/_build') p = subprocess.Popen(['make', 'html'], cwd='doc') exit = p.wait() if exit != 0: raise DistutilsError("sphinx-build failed") # Collect sphinx output if not os.path.exists('dist'): os.mkdir('dist') zf = zipfile.ZipFile('dist/%s-docs.zip'%(name,), 'w', compression=zipfile.ZIP_DEFLATED) for toplevel, dirs, files in os.walk('doc/_build/html'): for fn in files: fullname = os.path.join(toplevel, fn) relname = os.path.relpath(fullname, 'doc/_build/html') print ("%s -> %s"%(fullname, relname)) zf.write(fullname, relname) zf.close() # Upload the results, this code is based on the distutils # 'upload' command. content = open('dist/%s-docs.zip'%(name,), 'rb').read() data = { ':action': 'doc_upload', 'name': name, 'content': ('%s-docs.zip'%(name,), content), } auth = "Basic " + standard_b64encode(self.username + ":" + self.password) boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' sep_boundary = '\n--' + boundary end_boundary = sep_boundary + '--' body = StringIO.StringIO() for key, value in data.items(): if not isinstance(value, list): value = [value] for value in value: if isinstance(value, tuple): fn = ';filename="%s"'%(value[0]) value = value[1] else: fn = '' body.write(sep_boundary) body.write('\nContent-Disposition: form-data; name="%s"'%key) body.write(fn) body.write("\n\n") body.write(value) body.write(end_boundary) body.write('\n') body = body.getvalue() self.announce("Uploading documentation to %s"%(self.repository,), log.INFO) schema, netloc, url, params, query, fragments = \ urlparse.urlparse(self.repository) if schema == 'http': http = httplib.HTTPConnection(netloc) elif schema == 'https': http = httplib.HTTPSConnection(netloc) else: raise AssertionError("unsupported schema "+schema) data = '' loglevel = log.INFO try: http.connect() http.putrequest("POST", url) http.putheader('Content-type', 'multipart/form-data; boundary=%s'%boundary) http.putheader('Content-length', str(len(body))) http.putheader('Authorization', auth) http.endheaders() http.send(body) except socket.error: e = socket.exc_info()[1] self.announce(str(e), log.ERROR) return r = http.getresponse() if r.status in (200, 301): self.announce('Upload succeeded (%s): %s' % (r.status, r.reason), log.INFO) else: self.announce('Upload failed (%s): %s' % (r.status, r.reason), log.ERROR) print ('-'*75) print (r.read()) print ('-'*75) def recursiveGlob(root, pathPattern): """ Recursively look for files matching 'pathPattern'. Return a list of matching files/directories. """ result = [] for rootpath, dirnames, filenames in os.walk(root): for fn in filenames: if fnmatch(fn, pathPattern): result.append(os.path.join(rootpath, fn)) return result def importExternalTestCases(unittest, pathPattern="test_*.py", root=".", package=None): """ Import all unittests in the PyObjC tree starting at 'root' """ testFiles = recursiveGlob(root, pathPattern) testModules = map(lambda x:x[len(root)+1:-3].replace('/', '.'), testFiles) if package is not None: testModules = [(package + '.' + m) for m in testModules] suites = [] for modName in testModules: try: module = __import__(modName) except ImportError: print("SKIP %s: %s"%(modName, sys.exc_info()[1])) continue if '.' in modName: for elem in modName.split('.')[1:]: module = getattr(module, elem) s = unittest.defaultTestLoader.loadTestsFromModule(module) suites.append(s) return unittest.TestSuite(suites) class test (Command): description = "run test suite" user_options = [ ('verbosity=', None, "print what tests are run"), ] def initialize_options(self): self.verbosity='1' def finalize_options(self): if isinstance(self.verbosity, str): self.verbosity = int(self.verbosity) def cleanup_environment(self): ei_cmd = self.get_finalized_command('egg_info') egg_name = ei_cmd.egg_name.replace('-', '_') to_remove = [] for dirname in sys.path: bn = os.path.basename(dirname) if bn.startswith(egg_name + "-"): to_remove.append(dirname) for dirname in to_remove: log.info("removing installed %r from sys.path before testing"%( dirname,)) sys.path.remove(dirname) def add_project_to_sys_path(self): from pkg_resources import normalize_path, add_activation_listener from pkg_resources import working_set, require self.reinitialize_command('egg_info') self.run_command('egg_info') self.reinitialize_command('build_ext', inplace=1) self.run_command('build_ext') # Check if this distribution is already on sys.path # and remove that version, this ensures that the right # copy of the package gets tested. self.__old_path = sys.path[:] self.__old_modules = sys.modules.copy() ei_cmd = self.get_finalized_command('egg_info') sys.path.insert(0, normalize_path(ei_cmd.egg_base)) sys.path.insert(1, os.path.dirname(__file__)) # Strip the namespace packages defined in this distribution # from sys.modules, needed to reset the search path for # those modules. nspkgs = getattr(self.distribution, 'namespace_packages') if nspkgs is not None: for nm in nspkgs: del sys.modules[nm] # Reset pkg_resources state: add_activation_listener(lambda dist: dist.activate()) working_set.__init__() require('%s==%s'%(ei_cmd.egg_name, ei_cmd.egg_version)) def remove_from_sys_path(self): from pkg_resources import working_set sys.path[:] = self.__old_path sys.modules.clear() sys.modules.update(self.__old_modules) working_set.__init__() def run(self): import unittest # Ensure that build directory is on sys.path (py3k) self.cleanup_environment() self.add_project_to_sys_path() try: meta = self.distribution.metadata name = meta.get_name() test_pkg = name + "_tests" suite = importExternalTestCases(unittest, "test_*.py", test_pkg, test_pkg) runner = unittest.TextTestRunner(verbosity=self.verbosity) result = runner.run(suite) # Print out summary. This is a structured format that # should make it easy to use this information in scripts. summary = dict( count=result.testsRun, fails=len(result.failures), errors=len(result.errors), xfails=len(getattr(result, 'expectedFailures', [])), xpass=len(getattr(result, 'expectedSuccesses', [])), skip=len(getattr(result, 'skipped', [])), ) print("SUMMARY: %s"%(summary,)) finally: self.remove_from_sys_path() # # # # And finally run the setuptools main entry point. # # # metadata = parse_setup_cfg() setup( cmdclass=dict( upload_docs=upload_docs, test=test, ), **metadata )