#!/usr/bin/env python3 import sys if sys.version_info < (3, 10): print( f'A minimum Python version of at least 3.10 is required, ' f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} was found', file=sys.stderr ) sys.exit(1) import argparse import re import shutil import subprocess import shlex import tempfile class CoreDumper(): def __init__(self): self.build_directory = None self.app = None self.coredumpctl_matches = "" self.extra_flatpak_args = "" self.gdb_arguments = "" def clean_args(self): if not self.build_directory and not self.app: print("Either `--build-directory` or `APP` must be specified.", file=sys.stderr) return False return True def run(self): if not shutil.which("coredumpctl"): print("'coredumpctl' not present on the system, can't run.", file=sys.stderr) sys.exit(1) # We need access to the host from the sandbox to run. flatpak_command = ["flatpak", "build" if self.build_directory else "run", "--filesystem=home", f"--filesystem={tempfile.gettempdir()}"] + shlex.split(self.extra_flatpak_args) if not self.build_directory: flatpak_command.extend(["--command=gdb", "--devel", self.app]) else: flatpak_command.extend([self.build_directory, "gdb"]) with tempfile.NamedTemporaryFile() as coredump: dumpres = subprocess.run( ["coredumpctl", "dump"] + shlex.split(self.coredumpctl_matches), stdout=coredump, stderr=subprocess.PIPE, text=True ) if dumpres.returncode != 0: print("Failed to retrieve coredump via coredumpctl.", file=sys.stderr) if dumpres.stderr: print(f"Reason:\n{dumpres.stderr}", file=sys.stderr) sys.exit(dumpres.returncode) matches = re.findall(r".*Executable: (.*)", dumpres.stderr) if len(matches) != 1: print(f"Could not determine executable from coredumpctl output " f"(found {len(matches)} 'Executable:' line(s)).", file=sys.stderr) sys.exit(1) executable = matches[0] if not executable.startswith(("/newroot/", "/app/")): print(f"Executable {executable} doesn't seem to be a flatpaked application.", file=sys.stderr) executable = executable.replace("/newroot", "", 1) flatpak_command.extend([executable, coredump.name]) flatpak_command.extend(shlex.split(self.gdb_arguments)) print(f"Running: `{shlex.join(flatpak_command)}`") sys.exit(subprocess.run(flatpak_command).returncode) if __name__ == "__main__": parser = argparse.ArgumentParser(description= "Debug in gdb an application that crashed inside flatpak." " It uses (and thus requires) coredumpctl to retrieve the coredump file.") parser.add_argument("-b", "--build-directory", default=None, help="The build directory to use. It allows you to retrieve a coredump" " for applications being built") parser.add_argument("--extra-flatpak-args", default="", help="Extra argument to pass to flatpak") parser.add_argument("app", nargs="?", help="The flatpak application to use. eg. `org.gnome.Epiphany//3.28`.") parser.add_argument("-m", "--coredumpctl-matches", default="", nargs="?", help="Coredumpctl matches, see `man coredumpctl` for more information.") parser.add_argument("--gdb-arguments", default="", help="Arguments to pass to gdb.") coredumper = CoreDumper() options = parser.parse_args(namespace=coredumper) if not coredumper.clean_args(): parser.print_help(sys.stderr) sys.exit(1) coredumper.run()