# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import json import re import six from mozlog import get_proxy_logger LOG = get_proxy_logger("profiler") # Precompiled regex for validating lib names # Empty lib name means client couldn't associate frame with any lib gLibNameRE = re.compile("[0-9a-zA-Z_+\-\.]*$") # Maximum number of times a request can be forwarded to a different server # for symbolication. Also prevents loops. MAX_FORWARDED_REQUESTS = 3 if six.PY2: # Import for Python 2 from urllib2 import Request, urlopen else: # Import for Python 3 from urllib.request import Request, urlopen # Symbolication is broken when using type 'str' in python 2.7, so we use 'basestring'. # But for python 3.0 compatibility, 'basestring' isn't defined, but the 'str' type works. # So we force 'basestring' to 'str'. basestring = str class ModuleV3: def __init__(self, libName, breakpadId): self.libName = libName self.breakpadId = breakpadId def getModuleV3(libName, breakpadId): if not isinstance(libName, basestring) or not gLibNameRE.match(libName): LOG.debug("Bad library name: " + str(libName)) return None if not isinstance(breakpadId, basestring): LOG.debug("Bad breakpad id: " + str(breakpadId)) return None return ModuleV3(libName, breakpadId) class SymbolicationRequest: def __init__(self, symFileManager, rawRequests): self.Reset() self.symFileManager = symFileManager self.stacks = [] self.combinedMemoryMap = [] self.knownModules = [] self.symbolSources = [] self.ParseRequests(rawRequests) def Reset(self): self.symFileManager = None self.isValidRequest = False self.combinedMemoryMap = [] self.knownModules = [] self.stacks = [] self.forwardCount = 0 def ParseRequests(self, rawRequests): self.isValidRequest = False try: if not isinstance(rawRequests, dict): LOG.debug("Request is not a dictionary") return if "version" not in rawRequests: LOG.debug("Request is missing 'version' field") return version = rawRequests["version"] if version != 4: LOG.debug("Invalid version: %s" % version) return if "forwarded" in rawRequests: if not isinstance(rawRequests["forwarded"], (int, int)): LOG.debug("Invalid 'forwards' field: %s" % 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: LOG.debug("Unrecognized symbol source: " + source) continue except Exception: 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: LOG.debug("Request is missing 'memoryMap' field") return memoryMap = rawRequests["memoryMap"] if not isinstance(memoryMap, list): LOG.debug("'memoryMap' field in request is not a list") if "stacks" not in rawRequests: LOG.debug("Request is missing 'stacks' field") return stacks = rawRequests["stacks"] if not isinstance(stacks, list): LOG.debug("'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): LOG.debug("Entry in memory map is not a list: " + str(module)) return if len(module) != 2: LOG.debug( "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) # Check stack is well-formatted for stack in stacks: if not isinstance(stack, list): LOG.debug("stack is not a list") return for entry in stack: if not isinstance(entry, list): LOG.debug("stack entry is not a list") return if len(entry) != 2: LOG.debug("stack entry doesn't have exactly 2 elements") return self.stacks.append(stack) except Exception as e: LOG.debug("Exception while parsing request: " + str(e)) return self.isValidRequest = True def ForwardRequest(self, indexes, stack, modules, symbolicatedStack): LOG.debug("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).encode() headers = {"Content-Type": "application/json"} requestHandle = Request(url, requestJson, headers) try: response = 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: LOG.error("Exception while forwarding request: " + str(e)) return try: responseJson = json.loads(response.read()) except Exception as e: LOG.error( "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): LOG.error( 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: LOG.error( "Exception while parsing server response to forwarded" " request: " + str(e) ) return def Symbolicate(self, stackNum): # Check if we should forward requests when required sym files don't # exist shouldForwardRequests = False if ( self.symFileManager.sOptions["remoteSymbolServer"] and self.forwardCount < MAX_FORWARDED_REQUESTS ): shouldForwardRequests = True # Symbolicate each PC pcIndex = -1 symbolicatedStack = [] missingSymFiles = [] unresolvedIndexes = [] unresolvedStack = [] unresolvedModules = [] stack = self.stacks[stackNum] for moduleIndex, module in enumerate(self.combinedMemoryMap): if not self.symFileManager.GetLibSymbolMap( module.libName, module.breakpadId, self.symbolSources ): missingSymFiles.append((module.libName, module.breakpadId)) if shouldForwardRequests: unresolvedModules.append((moduleIndex, module)) else: self.knownModules[moduleIndex] = True for entry in stack: pcIndex += 1 moduleIndex = entry[0] offset = entry[1] if moduleIndex == -1: symbolicatedStack.append(hex(offset)) continue module = self.combinedMemoryMap[moduleIndex] if (module.libName, module.breakpadId) in missingSymFiles: if shouldForwardRequests: unresolvedIndexes.append(pcIndex) unresolvedStack.append(entry) symbolicatedStack.append(hex(offset) + " (in " + module.libName + ")") continue functionName = None libSymbolMap = self.symFileManager.GetLibSymbolMap( module.libName, module.breakpadId, self.symbolSources ) functionName = libSymbolMap.Lookup(offset) if functionName is None: functionName = hex(offset) symbolicatedStack.append(functionName + " (in " + module.libName + ")") # Ask another server for help symbolicating unresolved addresses if len(unresolvedStack) > 0 or len(unresolvedModules) > 0: self.ForwardRequest( unresolvedIndexes, unresolvedStack, unresolvedModules, symbolicatedStack ) return symbolicatedStack