__init__.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. # coding: utf-8
  2. import os
  3. import re
  4. import glob
  5. import socket
  6. import logging
  7. import platform
  8. import subprocess
  9. import six
  10. from library.python.reservoir_sampling import reservoir_sampling
  11. logger = logging.getLogger(__name__)
  12. def _read_file(filename):
  13. with open(filename) as afile:
  14. return afile.read().strip("\n")
  15. def recover_core_dump_file(binary_path, cwd, pid, core_pattern=None):
  16. class CoreFilePattern(object):
  17. def __init__(self, path, mask):
  18. self.path = path
  19. self.mask = mask
  20. cwd = cwd or os.getcwd()
  21. system = platform.system().lower()
  22. if system.startswith("linux"):
  23. import stat
  24. import resource
  25. logger.debug("hostname = '%s'", socket.gethostname())
  26. logger.debug("rlimit_core = '%s'", str(resource.getrlimit(resource.RLIMIT_CORE)))
  27. if core_pattern is None:
  28. core_pattern = _read_file("/proc/sys/kernel/core_pattern")
  29. logger.debug("core_pattern = '%s'", core_pattern)
  30. if core_pattern.startswith("/"):
  31. default_pattern = CoreFilePattern(os.path.dirname(core_pattern), '*')
  32. else:
  33. default_pattern = CoreFilePattern(cwd, '*')
  34. def resolve_core_mask(core_mask):
  35. def resolve(text):
  36. if text == "%p":
  37. return str(pid)
  38. elif text == "%e":
  39. # https://github.com/torvalds/linux/blob/7876320f88802b22d4e2daf7eb027dd14175a0f8/include/linux/sched.h#L847
  40. # https://github.com/torvalds/linux/blob/7876320f88802b22d4e2daf7eb027dd14175a0f8/fs/coredump.c#L278
  41. return os.path.basename(binary_path)[:15]
  42. elif text == "%E":
  43. return binary_path.replace("/", "!")
  44. elif text == "%%":
  45. return "%"
  46. elif text.startswith("%"):
  47. return "*"
  48. return text
  49. parts = filter(None, re.split(r"(%.)", core_mask))
  50. return "".join([resolve(p) for p in parts])
  51. # don't interpret a program for piping core dumps as a pattern
  52. if core_pattern and not core_pattern.startswith("|"):
  53. default_pattern.mask = os.path.basename(core_pattern)
  54. else:
  55. default_pattern.mask = "core"
  56. core_uses_pid = int(_read_file("/proc/sys/kernel/core_uses_pid"))
  57. logger.debug("core_uses_pid = '%d'", core_uses_pid)
  58. if core_uses_pid == 1 and "%p" not in re.split(r"(%.)", default_pattern.mask):
  59. default_pattern.mask += ".%p"
  60. # widely distributed core dump dir and mask (see DEVTOOLS-4408)
  61. yandex_pattern = CoreFilePattern('/coredumps', '%e.%p.%s')
  62. yandex_market_pattern = CoreFilePattern('/var/tmp/cores', 'core.%..%e.%s.%p.*')
  63. for pattern in [default_pattern, yandex_pattern, yandex_market_pattern]:
  64. pattern.mask = resolve_core_mask(pattern.mask)
  65. if not os.path.exists(pattern.path):
  66. logger.warning("Core dump dir doesn't exist: %s", pattern.path)
  67. continue
  68. logger.debug(
  69. "Core dump dir (%s) permission mask: %s (expected: %s (%s-dir, %s-sticky bit))",
  70. pattern.path,
  71. oct(os.stat(pattern.path)[stat.ST_MODE]),
  72. oct(stat.S_IFDIR | stat.S_ISVTX | 0o777),
  73. oct(stat.S_IFDIR),
  74. oct(stat.S_ISVTX),
  75. )
  76. logger.debug("Search for core dump files match pattern '%s' in '%s'", pattern.mask, pattern.path)
  77. escaped_pattern_path = pattern.path
  78. if six.PY3:
  79. escaped_pattern_path = glob.escape(pattern.path)
  80. cores = glob.glob(os.path.join(escaped_pattern_path, pattern.mask))
  81. files = os.listdir(pattern.path)
  82. logger.debug(
  83. "Matched core dump files (%d/%d): [%s] (mismatched samples: %s)",
  84. len(cores),
  85. len(files),
  86. ", ".join(cores),
  87. ", ".join(reservoir_sampling(files, 5)),
  88. )
  89. if len(cores) == 1:
  90. return cores[0]
  91. elif len(cores) > 1:
  92. core_stats = []
  93. for filename in cores:
  94. try:
  95. mtime = os.stat(filename).st_mtime
  96. except OSError:
  97. continue
  98. core_stats.append((filename, mtime))
  99. entry = sorted(core_stats, key=lambda x: x[1])[-1]
  100. logger.debug("Latest core dump file: '%s' with %d mtime", entry[0], entry[1])
  101. return entry[0]
  102. else:
  103. logger.debug("Core dump file recovering is not supported on '%s'", system)
  104. return None
  105. def get_gdb_full_backtrace(binary, core, gdb_path):
  106. # XXX ya tool gdb uses shell script as wrapper so we need directory with shell binary in PATH
  107. os.environ["PATH"] = os.pathsep.join(filter(None, [os.environ.get("PATH"), "/bin"]))
  108. cmd = [
  109. gdb_path, binary, core,
  110. "--eval-command", "set print thread-events off",
  111. "--eval-command", "thread apply all backtrace full",
  112. "--batch",
  113. "--quiet",
  114. ]
  115. proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  116. output, stderr = proc.communicate()
  117. output = six.ensure_str(output)
  118. if stderr:
  119. output += "\nstderr >>\n" + six.ensure_str(stderr)
  120. return output
  121. def get_problem_stack(backtrace):
  122. stack = []
  123. found_thread1 = False
  124. regex = re.compile(r'[Tt]hread (\d+)')
  125. for line in backtrace.split("\n"):
  126. match = regex.search(line)
  127. if match:
  128. if found_thread1:
  129. break
  130. if int(match.group(1)) == 1:
  131. found_thread1 = True
  132. if found_thread1:
  133. stack.append(line)
  134. if not stack:
  135. return backtrace
  136. return "\n".join(stack)
  137. # XXX
  138. def colorize_backtrace(text):
  139. filters = [
  140. # Function names and the class they belong to
  141. (re.compile(r"^(#[0-9]+ .*?)([a-zA-Z0-9_:\.@]+)(\s?\()", flags=re.MULTILINE), r"\1[[c:cyan]]\2[[rst]]\3"),
  142. # Function argument names
  143. (re.compile(r"([a-zA-Z0-9_#]*)(\s?=\s?)"), r"[[c:green]]\1[[rst]]\2"),
  144. # Stack frame number
  145. (re.compile(r"^(#[0-9]+)", flags=re.MULTILINE), r"[[c:red]]\1[[rst]]"),
  146. # Thread id colorization
  147. (re.compile(r"^([ \*]) ([0-9]+)", flags=re.MULTILINE), r"[[c:light-cyan]]\1 [[c:red]]\2[[rst]]"),
  148. # File path and line number
  149. (re.compile(r"(\.*[/A-Za-z0-9\+_\.\-]*):(([0-9]+)(:[0-9]+)?)$", flags=re.MULTILINE), r"[[c:light-grey]]\1[[rst]]:[[c:magenta]]\2[[rst]]"),
  150. # Addresses
  151. (re.compile(r"\b(0x[a-f0-9]{6,})\b"), r"[[c:light-grey]]\1[[rst]]"),
  152. ]
  153. text = six.ensure_str(text)
  154. for regex, substitution in filters:
  155. text = regex.sub(substitution, text)
  156. return text
  157. def resolve_addresses(addresses, symbolizer, binary):
  158. addresses = list(set(addresses))
  159. cmd = [
  160. symbolizer,
  161. "--demangle",
  162. "--obj",
  163. binary,
  164. ]
  165. proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **({'text': True} if six.PY3 else {}))
  166. out, err = proc.communicate(input="\n".join(addresses))
  167. if proc.returncode:
  168. raise Exception("Symbolizer failed with rc:{}\nstderr: {}".format(proc.returncode, err))
  169. resolved = list(filter(None, out.split("\n\n")))
  170. if len(addresses) != len(resolved):
  171. raise Exception("llvm-symbolizer can not extract lines from addresses (count mismatch: {}-{})".format(len(addresses), len(resolved)))
  172. return {k: v.strip(" \n") for k, v in zip(addresses, resolved)}