blob: b9a5ffc8816cde9c9f38467b95ead2ef6c7ec548 [file] [log] [blame]
Patrick Williamsb48b7b42016-08-17 15:04:38 -05001# 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"
Patrick Williams213cb262021-08-07 19:21:33 -050019FILES:${PN}-socorro-syms = "/usr/share/socorro-syms"
Patrick Williamsb48b7b42016-08-17 15:04:38 -050020
21
22python symbol_file_preprocess() {
23
Brad Bishop6e60e8b2018-02-01 10:27:11 -050024 package_dir = d.getVar("PKGD")
25 breakpad_bin = d.getVar("BREAKPAD_BIN")
Patrick Williamsb48b7b42016-08-17 15:04:38 -050026 if not breakpad_bin:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050027 package_name = d.getVar("PN")
Patrick Williamsb48b7b42016-08-17 15:04:38 -050028 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))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500109 if not git_root_dir.startswith(d.getVar("TOPDIR")):
Patrick Williamsb48b7b42016-08-17 15:04:38 -0500110 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