From b74a28e101eb7b59b99ece156b7a27cf4277511c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 20 Oct 2026 19:50:42 +0200 Subject: [PATCH 1/9] feat(linux): RISC-V support Support register capture, stack unwinding, ELF metadata, and native minidump generation. --- .github/workflows/ci.yml | 31 +++++++++--- README.md | 10 ++-- .../native/minidump/sentry_minidump_common.c | 2 + .../native/minidump/sentry_minidump_format.h | 13 +++++ .../native/minidump/sentry_minidump_linux.c | 50 +++++++++++++++++-- src/backends/native/sentry_crash_daemon.c | 30 ++++++++--- src/backends/native/sentry_crash_handler.c | 11 +++- src/sentry_elf.h | 12 +++-- src/sentry_os.c | 11 ++++ src/sentry_os.h | 4 ++ tests/build_config.py | 10 ++++ tests/conditions.py | 3 ++ tests/requirements.txt | 2 +- tests/test_dotnet_signals.py | 5 +- tests/test_integration_native.py | 4 ++ tests/unit/test_native_backend.c | 10 +++- vendor/libunwind/CMakeLists.txt | 46 ++++++++++++++++- vendor/libunwind/VENDORING.md | 7 +-- vendor/stb_sprintf.c | 3 ++ 19 files changed, 231 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d22d1ee7eb..76fda8d055 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,12 +69,18 @@ jobs: CC: gcc-9 CXX: g++-9 TEST_X86: 1 - - name: Linux Arm32 (gcc-arm-linux-gnueabihf + qemu-user) + - name: Linux Arm32 (cross-GCC + QEMU) os: ubuntu-24.04-arm CC: arm-linux-gnueabihf-gcc CXX: arm-linux-gnueabihf-g++ TEST_ARM32: 1 TEST_QEMU: 1 + - name: Linux RISC-V (cross-GCC + QEMU) + os: ubuntu-24.04-arm + CC: riscv64-linux-gnu-gcc + CXX: riscv64-linux-gnu-g++ + TEST_RISCV: 1 + TEST_QEMU: 1 - name: Linux (GCC 9.5.0) os: ubuntu-24.04 CC: gcc-9 @@ -260,6 +266,7 @@ jobs: TEST_MINGW: ${{ matrix.TEST_MINGW }} TEST_WINE: ${{ matrix.TEST_WINE }} TEST_ARM32: ${{ matrix.TEST_ARM32 }} + TEST_RISCV: ${{ matrix.TEST_RISCV }} TEST_QEMU: ${{ matrix.TEST_QEMU }} TEST_SYSTEM_PROXY: ${{ matrix.TEST_SYSTEM_PROXY }} USE_SCCACHE: ${{ matrix.USE_SCCACHE }} @@ -297,7 +304,7 @@ jobs: [ -n "$CC" ] && [ -n "$CXX" ] || { echo "Ubuntu runner configurations require toolchain selection via CC and CXX" >&2; exit 1; } - name: Installing Linux Dependencies - if: ${{ runner.os == 'Linux' && !env['TEST_X86'] && !env['TEST_ARM32'] && !env['ANDROID_API'] && !matrix.container }} + if: ${{ runner.os == 'Linux' && !env['TEST_X86'] && !env['TEST_ARM32'] && !env['TEST_RISCV'] && !env['ANDROID_API'] && !matrix.container }} run: | sudo apt update # Install common dependencies @@ -367,11 +374,23 @@ jobs: liblzma-dev:armhf \ libstdc++6:armhf - - name: Installing qemu-user - if: ${{ runner.os == 'Linux' && env['TEST_QEMU'] && !matrix.container }} + - name: Installing Linux RISC-V Dependencies + if: ${{ runner.os == 'Linux' && env['TEST_RISCV'] && !matrix.container }} run: | - sudo apt update - sudo apt install -y qemu-user-static binfmt-support + sudo dpkg --add-architecture riscv64 + sudo apt-get update + sudo apt-get install -y \ + cmake llvm \ + gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ + zlib1g-dev:riscv64 libssl-dev:riscv64 \ + libcurl4-openssl-dev:riscv64 liblzma-dev:riscv64 \ + libstdc++6:riscv64 + echo "PKG_CONFIG_LIBDIR=/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV" + + - uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 + if: ${{ runner.os == 'Linux' && env['TEST_QEMU'] && !matrix.container }} + with: + platforms: ${{ env['TEST_ARM32'] && 'arm' || 'riscv64' }} - name: Installing Alpine Linux Dependencies if: ${{ contains(matrix.container, 'alpine') }} diff --git a/README.md b/README.md index 798b60c8a9..1bafb6d971 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,9 @@ The SDK bundle contains the following folders: ## Platform and Feature Support -The SDK currently supports and is tested on the following OS/Compiler variations: +The SDK is fully supported and tested on the following OS/Compiler variations: -- Linux (x86, x64, arm32 and arm64) with GCC 9+ and Clang 11+ +- Linux (x86, x64 and arm64) with GCC 9+ and Clang 11+ - Windows (x86, x64 and arm64) with MSVC (Visual Studio 2022+) and LLVM-MinGW - macOS 15+ with Apple Clang and LLVM Clang 15+ - Android API 21+ with NDK 23+ @@ -80,9 +80,11 @@ The SDK currently supports and is tested on the following OS/Compiler variations - Nintendo Switch via [sentry-switch](https://github.com/getsentry/sentry-switch). See [Nintendo Switch documentation](https://docs.sentry.io/platforms/nintendo-switch/) to get access. Additionally, the SDK should support the following platforms, although they are -not automatically tested, so breakage may occur: +not fully tested, so breakage may occur: -- Windows Versions lower than Windows 10 / Windows Server 2016 +- Windows versions lower than Windows 10 / Windows Server 2016: not tested in CI. +- Linux arm32 and riscv: cross-compiled in CI, with only a subset of non-crash + tests run under QEMU. The SDK supports different features on the target platform: diff --git a/src/backends/native/minidump/sentry_minidump_common.c b/src/backends/native/minidump/sentry_minidump_common.c index 84fb4d0bc6..fa8b507699 100644 --- a/src/backends/native/minidump/sentry_minidump_common.c +++ b/src/backends/native/minidump/sentry_minidump_common.c @@ -213,6 +213,8 @@ sentry__minidump_get_context_size(void) return sizeof(minidump_context_x86_t); # elif defined(__arm__) return sizeof(minidump_context_arm_t); +# elif defined(__riscv) && __riscv_xlen == 64 + return sizeof(minidump_context_riscv64_t); # else # error "Unsupported architecture" # endif diff --git a/src/backends/native/minidump/sentry_minidump_format.h b/src/backends/native/minidump/sentry_minidump_format.h index 6daf02f360..9770c5b7e8 100644 --- a/src/backends/native/minidump/sentry_minidump_format.h +++ b/src/backends/native/minidump/sentry_minidump_format.h @@ -50,6 +50,7 @@ typedef enum { MINIDUMP_CPU_ARM = 5, // PROCESSOR_ARCHITECTURE_ARM MINIDUMP_CPU_X86_64 = 9, // PROCESSOR_ARCHITECTURE_AMD64 MINIDUMP_CPU_ARM64 = 12, // PROCESSOR_ARCHITECTURE_ARM64 + MINIDUMP_CPU_RISCV64 = 0x8006, // MD_CPU_ARCHITECTURE_RISCV64 (Breakpad) } minidump_cpu_type_t; // OS types (must match Breakpad MDOSPlatform values) @@ -318,6 +319,18 @@ typedef struct { uint32_t fpextra[8]; } PACKED_ATTR minidump_context_arm_t; PACKED_STRUCT_END + +#elif defined(__riscv) && __riscv_xlen == 64 +PACKED_STRUCT_BEGIN +typedef struct { + uint32_t context_flags; + uint32_t version; + uint64_t regs[32]; // PC, X1-X31 + uint64_t fpregs[32]; // F0-F31 + uint32_t fcsr; + uint32_t reserved; +} PACKED_ATTR minidump_context_riscv64_t; +PACKED_STRUCT_END #endif /** diff --git a/src/backends/native/minidump/sentry_minidump_linux.c b/src/backends/native/minidump/sentry_minidump_linux.c index 39a2d5be97..b80ac671ab 100644 --- a/src/backends/native/minidump/sentry_minidump_linux.c +++ b/src/backends/native/minidump/sentry_minidump_linux.c @@ -259,6 +259,18 @@ ptrace_get_thread_registers(pid_t tid, ucontext_t *uctx) SENTRY_DEBUGF("ptrace(PTRACE_GETREGSET) failed for thread %d: %s", tid, strerror(errno)); } +# elif defined(__riscv) && __riscv_xlen == 64 + struct iovec iov + = { uctx->uc_mcontext.__gregs, sizeof(uctx->uc_mcontext.__gregs) }; + if (ptrace(PTRACE_GETREGSET, tid, (void *)NT_PRSTATUS, &iov) == 0) { + success = true; + struct iovec fp_iov = { &uctx->uc_mcontext.__fpregs.__d, + sizeof(uctx->uc_mcontext.__fpregs.__d) }; + ptrace(PTRACE_GETREGSET, tid, (void *)NT_FPREGSET, &fp_iov); + } else { + SENTRY_DEBUGF("ptrace(PTRACE_GETREGSET) failed for thread %d: %s", tid, + strerror(errno)); + } # elif defined(__i386__) struct user_regs_struct regs; if (ptrace(PTRACE_GETREGS, tid, NULL, ®s) == 0) { @@ -548,6 +560,8 @@ write_system_info_stream(minidump_writer_t *writer, minidump_directory_t *dir) sysinfo.processor_architecture = MINIDUMP_CPU_X86; # elif defined(__arm__) sysinfo.processor_architecture = MINIDUMP_CPU_ARM; +# elif defined(__riscv) && __riscv_xlen == 64 + sysinfo.processor_architecture = MINIDUMP_CPU_RISCV64; # endif # if defined(SENTRY_PLATFORM_ANDROID) @@ -856,6 +870,18 @@ write_thread_context( return write_data(writer, &context, sizeof(context)); +# elif defined(__riscv) && __riscv_xlen == 64 + (void)tid; + + minidump_context_riscv64_t context = { 0 }; + context.context_flags = 0x08000003; // RISCV64 | Integer | Floating point + memcpy(context.regs, uctx->uc_mcontext.__gregs, sizeof(context.regs)); + memcpy(context.fpregs, uctx->uc_mcontext.__fpregs.__d.__f, + sizeof(context.fpregs)); + context.fcsr = uctx->uc_mcontext.__fpregs.__d.__fcsr; + + return write_data(writer, &context, sizeof(context)); + # else # error "Unsupported architecture for Linux" # endif @@ -874,7 +900,8 @@ extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) } // Read ELF header -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Ehdr ehdr; # else Elf32_Ehdr ehdr; @@ -913,7 +940,8 @@ extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Shdr *sections = (Elf64_Shdr *)shdr_buf; # else Elf32_Shdr *sections = (Elf32_Shdr *)shdr_buf; @@ -974,7 +1002,8 @@ compute_elf_size_from_phdrs(const char *elf_path) return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Ehdr ehdr; # else Elf32_Ehdr ehdr; @@ -1006,7 +1035,8 @@ compute_elf_size_from_phdrs(const char *elf_path) return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Phdr *phdrs = (Elf64_Phdr *)phdr_buf; # else Elf32_Phdr *phdrs = (Elf32_Phdr *)phdr_buf; @@ -1054,7 +1084,8 @@ read_elf_soname(const char *elf_path, char *soname_buf, size_t soname_buf_size) return false; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Ehdr ehdr; typedef Elf64_Dyn DynT; typedef Elf64_Shdr ShdrT; @@ -1390,6 +1421,8 @@ ptrace_capture_thread( ptrace_sp = (uint32_t)ptrace_ctx.uc_mcontext.gregs[REG_ESP]; # elif defined(__arm__) ptrace_sp = ptrace_ctx.uc_mcontext.arm_sp; +# elif defined(__riscv) && __riscv_xlen == 64 + ptrace_sp = ptrace_ctx.uc_mcontext.__gregs[REG_SP]; # endif if (ptrace_sp != 0) { @@ -1477,6 +1510,8 @@ write_thread_list_stream(minidump_writer_t *writer, minidump_directory_t *dir) sp = (uint32_t)uctx->uc_mcontext.gregs[REG_ESP]; # elif defined(__arm__) sp = uctx->uc_mcontext.arm_sp; +# elif defined(__riscv) && __riscv_xlen == 64 + sp = uctx->uc_mcontext.__gregs[REG_SP]; # endif SENTRY_DEBUGF("Thread %u: has context, SP=0x%llx", @@ -2143,6 +2178,11 @@ walk_indirect_registers_for_tid(minidump_writer_t *writer, pid_t tid, sentry__indirect_consider(acc, base, (uint64_t)(uint32_t)uctx->uc_mcontext.gregs[x86_gprs[i]], ops); } +# elif defined(__riscv) && __riscv_xlen == 64 + for (int r = 1; r < 32; r++) { + sentry__indirect_consider( + acc, base, (uint64_t)uctx->uc_mcontext.__gregs[r], ops); + } # elif defined(__arm__) // ARMv7's mcontext fields are individual struct members rather than // an array; walk them explicitly. Most heap pointers we care about diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index f27043ee13..bbc9e22b83 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -777,6 +777,10 @@ build_stacktrace_for_thread( ip = (uint64_t)thread_context->uc_mcontext.arm_pc; fp = (uint64_t)thread_context->uc_mcontext.arm_fp; sp = (uint64_t)thread_context->uc_mcontext.arm_sp; +# elif defined(__riscv) && __riscv_xlen == 64 + ip = (uint64_t)thread_context->uc_mcontext.__gregs[REG_PC]; + fp = (uint64_t)thread_context->uc_mcontext.__gregs[REG_S0]; + sp = (uint64_t)thread_context->uc_mcontext.__gregs[REG_SP]; # endif #elif defined(SENTRY_PLATFORM_MACOS) # if defined(__x86_64__) @@ -1170,6 +1174,14 @@ build_stacktrace_for_thread( break; } } +#elif defined(__riscv) && __riscv_xlen == 64 + // RISC-V frame records precede the CFA held in s0 + if (!read_stack_value(stack_buf, stack_start, stack_size, + current_fp - 2 * sizeof(uintptr_t), &saved_fp) + || !read_stack_value(stack_buf, stack_start, stack_size, + current_fp - sizeof(uintptr_t), &return_addr)) { + break; + } #else // Read saved frame pointer and return address // Frame layout: [FP] = saved FP, [FP + pointer size] = return addr @@ -1285,7 +1297,8 @@ extract_elf_build_id_for_module( } // Read ELF header -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Ehdr ehdr; # else Elf32_Ehdr ehdr; @@ -1322,7 +1335,8 @@ extract_elf_build_id_for_module( return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Shdr *sections = (Elf64_Shdr *)shdr_buf; # else Elf32_Shdr *sections = (Elf32_Shdr *)shdr_buf; @@ -1418,7 +1432,8 @@ elf_locate_symtab(const char *path, int allow_dynsym, sym_source_t *src, return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Ehdr ehdr; # else Elf32_Ehdr ehdr; @@ -1449,7 +1464,8 @@ elf_locate_symtab(const char *path, int allow_dynsym, sym_source_t *src, return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Shdr *sections = (Elf64_Shdr *)shdr_buf; # else Elf32_Shdr *sections = (Elf32_Shdr *)shdr_buf; @@ -1652,7 +1668,8 @@ sym_source_lookup(const sym_source_t *src, uint64_t sym_target, char *name_out, return 0; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) Elf64_Sym chunk[256]; # else Elf32_Sym chunk[256]; @@ -1676,7 +1693,8 @@ sym_source_lookup(const sym_source_t *src, uint64_t sym_target, char *name_out, } for (size_t k = 0; k < n; k++) { -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) unsigned char sym_type = ELF64_ST_TYPE(chunk[k].st_info); # else unsigned char sym_type = ELF32_ST_TYPE(chunk[k].st_info); diff --git a/src/backends/native/sentry_crash_handler.c b/src/backends/native/sentry_crash_handler.c index db8ce22919..68d0d87d77 100644 --- a/src/backends/native/sentry_crash_handler.c +++ b/src/backends/native/sentry_crash_handler.c @@ -324,6 +324,8 @@ capture_frame_pointer_stack( fp = (uintptr_t)uctx->uc_mcontext.gregs[REG_EBP]; # elif defined(__aarch64__) fp = (uintptr_t)uctx->uc_mcontext.regs[29]; +# elif defined(__riscv) && __riscv_xlen == 64 + fp = (uintptr_t)uctx->uc_mcontext.__gregs[REG_S0]; # endif capture->frame_pointer = true; while (fp && fp >= sp && fp - sp <= SENTRY_CRASH_MAX_STACK_CAPTURE @@ -331,7 +333,14 @@ capture_frame_pointer_stack( && capture->count < SENTRY_CRASH_MAX_BACKTRACE_FRAMES) { uintptr_t pair[2]; struct iovec local = { pair, sizeof(pair) }; - struct iovec remote = { (void *)fp, sizeof(pair) }; + uintptr_t record = fp; +# if defined(__riscv) && __riscv_xlen == 64 + record -= sizeof(pair); + if (record < sp) { + break; + } +# endif + struct iovec remote = { (void *)record, sizeof(pair) }; if (syscall(SYS_process_vm_readv, getpid(), &local, 1, &remote, 1, 0) != (ssize_t)sizeof(pair)) { break; diff --git a/src/sentry_elf.h b/src/sentry_elf.h index bb80d3f019..8312c4debd 100644 --- a/src/sentry_elf.h +++ b/src/sentry_elf.h @@ -56,7 +56,8 @@ sentry__elf_is_file(const char *path) static inline bool sentry__elf_is_native_class(const unsigned char e_ident[EI_NIDENT]) { -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) return e_ident[EI_CLASS] == ELFCLASS64; # else return e_ident[EI_CLASS] == ELFCLASS32; @@ -71,7 +72,8 @@ sentry__elf_has_shdr_size( return false; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) return e_shentsize == sizeof(Elf64_Shdr); # else return e_shentsize == sizeof(Elf32_Shdr); @@ -86,7 +88,8 @@ sentry__elf_has_sym_entsize( return false; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) return sh_entsize == sizeof(Elf64_Sym); # else return sh_entsize == sizeof(Elf32_Sym); @@ -101,7 +104,8 @@ sentry__elf_has_phdr_size( return false; } -# if defined(__x86_64__) || defined(__aarch64__) +# if defined(__x86_64__) || defined(__aarch64__) \ + || (defined(__riscv) && __riscv_xlen == 64) return e_phentsize == sizeof(Elf64_Phdr); # else return e_phentsize == sizeof(Elf32_Phdr); diff --git a/src/sentry_os.c b/src/sentry_os.c index b6296dad73..af65a84aae 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -1035,6 +1035,17 @@ sentry__build_registers(const sentry_ucontext_t *uctx) sentry_value_set_by_key(registers, "cpsr", sentry__value_new_addr(uctx->user_context->uc_mcontext.arm_cpsr)); +# elif defined(__riscv) && __riscv_xlen == 64 + + static const char *names[] + = { "pc", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", + "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", + "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" }; + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + sentry_value_set_by_key(registers, names[i], + sentry__value_new_addr(uctx->user_context->uc_mcontext.__gregs[i])); + } + # endif # undef SET_REG diff --git a/src/sentry_os.h b/src/sentry_os.h index 90bce1c847..aa5fe55fe0 100644 --- a/src/sentry_os.h +++ b/src/sentry_os.h @@ -26,6 +26,8 @@ sentry__ucontext_get_ip(const ucontext_t *uctx) return (uintptr_t)uctx->uc_mcontext.arm_pc; # elif defined(__aarch64__) return (uintptr_t)uctx->uc_mcontext.pc; +# elif defined(__riscv) && __riscv_xlen == 64 + return (uintptr_t)uctx->uc_mcontext.__gregs[REG_PC]; # else (void)uctx; return 0; @@ -43,6 +45,8 @@ sentry__ucontext_get_sp(const ucontext_t *uctx) return (uintptr_t)uctx->uc_mcontext.arm_sp; # elif defined(__aarch64__) return (uintptr_t)uctx->uc_mcontext.sp; +# elif defined(__riscv) && __riscv_xlen == 64 + return (uintptr_t)uctx->uc_mcontext.__gregs[REG_SP]; # else (void)uctx; return 0; diff --git a/tests/build_config.py b/tests/build_config.py index b0ecec3f3f..727e459a0b 100644 --- a/tests/build_config.py +++ b/tests/build_config.py @@ -62,6 +62,16 @@ def get_platform_cmake_args(): "-DCMAKE_ASM_COMPILER=arm-linux-gnueabihf-gcc", ] ) + elif sys.platform == "linux" and os.environ.get("TEST_RISCV"): + args.extend( + [ + "-DCMAKE_SYSTEM_NAME=Linux", + "-DCMAKE_SYSTEM_PROCESSOR=riscv64", + "-DCMAKE_C_COMPILER=riscv64-linux-gnu-gcc", + "-DCMAKE_CXX_COMPILER=riscv64-linux-gnu-g++", + "-DCMAKE_ASM_COMPILER=riscv64-linux-gnu-gcc", + ] + ) elif sys.platform == "linux" and os.environ.get("TEST_WINE"): cc = os.environ.get("CC") or "clang" cxx = os.environ.get("CXX") or "clang++" diff --git a/tests/conditions.py b/tests/conditions.py index 04e21514fc..8d4cc7ddb7 100644 --- a/tests/conditions.py +++ b/tests/conditions.py @@ -7,6 +7,7 @@ is_wine = bool(os.environ.get("TEST_WINE")) is_x86 = os.environ.get("TEST_X86") is_arm32 = bool(os.environ.get("TEST_ARM32")) +is_riscv = bool(os.environ.get("TEST_RISCV")) is_qemu = bool(os.environ.get("TEST_QEMU")) # opt-in because the tests change the user's system proxy settings (with sudo on macOS) has_system_proxy = bool(os.environ.get("TEST_SYSTEM_PROXY")) @@ -41,6 +42,8 @@ and not is_aix and not is_tsan and not is_wine + # ptrace is unavailable under RISC-V user-mode emulation + and not (is_riscv and is_qemu) ) # android has no local filesystem has_files = not is_android diff --git a/tests/requirements.txt b/tests/requirements.txt index 63129bdac1..36411d772d 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -4,7 +4,7 @@ pytest-httpserver==1.0.10 flaky==3.8.1 msgpack==1.2.1 pytest-xdist==3.8.0 -clang-format==20.1.5 +clang-format==20.1.5; platform_machine != "riscv64" pywin32==311; sys_platform == "win32" psutil==7.1.1 # For E2E tests that call Sentry API diff --git a/tests/test_dotnet_signals.py b/tests/test_dotnet_signals.py index 196dfcec77..0de2ae33c9 100644 --- a/tests/test_dotnet_signals.py +++ b/tests/test_dotnet_signals.py @@ -13,6 +13,8 @@ has_native, is_android, is_arm32, + is_qemu, + is_riscv, is_tsan, is_wine, is_x86, @@ -22,7 +24,8 @@ project_fixture_path = pathlib.Path("tests/fixtures/dotnet_signal") pytestmark = pytest.mark.skipif( - is_wine, reason=".NET signal tests do not support Windows targets under Wine" + is_wine or (is_riscv and is_qemu), + reason=".NET signal tests do not support Wine or emulated RISC-V targets", ) diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index 8ff820d2e4..2aa36c8ab0 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -773,6 +773,7 @@ def _parse_minidump(path): is_arm64 = cpu_arch in (0x8003, 0xC) is_amd64 = cpu_arch == 9 is_x86 = cpu_arch == 0 + is_riscv = cpu_arch == 0x8006 threads = [] if 3 in streams: # ThreadList @@ -799,6 +800,9 @@ def _parse_minidump(path): elif is_x86: # CONTEXT_X86: esp at offset 0xC4. sp = struct.unpack_from("/` and `include/tdep-/` directories from the diff --git a/vendor/stb_sprintf.c b/vendor/stb_sprintf.c index d60a91bae9..6cbd0f5ec1 100644 --- a/vendor/stb_sprintf.c +++ b/vendor/stb_sprintf.c @@ -1,2 +1,5 @@ +#include + +#define stbsp__uintptr uintptr_t #define STB_SPRINTF_IMPLEMENTATION #include "stb_sprintf.h" From 410961a859ee1b3380ceeea7121b11aa71402f6b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 21 Oct 2026 09:42:30 +0200 Subject: [PATCH 2/9] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b67cba65..4ae17410ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Native: Provide exception metadata, registers, and a basic crashing-thread stack to `on_crash` for crash identification. ([#2174](https://github.com/getsentry/sentry-native/pull/2174)) - Native: reduce disk I/O on scope changes by sending incremental updates to the crash daemon over IPC. ([#2155](https://github.com/getsentry/sentry-native/pull/2155), [#2157](https://github.com/getsentry/sentry-native/pull/2157)) - Native: install handlers for the daemon's own fatal signals and unhandled exceptions, allowing best-effort recovery actions such as dumping pending envelopes before termination. ([#2220](https://github.com/getsentry/sentry-native/pull/2220)) +- Linux: add RISC-V support. ([#2232](https://github.com/getsentry/sentry-native/pull/2232)) **Fixes**: From df9a24a942a2f8f2323b35f6b7fca86dbc5b0ac3 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 21 Oct 2026 09:56:11 +0200 Subject: [PATCH 3/9] context.version --- src/backends/native/minidump/sentry_minidump_linux.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backends/native/minidump/sentry_minidump_linux.c b/src/backends/native/minidump/sentry_minidump_linux.c index b80ac671ab..885a8b9ee7 100644 --- a/src/backends/native/minidump/sentry_minidump_linux.c +++ b/src/backends/native/minidump/sentry_minidump_linux.c @@ -875,6 +875,7 @@ write_thread_context( minidump_context_riscv64_t context = { 0 }; context.context_flags = 0x08000003; // RISCV64 | Integer | Floating point + context.version = 1; memcpy(context.regs, uctx->uc_mcontext.__gregs, sizeof(context.regs)); memcpy(context.fpregs, uctx->uc_mcontext.__fpregs.__d.__f, sizeof(context.fpregs)); From 717a4345402e0643b6a83f3898a6a6ce5d884dbe Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 21 Oct 2026 10:10:32 +0200 Subject: [PATCH 4/9] llvm --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76fda8d055..6eab530854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -380,7 +380,7 @@ jobs: sudo dpkg --add-architecture riscv64 sudo apt-get update sudo apt-get install -y \ - cmake llvm \ + cmake \ gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ zlib1g-dev:riscv64 libssl-dev:riscv64 \ libcurl4-openssl-dev:riscv64 liblzma-dev:riscv64 \ From 138e66ecb7d6682fec2a90993def9456c5d9a4b3 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 21 Oct 2026 10:12:37 +0200 Subject: [PATCH 5/9] setup-qemu-action for riscv64 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6eab530854..fa8f85b081 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -388,9 +388,9 @@ jobs: echo "PKG_CONFIG_LIBDIR=/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV" - uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 - if: ${{ runner.os == 'Linux' && env['TEST_QEMU'] && !matrix.container }} + if: ${{ runner.os == 'Linux' && env['TEST_RISCV'] && !matrix.container }} with: - platforms: ${{ env['TEST_ARM32'] && 'arm' || 'riscv64' }} + platforms: riscv64 - name: Installing Alpine Linux Dependencies if: ${{ contains(matrix.container, 'alpine') }} From 0c82a80e1bedc9f8bd2fd90f4929cb6e23106f11 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 21 Oct 2026 10:20:02 +0200 Subject: [PATCH 6/9] tweak skip conditions --- tests/conditions.py | 2 -- tests/test_integration_crashpad.py | 12 ++++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/conditions.py b/tests/conditions.py index 8d4cc7ddb7..2c0bd567ee 100644 --- a/tests/conditions.py +++ b/tests/conditions.py @@ -42,8 +42,6 @@ and not is_aix and not is_tsan and not is_wine - # ptrace is unavailable under RISC-V user-mode emulation - and not (is_riscv and is_qemu) ) # android has no local filesystem has_files = not is_android diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 41015d439c..9a9edf6042 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -23,7 +23,14 @@ REPLAY_ID, SENTRY_VERSION, ) -from .conditions import has_crashpad, has_files, has_oom, has_system_proxy +from .conditions import ( + has_crashpad, + has_files, + has_oom, + has_system_proxy, + is_qemu, + is_riscv, +) from .proxy import ( setup_proxy_env_vars, cleanup_proxy_env_vars, @@ -47,8 +54,9 @@ wait_for_file, ) +# ptrace is unavailable under RISC-V user-mode emulation pytestmark = pytest.mark.skipif( - not has_crashpad, + not has_crashpad or (is_riscv and is_qemu), reason="Tests need a crashpad backend and a valid environment for it", ) From 0f7aaf5555025aebcabf7c3181599c08ee9ee1c6 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 21 Oct 2026 10:21:56 +0200 Subject: [PATCH 7/9] gcc < 14 --- src/backends/native/sentry_crash_daemon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index bbc9e22b83..c746198569 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -1182,6 +1182,13 @@ build_stacktrace_for_thread( current_fp - sizeof(uintptr_t), &return_addr)) { break; } + // GCC < 14 leaf frames save only s0, so the caller is in ra + if (walk_count == 0 && return_addr >= stack_start + && return_addr < stack_end) { + saved_fp = return_addr; + return_addr + = (uint64_t)thread_context->uc_mcontext.__gregs[REG_RA]; + } #else // Read saved frame pointer and return address // Frame layout: [FP] = saved FP, [FP + pointer size] = return addr From 766bbd4b82378df6ef89927c88bf068c2b060072 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 29 Oct 2026 13:21:34 +0100 Subject: [PATCH 8/9] ci: restore qemu-user for Arm32 --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7ed5b3d17..e7b482e737 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -374,6 +374,12 @@ jobs: liblzma-dev:armhf \ libstdc++6:armhf + - name: Installing qemu-user + if: ${{ runner.os == 'Linux' && env['TEST_ARM32'] && !matrix.container }} + run: | + sudo apt update + sudo apt install -y qemu-user-static binfmt-support + - name: Installing Linux RISC-V Dependencies if: ${{ runner.os == 'Linux' && env['TEST_RISCV'] && !matrix.container }} run: | From 5a7aea62f9657129922e3a3f14a03c9cce366e79 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 29 Oct 2026 13:27:40 +0100 Subject: [PATCH 9/9] clarify & restore original phrasing --- CHANGELOG.md | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 665cbf5317..aa8f7ea84a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ - Native: Provide exception metadata, registers, and a basic crashing-thread stack to `on_crash` for crash identification. ([#2174](https://github.com/getsentry/sentry-native/pull/2174)) - Native: reduce disk I/O on scope changes by sending incremental updates to the crash daemon over IPC. ([#2155](https://github.com/getsentry/sentry-native/pull/2155), [#2157](https://github.com/getsentry/sentry-native/pull/2157)) - Native: install handlers for the daemon's own fatal signals and unhandled exceptions, allowing best-effort recovery actions such as dumping pending envelopes before termination. ([#2220](https://github.com/getsentry/sentry-native/pull/2220)) -- Linux: add RISC-V support. ([#2232](https://github.com/getsentry/sentry-native/pull/2232)) +- Linux: add RISC-V 64 support. ([#2232](https://github.com/getsentry/sentry-native/pull/2232)) - Report the host OS in the OS context under Wine and Proton, instead of the Windows version that Wine emulates. On Linux, prefer the host `os-release` inside Steam Runtime and Flatpak containers, and report SteamOS and Bazzite as the OS name. ([#2241](https://github.com/getsentry/sentry-native/pull/2241)) - Add `sentry_scope_set_trace` for per-scope traces. ([#2236](https://github.com/getsentry/sentry-native/pull/2236)) - Add `sentry_continue_trace` to continue incoming traces without a transaction. ([#2237](https://github.com/getsentry/sentry-native/pull/2237)) diff --git a/README.md b/README.md index 1bafb6d971..3ad98fa43a 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ The SDK bundle contains the following folders: ## Platform and Feature Support -The SDK is fully supported and tested on the following OS/Compiler variations: +The SDK currently supports and is tested on the following OS/Compiler variations: - Linux (x86, x64 and arm64) with GCC 9+ and Clang 11+ - Windows (x86, x64 and arm64) with MSVC (Visual Studio 2022+) and LLVM-MinGW @@ -83,7 +83,7 @@ Additionally, the SDK should support the following platforms, although they are not fully tested, so breakage may occur: - Windows versions lower than Windows 10 / Windows Server 2016: not tested in CI. -- Linux arm32 and riscv: cross-compiled in CI, with only a subset of non-crash +- Linux arm32 and riscv64: cross-compiled in CI, with only a subset of non-crash tests run under QEMU. The SDK supports different features on the target platform: