socorro-syms.bbclass 8.4 KB

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