forked from NixOS/nix
-
Notifications
You must be signed in to change notification settings - Fork 11
Upload crash info to Sentry #418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
edolstra
wants to merge
28
commits into
main
Choose a base branch
from
eelcodolstra/nix-361
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
19dbf7d
Add undocumented command category
edolstra a41fc8e
Add `nix crash` command for testing crash reporting
edolstra 46eb006
everything.nix: Add a debug output for convenience
edolstra 241d161
Add script for uploading debug symbols to sentry
edolstra 289ac80
WIP sentry support
edolstra 4f15bd5
Upload debug info to sentry
edolstra d7e597a
Disable sentry on static builds
edolstra 953049c
Add sentry test
edolstra 37f0922
Disable sentry on macOS
edolstra 957c52a
nix crash -> nix __crash
edolstra ec810ad
sigsegvHandler(): Restore previous signal handler
edolstra 01b59e1
Fix eval on macOS
edolstra c0a0284
Fix test
edolstra 4b821b2
Only upload debug symbols on Linux
edolstra fd2f0bf
dev-shell.nix: Propagate mesonFlags from the nix component
edolstra 087b993
Code review
edolstra 70a6614
Use crashpad
edolstra 9e2138b
Try on macOS
edolstra 93bc0bd
Fix CI
edolstra eb6fb7f
Fix warning
edolstra 1572f30
Fix compilation error
edolstra 911f3d6
Fix sentry-native build on macOS
edolstra 0581739
upload-debug-info-to-sentry.py: Support macOS
edolstra fb68913
Fix upload
edolstra b60ca3b
Get the Sentry endpoint from /etc/nix/sentry-endpoint
edolstra 0f6eb2f
Print errors reading sentry-endpoint
edolstra 05e2b13
Re-enable wrap-assert-fail
edolstra cd35a29
Don't show `nix __crash` in the manual
edolstra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| #!/usr/bin/env nix | ||
| #!nix shell --inputs-from . nixpkgs#sentry-cli --command python3 | ||
|
|
||
| import argparse | ||
| import json | ||
| import os | ||
| import platform | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| import urllib.error | ||
| import urllib.parse | ||
| import urllib.request | ||
|
|
||
| NAR_DIR = "/tmp/nars" | ||
| DEBUG_INFO_DIR = "/tmp/debug-info" | ||
|
|
||
|
|
||
| def get_dynamic_libraries(executable: str) -> list[str]: | ||
| if platform.system() == "Darwin": | ||
| result = subprocess.run(["otool", "-L", executable], capture_output=True, text=True, check=True) | ||
| libs = [] | ||
| for line in result.stdout.splitlines()[1:]: # skip first line (the binary path itself) | ||
| # otool -L output lines look like: | ||
| # /nix/store/.../libfoo.dylib (compatibility version X.Y.Z, current version A.B.C) | ||
| m = re.match(r"\s+(\S+)\s+\(", line) | ||
| if m: | ||
| libs.append(m.group(1)) | ||
| return libs | ||
| else: | ||
| result = subprocess.run(["ldd", executable], capture_output=True, text=True, check=True) | ||
| libs = [] | ||
| for line in result.stdout.splitlines(): | ||
| # ldd output lines look like: | ||
| # libfoo.so.1 => /nix/store/.../libfoo.so.1 (0x...) | ||
| # /lib64/ld-linux-x86-64.so.2 (0x...) | ||
| m = re.search(r"=> (/\S+)", line) | ||
| if m: | ||
| libs.append(m.group(1)) | ||
| elif line.strip().startswith("/"): | ||
| path = line.strip().split()[0] | ||
| libs.append(path) | ||
| return libs | ||
|
|
||
|
|
||
| def get_build_id(path: str) -> str | None: | ||
| result = subprocess.run(["readelf", "-n", path], capture_output=True, text=True) | ||
| m = re.search(r"Build ID:\s+([0-9a-f]+)", result.stdout) | ||
| return m.group(1) if m else None | ||
|
|
||
|
|
||
| def download_nar(build_id: str, archive: str) -> str: | ||
| """Download a NAR to /tmp/nars and return the local path. Skips if already present.""" | ||
| base_url = f"https://cache.nixos.org/debuginfo/{build_id}" | ||
| nar_url = urllib.parse.urljoin(base_url, archive) | ||
| filename = nar_url.split("/")[-1] | ||
| local_path = os.path.join(NAR_DIR, filename) | ||
| if not os.path.exists(local_path): | ||
| os.makedirs(NAR_DIR, exist_ok=True) | ||
| print(f" downloading {nar_url} ...", file=sys.stderr) | ||
| urllib.request.urlretrieve(nar_url, local_path) | ||
| else: | ||
| print(f" already have {filename}", file=sys.stderr) | ||
| return local_path | ||
|
|
||
|
|
||
| def extract_debug_symbols(nar_path: str, member: str, build_id: str) -> str: | ||
| """Extract a member from a .nar.xz into /tmp/debug-info/<build_id>.debug. Returns the output path.""" | ||
| out_path = os.path.join(DEBUG_INFO_DIR, f"{build_id}.debug") | ||
| if os.path.exists(out_path): | ||
| print(f" already extracted {out_path}", file=sys.stderr) | ||
| return out_path | ||
| os.makedirs(DEBUG_INFO_DIR, exist_ok=True) | ||
| print(f" extracting {member} -> {out_path} ...", file=sys.stderr) | ||
| xz = subprocess.Popen(["xz", "-d"], stdin=open(nar_path, "rb"), stdout=subprocess.PIPE) | ||
| nar_cat = subprocess.run( | ||
| ["nix", "nar", "cat", "/dev/stdin", member], | ||
| stdin=xz.stdout, | ||
| capture_output=True, | ||
| check=True, | ||
| ) | ||
| xz.wait() | ||
| with open(out_path, "wb") as f: | ||
| f.write(nar_cat.stdout) | ||
| return out_path | ||
edolstra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def find_debug_file_in_dirs(build_id: str, debug_dirs: list[str]) -> str | None: | ||
| """Look for a .debug file by build ID under <dir>/lib/debug/.build-id/NN/NNN.debug.""" | ||
| subpath = os.path.join("lib", "debug", ".build-id", build_id[:2], build_id[2:] + ".debug") | ||
| for d in debug_dirs: | ||
| candidate = os.path.join(d, subpath) | ||
| if os.path.exists(candidate): | ||
| return candidate | ||
| return None | ||
|
|
||
|
|
||
| def fetch_debuginfo(build_id: str) -> dict | None: | ||
| url = f"https://cache.nixos.org/debuginfo/{build_id}" | ||
| try: | ||
| with urllib.request.urlopen(url) as resp: | ||
| return json.loads(resp.read()) | ||
| except urllib.error.HTTPError as e: | ||
| if e.code == 404: | ||
| return None | ||
| raise | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser( | ||
| description="Upload debug symbols to Sentry." | ||
| ) | ||
| parser.add_argument("executable", help="Path to the executable (e.g. ./result/bin/nix)") | ||
| parser.add_argument("--project", help="Sentry project ID") | ||
| parser.add_argument("--debug-dir", action="append", default=[], metavar="DIR", | ||
| help="Directory to search for debug files (may be repeated, Linux only)") | ||
| args = parser.parse_args() | ||
|
|
||
| libs = [args.executable] + get_dynamic_libraries(args.executable) | ||
|
|
||
| if platform.system() == "Darwin": | ||
| # On macOS there are no separate debug info files; upload the binaries directly. | ||
| print("Files to upload:", file=sys.stderr) | ||
| for lib in libs: | ||
| print(f" {lib}", file=sys.stderr) | ||
| files_to_upload = libs | ||
| else: | ||
| debug_files = [] | ||
| print("ELF files to process:", file=sys.stderr) | ||
| for lib in libs: | ||
| build_id = get_build_id(lib) | ||
| if build_id is None: | ||
| print(f" {lib} (no build ID)", file=sys.stderr) | ||
| continue | ||
|
|
||
| local = find_debug_file_in_dirs(build_id, args.debug_dir) | ||
| if local: | ||
| print(f" {lib} ({build_id}): found locally at {local}", file=sys.stderr) | ||
| debug_files.append(local) | ||
| continue | ||
|
|
||
| debuginfo = fetch_debuginfo(build_id) | ||
| if debuginfo is None: | ||
| print(f" {lib} ({build_id}, no debug info in cache)", file=sys.stderr) | ||
| continue | ||
| print(f" {lib} ({build_id}): member={debuginfo['member']}", file=sys.stderr) | ||
| nar_path = download_nar(build_id, debuginfo["archive"]) | ||
| debug_file = extract_debug_symbols(nar_path, debuginfo["member"], build_id) | ||
| debug_files.append(debug_file) | ||
| files_to_upload = debug_files | ||
|
|
||
| if files_to_upload: | ||
| print(f"Uploading {len(files_to_upload)} file(s) to Sentry...", file=sys.stderr) | ||
| cmd = ["sentry-cli", "debug-files", "upload"] | ||
| if args.project: | ||
| cmd += ["--project", args.project] | ||
| subprocess.run(cmd + files_to_upload, check=True) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| { | ||
| lib, | ||
| stdenv, | ||
| fetchgit, | ||
| cmake, | ||
| curl, | ||
| pkg-config, | ||
| python3, | ||
| darwin, | ||
| }: | ||
|
|
||
| stdenv.mkDerivation rec { | ||
| pname = "sentry-native"; | ||
| version = "0.13.5"; | ||
|
|
||
| src = fetchgit { | ||
| url = "https://github.com/getsentry/sentry-native"; | ||
| tag = version; | ||
| hash = "sha256-vDBI6lB1DMLleAgRCfsHvTSdtmXOzvJSaNAt+NwOd3c="; | ||
| fetchSubmodules = true; | ||
| }; | ||
|
|
||
| dontFixCmake = true; | ||
|
|
||
| nativeBuildInputs = [ | ||
| cmake | ||
| pkg-config | ||
| ] | ||
| ++ lib.optionals stdenv.hostPlatform.isDarwin [ | ||
| python3 | ||
| darwin.bootstrap_cmds | ||
| ]; | ||
|
|
||
| postPatch = '' | ||
| # Borrowed from psutil: stick to the old SDK name for now. | ||
| substituteInPlace external/crashpad/util/mac/mac_util.cc \ | ||
| --replace-fail kIOMainPortDefault kIOMasterPortDefault | ||
| ''; | ||
|
|
||
| buildInputs = [ | ||
| curl | ||
| ]; | ||
|
|
||
| cmakeBuildType = "RelWithDebInfo"; | ||
|
|
||
| cmakeFlags = [ ]; | ||
|
|
||
| outputs = [ | ||
| "out" | ||
| "dev" | ||
| ]; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.