Skip to main content

analyze-crash

Native crash log analysis for dd-trace-py

Zur Installation springen

Quellinformationen

Repository
DataDog/dd-trace-py
Letzte Quellaktivität
5. Juni 2026 um 14:32
Erkannte Sprache von SKILL.md
Englisch
Sterne
650
Forks
552

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
analyze-crash
description
Native crash log analysis for dd-trace-py
argument-hint
<crash-uuid-or-paste-log>
disable-model-invocation
false
context
fork
agent
general-purpose
allowed-tools
["Bash","Read","Grep","Glob","TodoWrite","WebSearch"]
# Native Crash Stack Trace Analysis for dd-trace-py You are analyzing a native crash captured by dd-trace-py's crashtracker. Perform a comprehensive investigation to help engineers understand and triage the crash. The crashtracker captures **all process-level native crashes**, so the crashing code may or may not be in dd-trace-py itself — investigate the code and patterns thoroughly before making the attribution call. ## Input Processing The user will provide crash data in one of two ways: ### Option A: Crash UUID (preferred) The user provides a crash UUID from Datadog crash telemetry. Use the fetch script bundled with this skill to retrieve and process the full crash log: ```bash cd .claude/skills/analyze-crash && dd-auth .venv/bin/python fetch_crash.py <UUID> [--tracer-version X.Y.Z] [--days 14] ``` This fetches the crash log from Datadog, processes the stack frames (demangling C++ symbols, resolving addresses against `/proc/self/maps`), and outputs structured JSON with: - **Metadata**: tracer version, runtime version, service, org, platform, architecture, signal - **Processed stack**: each frame with demangled symbol, binary file, offset, source file/line - **Binary mappings**: summary of loaded shared objects - **Datadog log link**: direct URL to the crash event If the user provides only a UUID with no other context, ask: > What is the crash UUID? Any additional filters (tracer version, org ID, language)? **Setup** (one-time): run `cd .claude/skills/analyze-crash && uv sync` to create the venv and install dependencies. **Authentication**: use `dd-auth` as a command wrapper — it injects Datadog API credentials into the environment for the wrapped process. ### Option B: Pasted crash log or stack trace The user pastes a raw crash log or stack trace directly. The format is typically: ``` 0x7f6e81c51fb7 PyUnicode_AsUTF8AndSize 0x7f6e769d7850 pycbc::Connection::open_bucket(_object*) 0x7f6e81ccd60c 0x7f6e81c53239 _PyObject_Call ... ``` Frames may have: hex address only, hex address + symbol, or symbol + `.so` name + offset. Parse all available information. If the paste includes full crash JSON (with `error.stack` and `files./proc/self/maps`), extract the metadata and processed stack from it. ### Determine the source revision The crash may come from a released wheel or a branch other than `main`. GitHub links must point at the exact revision that built the crashing binary so that line numbers match. **Ask the user**: Before starting analysis, ask: > Do you know the commit SHA or release tag for the dd-trace-py version that crashed? > (e.g., `v2.18.0`, `abc1234`). If not, I'll use `main`. **Resolution order** (use the first that succeeds): 1. **User-provided value** — commit SHA or tag supplied in the prompt or in response to the question above. Use it verbatim as `GIT_REF`. 2. **Crash metadata** — if the crash was fetched via UUID and includes a `tracer_version` field (e.g., `2.18.0`), try the corresponding git tag (`v2.18.0`). Verify it exists: `git rev-parse --verify "v2.18.0" 2>/dev/null`. 3. **Version string in the stack trace** — if the wheel filename or ddtrace version appears in the trace (e.g., `ddtrace-2.18.0`), try the corresponding git tag. 4. **Current checkout** — if the local repo is not on `main`, use `git rev-parse HEAD` to get the current SHA. 5. **Fallback** — use `main`. Store the resolved value as `GIT_REF` and use it in all GitHub links for the rest of the analysis. State which ref you are using and why at the top of the report (in Additional Context). ## Analysis Workflow ### GitHub Link Generation When referencing files in dd-trace-py, always provide clickable GitHub links using the resolved `GIT_REF` (see "Determine the source revision" above). **Format**: ``` [filename:line](https://github.com/DataDog/dd-trace-py/blob/{GIT_REF}/path/to/file#Lline) ``` **Examples** (assuming `GIT_REF=v2.18.0`): - Single line: `[_threads.cpp:226](https://github.com/DataDog/dd-trace-py/blob/v2.18.0/ddtrace/internal/_threads.cpp#L226)` - Range: `[_threads.cpp:226-252](https://github.com/DataDog/dd-trace-py/blob/v2.18.0/ddtrace/internal/_threads.cpp#L226-L252)` **Path construction**: base URL is `https://github.com/DataDog/dd-trace-py/blob/{GIT_REF}/`, then append the repo-relative path (strip any build-specific prefixes like `/home/runner/work/`, `/usr/local/lib/`, etc.). --- ### Phase 1: Parse & Classify Stack Frames Extract all stack frames and classify each one using the categories below. #### Frame Classification **CPython Runtime** — the Python interpreter itself: - `.so` name: `python3.X`, `libpython3.X.so`, `libpython3.X.so.1.0` - Symbols: `_PyEval_EvalFrameDefault`, `PyGILState_Ensure`, `take_gil`, `new_threadstate`, `PyEval_RestoreThread`, `PyEval_SaveThread`, `Py_RunMain`, `PyObject_Call*`, `_Py_*`, `PyThread_exit_thread`, `_PyRuntime`, `_PyThreadState_*` **dd-trace-py Native (_threads)** — periodic thread / GIL management: - `.so` name: `_threads.cpython-*.so` - Symbols: `PeriodicThread_start`, `PeriodicThread_stop`, `PeriodicThread_join`, `PeriodicThread__periodic`, `PeriodicThread__before_fork`, `PeriodicThread__after_fork`, `PeriodicThread__on_shutdown`, `PeriodicThread_awake`, `PeriodicThread_dealloc`, `GILGuard`, `AllowThreads`, `PyRef`, `execute_native_thread_routine` **dd-trace-py Native (_native / Rust)** — crashtracker, data pipeline, tracer utilities: - `.so` name: `_native.cpython-*.so` (under `ddtrace/internal/native/`) - Symbols: `crashtracker_*`, `TraceExporterPy::send`, `pyo3::*`, `rust_begin_unwind`, `rust_panic`, `libdd_crashtracker::*`, `datadog_profiling_ffi::*`, `ffe::*`, `ddsketch::*` **dd-trace-py Native (_memalloc)** — memory profiler: - `.so` name: `_memalloc.cpython-*.so` - Symbols: `memalloc_*`, `Datadog::Sample*` (when called from memalloc context) **dd-trace-py Native (_ddup / _stack / libdd_wrapper)** — profiling upload/sampling: - `.so` names: `_ddup.cpython-*.so`, `_stack.cpython-*.so`, `libdd_wrapper.cpython-*.so` - Symbols: `ddup_*`, `Datadog::Profile*`, `Datadog::Sample*`, `Datadog::Uploader*`, `Datadog::Sampler*`, `Datadog::EchionSampler*`, `Datadog::StackRenderer*`, `echion_*` **dd-trace-py IAST/AppSec Native** — security instrumentation: - `.so` name: `_native.cpython-*.so` (under `ddtrace/appsec/`), `_stacktrace.cpython-*.so` - Symbols: `taint_*`, `TaintEngine*`, `tainted_ops::*` **dd-trace-py Cython** — Python-level profiling helpers: - `.so` names: `_threading.cpython-*.so`, `_exception.cpython-*.so`, `_sampler.cpython-*.so`, `_lock.cpython-*.so`, `_task.cpython-*.so`, `_encoding.cpython-*.so` - Symbols: Cython-generated (often `__pyx_*` prefixes or module function names) **Third-Party Native (NOT dd-trace-py)** — known third-party libs: - `libev-*.so` / `libev.so.4` — gevent's embedded libev (NOT dd-trace-py) - `_native__lib.cpython-*.so` — Pyroscope's native extension (path contains `pyroscope/`) **Note:** Pyroscope crashes appear in dd-trace-py crash telemetry because the crashtracker captures all process-level crashes. `_native__lib` (double underscore, in `pyroscope/`) is ALWAYS Pyroscope, never dd-trace-py. dd-trace-py's module is `_native` (single underscore, in `ddtrace/internal/native/`). - `anyhow::error::object_drop`, `py_spy::*`, `goblin::*` — Pyroscope/py-spy Rust code - Customer application code, third-party Python extensions **OS/libc** — system libraries: - `libc.so.6`, `libpthread.so`, `libm.so` - Symbols: `gsignal`, `abort`, `pthread_exit`, `pthread_mutex_lock`, `__clone3`, `start_thread`, `cfree`, `free`, `malloc` Create a classification table: | # | Type | Symbol / Location | Description | |---|------|------------------|-------------| | 0 | {type} | {symbol} | {brief description} | | ... | | | | --- ### Phase 2: Locate dd-trace-py Source Code For each dd-trace-py frame that can be mapped to source code: 1. **Map `.so` name to source file**: - `_threads.cpython-*.so` → `ddtrace/internal/_threads.cpp` - `_native.cpython-*.so` (in `ddtrace/internal/native/`) → `src/native/` (Rust) - `_memalloc.cpython-*.so` → `ddtrace/profiling/collector/_memalloc.cpp` - `_ddup.cpython-*.so` → `ddtrace/internal/datadog/profiling/ddup/` - `_stack.cpython-*.so` → `ddtrace/internal/datadog/profiling/stack/` - `libdd_wrapper.cpython-*.so` → `ddtrace/internal/datadog/profiling/dd_wrapper/` 2. **Find the function** in the source file using Glob/Bash grep. 3. **Read code with context**: find the function body, read 10-15 lines before the relevant line, mark the crash point, read 5-10 lines after. Show enough to understand what the code is doing. Format: ``` ### Frame N: {symbol} ([{file}:{line}](GitHub link)) ​```cpp // {file}:{start}-{end} {code with relevant line marked: // >>> CRASH POINT <<<} ​``` **Analysis**: {what this code does and why it may have failed} ``` --- ### Phase 3: Apply General Crash Heuristics dd-trace-py maintains two complementary native code safety references: - `docs/native-code-review.md` — full incident-derived rules with historical PR links (10 sections) - `.cursor/rules/native-code.mdc` — compact triage checklist with trigger symbols and stop conditions Read **both files now**. Use their framework to classify the crash. The categories below summarize the key stack-trace signals; refer to the docs for full rationale and PR history. #### 3.1 — GIL Lifecycle During Finalization **Stack signals**: `PyGILState_Ensure`, `PyEval_RestoreThread`, `take_gil`, `new_threadstate`, `PyThread_exit_thread`, `pthread_exit`, `abort`, `gsignal`, `__forced_unwind`, `abi::__cxa_throw`, `std::terminate` — especially combined with thread-entry frames like `execute_native_thread_routine` or Rust `extern "C"` boundaries. **What to check**: - Is there a `py_is_finalizing()` / `is_finalizing()` check immediately before each GIL acquire/restore call? These checks are inherently TOCTTOU — finalization can begin in the window between the check and the call. - Does the crash involve C++ RAII unwinding? `pthread_exit` on glibc throws `abi::__forced_unwind`; any C++ destructor or `catch(...)` that does not re-throw it will cause `std::terminate` → `SIGABRT`. On musl (Alpine Linux), `pthread_exit` uses `longjmp` instead — `catch(abi::__forced_unwind&)` never fires. - For Rust/pyo3 code: `__forced_unwind` cannot propagate through `extern "C"` boundaries. Rust code that calls `PyEval_RestoreThread` (or uses `py.detach()`) needs its own finalization check — the C++ caller's `try/catch` cannot protect it. **CPython version behavior** (from `docs/native-code-review.md`): | CPython | Behavior when `take_gil()` detects finalization | |---------|------------------------------------------------| | 3.12 (all) | `PyThread_exit_thread()` → `pthread_exit()` → crash | | 3.13.0–3.13.7 | Same as 3.12 | | 3.13.8+ | `PyThread_hang_thread()` → `pause()` forever (hang, not crash) |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen