Skip to main content

analyze-crash

Native crash log analysis for dd-trace-py

설치로 이동

소스 정보

저장소
DataDog/dd-trace-py
최근 소스 활동
2026년 6월 5일 14:32
감지된 SKILL.md 언어
영어
스타
650
포크
548

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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) |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기