1eb8dc403SDave Cobbley# Inherit this class when you want to allow Mozilla Socorro to link Breakpad's 2eb8dc403SDave Cobbley# stack trace information to the correct source code revision. 3eb8dc403SDave Cobbley# This class creates a new version of the symbol file (.sym) created by 4eb8dc403SDave Cobbley# Breakpad. The absolute file paths in the symbol file will be replaced by VCS, 5eb8dc403SDave Cobbley# branch, file and revision of the source file. That information facilitates the 6eb8dc403SDave Cobbley# lookup of a particular source code line in the stack trace. 7eb8dc403SDave Cobbley# 8eb8dc403SDave Cobbley# Use example: 9eb8dc403SDave Cobbley# 10eb8dc403SDave Cobbley# BREAKPAD_BIN = "YourBinary" 11eb8dc403SDave Cobbley# inherit socorro-syms 12eb8dc403SDave Cobbley# 13eb8dc403SDave Cobbley 14eb8dc403SDave Cobbley# We depend on Breakpad creating the original symbol file. 15eb8dc403SDave Cobbleyinherit breakpad 16eb8dc403SDave Cobbley 17eb8dc403SDave CobbleyPACKAGE_PREPROCESS_FUNCS += "symbol_file_preprocess" 18eb8dc403SDave CobbleyPACKAGES =+ "${PN}-socorro-syms" 19*213cb269SPatrick WilliamsFILES:${PN}-socorro-syms = "/usr/share/socorro-syms" 20eb8dc403SDave Cobbley 21eb8dc403SDave Cobbley 22eb8dc403SDave Cobbleypython symbol_file_preprocess() { 23eb8dc403SDave Cobbley 24eb8dc403SDave Cobbley package_dir = d.getVar("PKGD") 25eb8dc403SDave Cobbley breakpad_bin = d.getVar("BREAKPAD_BIN") 26eb8dc403SDave Cobbley if not breakpad_bin: 27eb8dc403SDave Cobbley package_name = d.getVar("PN") 28eb8dc403SDave Cobbley bb.error("Package %s depends on Breakpad via socorro-syms. See " 29eb8dc403SDave Cobbley "breakpad.bbclass for instructions on setting up the Breakpad " 30eb8dc403SDave Cobbley "configuration." % package_name) 31eb8dc403SDave Cobbley raise ValueError("BREAKPAD_BIN not defined in %s." % package_name) 32eb8dc403SDave Cobbley 33eb8dc403SDave Cobbley sym_file_name = breakpad_bin + ".sym" 34eb8dc403SDave Cobbley 35eb8dc403SDave Cobbley breakpad_syms_dir = os.path.join( 36eb8dc403SDave Cobbley package_dir, "usr", "share", "breakpad-syms") 37eb8dc403SDave Cobbley socorro_syms_dir = os.path.join( 38eb8dc403SDave Cobbley package_dir, "usr", "share", "socorro-syms") 39eb8dc403SDave Cobbley if not os.path.exists(socorro_syms_dir): 40eb8dc403SDave Cobbley os.makedirs(socorro_syms_dir) 41eb8dc403SDave Cobbley 42eb8dc403SDave Cobbley breakpad_sym_file_path = os.path.join(breakpad_syms_dir, sym_file_name) 43eb8dc403SDave Cobbley socorro_sym_file_path = os.path.join(socorro_syms_dir, sym_file_name) 44eb8dc403SDave Cobbley 45eb8dc403SDave Cobbley create_socorro_sym_file(d, breakpad_sym_file_path, socorro_sym_file_path) 46eb8dc403SDave Cobbley 47eb8dc403SDave Cobbley arrange_socorro_sym_file(socorro_sym_file_path, socorro_syms_dir) 48eb8dc403SDave Cobbley 49eb8dc403SDave Cobbley return 50eb8dc403SDave Cobbley} 51eb8dc403SDave Cobbley 52eb8dc403SDave Cobbley 53eb8dc403SDave Cobbleydef run_command(command, directory): 54eb8dc403SDave Cobbley 55eb8dc403SDave Cobbley (output, error) = bb.process.run(command, cwd=directory) 56eb8dc403SDave Cobbley if error: 57eb8dc403SDave Cobbley raise bb.process.ExecutionError(command, error) 58eb8dc403SDave Cobbley 59eb8dc403SDave Cobbley return output.rstrip() 60eb8dc403SDave Cobbley 61eb8dc403SDave Cobbley 62eb8dc403SDave Cobbleydef create_socorro_sym_file(d, breakpad_sym_file_path, socorro_sym_file_path): 63eb8dc403SDave Cobbley 64eb8dc403SDave Cobbley # In the symbol file, all source files are referenced like the following. 65eb8dc403SDave Cobbley # FILE 123 /path/to/some/File.cpp 66eb8dc403SDave Cobbley # Go through all references and replace the file paths with repository 67eb8dc403SDave Cobbley # paths. 68eb8dc403SDave Cobbley with open(breakpad_sym_file_path, 'r') as breakpad_sym_file, \ 69eb8dc403SDave Cobbley open(socorro_sym_file_path, 'w') as socorro_sym_file: 70eb8dc403SDave Cobbley 71eb8dc403SDave Cobbley for line in breakpad_sym_file: 72eb8dc403SDave Cobbley if line.startswith("FILE "): 73eb8dc403SDave Cobbley socorro_sym_file.write(socorro_file_reference(d, line)) 74eb8dc403SDave Cobbley else: 75eb8dc403SDave Cobbley socorro_sym_file.write(line) 76eb8dc403SDave Cobbley 77eb8dc403SDave Cobbley return 78eb8dc403SDave Cobbley 79eb8dc403SDave Cobbley 80eb8dc403SDave Cobbleydef socorro_file_reference(d, line): 81eb8dc403SDave Cobbley 82eb8dc403SDave Cobbley # The 3rd position is the file path. See example above. 83eb8dc403SDave Cobbley source_file_path = line.split()[2] 84eb8dc403SDave Cobbley source_file_repo_path = repository_path( 85eb8dc403SDave Cobbley d, os.path.normpath(source_file_path)) 86eb8dc403SDave Cobbley 87eb8dc403SDave Cobbley # If the file could be found in any repository then replace it with the 88eb8dc403SDave Cobbley # repository's path. 89eb8dc403SDave Cobbley if source_file_repo_path: 90eb8dc403SDave Cobbley return line.replace(source_file_path, source_file_repo_path) 91eb8dc403SDave Cobbley 92eb8dc403SDave Cobbley return line 93eb8dc403SDave Cobbley 94eb8dc403SDave Cobbley 95eb8dc403SDave Cobbleydef repository_path(d, source_file_path): 96eb8dc403SDave Cobbley 97eb8dc403SDave Cobbley if not os.path.isfile(source_file_path): 98eb8dc403SDave Cobbley return None 99eb8dc403SDave Cobbley 100eb8dc403SDave Cobbley # Check which VCS is used and use that to extract repository information. 101eb8dc403SDave Cobbley (output, error) = bb.process.run("git status", 102eb8dc403SDave Cobbley cwd=os.path.dirname(source_file_path)) 103eb8dc403SDave Cobbley if not error: 104eb8dc403SDave Cobbley # Make sure the git repository we just found wasn't the yocto repository 105eb8dc403SDave Cobbley # itself, i.e. the root of the repository we're looking for must be a 106eb8dc403SDave Cobbley # child of the build directory TOPDIR. 107eb8dc403SDave Cobbley git_root_dir = run_command( 108eb8dc403SDave Cobbley "git rev-parse --show-toplevel", os.path.dirname(source_file_path)) 109eb8dc403SDave Cobbley if not git_root_dir.startswith(d.getVar("TOPDIR")): 110eb8dc403SDave Cobbley return None 111eb8dc403SDave Cobbley 112eb8dc403SDave Cobbley return git_repository_path(source_file_path) 113eb8dc403SDave Cobbley 114eb8dc403SDave Cobbley # Here we can add support for other VCSs like hg, svn, cvs, etc. 115eb8dc403SDave Cobbley 116eb8dc403SDave Cobbley # The source file isn't under any VCS so we leave it be. 117eb8dc403SDave Cobbley return None 118eb8dc403SDave Cobbley 119eb8dc403SDave Cobbley 120eb8dc403SDave Cobbleydef is_local_url(url): 121eb8dc403SDave Cobbley 122eb8dc403SDave Cobbley return \ 123eb8dc403SDave Cobbley url.startswith("file:") or url.startswith("/") or url.startswith("./") 124eb8dc403SDave Cobbley 125eb8dc403SDave Cobbley 126eb8dc403SDave Cobbleydef git_repository_path(source_file_path): 127eb8dc403SDave Cobbley 128eb8dc403SDave Cobbley import re 129eb8dc403SDave Cobbley 130eb8dc403SDave Cobbley # We need to extract the following. 131eb8dc403SDave Cobbley # (1): VCS URL, (2): branch, (3): repo root directory name, (4): repo file, 132eb8dc403SDave Cobbley # (5): revision. 133eb8dc403SDave Cobbley 134eb8dc403SDave Cobbley source_file_dir = os.path.dirname(source_file_path) 135eb8dc403SDave Cobbley 136eb8dc403SDave Cobbley # (1) Get the VCS URL and extract the server part, i.e. change the URL from 137eb8dc403SDave Cobbley # gitolite@git.someserver.com:SomeRepo.git to just git.someserver.com. 138eb8dc403SDave Cobbley source_long_url = run_command( 139eb8dc403SDave Cobbley "git config --get remote.origin.url", source_file_dir) 140eb8dc403SDave Cobbley 141eb8dc403SDave Cobbley # The URL could be a local download directory. If so, get the URL again 142eb8dc403SDave Cobbley # using the local directory's config file. 143eb8dc403SDave Cobbley if is_local_url(source_long_url): 144eb8dc403SDave Cobbley git_config_file = os.path.join(source_long_url, "config") 145eb8dc403SDave Cobbley source_long_url = run_command( 146eb8dc403SDave Cobbley "git config --file %s --get remote.origin.url" % git_config_file, 147eb8dc403SDave Cobbley source_file_dir) 148eb8dc403SDave Cobbley 149eb8dc403SDave Cobbley # If also the download directory redirects to a local git directory, 150eb8dc403SDave Cobbley # then we're probably using source code from a local debug branch which 151eb8dc403SDave Cobbley # won't be accessible by Socorro. 152eb8dc403SDave Cobbley if is_local_url(source_long_url): 153eb8dc403SDave Cobbley return None 154eb8dc403SDave Cobbley 155eb8dc403SDave Cobbley # The URL can have several formats. A full list can be found using 156eb8dc403SDave Cobbley # git help clone. Extract the server part with a regex. 157eb8dc403SDave Cobbley url_match = re.search(".*(://|@)([^:/]*).*", source_long_url) 158eb8dc403SDave Cobbley source_server = url_match.group(2) 159eb8dc403SDave Cobbley 160eb8dc403SDave Cobbley # (2) Get the branch for this file. 161eb8dc403SDave Cobbley source_branch_list = run_command("git show-branch --list", source_file_dir) 162eb8dc403SDave Cobbley source_branch_match = re.search(".*?\[(.*?)\].*", source_branch_list) 163eb8dc403SDave Cobbley source_branch = source_branch_match.group(1) 164eb8dc403SDave Cobbley 165eb8dc403SDave Cobbley # (3) Since the repo root directory name can be changed without affecting 166eb8dc403SDave Cobbley # git, we need to extract the name from something more reliable. 167eb8dc403SDave Cobbley # The git URL has a repo name that we could use. We just need to strip off 168eb8dc403SDave Cobbley # everything around it - from gitolite@git.someserver.com:SomeRepo.git/ to 169eb8dc403SDave Cobbley # SomeRepo. 170eb8dc403SDave Cobbley source_repo_dir = re.sub("/$", "", source_long_url) 171eb8dc403SDave Cobbley source_repo_dir = re.sub("\.git$", "", source_repo_dir) 172eb8dc403SDave Cobbley source_repo_dir = re.sub(".*[:/]", "", source_repo_dir) 173eb8dc403SDave Cobbley 174eb8dc403SDave Cobbley # (4) We know the file but want to remove all of the build system dependent 175eb8dc403SDave Cobbley # path up to and including the repository's root directory, e.g. remove 176eb8dc403SDave Cobbley # /home/someuser/dev/repo/projectx/ 177eb8dc403SDave Cobbley source_toplevel = run_command( 178eb8dc403SDave Cobbley "git rev-parse --show-toplevel", source_file_dir) 179eb8dc403SDave Cobbley source_toplevel = source_toplevel + os.path.sep 180eb8dc403SDave Cobbley source_file = source_file_path.replace(source_toplevel, "") 181eb8dc403SDave Cobbley 182eb8dc403SDave Cobbley # (5) Get the source revision this file is part of. 183eb8dc403SDave Cobbley source_revision = run_command("git rev-parse HEAD", source_file_dir) 184eb8dc403SDave Cobbley 185eb8dc403SDave Cobbley # Assemble the repository path according to the Socorro format. 186eb8dc403SDave Cobbley socorro_reference = "git:%s/%s:%s/%s:%s" % \ 187eb8dc403SDave Cobbley (source_server, source_branch, 188eb8dc403SDave Cobbley source_repo_dir, source_file, 189eb8dc403SDave Cobbley source_revision) 190eb8dc403SDave Cobbley 191eb8dc403SDave Cobbley return socorro_reference 192eb8dc403SDave Cobbley 193eb8dc403SDave Cobbley 194eb8dc403SDave Cobbleydef arrange_socorro_sym_file(socorro_sym_file_path, socorro_syms_dir): 195eb8dc403SDave Cobbley 196eb8dc403SDave Cobbley import re 197eb8dc403SDave Cobbley 198eb8dc403SDave Cobbley # Breakpad's minidump_stackwalk needs a certain directory structure in order 199eb8dc403SDave Cobbley # to find correct symbols when extracting a stack trace out of a minidump. 200eb8dc403SDave Cobbley # The directory structure must look like the following. 201eb8dc403SDave Cobbley # YourBinary/<hash>/YourBinary.sym 202eb8dc403SDave Cobbley # YourLibrary.so/<hash>/YourLibrary.so.sym 203eb8dc403SDave Cobbley # To be able to create such structure we need to extract the hash value that 204eb8dc403SDave Cobbley # is found in each symbol file. The header of the symbol file looks 205eb8dc403SDave Cobbley # something like this: 206eb8dc403SDave Cobbley # MODULE Linux x86 A079E473106CE51C74C1C25AF536CCD30 YourBinary 207eb8dc403SDave Cobbley # See 208eb8dc403SDave Cobbley # http://code.google.com/p/google-breakpad/wiki/LinuxStarterGuide 209eb8dc403SDave Cobbley 210eb8dc403SDave Cobbley # Create the directory with the same name as the binary. 211eb8dc403SDave Cobbley binary_dir = re.sub("\.sym$", "", socorro_sym_file_path) 212eb8dc403SDave Cobbley if not os.path.exists(binary_dir): 213eb8dc403SDave Cobbley os.makedirs(binary_dir) 214eb8dc403SDave Cobbley 215eb8dc403SDave Cobbley # Get the hash from the header of the symbol file. 216eb8dc403SDave Cobbley with open(socorro_sym_file_path, 'r') as socorro_sym_file: 217eb8dc403SDave Cobbley 218eb8dc403SDave Cobbley # The hash is the 4th argument of the first line. 219eb8dc403SDave Cobbley sym_file_hash = socorro_sym_file.readline().split()[3] 220eb8dc403SDave Cobbley 221eb8dc403SDave Cobbley # Create the hash directory. 222eb8dc403SDave Cobbley hash_dir = os.path.join(binary_dir, sym_file_hash) 223eb8dc403SDave Cobbley if not os.path.exists(hash_dir): 224eb8dc403SDave Cobbley os.makedirs(hash_dir) 225eb8dc403SDave Cobbley 226eb8dc403SDave Cobbley # Move the symbol file to the hash directory. 227eb8dc403SDave Cobbley sym_file_name = os.path.basename(socorro_sym_file_path) 228eb8dc403SDave Cobbley os.rename(socorro_sym_file_path, os.path.join(hash_dir, sym_file_name)) 229eb8dc403SDave Cobbley 230eb8dc403SDave Cobbley return 231eb8dc403SDave Cobbley 232