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