From 61e22cd85cd4c84fff391da67018c92bf21a8e19 Mon Sep 17 00:00:00 2001 From: Gregory Szorc Date: Sat, 10 Mar 2012 04:41:24 +0000 Subject: [PATCH] [llvm.py] Implement interface to object files It is now possible to load object files and scan over sections, symbols, and relocations! Includes test code with partial coverage. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@152482 91177308-0d34-0410-b5e6-96231b3b80d8 --- bindings/python/README.txt | 67 +++ bindings/python/llvm/common.py | 72 +++- bindings/python/llvm/core.py | 27 +- bindings/python/llvm/object.py | 473 +++++++++++++++++----- bindings/python/llvm/tests/__init__.py | 0 bindings/python/llvm/tests/base.py | 29 ++ bindings/python/llvm/tests/test_core.py | 9 + bindings/python/llvm/tests/test_object.py | 67 +++ bindings/python/tests/test_core.py | 11 - bindings/python/tests/test_object.py | 9 - 10 files changed, 620 insertions(+), 144 deletions(-) create mode 100644 bindings/python/llvm/tests/__init__.py create mode 100644 bindings/python/llvm/tests/base.py create mode 100644 bindings/python/llvm/tests/test_core.py create mode 100644 bindings/python/llvm/tests/test_object.py delete mode 100644 bindings/python/tests/test_core.py delete mode 100644 bindings/python/tests/test_object.py diff --git a/bindings/python/README.txt b/bindings/python/README.txt index e69de29bb2d..96e334319bd 100644 --- a/bindings/python/README.txt +++ b/bindings/python/README.txt @@ -0,0 +1,67 @@ +This directory contains Python bindings for LLVM's C library. + +The bindings are currently a work in progress and are far from complete. +Use at your own risk. + +Developer Info +============== + +The single Python package is "llvm." Modules inside this package roughly +follow the names of the modules/headers defined by LLVM's C API. + +Testing +------- + +All test code is location in llvm/tests. Tests are written as classes +which inherit from llvm.tests.base.TestBase, which is a convenience base +class that provides common functionality. + +Tests can be executed by installing nose: + + pip install nosetests + +Then by running nosetests: + + nosetests + +To see more output: + + nosetests -v + +To step into the Python debugger while running a test, add the following +to your test at the point you wish to enter the debugger: + + import pdb; pdb.set_trace() + +Then run nosetests: + + nosetests -s -v + +You should strive for high code coverage. To see current coverage: + + pip install coverage + nosetests --with-coverage --cover-html + +Then open cover/index.html in your browser of choice to see the code coverage. + +Style Convention +---------------- + +All code should pass PyFlakes. First, install PyFlakes: + + pip install pyflakes + +Then at any time run it to see a report: + + pyflakes . + +Eventually we'll provide a Pylint config file. In the meantime, install +Pylint: + + pip install pylint + +And run: + + pylint llvm + +And try to keep the number of violations to a minimum. diff --git a/bindings/python/llvm/common.py b/bindings/python/llvm/common.py index fe35bf124fe..9911e694ba4 100644 --- a/bindings/python/llvm/common.py +++ b/bindings/python/llvm/common.py @@ -14,23 +14,85 @@ from ctypes import cdll import ctypes.util __all__ = [ - 'LLVMObject', + 'c_object_p', 'find_library', 'get_library', ] -LLVMObject = POINTER(c_void_p) +c_object_p = POINTER(c_void_p) + +class LLVMObject(object): + """Base class for objects that are backed by an LLVM data structure. + + This class should never be instantiated outside of this package. + """ + def __init__(self, ptr, ownable=True, disposer=None): + assert isinstance(ptr, c_object_p) + + self._ptr = self._as_parameter_ = ptr + + self._self_owned = True + self._ownable = ownable + self._disposer = disposer + + self._owned_objects = [] + + def take_ownership(self, obj): + """Take ownership of another object. + + When you take ownership of another object, you are responsible for + destroying that object. In addition, a reference to that object is + placed inside this object so the Python garbage collector will not + collect the object while it is still alive in libLLVM. + + This method should likely only be called from within modules inside + this package. + """ + assert isinstance(obj, LLVMObject) + + self._owned_objects.append(obj) + obj._self_owned = False + + def from_param(self): + """ctypes function that converts this object to a function parameter.""" + return self._as_parameter_ + + def __del__(self): + if self._self_owned and self._disposer: + self._disposer(self) + +class CachedProperty(object): + """Decorator that caches the result of a property lookup. + + This is a useful replacement for @property. It is recommended to use this + decorator on properties that invoke C API calls for which the result of the + call will be idempotent. + """ + def __init__(self, wrapped): + self.wrapped = wrapped + try: + self.__doc__ = wrapped.__doc__ + except: # pragma: no cover + pass + + def __get__(self, instance, instance_type=None): + if instance is None: + return self + + value = self.wrapped(instance) + setattr(instance, self.wrapped.__name__, value) + + return value def find_library(): # FIXME should probably have build system define absolute path of shared # library at install time. - for lib in ['LLVM-3.1svn', 'LLVM']: + for lib in ['LLVM-3.1svn', 'libLLVM-3.1svn', 'LLVM', 'libLLVM']: result = ctypes.util.find_library(lib) if result: return result - # FIXME This is a local hack to ease development. - return "/usr/local/llvm/lib/libLLVM-3.1svn.so" + return None def get_library(): """Obtain a reference to the llvm library.""" diff --git a/bindings/python/llvm/core.py b/bindings/python/llvm/core.py index bd9f8aaa2b7..e0f35203bac 100644 --- a/bindings/python/llvm/core.py +++ b/bindings/python/llvm/core.py @@ -8,21 +8,21 @@ #===------------------------------------------------------------------------===# from .common import LLVMObject +from .common import c_object_p from .common import get_library from ctypes import POINTER from ctypes import byref from ctypes import c_char_p -from ctypes import c_void_p __all__ = [ "lib", - "MemoryBufferRef", + "MemoryBuffer", ] lib = get_library() -class MemoryBuffer(object): +class MemoryBuffer(LLVMObject): """Represents an opaque memory buffer.""" def __init__(self, filename=None): @@ -34,7 +34,7 @@ class MemoryBuffer(object): if filename is None: raise Exception("filename argument must be defined") - memory = LLVMObject() + memory = c_object_p() out = c_char_p(None) result = lib.LLVMCreateMemoryBufferWithContentsOfFile(filename, @@ -43,26 +43,13 @@ class MemoryBuffer(object): if result: raise Exception("Could not create memory buffer: %s" % out.value) - self._memory = memory - self._as_parameter_ = self._memory - self._owned = True - - def __del__(self): - if self._owned: - lib.LLVMDisposeMemoryBuffer(self._memory) - - def from_param(self): - return self._as_parameter_ - - def release_ownership(self): - self._owned = False - + LLVMObject.__init__(self, memory, disposer=lib.LLVMDisposeMemoryBuffer) def register_library(library): library.LLVMCreateMemoryBufferWithContentsOfFile.argtypes = [c_char_p, - POINTER(LLVMObject), POINTER(c_char_p)] + POINTER(c_object_p), POINTER(c_char_p)] library.LLVMCreateMemoryBufferWithContentsOfFile.restype = bool - library.LLVMDisposeMemoryBuffer.argtypes = [c_void_p] + library.LLVMDisposeMemoryBuffer.argtypes = [MemoryBuffer] register_library(lib) diff --git a/bindings/python/llvm/object.py b/bindings/python/llvm/object.py index f633f609536..473aa3a1089 100644 --- a/bindings/python/llvm/object.py +++ b/bindings/python/llvm/object.py @@ -7,11 +7,82 @@ # #===------------------------------------------------------------------------===# +r""" +Object File Interface +===================== + +This module provides an interface for reading information from object files +(e.g. binary executables and libraries). + +Using this module, you can obtain information about an object file's sections, +symbols, and relocations. These are represented by the classes ObjectFile, +Section, Symbol, and Relocation, respectively. + +Usage +----- + +The only way to use this module is to start by creating an ObjectFile. You can +create an ObjectFile by loading a file (specified by its path) or by creating a +llvm.core.MemoryBuffer and loading that. + +Once you have an object file, you can inspect its sections and symbols directly +by calling get_sections() and get_symbols() respectively. To inspect +relocations, call get_relocations() on a Section instance. + +Iterator Interface +------------------ + +The LLVM bindings expose iteration over sections, symbols, and relocations in a +way that only allows one instance to be operated on at a single time. This is +slightly annoying from a Python perspective, as it isn't very Pythonic to have +objects that "expire" but are still active from a dynamic language. + +To aid working around this limitation, each Section, Symbol, and Relocation +instance caches its properties after first access. So, if the underlying +iterator is advanced, the properties can still be obtained provided they have +already been retrieved. + +In addition, we also provide a "cache" method on each class to cache all +available data. You can call this on each obtained instance. Or, you can pass +cache=True to the appropriate get_XXX() method to have this done for you. + +Here are some examples on how to perform iteration: + + obj = ObjectFile(filename='/bin/ls') + + # This is OK. Each Section is only accessed inside its own iteration slot. + section_names = [] + for section in obj.get_sections(): + section_names.append(section.name) + + # This is NOT OK. You perform a lookup after the object has expired. + symbols = list(obj.get_symbols()) + for symbol in symbols: + print symbol.name # This raises because the object has expired. + + # In this example, we mix a working and failing scenario. + symbols = [] + for symbol in obj.get_symbols(): + symbols.append(symbol) + print symbol.name + + for symbol in symbols: + print symbol.name # OK + print symbol.address # NOT OK. We didn't look up this property before. + + # Cache everything up front. + symbols = list(obj.get_symbols(cache=True)) + for symbol in symbols: + print symbol.name # OK + +""" + from ctypes import c_char_p from ctypes import c_uint64 -from ctypes import c_void_p +from .common import CachedProperty from .common import LLVMObject +from .common import c_object_p from .common import get_library from .core import MemoryBuffer @@ -23,7 +94,7 @@ __all__ = [ "Symbol", ] -class ObjectFile(object): +class ObjectFile(LLVMObject): """Represents an object/binary file.""" def __init__(self, filename=None, contents=None): @@ -39,209 +110,413 @@ class ObjectFile(object): if filename is not None: contents = MemoryBuffer(filename=filename) - self._memory = contents - self._obj = lib.LLVMCreateObjectFile(contents) - contents.release_ownership() - self._as_parameter_ = self._obj - - def __del__(self): - lib.LLVMDisposeObjectFile(self) + if contents is None: + raise Exception('No input found.') - def from_param(self): - return self._as_parameter_ + ptr = lib.LLVMCreateObjectFile(contents) + LLVMObject.__init__(self, ptr, disposer=lib.LLVMDisposeObjectFile) + self.take_ownership(contents) - def get_sections(self): + def get_sections(self, cache=False): """Obtain the sections in this object file. - This is an iterator for llvm.object.Section instances. + This is a generator for llvm.object.Section instances. + + Sections are exposed as limited-use objects. See the module's + documentation on iterators for more. """ - pass + sections = lib.LLVMGetSections(self) + last = None + while True: + if lib.LLVMIsSectionIteratorAtEnd(self, sections): + break + + last = Section(sections) + if cache: + last.cache() - def get_symbols(self): + yield last + + lib.LLVMMoveToNextSection(sections) + last.expire() + + if last is not None: + last.expire() + + lib.LLVMDisposeSectionIterator(sections) + + def get_symbols(self, cache=False): """Obtain the symbols in this object file. - This is an iterator for llvm.object.Symbol instances. + This is a generator for llvm.object.Symbol instances. + + Each Symbol instance is a limited-use object. See this module's + documentation on iterators for more. """ + symbols = lib.LLVMGetSymbols(self) + last = None + while True: + if lib.LLVMIsSymbolIteratorAtEnd(self, symbols): + break + + last = Symbol(symbols, self) + if cache: + last.cache() + + yield last + + lib.LLVMMoveToNextSymbol(symbols) + last.expire() + + if last is not None: + last.expire() + + lib.LLVMDisposeSymbolIterator(symbols) -class Section(object): +class Section(LLVMObject): """Represents a section in an object file.""" - def __init__(self, obj=None): + def __init__(self, ptr): """Construct a new section instance. Section instances can currently only be created from an ObjectFile instance. Therefore, this constructor should not be used outside of this module. """ - pass + LLVMObject.__init__(self, ptr) - def __del__(self): - pass + self.expired = False - @property + @CachedProperty def name(self): - pass + """Obtain the string name of the section. - @property + This is typically something like '.dynsym' or '.rodata'. + """ + if self.expired: + raise Exception('Section instance has expired.') + + return lib.LLVMGetSectionName(self) + + @CachedProperty def size(self): - pass + """The size of the section, in long bytes.""" + if self.expired: + raise Exception('Section instance has expired.') - @property + return lib.LLVMGetSectionSize(self) + + @CachedProperty def contents(self): - pass + if self.expired: + raise Exception('Section instance has expired.') + + return lib.LLVMGetSectionContents(self) - @property + @CachedProperty def address(self): - pass + """The address of this section, in long bytes.""" + if self.expired: + raise Exception('Section instance has expired.') + + return lib.LLVMGetSectionAddress(self) - # TODO consider exposing more Pythonic interface, like __contains__ def has_symbol(self, symbol): - pass + """Returns whether a Symbol instance is present in this Section.""" + if self.expired: + raise Exception('Section instance has expired.') + + assert isinstance(symbol, Symbol) + return lib.LLVMGetSectionContainsSymbol(self, symbol) + + def get_relocations(self, cache=False): + """Obtain the relocations in this Section. - def get_relocations(self): - pass + This is a generator for llvm.object.Relocation instances. + + Each instance is a limited used object. See this module's documentation + on iterators for more. + """ + if self.expired: + raise Exception('Section instance has expired.') -class Symbol(object): - def __init__(self): - pass + relocations = lib.LLVMGetRelocations(self) + last = None + while True: + if lib.LLVMIsRelocationIteratorAtEnd(self, relocations): + break - @property + last = Relocation(relocations) + if cache: + last.cache() + + yield last + + lib.LLVMMoveToNextRelocation(relocations) + last.expire() + + if last is not None: + last.expire() + + lib.LLVMDisposeRelocationIterator(relocations) + + def cache(self): + """Cache properties of this Section. + + This can be called as a workaround to the single active Section + limitation. When called, the properties of the Section are fetched so + they are still available after the Section has been marked inactive. + """ + getattr(self, 'name') + getattr(self, 'size') + getattr(self, 'contents') + getattr(self, 'address') + + def expire(self): + """Expire the section. + + This is called internally by the section iterator. + """ + self.expired = True + +class Symbol(LLVMObject): + """Represents a symbol in an object file.""" + def __init__(self, ptr, object_file): + assert isinstance(ptr, c_object_p) + assert isinstance(object_file, ObjectFile) + + LLVMObject.__init__(self, ptr) + + self.expired = False + self._object_file = object_file + + @CachedProperty def name(self): - pass + """The str name of the symbol. + + This is often a function or variable name. Keep in mind that name + mangling could be in effect. + """ + if self.expired: + raise Exception('Symbol instance has expired.') + + return lib.LLVMGetSymbolName(self) - @property + @CachedProperty def address(self): - pass + """The address of this symbol, in long bytes.""" + if self.expired: + raise Exception('Symbol instance has expired.') + + return lib.LLVMGetSymbolAddress(self) - @property + @CachedProperty def file_offset(self): - pass + """The offset of this symbol in the file, in long bytes.""" + if self.expired: + raise Exception('Symbol instance has expired.') - @property + return lib.LLVMGetSymbolFileOffset(self) + + @CachedProperty def size(self): - pass + """The size of the symbol, in long bytes.""" + if self.expired: + raise Exception('Symbol instance has expired.') + + return lib.LLVMGetSymbolSize(self) -class Relocation(object): - def __init__(self): - pass + @CachedProperty + def section(self): + """The Section to which this Symbol belongs. - @property + The returned Section instance does not expire, unlike Sections that are + commonly obtained through iteration. + + Because this obtains a new section iterator each time it is accessed, + calling this on a number of Symbol instances could be expensive. + """ + sections = lib.LLVMGetSections(self._object_file) + lib.LLVMMoveToContainingSection(sections, self) + + return Section(sections) + + def cache(self): + """Cache all cacheable properties.""" + getattr(self, 'name') + getattr(self, 'address') + getattr(self, 'file_offset') + getattr(self, 'size') + + def expire(self): + """Mark the object as expired to prevent future API accesses. + + This is called internally by this module and it is unlikely that + external callers have a legitimate reason for using it. + """ + self.expired = True + +class Relocation(LLVMObject): + """Represents a relocation definition.""" + def __init__(self, ptr): + """Create a new relocation instance. + + Relocations are created from objects derived from Section instances. + Therefore, this constructor should not be called outside of this + module. See Section.get_relocations() for the proper method to obtain + a Relocation instance. + """ + assert isinstance(ptr, c_object_p) + + LLVMObject.__init__(self, ptr) + + self.expired = False + + @CachedProperty def address(self): - pass + """The address of this relocation, in long bytes.""" + if self.expired: + raise Exception('Relocation instance has expired.') - @property + return lib.LLVMGetRelocationAddress(self) + + @CachedProperty def offset(self): - pass + """The offset of this relocation, in long bytes.""" + if self.expired: + raise Exception('Relocation instance has expired.') + + return lib.LLVMGetRelocationOffset(self) - @property + @CachedProperty def symbol(self): - pass + """The Symbol corresponding to this Relocation.""" + if self.expired: + raise Exception('Relocation instance has expired.') - @property - def type(self): - pass + ptr = lib.LLVMGetRelocationSymbol(self) + return Symbol(ptr) - @property + @CachedProperty + def type_number(self): + """The relocation type, as a long.""" + if self.expired: + raise Exception('Relocation instance has expired.') + + return lib.LLVMGetRelocationType(self) + + @CachedProperty def type_name(self): - pass + """The relocation type's name, as a str.""" + if self.expired: + raise Exception('Relocation instance has expired.') + + return lib.LLVMGetRelocationTypeName(self) - @property + @CachedProperty def value_string(self): - pass + if self.expired: + raise Exception('Relocation instance has expired.') + + return lib.LLVMGetRelocationValueString(self) + + def expire(self): + """Expire this instance, making future API accesses fail.""" + self.expired = True -SectionIteratorRef = c_void_p -SymbolIteratorRef = c_void_p -RelocationIteratorRef = c_void_p + def cache(self): + """Cache all cacheable properties on this instance.""" + getattr(self, 'address') + getattr(self, 'offset') + getattr(self, 'symbol') + getattr(self, 'type') + getattr(self, 'type_name') + getattr(self, 'value_string') def register_library(library): """Register function prototypes with LLVM library instance.""" # Object.h functions library.LLVMCreateObjectFile.argtypes = [MemoryBuffer] - library.LLVMCreateObjectFile.restype = LLVMObject + library.LLVMCreateObjectFile.restype = c_object_p library.LLVMDisposeObjectFile.argtypes = [ObjectFile] library.LLVMGetSections.argtypes = [ObjectFile] - library.LLVMGetSections.restype = SectionIteratorRef + library.LLVMGetSections.restype = c_object_p - library.LLVMDisposeSectionIterator.argtypes = [SectionIteratorRef] + library.LLVMDisposeSectionIterator.argtypes = [c_object_p] - library.LLVMIsSectionIteratorAtEnd.argtypes = [ObjectFile, - SectionIteratorRef] + library.LLVMIsSectionIteratorAtEnd.argtypes = [ObjectFile, c_object_p] library.LLVMIsSectionIteratorAtEnd.restype = bool - library.LLVMMoveToNextSection.argtypes = [SectionIteratorRef] + library.LLVMMoveToNextSection.argtypes = [c_object_p] - library.LLVMMoveToContainingSection.argtypes = [SectionIteratorRef, - SymbolIteratorRef] + library.LLVMMoveToContainingSection.argtypes = [c_object_p, c_object_p] library.LLVMGetSymbols.argtypes = [ObjectFile] - library.LLVMGetSymbols.restype = SymbolIteratorRef + library.LLVMGetSymbols.restype = c_object_p - library.LLVMDisposeSymbolIterator.argtypes = [SymbolIteratorRef] + library.LLVMDisposeSymbolIterator.argtypes = [c_object_p] - library.LLVMIsSymbolIteratorAtEnd.argtypes = [ObjectFile, - SymbolIteratorRef] + library.LLVMIsSymbolIteratorAtEnd.argtypes = [ObjectFile, c_object_p] library.LLVMIsSymbolIteratorAtEnd.restype = bool - library.LLVMMoveToNextSymbol.argtypes = [SymbolIteratorRef] + library.LLVMMoveToNextSymbol.argtypes = [c_object_p] - library.LLVMGetSectionName.argtypes = [SectionIteratorRef] + library.LLVMGetSectionName.argtypes = [c_object_p] library.LLVMGetSectionName.restype = c_char_p - library.LLVMGetSectionSize.argtypes = [SectionIteratorRef] + library.LLVMGetSectionSize.argtypes = [c_object_p] library.LLVMGetSectionSize.restype = c_uint64 - library.LLVMGetSectionContents.argtypes = [SectionIteratorRef] + library.LLVMGetSectionContents.argtypes = [c_object_p] library.LLVMGetSectionContents.restype = c_char_p - library.LLVMGetSectionAddress.argtypes = [SectionIteratorRef] + library.LLVMGetSectionAddress.argtypes = [c_object_p] library.LLVMGetSectionAddress.restype = c_uint64 - library.LLVMGetSectionContainsSymbol.argtypes = [SectionIteratorRef, - SymbolIteratorRef] + library.LLVMGetSectionContainsSymbol.argtypes = [c_object_p, c_object_p] library.LLVMGetSectionContainsSymbol.restype = bool - library.LLVMGetRelocations.argtypes = [SectionIteratorRef] - library.LLVMGetRelocations.restype = RelocationIteratorRef + library.LLVMGetRelocations.argtypes = [c_object_p] + library.LLVMGetRelocations.restype = c_object_p - library.LLVMDisposeRelocationIterator.argtypes = [RelocationIteratorRef] + library.LLVMDisposeRelocationIterator.argtypes = [c_object_p] - library.LLVMIsRelocationIteratorAtEnd.argtypes = [SectionIteratorRef, - RelocationIteratorRef] + library.LLVMIsRelocationIteratorAtEnd.argtypes = [c_object_p, c_object_p] library.LLVMIsRelocationIteratorAtEnd.restype = bool - library.LLVMMoveToNextRelocation.argtypes = [RelocationIteratorRef] + library.LLVMMoveToNextRelocation.argtypes = [c_object_p] - library.LLVMGetSymbolName.argtypes = [SymbolIteratorRef] + library.LLVMGetSymbolName.argtypes = [Symbol] library.LLVMGetSymbolName.restype = c_char_p - library.LLVMGetSymbolAddress.argtypes = [SymbolIteratorRef] + library.LLVMGetSymbolAddress.argtypes = [Symbol] library.LLVMGetSymbolAddress.restype = c_uint64 - library.LLVMGetSymbolFileOffset.argtypes = [SymbolIteratorRef] + library.LLVMGetSymbolFileOffset.argtypes = [Symbol] library.LLVMGetSymbolFileOffset.restype = c_uint64 - library.LLVMGetSymbolSize.argtypes = [SymbolIteratorRef] + library.LLVMGetSymbolSize.argtypes = [Symbol] library.LLVMGetSymbolSize.restype = c_uint64 - library.LLVMGetRelocationAddress.argtypes = [SymbolIteratorRef] + library.LLVMGetRelocationAddress.argtypes = [c_object_p] library.LLVMGetRelocationAddress.restype = c_uint64 - library.LLVMGetRelocationOffset.argtypes = [RelocationIteratorRef] + library.LLVMGetRelocationOffset.argtypes = [c_object_p] library.LLVMGetRelocationOffset.restype = c_uint64 - library.LLVMGetRelocationSymbol.argtypes = [RelocationIteratorRef] - library.LLVMGetRelocationSymbol.restype = SymbolIteratorRef + library.LLVMGetRelocationSymbol.argtypes = [c_object_p] + library.LLVMGetRelocationSymbol.restype = c_object_p - library.LLVMGetRelocationType.argtypes = [RelocationIteratorRef] + library.LLVMGetRelocationType.argtypes = [c_object_p] library.LLVMGetRelocationType.restype = c_uint64 - library.LLVMGetRelocationTypeName.argtypes = [RelocationIteratorRef] + library.LLVMGetRelocationTypeName.argtypes = [c_object_p] library.LLVMGetRelocationTypeName.restype = c_char_p - library.LLVMGetRelocationValueString.argtypes = [RelocationIteratorRef] + library.LLVMGetRelocationValueString.argtypes = [c_object_p] library.LLVMGetRelocationValueString.restype = c_char_p lib = get_library() diff --git a/bindings/python/llvm/tests/__init__.py b/bindings/python/llvm/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bindings/python/llvm/tests/base.py b/bindings/python/llvm/tests/base.py new file mode 100644 index 00000000000..420423cd180 --- /dev/null +++ b/bindings/python/llvm/tests/base.py @@ -0,0 +1,29 @@ +import os.path +import unittest + +POSSIBLE_TEST_BINARIES = [ + 'libreadline.so.5', + 'libreadline.so.6', +] + +POSSIBLE_TEST_BINARY_PATHS = [ + '/lib', + '/usr/lib', + '/usr/local/lib', +] + +class TestBase(unittest.TestCase): + def get_test_binary(self): + """Helper to obtain a test binary for object file testing. + + FIXME Support additional, highly-likely targets or create one + ourselves. + """ + for d in POSSIBLE_TEST_BINARY_PATHS: + for lib in POSSIBLE_TEST_BINARIES: + path = os.path.join(d, lib) + + if os.path.exists(path): + return path + + raise Exception('No suitable test binaries available!') diff --git a/bindings/python/llvm/tests/test_core.py b/bindings/python/llvm/tests/test_core.py new file mode 100644 index 00000000000..be3df66c264 --- /dev/null +++ b/bindings/python/llvm/tests/test_core.py @@ -0,0 +1,9 @@ +from llvm.core import MemoryBuffer + +from .base import TestBase + +class TestCore(TestBase): + def test_memory_buffer_create_from_file(self): + source = self.get_test_binary() + + MemoryBuffer(filename=source) diff --git a/bindings/python/llvm/tests/test_object.py b/bindings/python/llvm/tests/test_object.py new file mode 100644 index 00000000000..7ff981b6a2f --- /dev/null +++ b/bindings/python/llvm/tests/test_object.py @@ -0,0 +1,67 @@ +from .base import TestBase +from ..object import ObjectFile +from ..object import Relocation +from ..object import Section +from ..object import Symbol + +class TestObjectFile(TestBase): + def get_object_file(self): + source = self.get_test_binary() + return ObjectFile(filename=source) + + def test_create_from_file(self): + self.get_object_file() + + def test_get_sections(self): + o = self.get_object_file() + + count = 0 + for section in o.get_sections(): + count += 1 + assert isinstance(section, Section) + assert isinstance(section.name, str) + assert isinstance(section.size, long) + assert isinstance(section.contents, str) + assert isinstance(section.address, long) + + self.assertGreater(count, 0) + + for section in o.get_sections(): + section.cache() + + def test_get_symbols(self): + o = self.get_object_file() + + count = 0 + for symbol in o.get_symbols(): + count += 1 + assert isinstance(symbol, Symbol) + assert isinstance(symbol.name, str) + assert isinstance(symbol.address, long) + assert isinstance(symbol.size, long) + assert isinstance(symbol.file_offset, long) + + self.assertGreater(count, 0) + + for symbol in o.get_symbols(): + symbol.cache() + + def test_symbol_section_accessor(self): + o = self.get_object_file() + + for symbol in o.get_symbols(): + section = symbol.section + assert isinstance(section, Section) + + break + + def test_get_relocations(self): + o = self.get_object_file() + for section in o.get_sections(): + for relocation in section.get_relocations(): + assert isinstance(relocation, Relocation) + assert isinstance(relocation.address, long) + assert isinstance(relocation.offset, long) + assert isinstance(relocation.type_number, long) + assert isinstance(relocation.type_name, str) + assert isinstance(relocation.value_string, str) diff --git a/bindings/python/tests/test_core.py b/bindings/python/tests/test_core.py deleted file mode 100644 index 8c4e9337053..00000000000 --- a/bindings/python/tests/test_core.py +++ /dev/null @@ -1,11 +0,0 @@ -from llvm.common import find_library -from llvm.core import MemoryBuffer - -import unittest - -class TestCore(unittest.TestCase): - def test_memory_buffer_create_from_file(self): - source = find_library() - self.assertIsNotNone(source) - - mb = MemoryBuffer(filename=source) diff --git a/bindings/python/tests/test_object.py b/bindings/python/tests/test_object.py deleted file mode 100644 index afb02019672..00000000000 --- a/bindings/python/tests/test_object.py +++ /dev/null @@ -1,9 +0,0 @@ -from llvm.common import find_library -from llvm.object import ObjectFile - -import unittest - -class TestObjectFile(unittest.TestCase): - def test_create_from_file(self): - source = find_library() - of = ObjectFile(filename=source) -- 2.34.1