Example #1
0
    def __init__(self, mappings):
        self.mappings = mappings
        self.mapFile = None
        self.resultCache = {}

        self.a2lProcs = {
        }  # holds a list of running addr2line processes (indexed by (binPath,section))

        # don't create core files if a subprocess crashes
        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

        self.libFinder = LibFinder()
        self.disassembler = Disassembler()
        self.nmResolver = NmResolver(self.libFinder)
Example #2
0
    def __init__ (self, mappings):
        self.mappings = mappings
        self.mapFile = None
        self.resultCache = {}

        self.a2lProcs = {} # holds a list of running addr2line processes (indexed by (binPath,section))

        # don't create core files if a subprocess crashes
        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

        self.libFinder = LibFinder()
        self.disassembler = Disassembler()
        self.nmResolver = NmResolver(self.libFinder)
Example #3
0
class SymbolResolver:
    # this is just a pile of hacks, based on looking at output from readelf and addr2line...

    def __init__(self, mappings):
        self.mappings = mappings
        self.mapFile = None
        self.resultCache = {}

        self.a2lProcs = {
        }  # holds a list of running addr2line processes (indexed by (binPath,section))

        # don't create core files if a subprocess crashes
        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

        self.libFinder = LibFinder()
        self.disassembler = Disassembler()
        self.nmResolver = NmResolver(self.libFinder)

    def __del__(self):
        # make sure we don't leave any addr2line processes running:
        for proc in self.a2lProcs.values():
            proc.poll()
            if proc.returncode is None:
                # process is still running
                os.kill(proc.pid, 9)

    def _getSections(self, binPath):
        sections = cache.get('sections', binPath, useDisk=False)
        if sections is not None:
            return sections

        sections = []
        #print "reading section list from %s ..." % binPath
        readelfProc = subprocess.Popen(["readelf", "-S", binPath],
                                       stdout=subprocess.PIPE)
        for line in readelfProc.stdout:
            if not (line.startswith('  [')):
                continue
            line = line.lstrip(' [')

            parts = line.split(None, 7)
            (index, name, typ, address, offset, size, es, remainder) = parts
            flags = remainder[:3]
            if not ('A' in flags):
                # section has no memory allocated in process image
                continue

            address = int(address, 16)
            offset = int(offset, 16)
            sections.append((name, address, offset))
        cache.store('sections', binPath, sections, useDisk=False)
        #print "... done (%d sections)" % len(sections)
        return sections

    def _findSection(self, binPath, addr):
        """
        Determine section in which the address is located.
        Returns (sectionName, sectionAddress, sectionOffset) tuple.
        """

        sections = self._getSections(binPath)
        lastSection = (None, None, None)
        for s in sections:
            (name, address, offset) = s
            if offset > addr:  # this assumes that readelf prints section list sorted by offset
                break
            lastSection = (name, address, offset)
        return lastSection

    def addr2line(self, binPath, section, addr):
        processKey = (binPath, section)
        if not (self.a2lProcs.has_key(processKey)
                ) or self.a2lProcs[processKey] is None:
            # start a2l process:
            cmd = ["addr2line", "-e", binPath, "-f", "-C", "-i", "-j", section]
            proc = subprocess.Popen(cmd,
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE)
            self.a2lProcs[processKey] = proc

        proc = self.a2lProcs[processKey]

        #if not(self.a2lProcs.has_key(binPath)):
        proc.poll()
        if proc.returncode is not None:
            # error starting a2l
            raise Exception("a2l isn't running")
            return (None, None, None)

        proc.stdin.write("0x%x\n" % addr)

        lines = []
        while True:
            line = proc.stdout.readline()
            if line == '':
                os.kill(proc.pid, 15)
                #proc.send_signal(15)
                self.a2lProcs[processKey] = None
                break
            line = line.rstrip('\n\r')
            lines.append(line)

            (rlist, wlist, xlist) = select.select([proc.stdout], [], [], 0)
            if not (proc.stdout in rlist):
                break

        frames = []
        for linePair in zip(lines[::2], lines[1::2]):
            funcName = linePair[0].strip()
            if funcName == '??':
                funcName = None

            (sourceFile, lineNo) = linePair[1].strip().rsplit(':', 1)
            lineNo = int(lineNo)
            if sourceFile == '??':
                sourceFile = None
                lineNo = None
            frames.append((funcName, sourceFile, lineNo))

        return frames

    def resolve(self, addr, fixAddress=False):
        """
        Returns dict with the following values:
        - binPath: path to binary which contains the address
        - offsetInBin: offset of address in binPath
        - frames: list of call frames for specified address; for each frame,
          contains a tuple of (function name, source file path, line number)

        The "frames" list usually contains only one entry; but if the address is
        inside an inlined function, multiple frames might be returned.

        All of the dict entries are optional and will be missing if the value
        could not be determined.
        In the frames list, a frame tuple will contain None for any value
        which could not be determined.

        If fixAddress is True, addr is treated as return address, and the matching
        call instruction is searched and resolved instead.
        """

        cacheKey = (addr, fixAddress)
        if self.resultCache.has_key(cacheKey):
            return self.resultCache[cacheKey]
        else:
            res = self._resolveUncached(addr, fixAddress)
            self.resultCache[cacheKey] = res
            return res

    def _resolveUncached(self, addr, fixAddress):
        assert (addr >= 0)

        m = self.mappings.find(addr)
        if m is None or m[5] is None:
            # addr is not in any mapped range
            return {}

        actualBin = m[5]
        if not (os.path.exists(actualBin)):
            return {'binPath': actualBin}

        # offset into the memory mapped for this binary
        offsetInBinMemory = addr - m[0][0]
        assert (offsetInBinMemory >= 0)

        # these values are the same for actual bin and for separate debug bin:
        (sectionName, sectionAddr,
         sectionOffset) = self._findSection(actualBin, offsetInBinMemory)

        if sectionOffset is None:
            # can happen eg. if function is in /dev/zero...
            return {'binPath': actualBin}
        assert (sectionOffset >= 0)
        offsetInSection = offsetInBinMemory - sectionOffset
        assert (offsetInSection >= 0)

        # this is the offset of the return address into the actual binary
        # note: this offset is not necessarily correct for external debug bins
        offsetInActualBin = sectionAddr + offsetInSection
        assert (offsetInActualBin >= 0)

        if fixAddress:
            newOffsetInActualBin = self.disassembler.findCallAddress(
                offsetInActualBin, actualBin)
            offsetDelta = offsetInActualBin - newOffsetInActualBin
            offsetInSection -= offsetDelta
            offsetInActualBin = newOffsetInActualBin

        # find separate debug bin (if available)
        targetBin = actualBin
        debugBin = self.libFinder.findDebugBin(actualBin)
        if debugBin is not None:
            targetBin = debugBin

        resultFrames = []
        for frame in self.addr2line(targetBin, sectionName, offsetInSection):
            (funcName, sourceFile, lineNo) = frame
            if funcName is None:
                # try fall back to "nm" on actual binary if addr2line can't resolve the function name
                (funcName, dummy,
                 dummy) = self.nmResolver.resolve(actualBin, offsetInActualBin)
            if funcName is None and debugBin is not None:
                # try fall back to "nm" on debug binary (TODO: find and use offsetInDebugBin)
                (funcName, dummy,
                 dummy) = self.nmResolver.resolve(debugBin, offsetInActualBin)
            resultFrames.append((funcName, sourceFile, lineNo))

        return {
            'binPath': actualBin,
            'offsetInBin': offsetInActualBin,
            'frames': resultFrames
        }
Example #4
0
class SymbolResolver:
    # this is just a pile of hacks, based on looking at output from readelf and addr2line...

    def __init__ (self, mappings):
        self.mappings = mappings
        self.mapFile = None
        self.resultCache = {}

        self.a2lProcs = {} # holds a list of running addr2line processes (indexed by (binPath,section))

        # don't create core files if a subprocess crashes
        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

        self.libFinder = LibFinder()
        self.disassembler = Disassembler()
        self.nmResolver = NmResolver(self.libFinder)

    def __del__ (self):
        # make sure we don't leave any addr2line processes running:
        for proc in self.a2lProcs.values():
            proc.poll()
            if proc.returncode is None:
                # process is still running
                os.kill(proc.pid, 9)

    def _getSections (self, binPath):
        sections = cache.get('sections', binPath, useDisk=False)
        if sections is not None:
            return sections

        sections = []
        #print "reading section list from %s ..." % binPath
        readelfProc = subprocess.Popen(["readelf", "-S", binPath], stdout=subprocess.PIPE)
        for line in readelfProc.stdout:
            if not(line.startswith('  [')):
                continue
            line = line.lstrip(' [')

            parts = line.split(None, 7)
            (index, name, typ, address, offset, size, es, remainder) = parts
            flags = remainder[:3]
            if not('A' in flags):
                # section has no memory allocated in process image
                continue

            address = int(address, 16)
            offset = int(offset, 16)
            sections.append( (name, address, offset) )
        cache.store('sections', binPath, sections, useDisk=False)
        #print "... done (%d sections)" % len(sections)
        return sections

    def _findSection (self, binPath, addr):
        """
        Determine section in which the address is located.
        Returns (sectionName, sectionAddress, sectionOffset) tuple.
        """

        sections = self._getSections(binPath)
        lastSection = (None, None, None)
        for s in sections:
            (name, address, offset) = s
            if offset > addr: # this assumes that readelf prints section list sorted by offset
                break
            lastSection = (name, address, offset)
        return lastSection

    def addr2line (self, binPath, section, addr):
        processKey = (binPath, section)
        if not(self.a2lProcs.has_key(processKey)) or self.a2lProcs[processKey] is None:
            # start a2l process:
            cmd = ["addr2line", "-e", binPath, "-f", "-C", "-i", "-j", section]
            proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
            self.a2lProcs[processKey] = proc

        proc = self.a2lProcs[processKey]

        #if not(self.a2lProcs.has_key(binPath)):
        proc.poll()
        if proc.returncode is not None:
            # error starting a2l
            raise Exception("a2l isn't running")
            return (None, None, None)

        proc.stdin.write("0x%x\n" % addr)

        lines = []
        while True:
            line = proc.stdout.readline()
            if line == '':
                os.kill(proc.pid, 15)
                #proc.send_signal(15)
                self.a2lProcs[processKey] = None
                break
            line = line.rstrip('\n\r')
            lines.append(line)

            (rlist, wlist, xlist) = select.select([proc.stdout], [], [], 0)
            if not(proc.stdout in rlist):
                break

        frames = []
        for linePair in zip( lines[::2], lines[1::2] ):
            funcName = linePair[0].strip()
            if funcName == '??':
                funcName = None

            (sourceFile, lineNo) = linePair[1].strip().rsplit(':', 1)
            lineNo = int(lineNo)
            if sourceFile == '??':
                sourceFile = None
                lineNo = None
            frames.append( (funcName, sourceFile, lineNo) )

        return frames

    def resolve (self, addr, fixAddress=False):
        """
        Returns dict with the following values:
        - binPath: path to binary which contains the address
        - offsetInBin: offset of address in binPath
        - frames: list of call frames for specified address; for each frame,
          contains a tuple of (function name, source file path, line number)

        The "frames" list usually contains only one entry; but if the address is
        inside an inlined function, multiple frames might be returned.

        All of the dict entries are optional and will be missing if the value
        could not be determined.
        In the frames list, a frame tuple will contain None for any value
        which could not be determined.

        If fixAddress is True, addr is treated as return address, and the matching
        call instruction is searched and resolved instead.
        """

        cacheKey = (addr, fixAddress)
        if self.resultCache.has_key(cacheKey):
            return self.resultCache[cacheKey]
        else:
            res = self._resolveUncached(addr, fixAddress)
            self.resultCache[cacheKey] = res
            return res

    def _resolveUncached (self, addr, fixAddress):
        assert(addr >= 0)

        m = self.mappings.find(addr)
        if m is None or m[5] is None:
            # addr is not in any mapped range
            return {}

        actualBin = m[5]
        if not(os.path.exists(actualBin)):
            return {'binPath': actualBin}

        # offset into the memory mapped for this binary
        offsetInBinMemory = addr - m[0][0]
        assert(offsetInBinMemory >= 0)

        # these values are the same for actual bin and for separate debug bin:
        (sectionName, sectionAddr, sectionOffset) = self._findSection(actualBin, offsetInBinMemory)

        if sectionOffset is None:
            # can happen eg. if function is in /dev/zero...
            return {'binPath': actualBin}
        assert(sectionOffset >= 0)
        offsetInSection = offsetInBinMemory - sectionOffset
        assert(offsetInSection >= 0)

        # this is the offset of the return address into the actual binary
        # note: this offset is not necessarily correct for external debug bins
        offsetInActualBin = sectionAddr + offsetInSection
        assert(offsetInActualBin >= 0)

        if fixAddress:
            newOffsetInActualBin = self.disassembler.findCallAddress(offsetInActualBin, actualBin)
            offsetDelta = offsetInActualBin - newOffsetInActualBin
            offsetInSection -= offsetDelta
            offsetInActualBin = newOffsetInActualBin

        # find separate debug bin (if available)
        targetBin = actualBin
        debugBin = self.libFinder.findDebugBin(actualBin)
        if debugBin is not None:
            targetBin = debugBin

        resultFrames = []
        for frame in self.addr2line(targetBin, sectionName, offsetInSection):
            (funcName, sourceFile, lineNo) = frame
            if funcName is None:
                # try fall back to "nm" on actual binary if addr2line can't resolve the function name
                (funcName, dummy, dummy) = self.nmResolver.resolve(actualBin, offsetInActualBin)
            if funcName is None and debugBin is not None:
                # try fall back to "nm" on debug binary (TODO: find and use offsetInDebugBin)
                (funcName, dummy, dummy) = self.nmResolver.resolve(debugBin, offsetInActualBin)
            resultFrames.append( (funcName, sourceFile, lineNo) )

        return {'binPath': actualBin, 'offsetInBin': offsetInActualBin, 'frames': resultFrames}