fix_linux_stack.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #!/usr/bin/python
  2. # vim:sw=4:ts=4:et:
  3. # This Source Code Form is subject to the terms of the Mozilla Public
  4. # License, v. 2.0. If a copy of the MPL was not distributed with this
  5. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. # This script uses addr2line (part of binutils) to post-process the entries
  7. # produced by NS_FormatCodeAddress(), which on Linux often lack a function
  8. # name, a file name and a line number.
  9. import subprocess
  10. import sys
  11. import re
  12. import os
  13. import pty
  14. import termios
  15. from StringIO import StringIO
  16. class unbufferedLineConverter:
  17. """
  18. Wrap a child process that responds to each line of input with one line of
  19. output. Uses pty to trick the child into providing unbuffered output.
  20. """
  21. def __init__(self, command, args = []):
  22. pid, fd = pty.fork()
  23. if pid == 0:
  24. # We're the child. Transfer control to command.
  25. os.execvp(command, [command] + args)
  26. else:
  27. # Disable echoing.
  28. attr = termios.tcgetattr(fd)
  29. attr[3] = attr[3] & ~termios.ECHO
  30. termios.tcsetattr(fd, termios.TCSANOW, attr)
  31. # Set up a file()-like interface to the child process
  32. self.r = os.fdopen(fd, "r", 1)
  33. self.w = os.fdopen(os.dup(fd), "w", 1)
  34. def convert(self, line):
  35. self.w.write(line + "\n")
  36. return (self.r.readline().rstrip("\r\n"), self.r.readline().rstrip("\r\n"))
  37. @staticmethod
  38. def test():
  39. assert unbufferedLineConverter("rev").convert("123") == "321"
  40. assert unbufferedLineConverter("cut", ["-c3"]).convert("abcde") == "c"
  41. print "Pass"
  42. objdump_section_re = re.compile("^ [0-9a-f]* ([0-9a-f ]{8}) ([0-9a-f ]{8}) ([0-9a-f ]{8}) ([0-9a-f ]{8}).*")
  43. def elf_section(file, section):
  44. """
  45. Return the requested ELF section of the file as a str, representing
  46. a sequence of bytes.
  47. """
  48. # We can read the .gnu_debuglink section using either of:
  49. # objdump -s --section=.gnu_debuglink $file
  50. # readelf -x .gnu_debuglink $file
  51. # Since readelf prints things backwards on little-endian platforms
  52. # for some versions only (backwards on Fedora Core 6, forwards on
  53. # Fedora 7), use objdump.
  54. objdump = subprocess.Popen(['objdump', '-s', '--section=' + section, file],
  55. stdout=subprocess.PIPE,
  56. # redirect stderr so errors don't get printed
  57. stderr=subprocess.PIPE)
  58. (objdump_stdout, objdump_stderr) = objdump.communicate()
  59. if objdump.returncode != 0:
  60. return None
  61. result = ""
  62. # Turn hexadecimal dump into the bytes it represents
  63. for line in StringIO(objdump_stdout).readlines():
  64. m = objdump_section_re.match(line)
  65. if m:
  66. for gnum in [0, 1, 2, 3]:
  67. word = m.groups()[gnum]
  68. if word != " ":
  69. for idx in [0, 2, 4, 6]:
  70. result += chr(int(word[idx:idx+2], 16))
  71. return result
  72. # FIXME: Hard-coded to gdb defaults (works on Fedora and Ubuntu).
  73. global_debug_dir = '/usr/lib/debug';
  74. endian_re = re.compile("\s*Data:\s+.*(little|big) endian.*$")
  75. # Table of 256 values, per documentation of .gnu_debuglink sections.
  76. gnu_debuglink_crc32_table = [
  77. 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
  78. 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
  79. 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
  80. 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
  81. 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
  82. 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
  83. 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
  84. 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
  85. 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
  86. 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
  87. 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
  88. 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
  89. 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
  90. 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
  91. 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
  92. 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
  93. 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
  94. 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
  95. 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
  96. 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
  97. 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
  98. 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
  99. 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
  100. 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
  101. 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
  102. 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
  103. 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
  104. 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
  105. 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
  106. 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
  107. 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
  108. 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
  109. 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
  110. 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
  111. 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
  112. 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
  113. 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
  114. 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
  115. 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
  116. 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
  117. 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
  118. 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
  119. 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
  120. 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
  121. 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
  122. 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
  123. 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
  124. 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
  125. 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
  126. 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
  127. 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
  128. 0x2d02ef8d
  129. ]
  130. def gnu_debuglink_crc32(stream):
  131. # Note that python treats bitwise operators as though integers have
  132. # an infinite number of bits (and thus such that negative integers
  133. # 1-pad out to infinity).
  134. crc = 0xffffffff
  135. while True:
  136. # Choose to read in 4096 byte chunks.
  137. bytes = stream.read(4096)
  138. if len(bytes) == 0:
  139. break
  140. for byte in bytes:
  141. crc = gnu_debuglink_crc32_table[(crc ^ ord(byte)) & 0xff] ^ (crc >> 8)
  142. return ~crc & 0xffffffff
  143. def separate_debug_file_for(file):
  144. """
  145. Finds a separated file with the debug sections for a binary. Such
  146. files are commonly installed by debug packages on linux distros.
  147. Rules for finding them are documented in:
  148. https://sourceware.org/gdb/current/onlinedocs/gdb/Separate-Debug-Files.html
  149. """
  150. def have_debug_file(debugfile):
  151. return os.path.isfile(debugfile)
  152. endian = None
  153. readelf = subprocess.Popen(['readelf', '-h', file],
  154. stdout=subprocess.PIPE)
  155. for line in readelf.stdout.readlines():
  156. m = endian_re.match(line)
  157. if m:
  158. endian = m.groups()[0]
  159. break
  160. readelf.terminate()
  161. if endian is None:
  162. sys.stderr.write("Could not determine endianness of " + file + "\n")
  163. return None
  164. def word32(s):
  165. if type(s) != str or len(s) != 4:
  166. raise StandardError("expected 4 byte string input")
  167. s = list(s)
  168. if endian == "big":
  169. s.reverse()
  170. return sum(map(lambda idx: ord(s[idx]) * (256 ** idx), range(0, 4)))
  171. buildid = elf_section(file, ".note.gnu.build-id");
  172. if buildid is not None:
  173. # The build ID is an ELF note section, so it begins with a
  174. # name size (4), a description size (size of contents), a
  175. # type (3), and the name "GNU\0".
  176. note_header = buildid[0:16]
  177. buildid = buildid[16:]
  178. if word32(note_header[0:4]) != 4 or \
  179. word32(note_header[4:8]) != len(buildid) or \
  180. word32(note_header[8:12]) != 3 or \
  181. note_header[12:16] != "GNU\0":
  182. sys.stderr.write("malformed .note.gnu.build_id in " + file + "\n")
  183. else:
  184. buildid = "".join(map(lambda ch: "%02X" % ord(ch), buildid)).lower()
  185. f = os.path.join(global_debug_dir, ".build-id", buildid[0:2], buildid[2:] + ".debug")
  186. if have_debug_file(f):
  187. return f
  188. debuglink = elf_section(file, ".gnu_debuglink");
  189. if debuglink is not None:
  190. # The debuglink section contains a string, ending with a
  191. # null-terminator and then 0 to three bytes of padding to fill the
  192. # current 32-bit unit. (This padding is usually null bytes, but
  193. # I've seen null-null-H, on Ubuntu x86_64.) This is followed by
  194. # a 4-byte CRC.
  195. debuglink_name = debuglink[:-4]
  196. null_idx = debuglink_name.find("\0")
  197. if null_idx == -1 or null_idx + 4 < len(debuglink_name):
  198. sys.stderr.write("Malformed .gnu_debuglink in " + file + "\n")
  199. return None
  200. debuglink_name = debuglink_name[0:null_idx]
  201. debuglink_crc = word32(debuglink[-4:])
  202. dirname = os.path.dirname(file)
  203. possible_files = [
  204. os.path.join(dirname, debuglink_name),
  205. os.path.join(dirname, ".debug", debuglink_name),
  206. os.path.join(global_debug_dir, dirname.lstrip("/"), debuglink_name)
  207. ]
  208. for f in possible_files:
  209. if have_debug_file(f):
  210. fio = open(f, mode="r")
  211. file_crc = gnu_debuglink_crc32(fio)
  212. fio.close()
  213. if file_crc == debuglink_crc:
  214. return f
  215. return None
  216. elf_type_re = re.compile("^\s*Type:\s+(\S+)")
  217. elf_text_section_re = re.compile("^\s*\[\s*\d+\]\s+\.text\s+\w+\s+(\w+)\s+(\w+)\s+")
  218. def address_adjustment_for(file):
  219. """
  220. Return the address adjustment to use for a file.
  221. addr2line wants offsets relative to the base address for shared
  222. libraries, but it wants addresses including the base address offset
  223. for executables. This returns the appropriate address adjustment to
  224. add to an offset within file. See bug 230336.
  225. """
  226. readelf = subprocess.Popen(['readelf', '-h', file],
  227. stdout=subprocess.PIPE)
  228. elftype = None
  229. for line in readelf.stdout.readlines():
  230. m = elf_type_re.match(line)
  231. if m:
  232. elftype = m.groups()[0]
  233. break
  234. readelf.terminate()
  235. if elftype != "EXEC":
  236. # If we're not dealing with an executable, return 0.
  237. return 0
  238. adjustment = 0
  239. readelf = subprocess.Popen(['readelf', '-S', file],
  240. stdout=subprocess.PIPE)
  241. for line in readelf.stdout.readlines():
  242. m = elf_text_section_re.match(line)
  243. if m:
  244. # Subtract the .text section's offset within the
  245. # file from its base address.
  246. adjustment = int(m.groups()[0], 16) - int(m.groups()[1], 16);
  247. break
  248. readelf.terminate()
  249. return adjustment
  250. addr2lines = {}
  251. def addressToSymbol(file, address):
  252. converter = None
  253. address_adjustment = None
  254. cache = None
  255. if not file in addr2lines:
  256. debug_file = separate_debug_file_for(file) or file
  257. converter = unbufferedLineConverter('/usr/bin/addr2line', ['-C', '-f', '-e', debug_file])
  258. address_adjustment = address_adjustment_for(file)
  259. cache = {}
  260. addr2lines[file] = (converter, address_adjustment, cache)
  261. else:
  262. (converter, address_adjustment, cache) = addr2lines[file]
  263. if address in cache:
  264. return cache[address]
  265. result = converter.convert(hex(int(address, 16) + address_adjustment))
  266. cache[address] = result
  267. return result
  268. # Matches lines produced by NS_FormatCodeAddress().
  269. line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
  270. def fixSymbols(line):
  271. result = line_re.match(line)
  272. if result is not None:
  273. (before, fn, file, address, after) = result.groups()
  274. if os.path.exists(file) and os.path.isfile(file):
  275. (name, fileline) = addressToSymbol(file, address)
  276. # If addr2line gave us something useless, keep what we had before.
  277. if name == "??":
  278. name = fn
  279. if fileline == "??:0" or fileline == "??:?":
  280. fileline = file
  281. nl = '\n' if line[-1] == '\n' else ''
  282. return "%s%s (%s)%s%s" % (before, name, fileline, after, nl)
  283. else:
  284. sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
  285. return line
  286. else:
  287. return line
  288. if __name__ == "__main__":
  289. for line in sys.stdin:
  290. sys.stdout.write(fixSymbols(line))