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 __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)
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 }
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}