diff --git a/upload-coredump.py b/upload-coredump.py index fcf822b..4b72685 100644 --- a/upload-coredump.py +++ b/upload-coredump.py @@ -11,12 +11,18 @@ import time class Frame: def __init__( - self, instruction_addr="", name_of_function="", path="abs_path", lineno=None, + self, + instruction_addr=None, + function=None, + filename="abs_path", + lineno=None, + package=None, ): self.instruction_addr = instruction_addr - self.name_of_function = name_of_function - self.path = path + self.function = function + self.filename = filename self.lineno = lineno + self.package = package def to_json(self): return self.__dict__ @@ -43,37 +49,64 @@ class Image: return self.__dict__ +class Stacktrace_for_thread: + def __init__(self): + self.frames = [] + + def append_frame(self, frame=None): + self.frames.append(frame) + + def reverse_list(self): + self.frames.reverse() + + def to_json(self): + return self.__dict__ + + +class Thread: + def __init__(self, id="", name=None, crashed=None, frames=None): + self.stacktrace = {} + self.id = id + self.name = name + self.crashed = crashed + self.stacktrace = frames + + def to_json(self): + return self.__dict__ + + _frame_re = re.compile( - r"""(?x) + r"""(?xim) + ^ + # frame number + \#\d+\s+ + # instruction address (missing for first frame) + (?: + (?P0x[0-9a-f]+) + \sin\s + )? + # function name (?? if unknown) + (?P.*) + \s + # arguments, ignored + \([^)]*\) - #address of instruction - (?P - 0[xX][a-fA-F0-9]+ - ) - - #name of function - (\sin)? - \s? - (.*::)? - (?P - [a-zA-z]+ + \s* + (?: + # package name, without debug info + from\s + (?P[^ ]+) + | + # file name and line number + at\s + (?P[^ ]+) + : + (?P\d+) )? - - #path from the file - (\s\(.*\))? (\sat\s)? - (?P - .*\.c - )? - - #Number of the line - :? - (?P - [0-9]+ - )* -""" + $ + """ ) - _image_re = re.compile( r"""(?x) @@ -108,7 +141,7 @@ _image_re = re.compile( (?P [\/|\/][\w|\S]+|\S+\.\S+|[a-zA-Z]* )? -""" + """ ) @@ -121,22 +154,21 @@ def error(message): sys.exit(1) -def get_frame(gdb_output): - """Parses the output from gdb """ +def get_frame(temp): + """ """ frame = Frame() - temp = _frame_re.search(gdb_output) - if temp is None: - return - frame.instruction_addr = temp.group("instruction_addr") - frame.name_of_function = temp.group("name_of_function") + frame.function = temp.group("function") if temp.group("lineno") is not None: frame.lineno = int(temp.group("lineno")) - if temp.group("path") is not None: - frame.path = temp.group("path") + if temp.group("filename") is not None: + frame.filename = temp.group("filename") + + if temp.group("package") is not None: + frame.package = temp.group("package") return frame @@ -157,14 +189,82 @@ def get_image(image_string): ) +def get_all_threads(path_to_core, path_to_executable, gdb_path): + + thread_list = [] + counter_threads = 0 + + # execute gdb + if gdb_path is None: + process = subprocess.Popen( + ["gdb", "-c", path_to_core, path_to_executable], + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + ) + else: + try: + process = subprocess.Popen( + [gdb_path, "gdb", "-c", path_to_core, path_to_executable], + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + ) + except OSError as err: + error(err) + + output, errors = process.communicate(input="thread apply all bt") + if errors: + error(errors) + + gdb_output = output.decode("utf-8") + + # splits gdb output to get each Thread + try: + gdb_output = output.split("Thread") + except: + error("gdb output error") + + del gdb_output[0] + gdb_output.reverse() + + for thread_backtrace in gdb_output: + stacktrace = Stacktrace_for_thread() + + # gets the Thread ID + thread_id = re.search(r"(?x)LWP\s(?P[a-fA-F0-9]+)", thread_backtrace) + + # gets each frame from the Thread + for match in re.finditer(_frame_re, thread_backtrace): + frame = get_frame(match) + if frame is not None: + stacktrace.append_frame(frame.to_json()) + + stacktrace.reverse_list() + + if counter_threads == 0: + crashed = True + else: + crashed = False + + # appends a Thread to the thread_list + thread_list.append( + Thread(thread_id.group("thread_id"), None, crashed, stacktrace.to_json()) + ) + counter_threads += 1 + + print("Threads found: " + str(counter_threads)) + return thread_list + + @click.command() @click.argument("path_to_core") @click.argument("path_to_executable") @click.option("--sentry-dsn", required=False) @click.option("--gdb-path", required=False) @click.option("--elfutils-path", required=False) -def main(path_to_core, path_to_executable, sentry_dsn, gdb_path, elfutils_path): - +@click.option("--all-threads", is_flag=True) +def main( + path_to_core, path_to_executable, sentry_dsn, gdb_path, elfutils_path, all_threads +): # Validate input Path if os.path.isfile(path_to_core) is not True: error("Wrong path to coredump") @@ -181,9 +281,6 @@ def main(path_to_core, path_to_executable, sentry_dsn, gdb_path, elfutils_path): image_list = [] frame_list = [] - gdb_output = [] - eu_unstrip_output = [] - # execute gdb if gdb_path is None: process = subprocess.Popen( @@ -201,12 +298,21 @@ def main(path_to_core, path_to_executable, sentry_dsn, gdb_path, elfutils_path): except OSError as err: error(format(err)) - output = re.search(r"#0.*", str(process.communicate(input="bt"))) - try: - gdb_output = output.group().split("#") - except: + output, errors = process.communicate(input="bt") + if errors: + error(errors) + + gdb_output = output.decode("utf-8") + if not "#0" in gdb_output: error("gdb output error") + try: + type_of_event = re.search( + r"terminated with signal (?P.*),", gdb_output + ).group("type") + except: + type_of_event = "Core" + # execute eu-unstrip if elfutils_path is None: process = subprocess.Popen( @@ -234,8 +340,8 @@ def main(path_to_core, path_to_executable, sentry_dsn, gdb_path, elfutils_path): eu_unstrip_output = str(output[0]).split("\n") - for x in range(1, len(gdb_output)): - frame = get_frame(gdb_output[x]) + for match in re.finditer(_frame_re, gdb_output): + frame = get_frame(match) if frame is not None: frame_list.append(frame) @@ -244,20 +350,36 @@ def main(path_to_core, path_to_executable, sentry_dsn, gdb_path, elfutils_path): if image is not None: image_list.append(image) + frame_list.reverse() + type_of_event = "Core" # build the json for sentry - data = { - "platform": "native", - "exception": { - "type": "Core", - "handled": "false", - "stacktrace": {"frames": [ob.to_json() for ob in frame_list]}, - }, - "debug_meta": {"images": [ob.to_json() for ob in image_list]}, - } + if all_threads: + thread_list = get_all_threads(path_to_core, path_to_executable, gdb_path) + + data = { + "platform": "native", + "exception": { + "type": type_of_event, + "mechanism": {"type": "coredump", "handled": False, "synthetic": True}, + "stacktrace": {"frames": [ob.to_json() for ob in frame_list]}, + }, + "debug_meta": {"images": [ob.to_json() for ob in image_list]}, + "threads": {"values": [ob.to_json() for ob in thread_list]}, + } + else: + data = { + "platform": "native", + "exception": { + "type": type_of_event, + "mechanism": {"type": "coredump", "handled": False, "synthetic": True}, + "stacktrace": {"frames": [ob.to_json() for ob in frame_list]}, + }, + "debug_meta": {"images": [ob.to_json() for ob in image_list]}, + } sentry_sdk.init(sentry_dsn) - sentry_sdk.capture_event(data) - print("Core dump sent to sentry!") + event_id = sentry_sdk.capture_event(data) + print("Core dump sent to sentry: %s" % (event_id,)) if __name__ == "__main__":