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