xref: /OK3568_Linux_fs/yocto/meta-openembedded/meta-oe/classes/socorro-syms.bbclass (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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