diff options
| author | Stefan Boberg <[email protected]> | 2026-03-24 13:18:12 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-24 13:18:12 +0100 |
| commit | fec7fea085968bb93291638fd6d8c037682595ec (patch) | |
| tree | 1871b36ead5b8fea3db12a347e658344afedf572 | |
| parent | refactor hub notifications (#888) (diff) | |
| download | zen-fec7fea085968bb93291638fd6d8c037682595ec.tar.xz zen-fec7fea085968bb93291638fd6d8c037682595ec.zip | |
Linux Crashpad fix (#890)
- **Replace crashpad static-libc++ patch file with `io.replace()` in `on_install`** — The old `.patch` file was fragile (trailing-whitespace stripping on Windows would silently break it). Using `io.replace()` in the xmake build script is more robust and easier to maintain.
- **Skip sentry-native `on_test` link check on Linux** — The link test requires `-lc++abi` when building with the UE clang toolchain but adding it unconditionally breaks GCC/libstdc++ builds. The zenserver build itself validates that the library is usable.
- **Add `crashpad-test.sh`** — A test script that launches a release zenserver, waits for the health endpoint, then verifies that `crashpad_handler` is running, no `sentry_init` failure was logged, and the handler has no dynamic `libc++.so.1` dependency.
- **Add Crashpad Check step to Linux release CI** — Runs `crashpad-test.sh` in the `validate` workflow for release builds to catch crashpad regressions before merge.
| -rw-r--r-- | .github/workflows/validate.yml | 6 | ||||
| -rw-r--r-- | repo/packages/s/sentry-native/patches/0.12.1/crashpad_static_libcxx.patch | 25 | ||||
| -rw-r--r-- | repo/packages/s/sentry-native/xmake.lua | 37 | ||||
| -rwxr-xr-x | scripts/test_linux/crashpad-test.sh | 184 |
4 files changed, 226 insertions, 26 deletions
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 932020337..601f000fe 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -178,6 +178,12 @@ jobs: run: | xmake bundle -v -y + - name: Crashpad Check + if: ${{ matrix.config == 'release' }} + shell: bash + run: | + ./scripts/test_linux/crashpad-test.sh build/linux/${{ matrix.arch }}/release/zenserver + - name: Upload zenserver-linux if: ${{ github.ref_name == 'main' && matrix.config == 'release' }} uses: actions/upload-artifact@v3-node20 diff --git a/repo/packages/s/sentry-native/patches/0.12.1/crashpad_static_libcxx.patch b/repo/packages/s/sentry-native/patches/0.12.1/crashpad_static_libcxx.patch deleted file mode 100644 index 64458821d..000000000 --- a/repo/packages/s/sentry-native/patches/0.12.1/crashpad_static_libcxx.patch +++ /dev/null @@ -1,25 +0,0 @@ ---- a/external/crashpad/handler/CMakeLists.txt 2026-03-09 14:47:42.109197582 +0000 -+++ b/external/crashpad/handler/CMakeLists.txt 2026-03-09 14:51:45.343538268 +0000 -@@ -120,6 +120,22 @@ - endif() - endif() - -+ # Statically link libc++ and libc++abi into crashpad_handler so it has -+ # no runtime dependency on libc++.so.1. This is needed when building with -+ # a toolchain that uses libc++ (e.g. UE clang) but deploys to systems -+ # where libc++.so.1 is not available. -+ # Only applied when -stdlib=libc++ is active (i.e. not GCC or system clang -+ # using libstdc++). -+ if(LINUX) -+ string(FIND "${CMAKE_CXX_FLAGS}" "-stdlib=libc++" _libcxx_pos) -+ if(NOT _libcxx_pos EQUAL -1) -+ target_link_options(crashpad_handler PRIVATE -nostdlib++) -+ target_link_libraries(crashpad_handler PRIVATE -+ -Wl,-Bstatic,-lc++,-lc++abi,-Bdynamic -+ ) -+ endif() -+ endif() -+ - set_property(TARGET crashpad_handler PROPERTY EXPORT_NAME crashpad_handler) - add_executable(crashpad::handler ALIAS crashpad_handler) - diff --git a/repo/packages/s/sentry-native/xmake.lua b/repo/packages/s/sentry-native/xmake.lua index 59418c835..2d7ab7e59 100644 --- a/repo/packages/s/sentry-native/xmake.lua +++ b/repo/packages/s/sentry-native/xmake.lua @@ -37,9 +37,11 @@ package("sentry-native") add_versions("0.4.4", "fe6c711d42861e66e53bfd7ee0b2b226027c64446857f0d1bbb239ca824a3d8d") add_patches("0.4.4", path.join(os.scriptdir(), "patches", "0.4.4", "zlib_fix.patch"), "1a6ac711b7824112a9062ec1716a316facce5055498d1f87090d2cad031b865b") add_patches("0.7.6", path.join(os.scriptdir(), "patches", "0.7.6", "breakpad_exceptions.patch"), "7781bad0404a92252cbad39e865d17ac663eedade03cbd29c899636c7bfab1b5") - add_patches("0.12.1", path.join(os.scriptdir(), "patches", "0.12.1", "crashpad_static_libcxx.patch"), "5efa0e7b106b9fc121f819f305086f7f3a2b8aa519a052bf82ea60d86c15872d") add_patches("0.12.1", path.join(os.scriptdir(), "patches", "0.12.1", "breakpad_exceptions.patch"), "9e0cd152192f87b9ce182c8ddff22c0471acb99bd61a872ca48afbbacdf27575") + -- Internal: bump this value to invalidate the package cache after build-logic changes. + add_configs("build_patch", {description = "Internal cache-buster for build logic changes", default = "3", type = "string"}) + add_deps("cmake") add_configs("backend", {description = "Set the backend of sentry to use", type = "string"}) @@ -113,11 +115,44 @@ package("sentry-native") elseif package:is_plat("macosx") then opt.shflags = {"-framework", "SystemConfiguration"} table.insert(configs, "-DCMAKE_OSX_SYSROOT=" .. os.iorun("xcrun --show-sdk-path")) + elseif package:is_plat("linux") then + -- Statically link libc++ and libc++abi into crashpad_handler so it has no runtime + -- dependency on libc++.so.1, which may not be present on all Linux deployments. + -- Conditioned on -stdlib=libc++ being active so GCC and system-clang builds + -- (which use libstdc++) are unaffected. + -- Done via io.replace() rather than a patch file to avoid fragility from trailing- + -- whitespace stripping (a common issue with Windows-generated patch files). + local handler_cmake = path.join(os.curdir(), "external/crashpad/handler/CMakeLists.txt") + if os.isfile(handler_cmake) then + io.replace(handler_cmake, + " set_property(TARGET crashpad_handler PROPERTY EXPORT_NAME crashpad_handler)", + table.concat({ + " if(LINUX)", + " string(FIND \"${CMAKE_CXX_FLAGS}\" \"-stdlib=libc++\" _libcxx_pos)", + " if(NOT _libcxx_pos EQUAL -1)", + " target_link_options(crashpad_handler PRIVATE -nostdlib++)", + " target_link_libraries(crashpad_handler PRIVATE", + " -Wl,-Bstatic,-lc++,-lc++abi,-Bdynamic", + " )", + " endif()", + " endif()", + "", + " set_property(TARGET crashpad_handler PROPERTY EXPORT_NAME crashpad_handler)", + }, "\n"), + {plain = true}) + end end import("package.tools.cmake").install(package, configs, opt) end) on_test(function (package) + -- On Linux the link test requires libc++abi when building with the UE clang toolchain + -- (-stdlib=libc++ implies -lc++ but not -lc++abi), and adding it unconditionally would + -- break GCC/libstdc++ builds. Skip the link check on Linux; the zenserver build itself + -- validates that the library is usable. + if package:is_plat("linux") then + return + end assert(package:check_cxxsnippets({test = [[ void test(int args, char** argv) { sentry_options_t* options = sentry_options_new(); diff --git a/scripts/test_linux/crashpad-test.sh b/scripts/test_linux/crashpad-test.sh new file mode 100755 index 000000000..bd08cefc5 --- /dev/null +++ b/scripts/test_linux/crashpad-test.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +# Verify that crashpad_handler is active when zenserver (release build) starts. +# +# This test: +# 1. Launches zenserver from the release build. +# 2. Waits for the HTTP health endpoint to become ready. +# 3. Checks that a crashpad_handler child process is running. +# 4. Checks that the startup log contains "sentry initialized" (not a failure). +# +# Usage: +# ./scripts/test_linux/crashpad-test.sh [path-to-zenserver] +# +# If no path is given, defaults to build/linux/x86_64/release/zenserver +# relative to the repository root. +# +# The test exits 0 on success, 1 on failure. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +ZENSERVER_BINARY="${1:-$REPO_ROOT/build/linux/x86_64/release/zenserver}" +CRASHPAD_HANDLER="$(dirname "$ZENSERVER_BINARY")/crashpad_handler" +PORT=18558 # Use a non-default port so we don't collide with a running zenserver +DATA_DIR="$(mktemp -d)" +STDOUT_FILE="$DATA_DIR/stdout.log" +ZENSERVER_PID="" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +PASSED=0 +FAILED=0 + +pass() { + echo -e " ${GREEN}PASS${NC} $1" + (( PASSED++ )) || true +} + +fail() { + echo -e " ${RED}FAIL${NC} $1" + (( FAILED++ )) || true +} + +cleanup() { + set +e + if [ -n "$ZENSERVER_PID" ] && kill -0 "$ZENSERVER_PID" 2>/dev/null; then + kill "$ZENSERVER_PID" 2>/dev/null + wait "$ZENSERVER_PID" 2>/dev/null + fi + rm -rf "$DATA_DIR" +} +trap cleanup EXIT + +# ── Preflight ──────────────────────────────────────────────────────────────── + +echo "" +echo "==============================" +echo " Crashpad active check" +echo "==============================" +echo "" + +if [ ! -f "$ZENSERVER_BINARY" ]; then + echo -e "${RED}ERROR: zenserver binary not found: $ZENSERVER_BINARY${NC}" + echo " Build with: xmake config -m release && xmake build zenserver" + exit 1 +fi + +if [ ! -f "$CRASHPAD_HANDLER" ]; then + echo -e "${RED}ERROR: crashpad_handler not found alongside zenserver: $CRASHPAD_HANDLER${NC}" + echo " It should be copied there automatically by the build." + exit 1 +fi + +echo "zenserver: $ZENSERVER_BINARY" +echo "crashpad_handler: $CRASHPAD_HANDLER" +echo "port: $PORT" +echo "data dir: $DATA_DIR" +echo "" + +# ── Start zenserver ────────────────────────────────────────────────────────── + +# zenserver runs in the foreground until SIGINT/SIGTERM. Launch in background +# so we can poll its health endpoint and inspect child processes. +"$ZENSERVER_BINARY" \ + --port="$PORT" \ + --data-dir="$DATA_DIR" \ + > "$STDOUT_FILE" 2>&1 & +ZENSERVER_PID=$! + +echo "Started zenserver (pid $ZENSERVER_PID), waiting for health endpoint..." + +# ── Wait for health endpoint ───────────────────────────────────────────────── + +READY=false +for i in $(seq 1 40); do + if curl -sf "http://localhost:$PORT/health" > /dev/null 2>&1; then + READY=true + break + fi + sleep 0.5 +done + +if [ "$READY" = false ]; then + echo -e "${RED}ERROR: zenserver did not become ready within 20 seconds${NC}" + if [ -f "$STDOUT_FILE" ]; then + echo "" + echo "--- stdout ---" + cat "$STDOUT_FILE" + fi + exit 1 +fi + +echo "Server is ready." + +# Give the server a moment to finish startup logging (sentry init log +# message is emitted after the health endpoint comes up). +sleep 1 +echo "" + +# ── Test 1: crashpad_handler process is running for our data dir ───────────── +# +# sentry-native starts crashpad_handler with --database pointing at +# $DATA_DIR/.sentry-native. The process re-parents itself out of zenserver's +# process tree, so we look for it by its command-line arguments rather than +# by parent PID. + +HANDLER_PID="$(pgrep -f "crashpad_handler.*${DATA_DIR}" 2>/dev/null | head -1 || true)" +if [ -n "$HANDLER_PID" ]; then + pass "crashpad_handler is running (pid $HANDLER_PID) with database in our data dir" +else + fail "crashpad_handler process not found — sentry_init may have failed or sentry is disabled" +fi + +# ── Test 2: No sentry_init failure in startup log ──────────────────────────── +# +# The "sentry initialized" success message is logged at INFO level under the +# sentry-sdk log category, which is filtered to Warn by default — so it won't +# appear in normal stdout. We check for the absence of the failure message +# instead (which IS at Warn level and would appear if sentry_init failed). + +if grep -q "sentry_init returned failure" "$STDOUT_FILE" 2>/dev/null; then + ERRMSG="$(grep "sentry_init returned failure" "$STDOUT_FILE" | head -1)" + fail "sentry_init reported failure: $ERRMSG" +else + pass "No sentry_init failure message in startup log" +fi + +# ── Test 3: ldd sanity — crashpad_handler must not need libc++.so.1 ────────── + +MISSING_LIBCXX="$(ldd "$CRASHPAD_HANDLER" 2>/dev/null | grep "libc++\.so\.1" | grep "not found" || true)" +if [ -n "$MISSING_LIBCXX" ]; then + fail "crashpad_handler has an unsatisfied libc++.so.1 dependency (static linking patch not applied)" +elif ldd "$CRASHPAD_HANDLER" 2>/dev/null | grep -q "libc++\.so\.1"; then + fail "crashpad_handler links libc++.so.1 dynamically — it should be statically linked" +else + pass "crashpad_handler has no dynamic libc++.so.1 dependency" +fi + +# ── Summary ────────────────────────────────────────────────────────────────── + +echo "" +echo "==============================" +printf " Passed: " +echo -e "${GREEN}${PASSED}${NC}" +printf " Failed: " +if [ "$FAILED" -gt 0 ]; then + echo -e "${RED}${FAILED}${NC}" +else + echo -e "${GREEN}${FAILED}${NC}" +fi +echo "==============================" +echo "" + +if [ "$FAILED" -gt 0 ]; then + if [ -f "$STDOUT_FILE" ]; then + echo "--- stdout ---" + cat "$STDOUT_FILE" + fi + exit 1 +fi |