# Inherit this class when you want to allow Mozilla Socorro to link Breakpad's # stack trace information to the correct source code revision. # This class creates a new version of the symbol file (.sym) created by # Breakpad. The absolute file paths in the symbol file will be replaced by VCS, # branch, file and revision of the source file. That information facilitates the # lookup of a particular source code line in the stack trace. # # Use example: # # BREAKPAD_BIN = "YourBinary" # inherit socorro-syms # # We depend on Breakpad creating the original symbol file. inherit breakpad PACKAGE_PREPROCESS_FUNCS += "symbol_file_preprocess" PACKAGES =+ "${PN}-socorro-syms" FILES_${PN}-socorro-syms = "/usr/share/socorro-syms" python symbol_file_preprocess() { package_dir = d.getVar("PKGD") breakpad_bin = d.getVar("BREAKPAD_BIN") if not breakpad_bin: package_name = d.getVar("PN") bb.error("Package %s depends on Breakpad via socorro-syms. See " "breakpad.bbclass for instructions on setting up the Breakpad " "configuration." % package_name) raise ValueError("BREAKPAD_BIN not defined in %s." % package_name) sym_file_name = breakpad_bin + ".sym" breakpad_syms_dir = os.path.join( package_dir, "usr", "share", "breakpad-syms") socorro_syms_dir = os.path.join( package_dir, "usr", "share", "socorro-syms") if not os.path.exists(socorro_syms_dir): os.makedirs(socorro_syms_dir) breakpad_sym_file_path = os.path.join(breakpad_syms_dir, sym_file_name) socorro_sym_file_path = os.path.join(socorro_syms_dir, sym_file_name) create_socorro_sym_file(d, breakpad_sym_file_path, socorro_sym_file_path) arrange_socorro_sym_file(socorro_sym_file_path, socorro_syms_dir) return } def run_command(command, directory): (output, error) = bb.process.run(command, cwd=directory) if error: raise bb.process.ExecutionError(command, error) return output.rstrip() def create_socorro_sym_file(d, breakpad_sym_file_path, socorro_sym_file_path): # In the symbol file, all source files are referenced like the following. # FILE 123 /path/to/some/File.cpp # Go through all references and replace the file paths with repository # paths. with open(breakpad_sym_file_path, 'r') as breakpad_sym_file, \ open(socorro_sym_file_path, 'w') as socorro_sym_file: for line in breakpad_sym_file: if line.startswith("FILE "): socorro_sym_file.write(socorro_file_reference(d, line)) else: socorro_sym_file.write(line) return def socorro_file_reference(d, line): # The 3rd position is the file path. See example above. source_file_path = line.split()[2] source_file_repo_path = repository_path( d, os.path.normpath(source_file_path)) # If the file could be found in any repository then replace it with the # repository's path. if source_file_repo_path: return line.replace(source_file_path, source_file_repo_path) return line def repository_path(d, source_file_path): if not os.path.isfile(source_file_path): return None # Check which VCS is used and use that to extract repository information. (output, error) = bb.process.run("git status", cwd=os.path.dirname(source_file_path)) if not error: # Make sure the git repository we just found wasn't the yocto repository # itself, i.e. the root of the repository we're looking for must be a # child of the build directory TOPDIR. git_root_dir = run_command( "git rev-parse --show-toplevel", os.path.dirname(source_file_path)) if not git_root_dir.startswith(d.getVar("TOPDIR")): return None return git_repository_path(source_file_path) # Here we can add support for other VCSs like hg, svn, cvs, etc. # The source file isn't under any VCS so we leave it be. return None def is_local_url(url): return \ url.startswith("file:") or url.startswith("/") or url.startswith("./") def git_repository_path(source_file_path): import re # We need to extract the following. # (1): VCS URL, (2): branch, (3): repo root directory name, (4): repo file, # (5): revision. source_file_dir = os.path.dirname(source_file_path) # (1) Get the VCS URL and extract the server part, i.e. change the URL from # gitolite@git.someserver.com:SomeRepo.git to just git.someserver.com. source_long_url = run_command( "git config --get remote.origin.url", source_file_dir) # The URL could be a local download directory. If so, get the URL again # using the local directory's config file. if is_local_url(source_long_url): git_config_file = os.path.join(source_long_url, "config") source_long_url = run_command( "git config --file %s --get remote.origin.url" % git_config_file, source_file_dir) # If also the download directory redirects to a local git directory, # then we're probably using source code from a local debug branch which # won't be accessible by Socorro. if is_local_url(source_long_url): return None # The URL can have several formats. A full list can be found using # git help clone. Extract the server part with a regex. url_match = re.search(".*(://|@)([^:/]*).*", source_long_url) source_server = url_match.group(2) # (2) Get the branch for this file. source_branch_list = run_command("git show-branch --list", source_file_dir) source_branch_match = re.search(".*?\[(.*?)\].*", source_branch_list) source_branch = source_branch_match.group(1) # (3) Since the repo root directory name can be changed without affecting # git, we need to extract the name from something more reliable. # The git URL has a repo name that we could use. We just need to strip off # everything around it - from gitolite@git.someserver.com:SomeRepo.git/ to # SomeRepo. source_repo_dir = re.sub("/$", "", source_long_url) source_repo_dir = re.sub("\.git$", "", source_repo_dir) source_repo_dir = re.sub(".*[:/]", "", source_repo_dir) # (4) We know the file but want to remove all of the build system dependent # path up to and including the repository's root directory, e.g. remove # /home/someuser/dev/repo/projectx/ source_toplevel = run_command( "git rev-parse --show-toplevel", source_file_dir) source_toplevel = source_toplevel + os.path.sep source_file = source_file_path.replace(source_toplevel, "") # (5) Get the source revision this file is part of. source_revision = run_command("git rev-parse HEAD", source_file_dir) # Assemble the repository path according to the Socorro format. socorro_reference = "git:%s/%s:%s/%s:%s" % \ (source_server, source_branch, source_repo_dir, source_file, source_revision) return socorro_reference def arrange_socorro_sym_file(socorro_sym_file_path, socorro_syms_dir): import re # Breakpad's minidump_stackwalk needs a certain directory structure in order # to find correct symbols when extracting a stack trace out of a minidump. # The directory structure must look like the following. # YourBinary//YourBinary.sym # YourLibrary.so//YourLibrary.so.sym # To be able to create such structure we need to extract the hash value that # is found in each symbol file. The header of the symbol file looks # something like this: # MODULE Linux x86 A079E473106CE51C74C1C25AF536CCD30 YourBinary # See # http://code.google.com/p/google-breakpad/wiki/LinuxStarterGuide # Create the directory with the same name as the binary. binary_dir = re.sub("\.sym$", "", socorro_sym_file_path) if not os.path.exists(binary_dir): os.makedirs(binary_dir) # Get the hash from the header of the symbol file. with open(socorro_sym_file_path, 'r') as socorro_sym_file: # The hash is the 4th argument of the first line. sym_file_hash = socorro_sym_file.readline().split()[3] # Create the hash directory. hash_dir = os.path.join(binary_dir, sym_file_hash) if not os.path.exists(hash_dir): os.makedirs(hash_dir) # Move the symbol file to the hash directory. sym_file_name = os.path.basename(socorro_sym_file_path) os.rename(socorro_sym_file_path, os.path.join(hash_dir, sym_file_name)) return