xref: /openbmc/openbmc/meta-openembedded/meta-oe/classes/socorro-syms.bbclass (revision 213cb2696d00a85cd48d356cb5131824a302d828)
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