def MaybeEvict(self, freeEntriesNeeded): maxCacheSize = self.sOptions["maxCacheEntries"] LogTrace("Cache occupancy before MaybeEvict: " + str(self.sCacheCount) + "/" + str(maxCacheSize)) if self.sCacheCount == 0 or \ self.sCacheCount + freeEntriesNeeded <= maxCacheSize: # No need to lock mutex here, this doesn't need to be 100% return # If adding the new entries would exceed the max cache size, # evict so that cache is at 70% capacity after new entries added numOldEntriesAfterEvict = max(0, (0.70 * maxCacheSize) - freeEntriesNeeded) numToEvict = self.sCacheCount - numOldEntriesAfterEvict # Evict symbols until evict quota is met, starting with least recently # used for (pdbName, pdbId) in reversed(self.sMruSymbols): if numToEvict <= 0: break evicteeCount = self.sCache[pdbName][pdbId].GetEntryCount() del self.sCache[pdbName][pdbId] self.sCacheCount -= evicteeCount self.sMruSymbols.pop() numToEvict -= evicteeCount LogTrace("Cache occupancy after MaybeEvict: " + str(self.sCacheCount) + "/" + str(maxCacheSize))
def getModuleV2(libName, pdbAge, pdbSig, pdbName): if isinstance(pdbSig, basestring): matches = gPdbSigRE.match(pdbSig) if matches: pdbSig = "".join(matches.groups()).upper() elif gPdbSigRE2.match(pdbSig): pdbSig = pdbSig.upper() else: LogTrace("Bad PDB signature: " + pdbSig) return None else: LogTrace("Bad PDB signature: " + str(pdbSig)) return None if isinstance(pdbAge, basestring): pdbAge = int(pdbAge) if not isinstance(pdbAge, (int, long)) or int(pdbAge) < 0: LogTrace("Bad PDB age: " + str(pdbAge)) return None pdbAge = (hex(pdbAge)[2:]).lower() if not isinstance(pdbName, basestring) or not gLibNameRE.match(pdbName): LogTrace("Bad PDB name: " + str(pdbName)) return None return ModuleV3(pdbName, pdbSig + pdbAge)
def GetLibSymbolMap(self, libName, breakpadId, symbolSources): # Empty lib name means client couldn't associate frame with any lib if libName == "": return None # Check cache first libSymbolMap = None self.sCacheLock.acquire() try: if libName in self.sCache and breakpadId in self.sCache[libName]: libSymbolMap = self.sCache[libName][breakpadId] self.UpdateMruList(libName, breakpadId) finally: self.sCacheLock.release() if libSymbolMap is None: LogTrace("Need to fetch PDB file for " + libName + " " + breakpadId) # Guess the name of the .sym or .nmsym file on disk if libName[-4:] == ".pdb": symFileNameWithoutExtension = re.sub(r"\.[^\.]+$", "", libName) else: symFileNameWithoutExtension = libName # Look in the symbol dirs for this .sym or .nmsym file for extension, source in itertools.product([".sym", ".nmsym"], symbolSources): symFileName = symFileNameWithoutExtension + extension pathSuffix = os.sep + libName + os.sep + \ breakpadId + os.sep + symFileName path = self.sOptions["symbolPaths"][source] + pathSuffix libSymbolMap = self.FetchSymbolsFromFile(path) if libSymbolMap: break if not libSymbolMap: LogTrace("No matching sym files, tried " + str(symbolSources)) return None LogTrace("Storing libSymbolMap under [" + libName + "][" + breakpadId + "]") self.sCacheLock.acquire() try: self.MaybeEvict(libSymbolMap.GetEntryCount()) if libName not in self.sCache: self.sCache[libName] = {} self.sCache[libName][breakpadId] = libSymbolMap self.sCacheCount += libSymbolMap.GetEntryCount() self.UpdateMruList(libName, breakpadId) LogTrace( str(self.sCacheCount) + " symbols in cache after fetching symbol file") finally: self.sCacheLock.release() return libSymbolMap
def getModuleV3(libName, breakpadId): if not isinstance(libName, basestring) or not gLibNameRE.match(libName): LogTrace("Bad library name: " + str(libName)) return None if not isinstance(breakpadId, basestring): LogTrace("Bad breakpad id: " + str(breakpadId)) return None return ModuleV3(libName, breakpadId)
def FetchSymbolsFromFile(self, path): try: with open(path, "r") as symFile: LogMessage("Parsing SYM file at " + path) return self.FetchSymbolsFromFileObj(symFile) except Exception as e: LogTrace("Error opening file " + path + ": " + str(e)) return None
def FetchSymbolsFromFile(self, path): try: symFile = open(path, "r") except Exception as e: LogTrace("Error opening file " + path + ": " + str(e)) return None LogMessage("Parsing SYM file at " + path) try: symbolMap = {} lineNum = 0 publicCount = 0 funcCount = 0 for line in symFile: lineNum += 1 if line[0:7] == "PUBLIC ": line = line.rstrip() fields = line.split(" ") if len(fields) < 4: LogTrace("Line " + str(lineNum) + " is messed") continue address = int(fields[1], 16) symbolMap[address] = " ".join(fields[3:]) publicCount += 1 elif line[0:5] == "FUNC ": line = line.rstrip() fields = line.split(" ") if len(fields) < 5: LogTrace("Line " + str(lineNum) + " is messed") continue address = int(fields[1], 16) symbolMap[address] = " ".join(fields[4:]) funcCount += 1 except Exception as e: LogError("Error parsing SYM file " + path) return None logString = "Found " + str(len( symbolMap.keys())) + " unique entries from " logString += str(publicCount) + " PUBLIC lines, " + str( funcCount) + " FUNC lines" LogTrace(logString) return SymbolInfo(symbolMap)
def FetchSymbolsFromURL(self, url): try: with contextlib.closing(urllib2.urlopen(url)) as request: if request.getcode() != 200: return None LogMessage("Parsing SYM file at " + url) return self.FetchSymbolsFromFileObj(request) except Exception as e: LogTrace("Error opening URL " + url + ": " + str(e)) return None
def do_POST(self): LogTrace("Received request: " + self.path + " on thread " + threading.currentThread().getName()) try: length = int(self.headers["Content-Length"]) # Read in the request body without blocking self.connection.settimeout(SOCKET_READ_TIMEOUT) requestBody = self.rfile.read(length) # Put the connection back into blocking mode so rfile/wfile can be used safely self.connection.settimeout(None) if len(requestBody) < length: # This could be a broken connection, writing an error message into it could be a bad idea # See http://bugs.python.org/issue14574 LogTrace("Read " + str(len(requestBody)) + " bytes but Content-Length is " + str(length)) return LogTrace("Request body: " + requestBody) rawRequest = json.loads(requestBody) request = SymbolicationRequest(gSymFileManager, rawRequest) if not request.isValidRequest: LogTrace("Unable to parse request") self.sendHeaders(400) return except Exception as e: LogTrace("Unable to parse request body: " + str(e)) # Ensure connection is back in blocking mode so rfile/wfile can be used safely self.connection.settimeout(None) self.sendHeaders(400) return try: self.sendHeaders(200) response = {'symbolicatedStacks': []} for stackIndex in range(len(request.stacks)): symbolicatedStack = request.Symbolicate(stackIndex) # Free up memory ASAP request.stacks[stackIndex] = [] response['symbolicatedStacks'].append(symbolicatedStack) response['knownModules'] = request.knownModules[:] if not request.includeKnownModulesInResponse: response = response['symbolicatedStacks'] request.Reset() LogTrace("Response: " + json.dumps(response)) self.wfile.write(json.dumps(response)) except Exception as e: LogTrace("Exception in do_POST: " + str(e))
def MaybeEvict(self, freeEntriesNeeded): maxCacheSize = self.sOptions["maxCacheEntries"] LogTrace("Cache occupancy before MaybeEvict: " + str(self.sCacheCount) + "/" + str(maxCacheSize)) #print "Current MRU: " + str(self.sMruSymbols) #print "Maybe evicting to make room for ", freeEntriesNeeded, " new entries" if self.sCacheCount == 0 or self.sCacheCount + freeEntriesNeeded <= maxCacheSize: # No need to lock mutex here, this doesn't need to be 100% #print "Sufficient room for new entries, no need to evict" return # If adding the new entries would exceed the max cache size, # evict so that cache is at 70% capacity after new entries added numOldEntriesAfterEvict = max(0, (0.70 * maxCacheSize) - freeEntriesNeeded) numToEvict = self.sCacheCount - numOldEntriesAfterEvict #print "Evicting: " + str(numToEvict) # Evict symbols until evict quota is met, starting with least recently used for (pdbName, pdbId) in reversed(self.sMruSymbols): if numToEvict <= 0: break evicteeCount = self.sCache[pdbName][pdbId].GetEntryCount() #print "Evicting symbols at " + pdbName + "/" + pdbId + ": " + str(evicteeCount) + " entries" del self.sCache[pdbName][pdbId] self.sCacheCount -= evicteeCount self.sMruSymbols.pop() numToEvict -= evicteeCount #print "MRU after: " + str(self.sMruSymbols) LogTrace("Cache occupancy after MaybeEvict: " + str(self.sCacheCount) + "/" + str(maxCacheSize))
def PrefetchRecentSymbolFiles(self): try: mruSymbols = [] with open(self.sOptions["mruSymbolStateFile"], "rb") as f: mruSymbols = json.load( f)["symbols"][:self.sOptions["maxMRUSymbolsPersist"]] LogMessage("Going to prefetch %d recent symbol files" % len(mruSymbols)) self.sUpdateMRUFile = False for libName, breakpadId in mruSymbols: sym = self.GetLibSymbolMap(libName, breakpadId) if sym is None: LogTrace("Failed to prefetch symbols for (%s,%s)" % (libName, breakpadId)) LogMessage("Finished prefetching recent symbol files") except IOError: LogError("Error reading MRU symbols state file") finally: self.sUpdateMRUFile = True
def GetLibSymbolMap(self, libName, breakpadId): # Empty lib name means client couldn't associate frame with any lib if libName == "": return None # Check cache first libSymbolMap = None self.sCacheLock.acquire() try: if libName in self.sCache and breakpadId in self.sCache[libName]: libSymbolMap = self.sCache[libName][breakpadId] self.UpdateMruList(libName, breakpadId) finally: self.sCacheLock.release() if libSymbolMap is None: LogTrace("Need to fetch PDB file for " + libName + " " + breakpadId) # Guess the name of the .sym file on disk if libName[-4:] == ".pdb": symFileName = re.sub(r"\.[^\.]+$", ".sym", libName) else: symFileName = libName + ".sym" pathSuffix = os.path.join(libName, breakpadId, symFileName) # Look in the symbol dirs for this .sym file for symbolPath in self.sOptions["symbolPaths"]: path = os.path.join(symbolPath, pathSuffix) libSymbolMap = self.FetchSymbolsFromFile(path) if libSymbolMap: break # If not in symbolPaths try URLs if not libSymbolMap: for symbolURL in self.sOptions["symbolURLs"]: url = urlparse.urljoin(symbolURL, pathSuffix) libSymbolMap = self.FetchSymbolsFromURL(url) if libSymbolMap: break if not libSymbolMap: LogTrace( "No matching sym files, tried paths: %s and URLs: %s" % (", ".join(self.sOptions["symbolPaths"]), ", ".join( self.sOptions["symbolURLs"]))) return None LogTrace("Storing libSymbolMap under [" + libName + "][" + breakpadId + "]") self.sCacheLock.acquire() try: self.MaybeEvict(libSymbolMap.GetEntryCount()) if libName not in self.sCache: self.sCache[libName] = {} self.sCache[libName][breakpadId] = libSymbolMap self.sCacheCount += libSymbolMap.GetEntryCount() self.UpdateMruList(libName, breakpadId) LogTrace( str(self.sCacheCount) + " symbols in cache after fetching symbol file") finally: self.sCacheLock.release() return libSymbolMap
def FetchSymbolsFromFile(self, path): try: symFile = open(path, "r") except Exception as e: LogTrace("Error opening file " + path + ": " + str(e)) return None LogMessage("Parsing SYM file at " + path) try: symbolMap = {} lineNum = 0 publicCount = 0 funcCount = 0 if path.endswith(".sym"): for line in symFile: lineNum += 1 if line[0:7] == "PUBLIC ": line = line.rstrip() fields = line.split(" ") if len(fields) < 4: LogTrace("Line " + str(lineNum) + " is messed") continue address = int(fields[1], 16) symbolMap[address] = " ".join(fields[3:]) publicCount += 1 elif line[0:5] == "FUNC ": line = line.rstrip() fields = line.split(" ") if len(fields) < 5: LogTrace("Line " + str(lineNum) + " is messed") continue address = int(fields[1], 16) symbolMap[address] = " ".join(fields[4:]) funcCount += 1 elif path.endswith(".nmsym"): addressLength = 0 for line in symFile: lineNum += 1 if line.startswith(" "): continue if addressLength == 0: addressLength = line.find(" ") address = int(line[0:addressLength], 16) # Some lines have the form # "address space letter space symbol", # some have the form "address space symbol". # The letter has a meaning, but we ignore it. if line[addressLength + 2] == " ": symbol = line[addressLength + 3:].rstrip() else: symbol = line[addressLength + 1:].rstrip() symbolMap[address] = symbol publicCount += 1 except Exception as e: LogError("Error parsing SYM file " + path) return None logString = "Found " + \ str(len(symbolMap.keys())) + " unique entries from " logString += str(publicCount) + " PUBLIC lines, " + \ str(funcCount) + " FUNC lines" LogTrace(logString) return SymbolInfo(symbolMap)
def ParseRequests(self, rawRequests): self.isValidRequest = False try: if not isinstance(rawRequests, dict): LogTrace("Request is not a dictionary") return if "version" not in rawRequests: LogTrace("Request is missing 'version' field") return version = rawRequests["version"] if version != 4: LogTrace("Invalid version: %s" % version) return if "forwarded" in rawRequests: if not isinstance(rawRequests["forwarded"], (int, long)): LogTrace("Invalid 'forwards' field: " + str(rawRequests["forwarded"])) return self.forwardCount = rawRequests["forwarded"] # Client specifies which sets of symbols should be used if "symbolSources" in rawRequests: try: sourceList = [ x.upper() for x in rawRequests["symbolSources"] ] for source in sourceList: if source in self.symFileManager.sOptions[ "symbolPaths"]: self.symbolSources.append(source) else: LogTrace("Unrecognized symbol source: " + source) continue except: self.symbolSources = [] pass if not self.symbolSources: self.symbolSources.append( self.symFileManager.sOptions["defaultApp"]) self.symbolSources.append( self.symFileManager.sOptions["defaultOs"]) if "memoryMap" not in rawRequests: LogTrace("Request is missing 'memoryMap' field") return memoryMap = rawRequests["memoryMap"] if not isinstance(memoryMap, list): LogTrace("'memoryMap' field in request is not a list") if "stacks" not in rawRequests: LogTrace("Request is missing 'stacks' field") return stacks = rawRequests["stacks"] if not isinstance(stacks, list): LogTrace("'stacks' field in request is not a list") return # Check memory map is well-formatted cleanMemoryMap = [] for module in memoryMap: if not isinstance(module, list): LogTrace("Entry in memory map is not a list: " + str(module)) return if len(module) != 2: LogTrace("Entry in memory map is not a 2 item list: " + str(module)) return module = getModuleV3(*module) if module is None: return cleanMemoryMap.append(module) self.combinedMemoryMap = cleanMemoryMap self.knownModules = [False] * len(self.combinedMemoryMap) if version < 4: self.includeKnownModulesInResponse = False # Check stack is well-formatted for stack in stacks: if not isinstance(stack, list): LogTrace("stack is not a list") return for entry in stack: if not isinstance(entry, list): LogTrace("stack entry is not a list") return if len(entry) != 2: LogTrace("stack entry doesn't have exactly 2 elements") return self.stacks.append(stack) except Exception as e: LogTrace("Exception while parsing request: " + str(e)) return self.isValidRequest = True
def ForwardRequest(self, indexes, stack, modules, symbolicatedStack): LogTrace("Forwarding " + str(len(stack)) + " PCs for symbolication") try: url = self.symFileManager.sOptions["remoteSymbolServer"] rawModules = [] moduleToIndex = {} newIndexToOldIndex = {} for moduleIndex, m in modules: l = [m.libName, m.breakpadId] newModuleIndex = len(rawModules) rawModules.append(l) moduleToIndex[m] = newModuleIndex newIndexToOldIndex[newModuleIndex] = moduleIndex rawStack = [] for entry in stack: moduleIndex = entry[0] offset = entry[1] module = self.combinedMemoryMap[moduleIndex] newIndex = moduleToIndex[module] rawStack.append([newIndex, offset]) requestVersion = 4 while True: requestObj = { "symbolSources": self.symbolSources, "stacks": [rawStack], "memoryMap": rawModules, "forwarded": self.forwardCount + 1, "version": requestVersion } requestJson = json.dumps(requestObj) headers = {"Content-Type": "application/json"} requestHandle = urllib2.Request(url, requestJson, headers) try: response = urllib2.urlopen(requestHandle) except Exception as e: if requestVersion == 4: # Try again with version 3 requestVersion = 3 continue raise e succeededVersion = requestVersion break except Exception as e: LogError("Exception while forwarding request: " + str(e)) return try: responseJson = json.loads(response.read()) except Exception as e: LogError( "Exception while reading server response to forwarded request: " + str(e)) return try: if succeededVersion == 4: responseKnownModules = responseJson['knownModules'] for newIndex, known in enumerate(responseKnownModules): if known and newIndex in newIndexToOldIndex: self.knownModules[newIndexToOldIndex[newIndex]] = True responseSymbols = responseJson['symbolicatedStacks'][0] else: responseSymbols = responseJson[0] if len(responseSymbols) != len(stack): LogError( str(len(responseSymbols)) + " symbols in response, " + str(len(stack)) + " PCs in request!") return for index in range(0, len(stack)): symbol = responseSymbols[index] originalIndex = indexes[index] symbolicatedStack[originalIndex] = symbol except Exception as e: LogError( "Exception while parsing server response to forwarded request: " + str(e)) return
def ParseRequests(self, rawRequests): self.isValidRequest = False try: if not isinstance(rawRequests, dict): LogTrace("Request is not a dictionary") return if "version" not in rawRequests: LogTrace("Request is missing 'version' field") return version = rawRequests["version"] if version != 2 and version != 3 and version != 4: LogTrace("Invalid version: %s" % version) return if "forwarded" in rawRequests: if not isinstance(rawRequests["forwarded"], (int, long)): LogTrace("Invalid 'forwards' field: " + str(rawRequests["forwarded"])) return self.forwardCount = rawRequests["forwarded"] if "memoryMap" not in rawRequests: LogTrace("Request is missing 'memoryMap' field") return memoryMap = rawRequests["memoryMap"] if not isinstance(memoryMap, list): LogTrace("'memoryMap' field in request is not a list") if "stacks" not in rawRequests: LogTrace("Request is missing 'stacks' field") return stacks = rawRequests["stacks"] if not isinstance(stacks, list): LogTrace("'stacks' field in request is not a list") return # Check memory map is well-formatted cleanMemoryMap = [] for module in memoryMap: if not isinstance(module, list): LogTrace("Entry in memory map is not a list: " + str(module)) return if version == 2: if len(module) != 4: LogTrace("Entry in memory map is not a 4 item list: " + str(module)) return module = getModuleV2(*module) else: assert version == 3 or version == 4 if len(module) != 2: LogTrace("Entry in memory map is not a 2 item list: " + str(module)) return module = getModuleV3(*module) if module is None: return cleanMemoryMap.append(module) self.combinedMemoryMap = cleanMemoryMap self.knownModules = [False] * len(self.combinedMemoryMap) if version < 4: self.includeKnownModulesInResponse = False # Check stack is well-formatted for stack in stacks: if not isinstance(stack, list): LogTrace("stack is not a list") return for entry in stack: if not isinstance(entry, list): LogTrace("stack entry is not a list") return if len(entry) != 2: LogTrace("stack entry doesn't have exactly 2 elements") return self.stacks.append(stack) except Exception as e: LogTrace("Exception while parsing request: " + str(e)) return self.isValidRequest = True