diff --git a/.craft.yml b/.craft.yml index 6908dce36..fd2d7da4e 100644 --- a/.craft.yml +++ b/.craft.yml @@ -5,7 +5,9 @@ targets: - name: registry sdks: github:getsentry/sentry-native: + maven:io.sentry:sentry-native-ndk: - name: gcs + includeNames: /^(sentry-native\.zip)$/ bucket: sentry-sdk-assets paths: - path: /sentry-native/{{version}}/ @@ -14,5 +16,15 @@ targets: - path: /sentry-native/latest/ metadata: cacheControl: public, max-age=600 + - name: maven + includeNames: /^(sentry-native-ndk-).*\.zip$/ + mavenCliPath: scripts/mvnw + mavenSettingsPath: scripts/settings.xml + mavenRepoId: ossrh + mavenRepoUrl: https://oss.sonatype.org/service/local/staging/deploy/maven2/ + android: + distDirRegex: /^(sentry-native-ndk).*$/ + fileReplaceeRegex: /\d+\.\d+\.\d+(-\w+(\.\d+)?)?(-SNAPSHOT)?/ + fileReplacerStr: release.aar requireNames: - /^sentry-native.zip$/ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 98b60a2bc..2a5bd8a49 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: "" -labels: "" +labels: "Platform: Native" assignees: "" --- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9971ebcc2..97859cef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,21 +5,29 @@ on: branches: - master - "release/**" + paths-ignore: + - "*.md" pull_request: + paths-ignore: + - "*.md" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: lint: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: make style build-ios: name: Xcode Build for iOS runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: "recursive" - run: | @@ -31,125 +39,148 @@ jobs: fail-fast: false matrix: include: - - name: Linux (gcc 7, 32-bit) - os: ubuntu-18.04 + - name: Linux (GCC 7, 32-bit) + os: ubuntu-20.04 CC: gcc-7 CXX: g++-7 TEST_X86: 1 - - name: Linux (gcc 10) - os: ubuntu-20.04 - CC: gcc-10 - CXX: g++-10 + - name: Linux (GCC 12) + os: ubuntu-22.04 + CC: gcc-12 + CXX: g++-12 # ERROR_ON_WARNINGS: 1 # The GCC analyzer 10.0.1 (as on CI) has an internal compiler error # currently, and is not stable enough to activate. # RUN_ANALYZER: gcc - - name: Linux (clang 11 + asan + llvm-cov) - os: ubuntu-20.04 - CC: clang-11 - CXX: clang++-11 + - name: Linux (clang 15 + asan + llvm-cov) + os: ubuntu-22.04 + CC: clang-15 + CXX: clang++-15 ERROR_ON_WARNINGS: 1 RUN_ANALYZER: asan,llvm-cov - - name: Linux (clang 11 + kcov) - os: ubuntu-20.04 - CC: clang-11 - CXX: clang++-11 + - name: Linux (clang 15 + kcov) + os: ubuntu-22.04 + CC: clang-15 + CXX: clang++-15 ERROR_ON_WARNINGS: 1 RUN_ANALYZER: kcov - - name: Linux (code-checker + valgrind) - os: ubuntu-20.04 + - name: Linux (gcc 12 + code-checker + valgrind) + os: ubuntu-22.04 RUN_ANALYZER: code-checker,valgrind - - name: macOS (xcode llvm) - os: macOs-latest + - name: macOS 14 (xcode llvm) + os: macos-14 + ERROR_ON_WARNINGS: 1 + SYSTEM_VERSION_COMPAT: 0 + - name: macOS 13 (xcode llvm) + os: macos-13 ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 - - name: macOS (xcode llvm + universal) - os: macOs-latest + - name: macOS 14 (xcode llvm + universal) + os: macos-14 ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 CMAKE_DEFINES: -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 - - name: macOS (clang 11 + asan + llvm-cov) - os: macOs-latest + - name: macOS 15 (clang 18 + asan + llvm-cov) + os: macos-15-large CC: clang CXX: clang++ ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 RUN_ANALYZER: asan,llvm-cov - - name: Windows (VS2017, 32bit) - os: vs2017-win2016 + - name: Windows (old VS, 32-bit) + os: windows-2019 TEST_X86: 1 - name: Windows (latest) os: windows-latest + - name: LLVM-Mingw + os: windows-latest + TEST_MINGW: 1 + MINGW_PKG_PREFIX: x86_64-w64-mingw32 + MINGW_ASM_MASM_COMPILER: llvm-ml;-m64 # The Android emulator is currently only available on macos, see: # https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/android?view=azure-devops#test-on-the-android-emulator - - name: Android (API 16, NDK 20) - os: macOs-latest - ANDROID_API: 16 - ANDROID_NDK: 20.1.5948944 - - name: Android (API 30, NDK 22) - os: macOs-latest - ANDROID_API: 30 - ANDROID_NDK: 22.1.7171670 + # TODO: switch to reactivecircus/android-emulator-runner, concurrently running emulators continuously fail now. + # - name: Android (API 31, NDK 23) + # os: macos-15-large + # ANDROID_API: 31 + # ANDROID_NDK: 23.2.8568313 + # ANDROID_ARCH: x86_64 + - name: Android (API 35, NDK 27) + os: macos-15-large + ANDROID_API: 35 + ANDROID_NDK: 27.2.12479018 + ANDROID_ARCH: x86_64 name: ${{ matrix.name }} runs-on: ${{ matrix.os }} env: TEST_X86: ${{ matrix.TEST_X86 }} + TEST_MINGW: ${{ matrix.TEST_MINGW }} ERROR_ON_WARNINGS: ${{ matrix.ERROR_ON_WARNINGS }} RUN_ANALYZER: ${{ matrix.RUN_ANALYZER }} ANDROID_API: ${{ matrix.ANDROID_API }} ANDROID_NDK: ${{ matrix.ANDROID_NDK }} + ANDROID_ARCH: ${{ matrix.ANDROID_ARCH }} CMAKE_DEFINES: ${{ matrix.CMAKE_DEFINES }} SYSTEM_VERSION_COMPAT: ${{ matrix.SYSTEM_VERSION_COMPAT }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" - name: Installing Linux Dependencies if: ${{ runner.os == 'Linux' && !env['TEST_X86'] }} - # workaround: https://github.com/actions/virtual-environments/issues/1536#issuecomment-698537248 run: | - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main' -y sudo apt update - sudo apt install cmake clang-11 clang-tools llvm kcov g++-10 valgrind zlib1g-dev libcurl4-openssl-dev + sudo apt install cmake clang-14 clang-tools llvm kcov g++-12 valgrind zlib1g-dev libcurl4-openssl-dev - name: Installing Linux 32-bit Dependencies if: ${{ runner.os == 'Linux' && env['TEST_X86'] }} run: | sudo dpkg --add-architecture i386 sudo apt update - sudo apt install cmake gcc-multilib g++-multilib zlib1g-dev:i386 libssl-dev:i386 libcurl4-openssl-dev:i386 + sudo apt install cmake gcc-7-multilib g++-7-multilib zlib1g-dev:i386 libssl-dev:i386 libcurl4-openssl-dev:i386 + + # https://github.com/actions/runner-images/issues/9491 + - name: Decrease vm.mmap_rnd_bit to prevent ASLR ASAN issues + if: ${{ runner.os == 'Linux' && contains(env['RUN_ANALYZER'], 'asan') }} + run: sudo sysctl vm.mmap_rnd_bits=28 - name: Installing CodeChecker if: ${{ contains(env['RUN_ANALYZER'], 'code-checker') }} - run: | - sudo apt install clang-tidy curl doxygen gcc-multilib libxml2-dev libxslt1-dev python3-dev python3-virtualenv - git clone https://github.com/Ericsson/CodeChecker.git --depth 1 --branch v6.15.0 - cd CodeChecker - BUILD_LOGGER_64_BIT_ONLY=YES BUILD_UI_DIST=NO make standalone_package - echo "$PWD/build/CodeChecker/bin" >> $GITHUB_PATH + run: sudo snap install codechecker --classic - - name: Expose llvm PATH for Mac + - name: Expose llvm@15 PATH for Mac if: ${{ runner.os == 'macOS' }} - run: echo "/usr/local/opt/llvm/bin/" >> $GITHUB_PATH - - - name: Installing Android SDK Dependencies - if: ${{ env['ANDROID_API'] }} - run: | - export ANDROID_IMAGE="system-images;android-$ANDROID_API;google_apis;x86" - echo "Downloading ndk;$ANDROID_NDK and $ANDROID_IMAGE" - echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install \ - "ndk;$ANDROID_NDK" "$ANDROID_IMAGE" | \ - grep -v "\[=" || true # suppress the progress bar, so we get meaningful logs + run: echo $(brew --prefix llvm@15)/bin >> $GITHUB_PATH + + - name: Expose llvm@18 PATH for Mac + if: ${{ runner.os == 'macOS' && matrix.os == 'macos-15-large' && matrix.RUN_ANALYZER == 'asan,llvm-cov' }} + run: echo $(brew --prefix llvm@18)/bin >> $GITHUB_PATH + + - name: Installing LLVM-MINGW Dependencies + if: ${{ runner.os == 'Windows' && env['TEST_MINGW'] }} + shell: powershell + env: + MINGW_PKG_PREFIX: ${{ matrix.MINGW_PKG_PREFIX }} + MINGW_ASM_MASM_COMPILER: ${{ matrix.MINGW_ASM_MASM_COMPILER }} + run: . "scripts\install-llvm-mingw.ps1" + + - name: Set up zlib for Windows + if: ${{ runner.os == 'Windows' }} + shell: powershell + run: . "scripts\install-zlib.ps1" - name: Starting Android Simulator if: ${{ env['ANDROID_API'] }} run: bash scripts/start-android.sh + timeout-minutes: 20 - name: Test shell: bash @@ -161,9 +192,12 @@ jobs: - name: "Upload to codecov.io" if: ${{ contains(env['RUN_ANALYZER'], 'cov') }} - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4.5.0 with: directory: coverage + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false + verbose: true archive: name: Create Release Archive @@ -173,19 +207,35 @@ jobs: # run on master or the release branch if: ${{ needs.test.result == 'success' && github.event_name == 'push' }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive + - name: Setup Java Version + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@579fbbe7221704325eb4c4d4bf20c2b0859fba76 # pin@v3 + with: + gradle-home-cache-cleanup: true + - name: Create source archive run: | rm -rf build .c* .e* .git* scripts Makefile external/breakpad/src/tools external/breakpad/src/processor zip -r sentry-native.zip . - - name: Upload source artifact - uses: actions/upload-artifact@v2 + - name: Build NDK artifacts + working-directory: ndk + run: ./gradlew clean distZip + + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: name: ${{ github.sha }} - # When downloading artifacts, they are double-zipped: - # https://github.com/actions/upload-artifact#zipped-artifact-downloads - path: sentry-native.zip + if-no-files-found: error + path: | + ndk/lib/build/distributions/*.zip + sentry-native.zip diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..7963a37aa --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,67 @@ +name: 'CodeQL' + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '17 23 * * 3' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['cpp', 'java'] + + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Initialize CodeQL + uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # pin@v2 + with: + languages: ${{ matrix.language }} + + - name: Installing Linux Dependencies + run: | + sudo apt update + sudo apt install cmake clang-14 clang-tools llvm kcov g++-12 valgrind zlib1g-dev libcurl4-openssl-dev + + - if: matrix.language == 'java' + name: Setup Java Version + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - if: matrix.language == 'java' + name: Setup Gradle + uses: gradle/actions/setup-gradle@bb0c460cbf5354b0cddd15bacdf0d6aaa3e5a32b # pin@v3 + with: + gradle-home-cache-cleanup: true + + - if: matrix.language == 'java' + name: Build for Android NDK + working-directory: ./ndk + run: | + ./gradlew compileJava + + - if: matrix.language == 'cpp' + name: Build sentry-native + run: | + cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo && cmake --build build --parallel + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # pin@v2 diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 000000000..000b75ff3 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,9 @@ +name: Danger + +on: + pull_request: + types: [opened, synchronize, reopened, edited, ready_for_review] + +jobs: + danger: + uses: getsentry/github-workflows/.github/workflows/danger.yml@v2 diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml new file mode 100644 index 000000000..b33197471 --- /dev/null +++ b/.github/workflows/enforce-license-compliance.yml @@ -0,0 +1,16 @@ +name: Enforce License Compliance + +on: + push: + branches: [master, main, release/*] + pull_request: + branches: [master, main] + +jobs: + enforce-license-compliance: + runs-on: ubuntu-latest + steps: + - name: 'Enforce License Compliance' + uses: getsentry/action-enforce-license-compliance@main + with: + fossa_api_key: ${{ secrets.FOSSA_API_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de2a8c9b8..ee60c16bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest name: "Release a new version" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: token: ${{ secrets.GH_RELEASE_PAT }} fetch-depth: 0 diff --git a/.gitignore b/.gitignore index f5c8ce904..cf2764d18 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,11 @@ .DS_Store .vs CMakeSettings.json +.idea -# CMake builds -/build -/build* -leak-build +# CMake builds & install +/*build* +/*install* # Run files .sentry-native @@ -17,6 +17,14 @@ __pycache__ # Devtools CodeChecker +.ccls-cache # Coverage coverage + +# Gradle / Java +local.properties +.gradle +.cxx +build + diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100755 index 000000000..9ac51e497 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.io.*; +import java.net.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + + "/maven-wrapper-" + + WRAPPER_VERSION + + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use + * instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** Path where the maven-wrapper.jar will be saved to. */ + private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + + outputFile.getParentFile().getAbsolutePath() + + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault( + new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100755 index 000000000..2cc7d4a55 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 000000000..642d572ce --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ffbe090..ec6e85354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,465 @@ # Changelog +## 0.7.12 + +**Features**: + +- Add `sentry_capture_minidump()` to capture independently created minidumps ([#1067](https://github.com/getsentry/sentry-native/pull/1067)) + +**Fixes**: + +- Add breadcrumb ringbuffer to avoid O(n) memmove on adding more than max breadcrumbs ([#1060](https://github.com/getsentry/sentry-native/pull/1060)) + +## 0.7.11 + +**Fixes**: + +- Reject invalid trace- and span-ids in context update from header ([#1046](https://github.com/getsentry/sentry-native/pull/1046)) +- Lookup `GetSystemTimePreciseAsFileTime()` at runtime and fall back to `GetSystemTimeAsFileTime()` to allow running on Windows < 8. ([#1051](https://github.com/getsentry/sentry-native/pull/1051)) +- Allow for empty DSN to still initialize crash handler ([#1059](https://github.com/getsentry/sentry-native/pull/1059)) + +## 0.7.10 + +**Fixes**: + +- Correct the timestamp resolution to microseconds on Windows. ([#1039](https://github.com/getsentry/sentry-native/pull/1039)) + +## 0.7.9 + +**Fixes**: + +- Check file-writer construction when writing envelope to path. ([#1036](https://github.com/getsentry/sentry-native/pull/1036)) + +## 0.7.8 + +**Features**: + +- Let the envelope serialization stream directly to the file. ([#1021](https://github.com/getsentry/sentry-native/pull/1021)) +- Support 16kb page sizes on Android 15. ([#1028](https://github.com/getsentry/sentry-native/pull/1028)) + +## 0.7.7 + +**Fixes**: + +- Further clean up of the exported dependency configuration. ([#1013](https://github.com/getsentry/sentry-native/pull/1013), [crashpad#106](https://github.com/getsentry/crashpad/pull/106)) +- Clean-up scope flushing synchronization in crashpad-backend. ([#1019](https://github.com/getsentry/sentry-native/pull/1019), [crashpad#109](https://github.com/getsentry/crashpad/pull/109)) +- Rectify user-feedback comment parameter guard. ([#1020](https://github.com/getsentry/sentry-native/pull/1020)) + +**Internal**: + +- Updated `crashpad` to 2024-06-11. ([#1014](https://github.com/getsentry/sentry-native/pull/1014), [crashpad#105](https://github.com/getsentry/crashpad/pull/105)) + +**Thank you**: + +- [@JonLiu1993](https://github.com/JonLiu1993) +- [@dg0yt](https://github.com/dg0yt) +- [@stima](https://github.com/stima) + +## 0.7.6 + +**Fixes**: + +- Remove remaining build blockers for the `crashpad` backend on Windows ARM64 when using LLVM-MINGW. ([#1003](https://github.com/getsentry/sentry-native/pull/1003), [crashpad#101](https://github.com/getsentry/crashpad/pull/101)) +- Ensure `crashpad` targets are included when building as a shared library using our exported CMake config. ([#1007](https://github.com/getsentry/sentry-native/pull/1007)) +- Use `find_dependency()` instead of `find_package()` in the exported CMake config. ([#1007](https://github.com/getsentry/sentry-native/pull/1007), [#1008](https://github.com/getsentry/sentry-native/pull/1008), [crashpad#104](https://github.com/getsentry/crashpad/pull/104)) + +**Thank you**: + +- [@past-due](https://github.com/past-due) +- [@podlaszczyk](https://github.com/podlaszczyk) + +## 0.7.5 + +**Features**: + +- Change the timestamp resolution to microseconds. ([#995](https://github.com/getsentry/sentry-native/pull/995)) + +**Internal**: + +- (Android) Switch ndk back to `libc++_static`, and hide it from prefab ([#996](https://github.com/getsentry/sentry-native/pull/996)) + +## 0.7.4 + +**Fixes**: + +- Allow `crashpad` to run under [Epic's Anti-Cheat Client](https://dev.epicgames.com/docs/game-services/anti-cheat/using-anti-cheat#external-crash-dumpers) by deferring the full `crashpad_handler` access rights to the client application until a crash occurred. ([#980](https://github.com/getsentry/sentry-native/pull/980), [crashpad#99](https://github.com/getsentry/crashpad/pull/99)) +- Reserve enough stack space on Windows for our handler to run when the stack is exhausted from stack-overflow. ([#982](https://github.com/getsentry/sentry-native/pull/982)) +- Only configure a `sigaltstack` in `inproc` if no previous configuration exists on Linux and Android. ([#982](https://github.com/getsentry/sentry-native/pull/982)) +- Store transaction `data` in the event property `extra` since the `data` property is discarded by `relay`. ([#986](https://github.com/getsentry/sentry-native/issues/986)) + +**Docs**: + +- Add compile-time flag `SENTRY_TRANSPORT_COMPRESSION` description to the `README.md` file. ([#976](https://github.com/getsentry/sentry-native/pull/976)) + +**Internal**: + +- Move sentry-android-ndk JNI related parts from sentry-java to sentry-native ([#944](https://github.com/getsentry/sentry-native/pull/944)) + This will create a pre-built `io.sentry:sentry-native-ndk` maven artifact, suitable for being consumed by Android apps. + +**Thank you**: + +- [@AenBleidd](https://github.com/AenBleidd) +- [@kristjanvalur](https://github.com/kristjanvalur) + +## 0.7.2 + +**Features**: + +- Add optional Gzip transport compression via build option `SENTRY_TRANSPORT_COMPRESSION`. Requires system `zlib`. ([#954](https://github.com/getsentry/sentry-native/pull/954)) +- Enable automatic MIME detection of attachments sent with crash-reports from the `crashpad_handler`. ([#973](https://github.com/getsentry/sentry-native/pull/973), [crashpad#98](https://github.com/getsentry/crashpad/pull/98)) + +**Fixes**: + +- Fix the Linux build when targeting RISC-V. ([#972](https://github.com/getsentry/sentry-native/pull/972)) + +**Thank you**: + +- [@Strive-Sun](https://github.com/Strive-Sun) +- [@jwinarske](https://github.com/jwinarske) + +## 0.7.1 + +**Features**: + +- Add user feedback capability to the Native SDK. ([#966](https://github.com/getsentry/sentry-native/pull/966)) + +**Internal**: + +- Remove the `CRASHPAD_WER_ENABLED` build flag. The WER module is now built for all supported Windows targets, and registration is conditional on runtime Windows version checks. ([#950](https://github.com/getsentry/sentry-native/pull/950), [crashpad#96](https://github.com/getsentry/crashpad/pull/96)) + +**Docs**: + +- Add usage of the breadcrumb `data` property to the example. [#951](https://github.com/getsentry/sentry-native/pull/951) + +## 0.7.0 + +**Breaking changes**: + +- Make `crashpad` the default backend for Linux. ([#927](https://github.com/getsentry/sentry-native/pull/927)) +- Remove build option `SENTRY_CRASHPAD_SYSTEM`. ([#928](https://github.com/getsentry/sentry-native/pull/928)) + +**Fixes**: + +- Maintain `crashpad` client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) +- Specify correct dependencies for CMake client projects using a system-provided breakpad. ([#926](https://github.com/getsentry/sentry-native/pull/926)) +- Correct the Windows header include used by `sentry.h`, which fixes the build of [Swift bindings](https://github.com/thebrowsercompany/swift-sentry). ([#935](https://github.com/getsentry/sentry-native/pull/935)) + +**Internal**: + +- Updated `crashpad` to 2023-11-24. ([#912](https://github.com/getsentry/sentry-native/pull/912), [crashpad#91](https://github.com/getsentry/crashpad/pull/91)) +- Fixing `crashpad` build for Windows on ARM64. ([#919](https://github.com/getsentry/sentry-native/pull/919), [crashpad#90](https://github.com/getsentry/crashpad/pull/90), [crashpad#92](https://github.com/getsentry/crashpad/pull/92), [crashpad#93](https://github.com/getsentry/crashpad/pull/93), [crashpad#94](https://github.com/getsentry/crashpad/pull/94)) +- Remove options memory leak during consent setting. ([#922](https://github.com/getsentry/sentry-native/pull/922)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@compnerd](https://github.com/compnerd) +- [@stima](https://github.com/stima) +- [@hyp](https://github.com/hyp) + +## 0.6.7 + +**Fixes**: + +- Disable sigaltstack on Android. ([#901](https://github.com/getsentry/sentry-native/pull/901)) +- Prevent stuck crashpad-client on Windows. ([#902](https://github.com/getsentry/sentry-native/pull/902), [crashpad#89](https://github.com/getsentry/crashpad/pull/89)) + +## 0.6.6 + +**Fixes**: + +- Use a more up-to-date version of `mini_chromium` as a `crashpad` dependency, which fixes a build error on some systems. ([#891](https://github.com/getsentry/sentry-native/pull/891), [crashpad#88](https://github.com/getsentry/crashpad/pull/88)) + +**Internal**: + +- Updated `libunwindstack` to 2023-09-13. ([#884](https://github.com/getsentry/sentry-native/pull/884), [libunwindstack-ndk#8](https://github.com/getsentry/libunwindstack-ndk/pull/8)) +- Updated `crashpad` to 2023-09-28. ([#891](https://github.com/getsentry/sentry-native/pull/891), [crashpad#88](https://github.com/getsentry/crashpad/pull/88)) +- Updated `breakpad` to 2023-10-02. ([#892](https://github.com/getsentry/sentry-native/pull/892), [breakpad#38](https://github.com/getsentry/breakpad/pull/38)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@sapphonie](https://github.com/sapphonie) + +## 0.6.5 + +**Fixes**: + +- Remove deadlock pattern in dynamic sdk-name assignment ([#858](https://github.com/getsentry/sentry-native/pull/858)) + +## 0.6.4 + +**Fixes**: + +- Crash events are initialized with level `FATAL` ([#852](https://github.com/getsentry/sentry-native/pull/852)) +- Fix MSVC compiler error with on non-Unicode systems ([#846](https://github.com/getsentry/sentry-native/pull/846), [crashpad#85](https://github.com/getsentry/crashpad/pull/85)) + +**Features**: + +- crashpad_handler: log `body` if minidump endpoint response is not `OK` ([#851](https://github.com/getsentry/sentry-native/pull/851), [crashpad#87](https://github.com/getsentry/crashpad/pull/87)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@xyz1001](https://github.com/xyz1001) + +## 0.6.3 + +**Features**: + +- Disable PC adjustment in the backend for libunwindstack ([#839](https://github.com/getsentry/sentry-native/pull/839)) +- Crashpad backend allows inspection and enrichment of the crash event in the on_crash/before_send hooks ([#843](https://github.com/getsentry/sentry-native/pull/843)) +- Add http-proxy support to the `crashpad_handler` ([#847](https://github.com/getsentry/sentry-native/pull/847), [crashpad#86](https://github.com/getsentry/crashpad/pull/86)) + +**Internal**: + +- Updated Breakpad backend to 2023-05-03. ([#836](https://github.com/getsentry/sentry-native/pull/836), [breakpad#35](https://github.com/getsentry/breakpad/pull/35)) +- Updated Crashpad backend to 2023-05-03. ([#837](https://github.com/getsentry/sentry-native/pull/837), [crashpad#82](https://github.com/getsentry/crashpad/pull/82)) + +## 0.6.2 + +**Features**: + +- Extend API with ptr/len-string interfaces. ([#827](https://github.com/getsentry/sentry-native/pull/827)) +- Allow setting sdk_name at runtime ([#834](https://github.com/getsentry/sentry-native/pull/834)) + +## 0.6.1 + +**Fixes**: + +- Remove OpenSSL as direct dependency for the crashpad backend on Linux. ([#812](https://github.com/getsentry/sentry-native/pull/812), [crashpad#81](https://github.com/getsentry/crashpad/pull/81)) +- Check `libcurl` for feature `AsynchDNS` at compile- and runtime. ([#813](https://github.com/getsentry/sentry-native/pull/813)) +- Allow setting `CRASHPAD_WER_ENABLED` when using system crashpad. ([#816](https://github.com/getsentry/sentry-native/pull/816)) + +**Docs**: + +- Add badges for conan, nix and vcpkg package-repos to README. ([#795](https://github.com/getsentry/sentry-native/pull/795)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@Cyriuz](https://github.com/Cyriuz) +- [@MartinDelille](https://github.com/MartinDelille) + +## 0.6.0 + +**Breaking changes**: + +- When built as a shared library for Android or Linux, the Native SDK limits the export of symbols to the `sentry_`-prefix. The option `SENTRY_EXPORT_SYMBOLS` is no longer available and the linker settings are constrained to the Native SDK and no longer `PUBLIC` to parent projects. ([#363](https://github.com/getsentry/sentry-native/pull/363)) + +**Features**: + +- A session may be ended with a different status code. ([#801](https://github.com/getsentry/sentry-native/pull/801)) + +**Fixes**: + +- Switch Crashpad transport on Linux to use libcurl ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#75](https://github.com/getsentry/crashpad/pull/75), [crashpad#79](https://github.com/getsentry/crashpad/pull/79)) +- Avoid accidentally mutating CONTEXT when client-side stack walking in Crashpad ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#77](https://github.com/getsentry/crashpad/pull/77)) +- Fix various mingw compilation issues ([#794](https://github.com/getsentry/sentry-native/pull/794), [crashpad#78](https://github.com/getsentry/crashpad/pull/78)) + +**Internal**: + +- Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80)) +- CI: Updated GitHub Actions to test on LLVM-mingw. ([#797](https://github.com/getsentry/sentry-native/pull/797)) +- Updated Breakpad backend to 2023-02-08. ([#805](https://github.com/getsentry/sentry-native/pull/805), [breakpad#34](https://github.com/getsentry/breakpad/pull/34)) +- Updated libunwindstack to 2023-02-09. ([#807](https://github.com/getsentry/sentry-native/pull/807), [libunwindstack-ndk#7](https://github.com/getsentry/libunwindstack-ndk/pull/7)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@BogdanLivadariu](https://github.com/BogdanLivadariu) +- [@ShawnCZek](https://github.com/ShawnCZek) +- [@past-due](https://github.com/past-due) + +## 0.5.4 + +**Fixes**: + +- Better error messages in `sentry_transport_curl`. ([#777](https://github.com/getsentry/sentry-native/pull/777)) +- Increased curl headers buffer size to 512 (in `sentry_transport_curl`). ([#784](https://github.com/getsentry/sentry-native/pull/784)) +- Fix sporadic crash on Windows due to race condition when initializing background-worker thread-id. ([#785](https://github.com/getsentry/sentry-native/pull/785)) +- Open the database file-lock on "UNIX" with `O_RDRW` ([#791](https://github.com/getsentry/sentry-native/pull/791)) + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-12-12. ([#778](https://github.com/getsentry/sentry-native/pull/778)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@cnicolaescu](https://github.com/cnicolaescu) + +## 0.5.3 + +**Fixes**: + +- Linux module-finder now also searches for code-id in ".note" ELF sections ([#775](https://github.com/getsentry/sentry-native/pull/775)) + +**Internal**: + +- CI: updated github actions to upgrade deprecated node runners. ([#767](https://github.com/getsentry/sentry-native/pull/767)) +- CI: upgraded Ubuntu to 20.04 for "old gcc" (v7) job due to deprecation. ([#768](https://github.com/getsentry/sentry-native/pull/768)) + +## 0.5.2 + +**Fixes**: + +- Fix build when CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION is undefined. ([crashpad#73](https://github.com/getsentry/crashpad/pull/73)) + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-10-17. ([#765](https://github.com/getsentry/sentry-native/pull/765)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@AenBleidd](https://github.com/AenBleidd) + +## 0.5.1 + +**Features**: + +- Crashpad on Windows now supports `fast-fail` crashes via a registered Windows Error Reporting (WER) module. ([#735](https://github.com/getsentry/sentry-native/pull/735)) + +**Fixes**: + +- Fix "flush" implementation of winhttp transport. ([#763](https://github.com/getsentry/sentry-native/pull/763)) + +**Internal**: + +- Updated libunwindstack-ndk submodule to 2022-09-16. ([#759](https://github.com/getsentry/sentry-native/pull/759)) +- Updated Breakpad and Crashpad backends to 2022-09-14. ([#735](https://github.com/getsentry/sentry-native/pull/735)) +- Be more defensive around transactions ([#757](https://github.com/getsentry/sentry-native/pull/757)) +- Added a CI timeout for the Android simulator start. ([#764](https://github.com/getsentry/sentry-native/pull/764)) + +## 0.5.0 + +**Features**: + +- Provide `on_crash()` callback to allow clients to act on detected crashes. + Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook. `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use `before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward compatible for current users of `before_send()` and allows gradual migration to `on_crash()` ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). ([#724](https://github.com/getsentry/sentry-native/pull/724), [#734](https://github.com/getsentry/sentry-native/pull/734)) + +**Fixes**: + +- Make Windows ModuleFinder more resilient to missing Debug Info ([#732](https://github.com/getsentry/sentry-native/pull/732)) +- Aligned pre-send event processing in `sentry_capture_event()` with the [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) ([#729](https://github.com/getsentry/sentry-native/pull/729)) +- Align the default value initialization for the `environment` payload attribute with the [developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute) ([#739](https://github.com/getsentry/sentry-native/pull/739)) +- Iterate all debug directory entries when parsing PE modules for a valid CodeView record ([#740](https://github.com/getsentry/sentry-native/pull/740)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@espkk](https://github.com/espkk) + +## 0.4.18 + +**Features**: + +- The crashpad backend now captures thread names. ([#725](https://github.com/getsentry/sentry-native/pull/725)) +- The inproc backend now captures the context registers. ([#714](https://github.com/getsentry/sentry-native/pull/714)) +- A new set of APIs to get the sentry SDK version at runtime. ([#726](https://github.com/getsentry/sentry-native/pull/726)) +- Add more convenient APIs to attach stack traces to exception or thread values. ([#723](https://github.com/getsentry/sentry-native/pull/723)) +- Allow disabling the crash reporting backend at runtime. ([#717](https://github.com/getsentry/sentry-native/pull/717)) + +**Fixes**: + +- Improved heuristics flagging sessions as "crashed". ([#719](https://github.com/getsentry/sentry-native/pull/719)) + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-06-14. ([#725](https://github.com/getsentry/sentry-native/pull/725)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@olback](https://github.com/olback) + +## 0.4.17 + +**Fixes**: + +- sentry-native now successfully builds when examples aren't included. ([#702](https://github.com/getsentry/sentry-native/pull/702)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@AenBleidd](https://github.com/AenBleidd) + +## 0.4.16 + +**Features**: + +- Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API. +- New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)). +- Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. +- More aggressively prune the Crashpad database. ([#698](https://github.com/getsentry/sentry-native/pull/698)) + +**Internal**: + +- Project IDs are now treated as opaque strings instead of integer values. ([#690](https://github.com/getsentry/sentry-native/pull/690)) +- Updated Breakpad and Crashpad backends to 2022-04-12. ([#696](https://github.com/getsentry/sentry-native/pull/696)) + +**Fixes**: + +- Updated CI as well as list of supported platforms to reflect Windows Server 2016, and therefore MSVC 2017 losing active support. +- Correctly free Windows Mutexes in Crashpad backend. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@zhaowq32](https://github.com/zhaowq32) + +## 0.4.15 + +**Fixes**: + +- Fix contexts from the scope not being attached to events correctly. +- Improve performance of event serialization. + +## 0.4.14 + +**Features**: + +- The Sentry SDK now has experimental support for performance monitoring. + The performance monitoring API allows manually creating transactions and instrumenting spans, and offers APIs for distributed tracing. + The API is currently disabled by default and needs to be enabled via a compile-time `SENTRY_PERFORMANCE_MONITORING` flag. + For more information, take a look at the more detailed [documentation of performance monitoring](https://docs.sentry.io/platforms/native/performance/). +- Sentry now has an explicit `sentry_flush` method that blocks the calling thread for the given time, waiting for the transport queue to be flushed. Custom transports need to implement a new `flush_hook` for this to work. + +**Fixes**: + +- Fix Sentry API deadlocking when the SDK was not initialized (or `sentry_init` failed). +- The rate limit handling of the default transports was updated to match the expected behavior. +- The Windows OS version is now read from the Registry and is more accurate. +- The `SENTRY_LIBRARY_TYPE` CMake option is now correctly honored. +- The Linux Modulefinder was once again improved to increase its memory safety and reliability. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@Mixaill](https://github.com/Mixaill) + ## 0.4.13 -**Features** +**Features**: - Add client-side stackwalking on Linux, Windows, and macOS (disabled by default). - CMake: add ability to set solution folder name. - Add AIX support. -**Fixes** +**Fixes**: - CMake: check whether libcurl was already found. - Increment CXX standard version to 14 to allow crashpad to build. @@ -484,6 +935,7 @@ See [#220](https://github.com/getsentry/sentry-native/issues/220) for details. This function now takes a pointer to the new `sentry_transport_t` type. Migrating from the old API can be done by wrapping with `sentry_new_function_transport`, like this: + ```c sentry_options_set_transport( options, sentry_new_function_transport(send_envelope_func, &closure_data)); diff --git a/CMakeLists.txt b/CMakeLists.txt index e3bcc30d8..770ce074d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,11 +26,11 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) endif() if(NOT CMAKE_C_STANDARD) - set(CMAKE_C_STANDARD 11) + set(CMAKE_C_STANDARD 11) endif() if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD 17) endif() include(GNUInstallDirs) @@ -55,16 +55,22 @@ endif() option(SENTRY_PIC "Build sentry (and dependent) libraries as position independent libraries" ON) +option(SENTRY_TRANSPORT_COMPRESSION "Enable transport gzip compression" OFF) + option(SENTRY_BUILD_TESTS "Build sentry-native tests" "${SENTRY_MAIN_PROJECT}") option(SENTRY_BUILD_EXAMPLES "Build sentry-native example(s)" "${SENTRY_MAIN_PROJECT}") option(SENTRY_LINK_PTHREAD "Link platform threads library" ON) if(SENTRY_LINK_PTHREAD) + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) endif() if(MSVC) option(SENTRY_BUILD_RUNTIMESTATIC "Build sentry-native with static runtime" OFF) + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") endif() if(LINUX) @@ -93,7 +99,7 @@ else() endif() set(SENTRY_TRANSPORT ${SENTRY_DEFAULT_TRANSPORT} CACHE STRING - "The HTTP transport that sentry uses to submit events to the sentry server, can be either 'none', 'curl' or 'winhttp' on windows.") + "The HTTP transport that sentry uses to submit events to the sentry server, can be either 'none', 'curl' or 'winhttp' on windows.") if(SENTRY_TRANSPORT STREQUAL "winhttp") set(SENTRY_TRANSPORT_WINHTTP TRUE) @@ -122,10 +128,8 @@ option(SENTRY_ENABLE_INSTALL "Enable sentry installation" "${SENTRY_MAIN_PROJECT if(MSVC AND CMAKE_GENERATOR_TOOLSET MATCHES "_xp$") message(WARNING "Crashpad is not supported for MSVC with XP toolset. Default backend was switched to 'breakpad'") set(SENTRY_DEFAULT_BACKEND "breakpad") -elseif((APPLE AND NOT IOS) OR WIN32) +elseif((APPLE AND NOT IOS) OR WIN32 OR LINUX) set(SENTRY_DEFAULT_BACKEND "crashpad") -elseif(LINUX) - set(SENTRY_DEFAULT_BACKEND "breakpad") else() set(SENTRY_DEFAULT_BACKEND "inproc") endif() @@ -151,9 +155,12 @@ if(SENTRY_BACKEND_CRASHPAD AND ANDROID) message(FATAL_ERROR "The Crashpad backend is not currently supported on Android") endif() +set(SENTRY_SDK_NAME "" CACHE STRING "The SDK name to report when sending events.") + message(STATUS "SENTRY_TRANSPORT=${SENTRY_TRANSPORT}") message(STATUS "SENTRY_BACKEND=${SENTRY_BACKEND}") message(STATUS "SENTRY_LIBRARY_TYPE=${SENTRY_LIBRARY_TYPE}") +message(STATUS "SENTRY_SDK_NAME=${SENTRY_SDK_NAME}") if(ANDROID) set(SENTRY_WITH_LIBUNWINDSTACK TRUE) @@ -221,6 +228,10 @@ target_sources(sentry PRIVATE "${PROJECT_SOURCE_DIR}/include/sentry.h") add_library(sentry::sentry ALIAS sentry) add_subdirectory(src) +if (NOT SENTRY_SDK_NAME STREQUAL "") + target_compile_definitions(sentry PRIVATE SENTRY_SDK_NAME="${SENTRY_SDK_NAME}") +endif() + # we do not need this on android, only linux if(LINUX) target_sources(sentry PRIVATE @@ -266,21 +277,24 @@ if(CMAKE_SYSTEM_NAME STREQUAL "OS400") endif() if(SENTRY_TRANSPORT_CURL) - if(NOT CURL_FOUND) # Some other lib might bring libcurl already - find_package(CURL REQUIRED) + if(NOT TARGET CURL::libcurl) # Some other lib might bring libcurl already + find_package(CURL REQUIRED COMPONENTS AsynchDNS) endif() - if(TARGET CURL::libcurl) # Only available in cmake 3.12+ - target_link_libraries(sentry PRIVATE CURL::libcurl) - else() - # Needed for cmake < 3.12 support (cmake 3.12 introduced the target CURL::libcurl) - target_include_directories(sentry PRIVATE ${CURL_INCLUDE_DIR}) - # The exported sentry target must not contain any path of the build machine, therefore use generator expressions - string(REPLACE ";" "$" GENEX_CURL_LIBRARIES "${CURL_LIBRARIES}") - string(REPLACE ";" "$" GENEX_CURL_COMPILE_DEFINITIONS "${CURL_COMPILE_DEFINITIONS}") - target_link_libraries(sentry PRIVATE $) - target_compile_definitions(sentry PRIVATE $) + target_link_libraries(sentry PRIVATE CURL::libcurl) +endif() + +if(SENTRY_TRANSPORT_COMPRESSION) + if(NOT TARGET ZLIB::ZLIB) + find_package(ZLIB REQUIRED) endif() + + if(SENTRY_BACKEND_CRASHPAD) + set(CRASHPAD_ZLIB_SYSTEM ON CACHE BOOL "Force CRASHPAD_ZLIB_SYSTEM when enabling transport compression" FORCE) + endif() + + target_link_libraries(sentry PRIVATE ZLIB::ZLIB) + target_compile_definitions(sentry PRIVATE SENTRY_TRANSPORT_COMPRESSION) endif() set_property(TARGET sentry PROPERTY C_VISIBILITY_PRESET hidden) @@ -327,16 +341,6 @@ target_include_directories(sentry "$" ) -# The modulefinder and symbolizer need these two settings, and they are exported -# as `PUBLIC`, so libraries that depend on sentry get these too: -# `-E`: To have all symbols in the dynamic symbol table. -# `--build-id`: To have a build-id in the ELF object. -# FIXME: cmake 3.13 introduced target_link_options -option(SENTRY_EXPORT_SYMBOLS "Export symbols for modulefinder and symbolizer" ON) -if(SENTRY_EXPORT_SYMBOLS) - target_link_libraries(sentry PUBLIC - "$<$,$>:-Wl,-E,--build-id=sha1>") -endif() #respect CMAKE_SYSTEM_VERSION if(WIN32) @@ -367,11 +371,11 @@ endif() # handle platform libraries if(ANDROID) - set(_SENTRY_PLATFORM_LIBS "dl" "log") + set(_SENTRY_PLATFORM_LIBS "dl" "log") elseif(LINUX) - set(_SENTRY_PLATFORM_LIBS "dl" "rt") + set(_SENTRY_PLATFORM_LIBS "dl" "rt") elseif(WIN32) - set(_SENTRY_PLATFORM_LIBS "dbghelp" "shlwapi" "version") + set(_SENTRY_PLATFORM_LIBS "dbghelp" "shlwapi" "version") endif() if(SENTRY_TRANSPORT_WINHTTP) @@ -384,11 +388,7 @@ if(SENTRY_LINK_PTHREAD) endif() # apply platform libraries to sentry library -if(SENTRY_LIBRARY_TYPE STREQUAL "static") - target_link_libraries(sentry PUBLIC ${_SENTRY_PLATFORM_LIBS}) -else() - target_link_libraries(sentry PRIVATE ${_SENTRY_PLATFORM_LIBS}) -endif() +target_link_libraries(sentry PRIVATE ${_SENTRY_PLATFORM_LIBS}) # suppress some errors and warnings for MinGW target if(MINGW) @@ -415,61 +415,60 @@ if(SENTRY_WITH_LIBUNWINDSTACK) endif() if(SENTRY_BACKEND_CRASHPAD) - option(SENTRY_CRASHPAD_SYSTEM "Use system crashpad" OFF) - if(SENTRY_CRASHPAD_SYSTEM) - find_package(crashpad REQUIRED) - target_link_libraries(sentry PUBLIC crashpad::client) + if(SENTRY_BUILD_SHARED_LIBS) + set(CRASHPAD_ENABLE_INSTALL OFF CACHE BOOL "Enable crashpad installation" FORCE) else() - # FIXME: required for cmake 3.12 and lower: - # - NEW behavior lets normal variable override option - cmake_policy(SET CMP0077 NEW) - if(SENTRY_BUILD_SHARED_LIBS) - set(CRASHPAD_ENABLE_INSTALL OFF CACHE BOOL "Enable crashpad installation" FORCE) - else() - set(CRASHPAD_ENABLE_INSTALL ON CACHE BOOL "Enable crashpad installation" FORCE) - endif() - add_subdirectory(external/crashpad crashpad_build) - - # set static runtime if enabled - if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC) - set_property(TARGET crashpad_client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_compat PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_getopt PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_handler PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_handler_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_minidump PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_snapshot PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_tools PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_util PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_zlib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - endif() + set(CRASHPAD_ENABLE_INSTALL ON CACHE BOOL "Enable crashpad installation" FORCE) + endif() + add_subdirectory(external/crashpad crashpad_build) - if(DEFINED SENTRY_FOLDER) - set_target_properties(crashpad_client PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_compat PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_getopt PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_handler PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_handler_lib PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_minidump PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_snapshot PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_tools PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) - endif() + if(WIN32) + add_dependencies(sentry crashpad::wer) + endif() - target_link_libraries(sentry PRIVATE - $ - $ - ) - install(EXPORT crashpad_export NAMESPACE sentry_crashpad:: FILE sentry_crashpad-targets.cmake - DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" - ) - if(WIN32 AND MSVC) - sentry_install(FILES $ - DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) - endif() + # set static runtime if enabled + if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC) + set_property(TARGET crashpad_client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_compat PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_getopt PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_handler PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_handler_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_minidump PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_snapshot PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_tools PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_util PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_zlib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() + + if(DEFINED SENTRY_FOLDER) + set_target_properties(crashpad_client PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_compat PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_getopt PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_handler PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_handler_lib PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_minidump PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_snapshot PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_tools PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_wer PROPERTIES FOLDER ${SENTRY_FOLDER}) + endif() + + target_link_libraries(sentry PRIVATE + $ + $ + ) + install(EXPORT crashpad_export NAMESPACE sentry_crashpad:: FILE sentry_crashpad-targets.cmake + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" + ) + if(WIN32 AND MSVC) + sentry_install(FILES $ + DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + sentry_install(FILES $ + DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) endif() add_dependencies(sentry crashpad::handler) elseif(SENTRY_BACKEND_BREAKPAD) @@ -478,7 +477,11 @@ elseif(SENTRY_BACKEND_BREAKPAD) # system breakpad is using pkg-config, see `external/breakpad/breakpad-client.pc.in` find_package(PkgConfig REQUIRED) pkg_check_modules(BREAKPAD REQUIRED IMPORTED_TARGET breakpad-client) - target_link_libraries(sentry PUBLIC PkgConfig::BREAKPAD) + if(SENTRY_BUILD_SHARED_LIBS) + target_link_libraries(sentry PRIVATE PkgConfig::BREAKPAD) + else() + target_link_libraries(sentry PUBLIC PkgConfig::BREAKPAD) + endif() else() add_subdirectory(external) target_include_directories(sentry PRIVATE @@ -559,10 +562,15 @@ if(SENTRY_BUILD_EXAMPLES) add_executable(sentry_example examples/example.c) target_link_libraries(sentry_example PRIVATE sentry) - target_compile_definitions(sentry_example PRIVATE SENTRY_PERFORMANCE_MONITORING) - if(MSVC) - target_compile_options(sentry_example PRIVATE $) + target_compile_options(sentry_example PRIVATE $) + + # to test handling SEH by-passing exceptions we need to enable the control flow guard + target_compile_options(sentry_example PRIVATE $) + else() + # Disable all optimizations for the `sentry_example` in gcc/clang. This allows us to keep crash triggers simple. + # The effects besides reproducible code-gen across compiler versions, will be negligible for build- and runtime. + target_compile_options(sentry_example PRIVATE $) endif() # set static runtime if enabled @@ -574,5 +582,26 @@ if(SENTRY_BUILD_EXAMPLES) set_target_properties(sentry_example PROPERTIES FOLDER ${SENTRY_FOLDER}) endif() + add_custom_command(TARGET sentry_example POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/minidump.dmp" "$/minidump.dmp") + add_test(NAME sentry_example COMMAND sentry_example) endif() + +# Limit the exported symbols when sentry is built as a shared library to those with a "sentry_" prefix: +# - we do this at the end of the file as to not affect subdirectories reading target_link_libraries from the parent. +# - we do this as PRIVATE since our version script does not make sense in any other project that adds us. +# +# Used linker parameters: +# `--build-id`: To have a build-id in the ELF object. +# `--version-script`: version script either hides "foreign" symbols or defers them as unknown ("U") to system libraries. +# FIXME: cmake 3.13 introduced target_link_options (blocked by Android) +if(SENTRY_BUILD_SHARED_LIBS) + target_link_libraries(sentry PRIVATE + "$<$,$>:-Wl,--build-id=sha1,--version-script=${PROJECT_SOURCE_DIR}/src/exports.map>") + + # Support 16KB page sizes + target_link_libraries(sentry PRIVATE + "$<$:-Wl,-z,max-page-size=16384>" + ) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 005c30435..2bdf6e097 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,9 @@ Building and testing `sentry-native` currently requires the following tools: - **CMake** and a supported C/C++ compiler, to actually build the code. - **python** and **pytest**, to run integration tests. - **clang-format** and **black**, to format the C/C++ and python code respectively. +- **curl** and **zlib** libraries (e.g. on Ubuntu: libcurl4-openssl-dev, libz-dev) -`pytest` and `black` are installed as virtualenv dependencies automatically. +`pytest`, `clang-format` and `black` are installed as virtualenv dependencies automatically. ## Setting up Environment @@ -121,7 +122,7 @@ The example can be run manually with a variety of commands to test different scenarios. Additionally, it will use the `SENTRY_DSN` env-variable, and can thus also be used to capture events/crashes directly to sentry. -The example currently supports the following commends: +The example currently supports the following commands: - `capture-event`: Captures an event. - `crash`: Triggers a crash to be captured. @@ -138,3 +139,15 @@ The example currently supports the following commends: - `capture-multiple`: Captures a number of events. - `sleep`: Introduces a 10 second sleep. - `add-stacktrace`: Adds the current thread stacktrace to the captured event. +- `disable-backend`: Disables the build-configured crash-handler backend. +- `before-send`: Installs a `before_send()` callback that retains the event. +- `discarding-before-send`: Installs a `before_send()` callback that discards the event. +- `on-crash`: Installs an `on_crash()` callback that retains the crash event. +- `discarding-on-crash`: Installs an `on_crash()` callback that discards the crash event. +- `override-sdk-name`: Changes the SDK name via the options at runtime. +- `stack-overflow`: Provokes a stack-overflow. + +Only on Windows using crashpad with its WER handler module: + +- `fastfail`: Crashes the application using the `__fastfail` intrinsic directly, thus by-passing SEH. +- `stack-buffer-overrun`: Triggers the Windows Control Flow Guard, which also fast fails and in turn by-passes SEH. diff --git a/Makefile b/Makefile index 71e63094d..93a3177c4 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,17 @@ build: build/Makefile @cmake --build build --parallel .PHONY: build -build/sentry_test_unit: build - @cmake --build build --target sentry_test_unit --parallel - test: update-test-discovery test-integration .PHONY: test -test-unit: update-test-discovery build/sentry_test_unit - ./build/sentry_test_unit +test-unit: update-test-discovery CMakeLists.txt + @mkdir -p unit-build + @cd unit-build; cmake \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=$(PWD)/unit-build \ + -DSENTRY_BACKEND=none \ + .. + @cmake --build unit-build --target sentry_test_unit --parallel + ./unit-build/sentry_test_unit .PHONY: test-unit test-integration: setup-venv @@ -63,7 +66,7 @@ setup-venv: .venv/bin/python .venv/bin/pip install --upgrade --requirement tests/requirements.txt format: setup-venv - @clang-format -i \ + @.venv/bin/clang-format -i \ examples/*.c \ include/*.h \ src/*.c \ @@ -77,6 +80,8 @@ format: setup-venv .PHONY: format style: setup-venv - @.venv/bin/python ./scripts/check-clang-format.py -r examples include src tests/unit + @.venv/bin/python ./scripts/check-clang-format.py \ + --clang-format-executable .venv/bin/clang-format \ + -r examples include src tests/unit @.venv/bin/black --diff --check tests .PHONY: style diff --git a/README.md b/README.md index 0261cb3aa..6584e2271 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ +[![Conan Center](https://shields.io/conan/v/sentry-native)](https://conan.io/center/recipes/sentry-native) [![nixpkgs unstable](https://repology.org/badge/version-for-repo/nix_unstable/sentry-native.svg)](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/libraries/sentry-native/default.nix) [![vcpkg](https://shields.io/vcpkg/v/sentry-native)](https://vcpkg.link/ports/sentry-native) +

- - + + + + + Sentry + -

# Official Sentry SDK for C/C++ @@ -12,13 +17,14 @@ applications, optimized for C and C++. Sentry allows to add tags, breadcrumbs and arbitrary custom context to enrich error reports. Supports Sentry _20.6.0_ and later. -**Note**: This SDK is being actively developed and still in Beta. We recommend -to check for updates regularly to benefit from latest features and bug fixes. -Please see [Known Limitations](#known-limitations). +### Note + +Using the `sentry-native` SDK in a standalone use case is currently an experimental feature. The SDK’s primary function is to fuel our other SDKs, like [`sentry-java`](https://github.com/getsentry/sentry-java) or [`sentry-unreal`](https://github.com/getsentry/sentry-unreal). Support from our side is best effort and we do what we can to respond to issues in a timely fashion, but please understand if we won’t be able to address your issues or feature suggestions. ## Resources -- [Discord](https://discord.gg/ez5KZN7) server for project discussions. +- [SDK Documentation](https://docs.sentry.io/platforms/native/) +- [Discord](https://discord.gg/ez5KZN7) server for project discussions - Follow [@getsentry](https://twitter.com/getsentry) on Twitter for updates ## Table of Contents @@ -50,6 +56,7 @@ The SDK bundle contains the following folders: directory or copy the header file to your source tree so that it is available during the build. - `src`: Sources of the Sentry SDK required for building. +- `ndk`: Sources for the Android NDK JNI layer. ## Platform and Feature Support @@ -58,8 +65,8 @@ The SDK currently supports and is tested on the following OS/Compiler variations - 64bit Linux with GCC 9 - 64bit Linux with clang 9 - 32bit Linux with GCC 7 (cross compiled from 64bit host) -- 64bit Windows with MSVC 2019 -- 32bit Windows with MSVC 2017 +- 32bit Windows with MSVC 2019 +- 64bit Windows with MSVC 2022 - macOS Catalina with most recent Compiler toolchain - Android API29 built by NDK21 toolchain - Android API16 built by NDK19 toolchain @@ -116,11 +123,14 @@ Please refer to the CMake Manual for more details. **Android**: The CMake project can also be configured to correctly work with the Android NDK, -see the dedicated [CMake Guide] for details on how to integrate it with gradle +see the dedicated [CMake Guide] for details on how to integrate it with Gradle or use it on the command line. +The `ndk` folder provides Gradle project which adds a Java JNI layer for Android, suitable for accessing the sentry-native SDK from Java. See the [NDK Readme] for more details about this topic. + [cmake]: https://cmake.org/cmake/help/latest/ [cmake guide]: https://developer.android.com/ndk/guides/cmake +[NDK Readme]: ndk/README.md **MinGW**: @@ -182,19 +192,19 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. When using `sentry` as a static library, make sure to `#define SENTRY_BUILD_STATIC 1` before including the sentry header. - `SENTRY_PIC` (Default: ON): - By default, `sentry` is built as a position independent library. + By default, `sentry` is built as a position-independent library. - `SENTRY_EXPORT_SYMBOLS` (Default: ON): - By default, `sentry` exposes all symbols in the dynamic symbol table. You might want to disable it in case the program intends to `dlopen` third-party shared libraries and avoid symbol collisions. + By default, `sentry` exposes all symbols in the dynamic symbol table. You might want to disable it if the program intends to `dlopen` third-party shared libraries and avoid symbol collisions. - `SENTRY_BUILD_RUNTIMESTATIC` (Default: OFF): Enables linking with the static MSVC runtime. Has no effect if the compiler is not MSVC. - `SENTRY_LINK_PTHREAD` (Default: ON): - Links platform threads library like `pthread` on unix targets. + Links platform threads library like `pthread` on UNIX targets. - `SENTRY_BUILD_FORCE32` (Default: OFF): - Forces cross-compilation from 64-bit host to 32-bit target. Only has an effect on Linux. + Forces cross-compilation from 64-bit host to 32-bit target. Only affects Linux. - `CMAKE_SYSTEM_VERSION` (Default: depending on Windows SDK version): Sets up a minimal version of Windows where sentry-native can be guaranteed to run. @@ -225,52 +235,62 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. Sentry can use different backends depending on platform. - **crashpad**: This uses the out-of-process crashpad handler. It is currently - only supported on Desktop OSs, and used as the default on Windows and macOS. + only supported on Desktop OSs, and used as the default on Windows, Linux and macOS. - **breakpad**: This uses the in-process breakpad handler. It is currently - only supported on Desktop OSs, and used as the default on Linux. - - **inproc**: A small in-process handler which is supported on all platforms, - and is used as default on Android. + only supported on Desktop OSs. + - **inproc**: A small in-process handler that is supported on all platforms, + and is used as a default on Android. - **none**: This builds `sentry-native` without a backend, so it does not handle - crashes at all. It is primarily used for tests. + crashes. It is primarily used for tests. - `SENTRY_INTEGRATION_QT` (Default: OFF): Builds the Qt integration, which turns Qt log messages into breadcrumbs. -- `SENTRY_BREAKPAD_SYSTEM` / `SENTRY_CRASHPAD_SYSTEM` (Default: OFF): - This instructs the build system to use system-installed breakpad or crashpad - libraries instead of using the in-tree version. This is generally not recommended - for crashpad, as sentry uses a patched version that has attachment support. - This is being worked on upstream as well, and a future version might work with - an unmodified crashpad version as well. - -| Feature | Windows | macOS | Linux | Android | iOS | -| ---------- | ------- | ----- | ----- | ------- | --- | -| Transports | | | | | | -| - curl | | ☑ | ☑ | (✓) | | -| - winhttp | ☑ | | | | | -| - none | ✓ | ✓ | ✓ | ☑ | ☑ | -| | | | | | | -| Backends | | | | | | -| - inproc | ✓ | ✓ | ✓ | ☑ | | -| - crashpad | ☑ | ☑ | ✓ | | | -| - breakpad | ✓ | ✓ | ☑ | (✓) | (✓) | -| - none | ✓ | ✓ | ✓ | ✓ | | +- `SENTRY_BREAKPAD_SYSTEM` (Default: OFF): + This instructs the build system to use system-installed breakpad libraries instead of the in-tree version. -Legend: - -- ☑ default -- ✓ supported -- unsupported +- `SENTRY_TRANSPORT_COMPRESSION` (Default: OFF): + Adds Gzip transport compression. Requires `zlib`. - `SENTRY_FOLDER` (Default: not defined): - Sets the sentry-native projects folder name for generators which support project hierarchy (like Microsoft Visual Studio). - To use this feature you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html) + Sets the sentry-native projects folder name for generators that support project hierarchy (like Microsoft Visual Studio). + To use this feature, you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html) - `CRASHPAD_ENABLE_STACKTRACE` (Default: OFF): This enables client-side stackwalking when using the crashpad backend. Stack unwinding will happen on the client's machine and the result will be submitted to Sentry attached to the generated minidump. Note that this feature is still experimental. +- `SENTRY_SDK_NAME` (Default: sentry.native or sentry.native.android): + Sets the SDK name that should be included in the reported events. If you're overriding this, also define + the same value using `target_compile_definitions()` on your own targets that include `sentry.h`. + +### Support Matrix + +| Feature | Windows | macOS | Linux | Android | iOS | +| ---------- | ------- | ----- | ----- | ------- | ----- | +| Transports | | | | | | +| - curl | | ☑ | ☑ | (✓)*** | | +| - winhttp | ☑ | | | | | +| - none | ✓ | ✓ | ✓ | ☑ | ☑ | +| | | | | | | +| Backends | | | | | | +| - crashpad | ☑ | ☑ | ☑ | | | +| - breakpad | ✓ | ✓ | ✓ | (✓)** | (✓)** | +| - inproc | ✓ | (✓)* | ✓ | ☑ | | +| - none | ✓ | ✓ | ✓ | ✓ | | + +Legend: + +- ☑ default +- ✓ supported +- (✓) supported with limitations +- `*`: `inproc` has not produced valid stack traces on macOS since version 13 ("Ventura"). Tracking: https://github.com/getsentry/sentry-native/issues/906 +- `**`: `breakpad` on Android and iOS builds and should work according to upstream but is untested. +- `***`: `curl` as a transport works on Android but isn't used in any supported configuration to reduce the size of our artifacts. + +In addition to platform support, the "Advanced Usage" section of the SDK docs now [describes the tradeoffs](https://docs.sentry.io/platforms/native/advanced-usage/backend-tradeoffs/) involved in choosing a suitable backend for a particular use case. + ### Build Targets - `sentry`: This is the main library and the only default build target. @@ -308,8 +328,14 @@ Other important configuration options include: - The crashpad backend on macOS currently has no support for notifying the crashing process, and can thus not properly terminate sessions or call the registered - `before_send` hook. It will also lose any events that have been queued for + `before_send` or `on_crash` hook. It will also lose any events that have been queued for sending at time of crash. +- The Crashpad backend on Windows supports fast-fail crashes, which bypass SEH (Structured + Exception Handling) primarily for security reasons. `sentry-native` registers a WER (Windows Error + Reporting) module, which signals the `crashpad_handler` to send a minidump when a fast-fail crash occurs + But since this process bypasses SEH, the application local exception handler is no longer invoked, which + also means that for these kinds of crashes, `before_send` and `on_crash` will not be invoked before + sending the minidump and thus have no effect. ## Development diff --git a/examples/example.c b/examples/example.c index f03614908..750dffec1 100644 --- a/examples/example.c +++ b/examples/example.c @@ -9,20 +9,73 @@ #include #include #include + #ifdef NDEBUG # undef NDEBUG #endif + #include #ifdef SENTRY_PLATFORM_WINDOWS +# include # include -# define sleep_s(SECONDS) Sleep((SECONDS)*1000) +# define sleep_s(SECONDS) Sleep((SECONDS) * 1000) #else + # include # include + # define sleep_s(SECONDS) sleep(SECONDS) #endif +static sentry_value_t +before_send_callback(sentry_value_t event, void *hint, void *closure) +{ + (void)hint; + (void)closure; + + // make our mark on the event + sentry_value_set_by_key( + event, "adapted_by", sentry_value_new_string("before_send")); + + // tell the backend to proceed with the event + return event; +} + +static sentry_value_t +discarding_before_send_callback(sentry_value_t event, void *hint, void *closure) +{ + (void)hint; + (void)closure; + + // discard event and signal backend to stop further processing + sentry_value_decref(event); + return sentry_value_new_null(); +} + +static sentry_value_t +discarding_on_crash_callback( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure) +{ + (void)uctx; + (void)closure; + + // discard event and signal backend to stop further processing + sentry_value_decref(event); + return sentry_value_new_null(); +} + +static sentry_value_t +on_crash_callback( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure) +{ + (void)uctx; + (void)closure; + + // tell the backend to retain the event + return event; +} + static void print_envelope(sentry_envelope_t *envelope, void *unused_state) { @@ -45,6 +98,55 @@ has_arg(int argc, char **argv, const char *arg) return false; } +#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(__MINGW32__) \ + && !defined(__MINGW64__) + +int +call_rffe_many_times() +{ + RaiseFailFastException(NULL, NULL, 0); + RaiseFailFastException(NULL, NULL, 0); + RaiseFailFastException(NULL, NULL, 0); + RaiseFailFastException(NULL, NULL, 0); + return 1; +} + +typedef int (*crash_func)(); + +void +indirect_call(crash_func func) +{ + // This code always generates CFG guards. + func(); +} + +static void +trigger_stack_buffer_overrun() +{ + // Call into the middle of the Crashy function. + crash_func func = (crash_func)((uintptr_t)(call_rffe_many_times) + 16); + __try { + // Generates a STATUS_STACK_BUFFER_OVERRUN exception if CFG triggers. + indirect_call(func); + } __except (EXCEPTION_EXECUTE_HANDLER) { + // CFG fast fail should never be caught. + printf( + "If you see me, then CFG wasn't enabled (compile with /guard:cf)"); + } + // Should only reach here if CFG is disabled. + abort(); +} + +static void +trigger_fastfail_crash() +{ + // this bypasses WINDOWS SEH and will only be caught with the crashpad WER + // module enabled + __fastfail(77); +} + +#endif + #ifdef SENTRY_PLATFORM_AIX // AIX has a null page mapped to the bottom of memory, which means null derefs // don't segfault. try dereferencing the top of memory instead; the top nibble @@ -60,11 +162,22 @@ trigger_crash() memset((char *)invalid_mem, 1, 100); } +static void +trigger_stack_overflow() +{ + alloca(1024); + trigger_stack_overflow(); +} + int main(int argc, char **argv) { sentry_options_t *options = sentry_options_new(); + if (has_arg(argc, argv, "disable-backend")) { + sentry_options_set_backend(options, NULL); + } + // this is an example. for real usage, make sure to set this explicitly to // an app specific cache location. sentry_options_set_database_path(options, ".sentry-native"); @@ -93,11 +206,35 @@ main(int argc, char **argv) options, sentry_transport_new(print_envelope)); } -#ifdef SENTRY_PERFORMANCE_MONITORING if (has_arg(argc, argv, "capture-transaction")) { sentry_options_set_traces_sample_rate(options, 1.0); } -#endif + + if (has_arg(argc, argv, "child-spans")) { + sentry_options_set_max_spans(options, 5); + } + + if (has_arg(argc, argv, "before-send")) { + sentry_options_set_before_send(options, before_send_callback, NULL); + } + + if (has_arg(argc, argv, "discarding-before-send")) { + sentry_options_set_before_send( + options, discarding_before_send_callback, NULL); + } + + if (has_arg(argc, argv, "on-crash")) { + sentry_options_set_on_crash(options, on_crash_callback, NULL); + } + + if (has_arg(argc, argv, "discarding-on-crash")) { + sentry_options_set_on_crash( + options, discarding_on_crash_callback, NULL); + } + + if (has_arg(argc, argv, "override-sdk-name")) { + sentry_options_set_sdk_name(options, "sentry.native.android.flutter"); + } sentry_init(options); @@ -135,6 +272,21 @@ main(int argc, char **argv) debug_crumb, "category", sentry_value_new_string("example!")); sentry_value_set_by_key( debug_crumb, "level", sentry_value_new_string("debug")); + + // extend the `http` crumb with (optional) data properties as documented + // here: + // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types + sentry_value_t http_data = sentry_value_new_object(); + sentry_value_set_by_key(http_data, "url", + sentry_value_new_string("https://example.com/api/1.0/users")); + sentry_value_set_by_key( + http_data, "method", sentry_value_new_string("GET")); + sentry_value_set_by_key( + http_data, "status_code", sentry_value_new_int32(200)); + sentry_value_set_by_key( + http_data, "reason", sentry_value_new_string("OK")); + sentry_value_set_by_key(debug_crumb, "data", http_data); + sentry_add_breadcrumb(debug_crumb); sentry_value_t nl_crumb @@ -178,6 +330,18 @@ main(int argc, char **argv) if (has_arg(argc, argv, "crash")) { trigger_crash(); } + if (has_arg(argc, argv, "stack-overflow")) { + trigger_stack_overflow(); + } +#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(__MINGW32__) \ + && !defined(__MINGW64__) + if (has_arg(argc, argv, "fastfail")) { + trigger_fastfail_crash(); + } + if (has_arg(argc, argv, "stack-buffer-overrun")) { + trigger_stack_buffer_overrun(); + } +#endif if (has_arg(argc, argv, "assert")) { assert(0); } @@ -205,29 +369,68 @@ main(int argc, char **argv) sentry_value_t exc = sentry_value_new_exception( "ParseIntError", "invalid digit found in string"); if (has_arg(argc, argv, "add-stacktrace")) { - sentry_value_t stacktrace = sentry_value_new_stacktrace(NULL, 0); - sentry_value_set_by_key(exc, "stacktrace", stacktrace); + sentry_value_set_stacktrace(exc, NULL, 0); } sentry_value_t event = sentry_value_new_event(); sentry_event_add_exception(event, exc); sentry_capture_event(event); } + if (has_arg(argc, argv, "capture-user-feedback")) { + sentry_value_t event = sentry_value_new_message_event( + SENTRY_LEVEL_INFO, "my-logger", "Hello user feedback!"); + sentry_uuid_t event_id = sentry_capture_event(event); + + sentry_value_t user_feedback = sentry_value_new_user_feedback( + &event_id, "some-name", "some-email", "some-comment"); + + sentry_capture_user_feedback(user_feedback); + } -#ifdef SENTRY_PERFORMANCE_MONITORING if (has_arg(argc, argv, "capture-transaction")) { - sentry_value_t tx_ctx - = sentry_value_new_transaction_context("I'm a little teapot", + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("little.teapot", "Short and stout here is my handle and here is my spout"); if (has_arg(argc, argv, "unsample-tx")) { sentry_transaction_context_set_sampled(tx_ctx, 0); } + sentry_transaction_t *tx + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + + sentry_transaction_set_data( + tx, "url", sentry_value_new_string("https://example.com")); + + if (has_arg(argc, argv, "error-status")) { + sentry_transaction_set_status( + tx, SENTRY_SPAN_STATUS_INTERNAL_ERROR); + } + + if (has_arg(argc, argv, "child-spans")) { + sentry_span_t *child + = sentry_transaction_start_child(tx, "littler.teapot", NULL); + sentry_span_t *grandchild + = sentry_span_start_child(child, "littlest.teapot", NULL); + + sentry_span_set_data( + child, "span_data_says", sentry_value_new_string("hi!")); + + if (has_arg(argc, argv, "error-status")) { + sentry_span_set_status(child, SENTRY_SPAN_STATUS_NOT_FOUND); + sentry_span_set_status( + grandchild, SENTRY_SPAN_STATUS_ALREADY_EXISTS); + } + + sentry_span_finish(grandchild); + sentry_span_finish(child); + } - sentry_value_t tx = sentry_transaction_start(tx_ctx); sentry_transaction_finish(tx); } -#endif + + if (has_arg(argc, argv, "capture-minidump")) { + sentry_capture_minidump("minidump.dmp"); + } // make sure everything flushes sentry_close(); diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 569ccb18c..9f3a29869 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -20,6 +20,10 @@ set(BREAKPAD_SOURCES_COMMON_LINUX breakpad/src/common/linux/linux_libc_support.cc breakpad/src/common/linux/memory_mapped_file.cc breakpad/src/common/linux/safe_readlink.cc + breakpad/src/common/linux/scoped_pipe.cc + breakpad/src/common/linux/scoped_pipe.h + breakpad/src/common/linux/scoped_tmpfile.cc + breakpad/src/common/linux/scoped_tmpfile.h ) set(BREAKPAD_SOURCES_COMMON_LINUX_GETCONTEXT @@ -36,6 +40,8 @@ set(BREAKPAD_SOURCES_COMMON_WINDOWS ) set(BREAKPAD_SOURCES_COMMON_APPLE + breakpad/src/common/mac/arch_utilities.cc + breakpad/src/common/mac/arch_utilities.h breakpad/src/common/mac/file_id.cc breakpad/src/common/mac/file_id.h breakpad/src/common/mac/macho_id.cc @@ -74,6 +80,9 @@ set(BREAKPAD_SOURCES_CLIENT_LINUX breakpad/src/client/linux/minidump_writer/linux_dumper.cc breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc breakpad/src/client/linux/minidump_writer/minidump_writer.cc + breakpad/src/client/linux/minidump_writer/pe_file.cc + breakpad/src/client/linux/minidump_writer/pe_file.h + breakpad/src/client/linux/minidump_writer/pe_structs.h ) set(BREAKPAD_SOURCES_CLIENT_WINDOWS @@ -91,13 +100,13 @@ set(BREAKPAD_SOURCES_CLIENT_APPLE breakpad/src/client/mac/handler/breakpad_nlist_64.h breakpad/src/client/mac/handler/dynamic_images.cc breakpad/src/client/mac/handler/dynamic_images.h - breakpad/src/client/mac/handler/exception_handler.cc - breakpad/src/client/mac/handler/exception_handler.h breakpad/src/client/mac/handler/minidump_generator.cc breakpad/src/client/mac/handler/minidump_generator.h ) set(BREAKPAD_SOURCES_CLIENT_MAC + breakpad/src/client/mac/handler/exception_handler.cc + breakpad/src/client/mac/handler/exception_handler.h breakpad/src/client/mac/crash_generation/crash_generation_client.cc breakpad/src/client/mac/crash_generation/crash_generation_client.h ) @@ -115,12 +124,14 @@ set(BREAKPAD_SOURCES_CLIENT_IOS breakpad/src/client/mac/handler/ucontext_compat.h ) - add_library(breakpad_client STATIC) +set_property(TARGET breakpad_client PROPERTY CXX_STANDARD 17) +set_property(TARGET breakpad_client PROPERTY CXX_STANDARD_REQUIRED On) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON}) if(LINUX OR ANDROID) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_LINUX} ${BREAKPAD_SOURCES_CLIENT_LINUX}) + if(ANDROID) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_ANDROID}) target_include_directories(breakpad_client PRIVATE breakpad/src/common/android/include) @@ -128,6 +139,7 @@ if(LINUX OR ANDROID) include(CheckFunctionExists) check_function_exists(getcontext HAVE_GETCONTEXT) + if(HAVE_GETCONTEXT) target_compile_definitions(breakpad_client PRIVATE HAVE_GETCONTEXT) else() @@ -141,6 +153,7 @@ if(APPLE) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_APPLE} ${BREAKPAD_SOURCES_CLIENT_APPLE}) + if(NOT IOS) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_MAC} @@ -167,8 +180,8 @@ endif() # which are being resolved correctly when we add the current directory to # the include directories. A giant hack, yes, but it works target_include_directories(breakpad_client - PRIVATE - "$" - PUBLIC - "$" + PRIVATE + "$" + PUBLIC + "$" ) diff --git a/external/breakpad b/external/breakpad index e0f523e41..eb28e7ed9 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit e0f523e414f88fed3292f152035fd2743ee8a56e +Subproject commit eb28e7ed9c1c1e1a717fa34ce0178bf471a6311f diff --git a/external/crashpad b/external/crashpad index 007c8b515..04101eb87 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 007c8b51594201f288402721519a7d7a1916e04d +Subproject commit 04101eb874f109f98c22f341dfa3162879d5b92a diff --git a/external/libunwindstack-ndk b/external/libunwindstack-ndk index a4c27d48d..f064cc8da 160000 --- a/external/libunwindstack-ndk +++ b/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit a4c27d48deff95fe922fe9733ef5c1339bdbf4fb +Subproject commit f064cc8da606f38450ff5d345ae716ff9dab3d7c diff --git a/external/third_party/lss b/external/third_party/lss index 171a36a8e..9719c1e1e 160000 --- a/external/third_party/lss +++ b/external/third_party/lss @@ -1 +1 @@ -Subproject commit 171a36a8e0d1e456f63d342a09f811f9273a64af +Subproject commit 9719c1e1e676814c456b55f5f070eabad6709d31 diff --git a/include/sentry.h b/include/sentry.h index 9ee40058b..9ef068b41 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -23,8 +23,14 @@ extern "C" { #endif /* SDK Version */ -#define SENTRY_SDK_NAME "sentry.native" -#define SENTRY_SDK_VERSION "0.4.13" +#ifndef SENTRY_SDK_NAME +# ifdef __ANDROID__ +# define SENTRY_SDK_NAME "sentry.native.android" +# else +# define SENTRY_SDK_NAME "sentry.native" +# endif +#endif +#define SENTRY_SDK_VERSION "0.7.12" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ @@ -84,7 +90,7 @@ extern "C" { /* context type dependencies */ #ifdef _WIN32 -# include +# include #else # include #endif @@ -201,6 +207,8 @@ SENTRY_API sentry_value_t sentry_value_new_bool(int value); * Creates a new null terminated string. */ SENTRY_API sentry_value_t sentry_value_new_string(const char *value); +SENTRY_API sentry_value_t sentry_value_new_string_n( + const char *value, size_t value_len); /** * Creates a new list value. @@ -226,10 +234,15 @@ SENTRY_API sentry_value_type_t sentry_value_get_type(sentry_value_t value); SENTRY_API int sentry_value_set_by_key( sentry_value_t value, const char *k, sentry_value_t v); +SENTRY_API int sentry_value_set_by_key_n( + sentry_value_t value, const char *k, size_t k_len, sentry_value_t v); + /** * This removes a value from the map by key. */ SENTRY_API int sentry_value_remove_by_key(sentry_value_t value, const char *k); +SENTRY_API int sentry_value_remove_by_key_n( + sentry_value_t value, const char *k, size_t k_len); /** * Appends a value to a list. @@ -262,6 +275,8 @@ SENTRY_API int sentry_value_remove_by_index(sentry_value_t value, size_t index); */ SENTRY_API sentry_value_t sentry_value_get_by_key( sentry_value_t value, const char *k); +SENTRY_API sentry_value_t sentry_value_get_by_key_n( + sentry_value_t value, const char *k, size_t k_len); /** * Looks up a value in a map by key. If missing a null value is returned. @@ -272,6 +287,8 @@ SENTRY_API sentry_value_t sentry_value_get_by_key( */ SENTRY_API sentry_value_t sentry_value_get_by_key_owned( sentry_value_t value, const char *k); +SENTRY_API sentry_value_t sentry_value_get_by_key_owned_n( + sentry_value_t value, const char *k, size_t k_len); /** * Looks up a value in a list by index. If missing a null value is returned. @@ -359,6 +376,8 @@ SENTRY_API sentry_value_t sentry_value_new_event(void); */ SENTRY_API sentry_value_t sentry_value_new_message_event( sentry_level_t level, const char *logger, const char *text); +SENTRY_API sentry_value_t sentry_value_new_message_event_n(sentry_level_t level, + const char *logger, size_t logger_len, const char *text, size_t text_len); /** * Creates a new Breadcrumb with a specific type and message. @@ -369,6 +388,8 @@ SENTRY_API sentry_value_t sentry_value_new_message_event( */ SENTRY_API sentry_value_t sentry_value_new_breadcrumb( const char *type, const char *message); +SENTRY_API sentry_value_t sentry_value_new_breadcrumb_n( + const char *type, size_t type_len, const char *message, size_t message_len); /** * Creates a new Exception value. @@ -384,6 +405,8 @@ SENTRY_API sentry_value_t sentry_value_new_breadcrumb( */ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception( const char *type, const char *value); +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception_n( + const char *type, size_t type_len, const char *value, size_t value_len); /** * Creates a new Thread value. @@ -397,14 +420,16 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception( */ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( uint64_t id, const char *name); +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread_n( + uint64_t id, const char *name, size_t name_len); /** * Creates a new Stack Trace conforming to the Stack Trace Interface. * * See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ * - * The returned object needs to be attached to either an exception - * event, or a thread object. + * The returned object must be attached to either an exception or thread + * object. * * If `ips` is NULL the current stack trace is captured, otherwise `len` * stack trace instruction pointers are attached to the event. @@ -412,6 +437,17 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_stacktrace( void **ips, size_t len); +/** + * Sets the Stack Trace conforming to the Stack Trace Interface in a value. + * + * The value argument must be either an exception or thread object. + * + * If `ips` is NULL the current stack trace is captured, otherwise `len` stack + * trace instruction pointers are attached to the event. + */ +SENTRY_EXPERIMENTAL_API void sentry_value_set_stacktrace( + sentry_value_t value, void **ips, size_t len); + /** * Adds an Exception to an Event value. * @@ -517,6 +553,8 @@ SENTRY_API sentry_uuid_t sentry_uuid_new_v4(void); * Parses a uuid from a string. */ SENTRY_API sentry_uuid_t sentry_uuid_from_string(const char *str); +SENTRY_API sentry_uuid_t sentry_uuid_from_string_n( + const char *str, size_t str_len); /** * Creates a uuid from bytes. @@ -561,7 +599,6 @@ SENTRY_API void sentry_envelope_free(sentry_envelope_t *envelope); SENTRY_API sentry_value_t sentry_envelope_get_event( const sentry_envelope_t *envelope); -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Given an Envelope, returns the embedded Transaction if there is one. * @@ -569,7 +606,6 @@ SENTRY_API sentry_value_t sentry_envelope_get_event( */ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_envelope_get_transaction( const sentry_envelope_t *envelope); -#endif /** * Serializes the envelope. @@ -588,6 +624,8 @@ SENTRY_API char *sentry_envelope_serialize( */ SENTRY_API int sentry_envelope_write_to_file( const sentry_envelope_t *envelope, const char *path); +SENTRY_API int sentry_envelope_write_to_file_n( + const sentry_envelope_t *envelope, const char *path, size_t path_len); /** * The Sentry Client Options. @@ -616,13 +654,16 @@ typedef struct sentry_options_s sentry_options_t; * * `startup_func`: This hook will be called by sentry inside of `sentry_init` * and instructs the transport to initialize itself. Failures will bubble up * to `sentry_init`. + * * `flush_func`: Instructs the transport to flush its queue. + * This hook receives a millisecond-resolution `timeout` parameter and should + * return `0` if the transport queue is flushed within the timeout. * * `shutdown_func`: Instructs the transport to flush its queue and shut down. * This hook receives a millisecond-resolution `timeout` parameter and should - * return `true` when the transport was flushed and shut down successfully. - * In case of `false`, sentry will log an error, but continue with freeing the - * transport. + * return `0` if the transport is flushed and shut down successfully. + * In case of a non-zero return value, sentry will log an error, but continue + * with freeing the transport. * * `free_func`: Frees the transports `state`. This hook might be called even - * though `shutdown_func` returned `false` previously. + * though `shutdown_func` returned a failure code previously. * * The transport interface might be extended in the future with hooks to flush * its internal queue without shutting down, and to dump its internal queue to @@ -662,6 +703,16 @@ SENTRY_API void sentry_transport_set_free_func( SENTRY_API void sentry_transport_set_startup_func(sentry_transport_t *transport, int (*startup_func)(const sentry_options_t *options, void *state)); +/** + * Sets the transport flush hook. + * + * This hook will receive a millisecond-resolution timeout. + * It should return `0` if all the pending envelopes are + * sent within the timeout, or `1` if the timeout is hit. + */ +SENTRY_API void sentry_transport_set_flush_func(sentry_transport_t *transport, + int (*flush_func)(uint64_t timeout, void *state)); + /** * Sets the transport shutdown hook. * @@ -692,6 +743,20 @@ SENTRY_API void sentry_transport_free(sentry_transport_t *transport); SENTRY_API sentry_transport_t *sentry_new_function_transport( void (*func)(const sentry_envelope_t *envelope, void *data), void *data); +/** + * This represents an interface for user-defined backends. + * + * Backends are responsible to handle crashes. They are maintained at runtime + * via various life-cycle hooks from the sentry-core. + * + * At this point none of those interfaces are exposed in the API including + * creation and destruction. The main use-case of the backend in the API at this + * point is to disable it via `sentry_options_set_backend` at runtime before it + * is initialized. + */ +struct sentry_backend_s; +typedef struct sentry_backend_s sentry_backend_t; + /* -- Options APIs -- */ /** @@ -728,11 +793,28 @@ SENTRY_API void sentry_options_set_transport( * call `sentry_value_decref` on the provided event, and return a * `sentry_value_new_null()` instead. * + * If you have set an `on_crash` callback (independent of whether it discards or + * retains the event), `before_send` will no longer be invoked for crash-events, + * which allows you to better distinguish between crashes and all other events + * in client-side pre-processing. + * * This function may be invoked inside of a signal handler and must be safe for * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see * the documentation on SEH (structured exception handling) for more information * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * + * Up to version 0.4.18 the `before_send` callback wasn't invoked in case the + * event sampling discarded an event. In the current implementation the + * `before_send` callback is invoked even if the event sampling discards the + * event, following the cross-SDK session filter order: + * + * https://develop.sentry.dev/sdk/sessions/#filter-order + * + * On Windows the crashpad backend can capture fast-fail crashes which by-pass + * SEH. Since the `before_send` is called by a local exception-handler, it will + * not be invoked when such a crash happened, even though a minidump will be + * sent. */ typedef sentry_value_t (*sentry_event_function_t)( sentry_value_t event, void *hint, void *closure); @@ -745,10 +827,70 @@ typedef sentry_value_t (*sentry_event_function_t)( SENTRY_API void sentry_options_set_before_send( sentry_options_t *opts, sentry_event_function_t func, void *data); +/** + * Type of the `on_crash` callback. + * + * The `on_crash` callback replaces the `before_send` callback for crash events. + * The interface is analogous to `before_send` in that the callback takes + * ownership of the `event`, and should usually return that same event. In case + * the event should be discarded, the callback needs to call + * `sentry_value_decref` on the provided event, and return a + * `sentry_value_new_null()` instead. + * + * Only the `inproc` backend currently fills the passed-in event with useful + * data and processes any modifications to the return value. Since both + * `breakpad` and `crashpad` use minidumps to capture the crash state, the + * passed-in event is empty when using these backends, and they ignore any + * changes to the return value. + * + * If you set this callback in the options, it prevents a concurrently enabled + * `before_send` callback from being invoked in the crash case. This allows for + * better differentiation between crashes and other events and gradual migration + * from existing `before_send` implementations: + * + * - if you have a `before_send` implementation and do not define an `on_crash` + * callback your application will receive both normal and crash events as + * before + * - if you have a `before_send` implementation but only want to handle normal + * events with it, then you can define an `on_crash` callback that returns + * the passed-in event and does nothing else + * - if you are not interested in normal events, but only want to act on + * crashes (within the limits mentioned below), then only define an + * `on_crash` callback with the option to filter (on all backends) or enrich + * (only inproc) the crash event + * + * This function may be invoked inside of a signal handler and must be safe for + * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. + * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see + * the documentation on SEH (structured exception handling) for more information + * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * + * Platform-specific behavior: + * + * - does not work with crashpad on macOS. + * - for breakpad on Linux the `uctx` parameter is always NULL. + * - on Windows the crashpad backend can capture fast-fail crashes which + * by-pass SEH. Since `on_crash` is called by a local exception-handler, it will + * not be invoked when such a crash happened, even though a minidump will be + * sent. + */ +typedef sentry_value_t (*sentry_crash_function_t)( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure); + +/** + * Sets the `on_crash` callback. + * + * See the `sentry_crash_function_t` typedef above for more information. + */ +SENTRY_API void sentry_options_set_on_crash( + sentry_options_t *opts, sentry_crash_function_t func, void *data); + /** * Sets the DSN. */ SENTRY_API void sentry_options_set_dsn(sentry_options_t *opts, const char *dsn); +SENTRY_API void sentry_options_set_dsn_n( + sentry_options_t *opts, const char *dsn, size_t dsn_len); /** * Gets the DSN. @@ -759,6 +901,19 @@ SENTRY_API const char *sentry_options_get_dsn(const sentry_options_t *opts); * Sets the sample rate, which should be a double between `0.0` and `1.0`. * Sentry will randomly discard any event that is captured using * `sentry_capture_event` when a sample rate < 1 is set. + * + * The sampling happens at the end of the event processing according to the + * following order: + * + * https://develop.sentry.dev/sdk/sessions/#filter-order + * + * Only items 3. to 6. are currently applicable to sentry-native. This means + * each processing step is executed even if the sampling discards the event + * before sending it to the backend. This is particularly relevant to users of + * the `before_send` callback. + * + * The above is in contrast to versions up to 0.4.18 where the sampling happened + * at the beginning of the processing/filter sequence. */ SENTRY_API void sentry_options_set_sample_rate( sentry_options_t *opts, double sample_rate); @@ -773,6 +928,8 @@ SENTRY_API double sentry_options_get_sample_rate(const sentry_options_t *opts); */ SENTRY_API void sentry_options_set_release( sentry_options_t *opts, const char *release); +SENTRY_API void sentry_options_set_release_n( + sentry_options_t *opts, const char *release, size_t release_len); /** * Gets the release. @@ -784,6 +941,8 @@ SENTRY_API const char *sentry_options_get_release(const sentry_options_t *opts); */ SENTRY_API void sentry_options_set_environment( sentry_options_t *opts, const char *environment); +SENTRY_API void sentry_options_set_environment_n( + sentry_options_t *opts, const char *environment, size_t environment_len); /** * Gets the environment. @@ -796,6 +955,8 @@ SENTRY_API const char *sentry_options_get_environment( */ SENTRY_API void sentry_options_set_dist( sentry_options_t *opts, const char *dist); +SENTRY_API void sentry_options_set_dist_n( + sentry_options_t *opts, const char *dist, size_t dist_len); /** * Gets the dist. @@ -809,6 +970,8 @@ SENTRY_API const char *sentry_options_get_dist(const sentry_options_t *opts); */ SENTRY_API void sentry_options_set_http_proxy( sentry_options_t *opts, const char *proxy); +SENTRY_API void sentry_options_set_http_proxy_n( + sentry_options_t *opts, const char *proxy, size_t proxy_len); /** * Returns the configured http proxy. @@ -822,6 +985,8 @@ SENTRY_API const char *sentry_options_get_http_proxy( */ SENTRY_API void sentry_options_set_ca_certs( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_set_ca_certs_n( + sentry_options_t *opts, const char *path, size_t path_len); /** * Returns the configured path for ca certificates. @@ -834,6 +999,8 @@ SENTRY_API const char *sentry_options_get_ca_certs( */ SENTRY_API void sentry_options_set_transport_thread_name( sentry_options_t *opts, const char *name); +SENTRY_API void sentry_options_set_transport_thread_name_n( + sentry_options_t *opts, const char *name, size_t name_len); /** * Returns the configured http transport thread name. @@ -841,6 +1008,32 @@ SENTRY_API void sentry_options_set_transport_thread_name( SENTRY_API const char *sentry_options_get_transport_thread_name( const sentry_options_t *opts); +/* + * Configures the name of the sentry SDK. Returns 0 on success. + */ +SENTRY_API int sentry_options_set_sdk_name( + sentry_options_t *opts, const char *sdk_name); + +/* + * Configures the name of the sentry SDK. Returns 0 on success. + */ +SENTRY_API int sentry_options_set_sdk_name_n( + sentry_options_t *opts, const char *sdk_name, size_t sdk_name_len); + +/** + * Returns the configured sentry SDK name. Unless overwritten this defaults to + * SENTRY_SDK_NAME. + */ +SENTRY_API const char *sentry_options_get_sdk_name( + const sentry_options_t *opts); + +/** + * Returns the user agent. Unless overwritten this defaults to + * "SENTRY_SDK_NAME / SENTRY_SDK_VERSION". + */ +SENTRY_API const char *sentry_options_get_user_agent( + const sentry_options_t *opts); + /** * Enables or disables debug printing mode. */ @@ -875,6 +1068,11 @@ typedef void (*sentry_logger_function_t)( * Sets the sentry-native logger function. * * Used for logging debug events when the `debug` option is set to true. + * + * Note: Multiple threads may invoke your `func`. If you plan to mutate any data + * inside the `userdata` argument after initialization, you must ensure proper + * synchronization inside the logger function. + * */ SENTRY_API void sentry_options_set_logger( sentry_options_t *opts, sentry_logger_function_t func, void *userdata); @@ -939,6 +1137,8 @@ SENTRY_API int sentry_options_get_symbolize_stacktraces( */ SENTRY_API void sentry_options_add_attachment( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_add_attachment_n( + sentry_options_t *opts, const char *path, size_t path_len); /** * Sets the path to the crashpad handler if the crashpad backend is used. @@ -956,6 +1156,8 @@ SENTRY_API void sentry_options_add_attachment( */ SENTRY_API void sentry_options_set_handler_path( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_set_handler_path_n( + sentry_options_t *opts, const char *path, size_t path_len); /** * Sets the path to the Sentry Database Directory. @@ -988,6 +1190,8 @@ SENTRY_API void sentry_options_set_handler_path( */ SENTRY_API void sentry_options_set_database_path( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_set_database_path_n( + sentry_options_t *opts, const char *path, size_t path_len); #ifdef SENTRY_PLATFORM_WINDOWS /** @@ -995,18 +1199,24 @@ SENTRY_API void sentry_options_set_database_path( */ SENTRY_API void sentry_options_add_attachmentw( sentry_options_t *opts, const wchar_t *path); +SENTRY_API void sentry_options_add_attachmentw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len); /** * Wide char version of `sentry_options_set_handler_path`. */ SENTRY_API void sentry_options_set_handler_pathw( sentry_options_t *opts, const wchar_t *path); +SENTRY_API void sentry_options_set_handler_pathw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len); /** * Wide char version of `sentry_options_set_database_path`. */ SENTRY_API void sentry_options_set_database_pathw( sentry_options_t *opts, const wchar_t *path); +SENTRY_API void sentry_options_set_database_pathw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len); #endif /** @@ -1033,6 +1243,16 @@ SENTRY_API void sentry_options_set_shutdown_timeout( */ SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); +/** + * Sets a user-defined backend. + * + * Since creation and destruction of backends is not exposed in the API, this + * can only be used to set the backend to `NULL`, which disables the backend in + * the initialization. + */ +SENTRY_API void sentry_options_set_backend( + sentry_options_t *opts, sentry_backend_t *backend); + /* -- Global APIs -- */ /** @@ -1046,10 +1266,31 @@ SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); */ SENTRY_API int sentry_init(sentry_options_t *options); +/** + * Instructs the transport to flush its send queue. + * + * The `timeout` parameter is in milliseconds. + * + * Returns 0 on success, or a non-zero return value in case the timeout is hit. + * + * Note that this function will block the thread it was called from until the + * sentry background worker has finished its work or it timed out, whichever + * comes first. + */ +SENTRY_API int sentry_flush(uint64_t timeout); + /** * Shuts down the sentry client and forces transports to flush out. * * Returns 0 on success. + * + * Note that this does not uninstall any crash handler installed by our + * backends, which will still process crashes after `sentry_close()`, except + * when using `crashpad` on Linux or the `inproc` backend. + * + * Further note that this function will block the thread it was called from + * until the sentry background worker has finished its work or it timed out, + * whichever comes first. */ SENTRY_API int sentry_close(void); @@ -1115,16 +1356,29 @@ SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); /** * Sends a sentry event. * - * If SENTRY_PERFORMANCE_MONITORING is enabled, returns a nil UUID if the event - * being passed in is a transaction, and the transaction will not be sent nor - * consumed. `sentry_transaction_finish` should be used to send transactions. + * If returns a nil UUID if the event being passed in is a transaction, and the + * transaction will not be sent nor consumed. `sentry_transaction_finish` should + * be used to send transactions. */ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); +/** + * Allows capturing independently created minidumps. + * + * This generates a fatal error event, includes the scope and attachments. + * If the event isn't dropped by a before-send hook, the minidump is attached + * and the event is sent. + */ +SENTRY_API void sentry_capture_minidump(const char *path); +SENTRY_API void sentry_capture_minidump_n(const char *path, size_t path_len); + /** * Captures an exception to be handled by the backend. * * This is safe to be called from a crashing thread and may not return. + * + * Note: The `crashpad` client currently supports this only on Windows. `inproc` + * and `breakpad` support it on all platforms. */ SENTRY_EXPERIMENTAL_API void sentry_handle_exception( const sentry_ucontext_t *uctx); @@ -1148,31 +1402,40 @@ SENTRY_API void sentry_remove_user(void); * Sets a tag. */ SENTRY_API void sentry_set_tag(const char *key, const char *value); +SENTRY_API void sentry_set_tag_n( + const char *key, size_t key_len, const char *value, size_t value_len); /** * Removes the tag with the specified key. */ SENTRY_API void sentry_remove_tag(const char *key); +SENTRY_API void sentry_remove_tag_n(const char *key, size_t key_len); /** * Sets extra information. */ SENTRY_API void sentry_set_extra(const char *key, sentry_value_t value); +SENTRY_API void sentry_set_extra_n( + const char *key, size_t key_len, sentry_value_t value); /** * Removes the extra with the specified key. */ SENTRY_API void sentry_remove_extra(const char *key); +SENTRY_API void sentry_remove_extra_n(const char *key, size_t key_len); /** * Sets a context object. */ SENTRY_API void sentry_set_context(const char *key, sentry_value_t value); +SENTRY_API void sentry_set_context_n( + const char *key, size_t key_len, sentry_value_t value); /** * Removes the context object with the specified key. */ SENTRY_API void sentry_remove_context(const char *key); +SENTRY_API void sentry_remove_context_n(const char *key, size_t key_len); /** * Sets the event fingerprint. @@ -1181,6 +1444,8 @@ SENTRY_API void sentry_remove_context(const char *key); * trailing `NULL`. */ SENTRY_API void sentry_set_fingerprint(const char *fingerprint, ...); +SENTRY_API void sentry_set_fingerprint_n( + const char *fingerprint, size_t fingerprint_len, ...); /** * Removes the fingerprint. @@ -1191,28 +1456,14 @@ SENTRY_API void sentry_remove_fingerprint(void); * Sets the transaction. */ SENTRY_API void sentry_set_transaction(const char *transaction); - -/** - * Removes the transaction. - */ -SENTRY_API void sentry_remove_transaction(void); +SENTRY_API void sentry_set_transaction_n( + const char *transaction, size_t transaction_len); /** * Sets the event level. */ SENTRY_API void sentry_set_level(sentry_level_t level); -/** - * Starts a new session. - */ -SENTRY_API void sentry_start_session(void); - -/** - * Ends a session. - */ -SENTRY_API void sentry_end_session(void); - -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Sets the maximum number of spans that can be attached to a * transaction. @@ -1241,8 +1492,58 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sample_rate( SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( sentry_options_t *opts); +/* -- Session APIs -- */ + +typedef enum { + SENTRY_SESSION_STATUS_OK, + SENTRY_SESSION_STATUS_CRASHED, + SENTRY_SESSION_STATUS_ABNORMAL, + SENTRY_SESSION_STATUS_EXITED, +} sentry_session_status_t; + +/** + * Starts a new session. + */ +SENTRY_API void sentry_start_session(void); + +/** + * Ends a session. + */ +SENTRY_API void sentry_end_session(void); + +/** + * Ends a session with an explicit `status` code. + */ +SENTRY_EXPERIMENTAL_API void sentry_end_session_with_status( + sentry_session_status_t status); + /* -- Performance Monitoring/Tracing APIs -- */ +/** + * A sentry Transaction Context. + * + * See Transaction Interface under + * https://develop.sentry.dev/sdk/performance/#new-span-and-transaction-classes + */ +struct sentry_transaction_context_s; +typedef struct sentry_transaction_context_s sentry_transaction_context_t; + +/** + * A sentry Transaction. + * + * See https://develop.sentry.dev/sdk/event-payloads/transaction/ + */ +struct sentry_transaction_s; +typedef struct sentry_transaction_s sentry_transaction_t; + +/** + * A sentry Span. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ + */ +struct sentry_span_s; +typedef struct sentry_span_s sentry_span_t; + /** * Constructs a new Transaction Context. The returned value needs to be passed * into `sentry_transaction_start` in order to be recorded and sent to sentry. @@ -1256,16 +1557,30 @@ SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( * Also see https://develop.sentry.dev/sdk/event-payloads/transaction/#anatomy * for an explanation of `operation`, in addition to other properties and * actions that can be performed on a Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Transaction Context + * if it needs to be mutated across threads. Methods operating on the + * Transaction Context will mention what kind of expectations they carry if they + * need to mutate or access the object in a thread-safe way. */ -SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_transaction_context( - const char *name, const char *operation); +SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * +sentry_transaction_context_new(const char *name, const char *operation); +SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * +sentry_transaction_context_new_n(const char *name, size_t name_len, + const char *operation, size_t operation_len); /** * Sets the `name` on a Transaction Context, which will be used in the * Transaction constructed off of the context. + * + * The Transaction Context should not be mutated by other functions while + * setting a name on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( - sentry_value_t transaction, const char *name); + sentry_transaction_context_t *tx_cxt, const char *name); +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name_n( + sentry_transaction_context_t *tx_cxt, const char *name, size_t name_len); /** * Sets the `operation` on a Transaction Context, which will be used in the @@ -1273,9 +1588,15 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( * * See https://develop.sentry.dev/sdk/performance/span-operations/ for * conventions on `operation`s. + * + * The Transaction Context should not be mutated by other functions while + * setting an operation on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( - sentry_value_t transaction, const char *operation); + sentry_transaction_context_t *tx_cxt, const char *operation); +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation_n( + sentry_transaction_context_t *tx_cxt, const char *operation, + size_t operation_len); /** * Sets the `sampled` field on a Transaction Context, which will be used in the @@ -1284,40 +1605,471 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( * When passed any value above 0, the Transaction will bypass all sampling * options and always be sent to sentry. If passed 0, this Transaction and its * child spans will never be sent to sentry. + * + * The Transaction Context should not be mutated by other functions while + * setting `sampled` on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( - sentry_value_t transaction, int sampled); + sentry_transaction_context_t *tx_cxt, int sampled); /** - * Removes the sampled field on a Transaction Context, which will be used in the - * Transaction constructed off of the context. + * Removes the `sampled` field on a Transaction Context, which will be used in + * the Transaction constructed off of the context. * * The Transaction will use the sampling rate as defined in `sentry_options`. + * + * The Transaction Context should not be mutated by other functions while + * removing `sampled`. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( - sentry_value_t transaction); + sentry_transaction_context_t *tx_cxt); + +/** + * Update the Transaction Context with the given HTTP header key/value pair. + * + * This is used to propagate distributed tracing metadata from upstream + * services. Therefore, the headers of incoming requests should be fed into this + * function so that sentry is able to continue a trace that was started by an + * upstream service. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( + sentry_transaction_context_t *tx_cxt, const char *key, const char *value); +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header_n( + sentry_transaction_context_t *tx_cxt, const char *key, size_t key_len, + const char *value, size_t value_len); /** * Starts a new Transaction based on the provided context, restored from an * external integration (i.e. a span from a different SDK) or manually * constructed by a user. * - * Takes ownership of `transaction_context`. + * The second parameter is a custom Sampling Context to be used with a Traces + * Sampler to make a more informed sampling decision. The SDK does not currently + * support a custom Traces Sampler and this parameter is ignored for the time + * being but needs to be provided. + * + * Returns a Transaction, which is expected to be manually managed by the + * caller. Manual management involves ensuring that `sentry_transaction_finish` + * is invoked for the Transaction, and that the caller manually starts and + * finishes any child Spans as needed on the Transaction. + * + * Not invoking `sentry_transaction_finish` with the returned Transaction means + * it will be discarded, and will not be sent to sentry. + * + * To ensure that any Events or Message Events are associated with this + * Transaction while it is active, invoke and pass in the Transaction returned + * by this function to `sentry_set_transaction_object`. Further documentation on + * this can be found in `sentry_set_transaction_object`'s docstring. + * + * Takes ownership of `transaction_context`. A Transaction Context cannot be + * modified or re-used after it is used to start a Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Transaction if it + * needs to be mutated across threads. Methods operating on the Transaction will + * mention what kind of expectations they carry if they need to mutate or access + * the object in a thread-safe way. */ -SENTRY_EXPERIMENTAL_API sentry_value_t sentry_transaction_start( - sentry_value_t transaction_context); +SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( + sentry_transaction_context_t *tx_cxt, sentry_value_t sampling_ctx); /** - * Finishes and sends a transaction to sentry. The event ID of the transaction + * Finishes and sends a Transaction to sentry. The event ID of the Transaction * will be returned if this was successful; A nil UUID will be returned * otherwise. * * Always takes ownership of `transaction`, regardless of whether the operation - * was successful or not. + * was successful or not. A Transaction cannot be modified or re-used after it + * is finished. */ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( - sentry_value_t transaction); -#endif + sentry_transaction_t *tx); + +/** + * Sets the Transaction so any Events sent while the Transaction + * is active will be associated with the Transaction. + * + * If the Transaction being passed in is unsampled, it will still be associated + * with any new Events. This will lead to some Events pointing to orphan or + * missing traces in sentry, see + * https://docs.sentry.io/product/sentry-basics/tracing/trace-view/#orphan-traces-and-broken-subtraces + * + * This increases the number of references pointing to the Transaction. Invoke + * `sentry_transaction_finish` to remove the Transaction set by this function as + * well as its reference by passing in the same Transaction as the one passed + * into this function. + */ +SENTRY_EXPERIMENTAL_API void sentry_set_transaction_object( + sentry_transaction_t *tx); + +/** + * Sets the Span so any Events sent while the Span + * is active will be associated with the Span. + * + * This increases the number of references pointing to the Span. Invoke + * `sentry_span_finish` to remove the Span set by this function as well + * as its reference by passing in the same Span as the one passed into + * this function. + */ +SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_span_t *span); + +/** + * Starts a new Span. + * + * The return value of `sentry_transaction_start` should be passed in as + * `parent`. + * + * Both `operation` and `description` can be null, but it is recommended to + * supply the former. See + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around operations. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of + * the created Span's properties and expectations for `operation` and + * `description`. + * + * Returns a value that should be passed into `sentry_span_finish`. Not + * finishing the Span means it will be discarded, and will not be sent to + * sentry. `sentry_value_null` will be returned if the child Span could not be + * created. + * + * To ensure that any Events or Message Events are associated with this + * Span while it is active, invoke and pass in the Span returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. + * + * This increases the number of references pointing to the Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Span if it needs + * to be mutated across threads. Methods operating on the Span will mention what + * kind of expectations they carry if they need to mutate or access the object + * in a thread-safe way. + */ +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( + sentry_transaction_t *parent, const char *operation, + const char *description); +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child_n( + sentry_transaction_t *parent, const char *operation, size_t operation_len, + const char *description, size_t description_len); + +/** + * Starts a new Span. + * + * The return value of `sentry_span_start_child` may be passed in as `parent`. + * + * Both `operation` and `description` can be null, but it is recommended to + * supply the former. See + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around operations. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of + * the created Span's properties and expectations for `operation` and + * `description`. + * + * Returns a value that should be passed into `sentry_span_finish`. Not + * finishing the Span means it will be discarded, and will not be sent to + * sentry. `sentry_value_null` will be returned if the child Span could not be + * created. + * + * To ensure that any Events or Message Events are associated with this + * Span while it is active, invoke and pass in the Span returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Span if it needs + * to be mutated across threads. Methods operating on the Span will mention what + * kind of expectations they carry if they need to mutate or access the object + * in a thread-safe way. + */ +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child( + sentry_span_t *parent, const char *operation, const char *description); +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child_n( + sentry_span_t *parent, const char *operation, size_t operation_len, + const char *description, size_t description_len); + +/** + * Finishes a Span. + * + * This takes ownership of `span`. A Span cannot be modified or re-used after it + * is finished. + * + * This will mutate the `span`'s containing Transaction, so the containing + * Transaction should also not be mutated by other functions when finishing a + * span. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_finish(sentry_span_t *span); + +/** + * Sets a tag on a Transaction to the given string value. + * + * Tags longer than 200 bytes will be truncated. + * + * The Transaction should not be mutated by other functions while a tag is being + * set on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( + sentry_transaction_t *transaction, const char *tag, const char *value); +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag_n( + sentry_transaction_t *transaction, const char *tag, size_t tag_len, + const char *value, size_t value_len); + +/** + * Removes a tag from a Transaction. + * + * The Transaction should not be mutated by other functions while a tag is being + * removed from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( + sentry_transaction_t *transaction, const char *tag); +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag_n( + sentry_transaction_t *transaction, const char *tag, size_t tag_len); + +/** + * Sets the given key in a Transaction's "data" section to the given value. + * + * The Transaction should not be mutated by other functions while data is being + * set on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( + sentry_transaction_t *transaction, const char *key, sentry_value_t value); +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data_n( + sentry_transaction_t *transaction, const char *key, size_t key_len, + sentry_value_t value); + +/** + * Removes a key from a Transaction's "data" section. + * + * The Transaction should not be mutated by other functions while data is being + * removed from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( + sentry_transaction_t *transaction, const char *key); +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data_n( + sentry_transaction_t *transaction, const char *key, size_t key_len); + +/** + * Sets a tag on a Span to the given string value. + * + * Tags longer than 200 bytes will be truncated. + * + * The Span should not be mutated by other functions while a tag is being set on + * it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( + sentry_span_t *span, const char *tag, const char *value); +SENTRY_EXPERIMENTAL_API void sentry_span_set_tag_n(sentry_span_t *span, + const char *tag, size_t tag_len, const char *value, size_t value_len); + +/** + * Removes a tag from a Span. + * + * The Span should not be mutated by other functions while a tag is being + * removed from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( + sentry_span_t *span, const char *tag); +SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag_n( + sentry_span_t *span, const char *tag, size_t tag_len); + +/** + * Sets the given key in a Span's "data" section to the given value. + * + * The Span should not be mutated by other functions while data is being set on + * it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_data( + sentry_span_t *span, const char *key, sentry_value_t value); +SENTRY_EXPERIMENTAL_API void sentry_span_set_data_n( + sentry_span_t *span, const char *key, size_t key_len, sentry_value_t value); + +/** + * Removes a key from a Span's "data" section. + * + * The Span should not be mutated by other functions while data is being removed + * from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( + sentry_span_t *span, const char *key); +SENTRY_EXPERIMENTAL_API void sentry_span_remove_data_n( + sentry_span_t *span, const char *key, size_t key_len); + +/** + * Sets a Transaction's name. + * + * The Transaction should not be mutated by other functions while setting its + * name. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( + sentry_transaction_t *transaction, const char *name); +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name_n( + sentry_transaction_t *transaction, const char *name, size_t name_len); + +/** + * Creates a new User Feedback with a specific name, email and comments. + * + * See https://develop.sentry.dev/sdk/envelopes/#user-feedback + * + * User Feedback has to be associated with a specific event that has been + * sent to Sentry earlier. + */ +SENTRY_API sentry_value_t sentry_value_new_user_feedback( + const sentry_uuid_t *uuid, const char *name, const char *email, + const char *comments); +SENTRY_API sentry_value_t sentry_value_new_user_feedback_n( + const sentry_uuid_t *uuid, const char *name, size_t name_len, + const char *email, size_t email_len, const char *comments, + size_t comments_len); + +/** + * Captures a manually created User Feedback and sends it to Sentry. + */ +SENTRY_API void sentry_capture_user_feedback(sentry_value_t user_feedback); + +/** + * The status of a Span or Transaction. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for documentation. + */ +typedef enum { + // The operation completed successfully. + // HTTP status 100..299 + successful redirects from the 3xx range. + SENTRY_SPAN_STATUS_OK, + // The operation was cancelled (typically by the user). + SENTRY_SPAN_STATUS_CANCELLED, + // Unknown. Any non-standard HTTP status code. + // "We do not know whether the transaction failed or succeeded." + SENTRY_SPAN_STATUS_UNKNOWN, + // Client specified an invalid argument. 4xx. + // Note that this differs from FailedPrecondition. InvalidArgument + // indicates arguments that are problematic regardless of the + // state of the system. + SENTRY_SPAN_STATUS_INVALID_ARGUMENT, + // Deadline expired before operation could complete. + // For operations that change the state of the system, this error may be + // returned even if the operation has been completed successfully. + // HTTP redirect loops and 504 Gateway Timeout. + SENTRY_SPAN_STATUS_DEADLINE_EXCEEDED, + // 404 Not Found. Some requested entity (file or directory) was not found. + SENTRY_SPAN_STATUS_NOT_FOUND, + // Already exists (409) + // Some entity that we attempted to create already exists. + SENTRY_SPAN_STATUS_ALREADY_EXISTS, + // 403 Forbidden + // The caller does not have permission to execute the specified operation. + SENTRY_SPAN_STATUS_PERMISSION_DENIED, + // 429 Too Many Requests + // Some resource has been exhausted, perhaps a per-user quota or perhaps + // the entire file system is out of space. + SENTRY_SPAN_STATUS_RESOURCE_EXHAUSTED, + // Operation was rejected because the system is not in a state required for + // the operation's execution. + SENTRY_SPAN_STATUS_FAILED_PRECONDITION, + // The operation was aborted, typically due to a concurrency issue. + SENTRY_SPAN_STATUS_ABORTED, + // Operation was attempted past the valid range. + SENTRY_SPAN_STATUS_OUT_OF_RANGE, + // 501 Not Implemented + // Operation is not implemented or not enabled. + SENTRY_SPAN_STATUS_UNIMPLEMENTED, + // Other/generic 5xx + SENTRY_SPAN_STATUS_INTERNAL_ERROR, + // 503 Service Unavailable + SENTRY_SPAN_STATUS_UNAVAILABLE, + // Unrecoverable data loss or corruption + SENTRY_SPAN_STATUS_DATA_LOSS, + // 401 Unauthorized (actually does mean unauthenticated according to RFC + // 7235) + // Prefer PermissionDenied if a user is logged in. + SENTRY_SPAN_STATUS_UNAUTHENTICATED, +} sentry_span_status_t; + +/** + * Sets a Span's status. + * + * The Span should not be mutated by other functions while setting its status. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_status( + sentry_span_t *span, sentry_span_status_t status); + +/** + * Sets a Transaction's status. + * + * The Transaction should not be mutated by other functions while setting its + * status. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_status( + sentry_transaction_t *tx, sentry_span_status_t status); + +/** + * Type of the `iter_headers` callback. + * + * The callback is being called with HTTP header key/value pairs. + * These headers can be attached to outgoing HTTP requests to propagate + * distributed tracing metadata to downstream services. + * + */ +typedef void (*sentry_iter_headers_function_t)( + const char *key, const char *value, void *userdata); + +/** + * Iterates the distributed tracing HTTP headers for the given span. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_iter_headers(sentry_span_t *span, + sentry_iter_headers_function_t callback, void *userdata); + +/** + * Iterates the distributed tracing HTTP headers for the given transaction. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( + sentry_transaction_t *tx, sentry_iter_headers_function_t callback, + void *userdata); + +/** + * Returns whether the application has crashed on the last run. + * + * Notes: + * * The underlying value is set by sentry_init() - it must be called first. + * * Call sentry_clear_crashed_last_run() to reset for the next app run. + * + * Possible return values: + * 1 = the last run was a crash + * 0 = no crash recognized + * -1 = sentry_init() hasn't been called yet + */ +SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(void); + +/** + * Clear the status of the "crashed-last-run". You should explicitly call + * this after sentry_init() if you're using sentry_get_crashed_last_run(). + * Otherwise, the same information is reported on any subsequent runs. + * + * Notes: + * * This doesn't change the value of sentry_get_crashed_last_run() yet. + * However, if sentry_init() is called again, the value will change. + * * This may only be called after sentry_init() and before sentry_close(). + * + * Returns 0 on success, 1 on error. + */ +SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(void); + +/** + * Sentry SDK version. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_version(void); + +/** + * Sentry SDK name set during build time. + * Deprecated: Please use sentry_options_get_sdk_name instead. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_name(void); + +/** + * Sentry SDK User-Agent set during build time. + * Deprecated: Please use sentry_options_get_user_agent instead. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_user_agent(void); #ifdef __cplusplus } diff --git a/ndk/README.md b/ndk/README.md new file mode 100644 index 000000000..190b51251 --- /dev/null +++ b/ndk/README.md @@ -0,0 +1,75 @@ +# Android NDK support for sentry-native + +| Package | Maven Central | Minimum Android API Level | Supported ABIs | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------- | +| `io.sentry:sentry-native-ndk` | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-native-ndk/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-native-ndk) | 19 | "x86", "armeabi-v7a", "x86_64", "arm64-v8a" | + +## Resources + +- [SDK Documentation](https://docs.sentry.io/platforms/native/) +- [Discord](https://discord.gg/ez5KZN7) server for project discussions +- Follow [@getsentry](https://twitter.com/getsentry) on Twitter for updates + +## About + +The sub-project aims to automatically bundle pre-built `sentry-native` binaries together with a Java JNI layer into an Android friendly `.aar` package. + +The `.aar` package also provides [prefab](https://developer.android.com/build/native-dependencies?buildsystem=cmake) support, giving you the possibility to consume the native `sentry.h` APIs from your native app code. + +If you're using the [Sentry Android SDK](https://docs.sentry.io/platforms/android/), this package is included by default already. + +Besides the main package in `ndk/lib`, a simple Android app for for testing purposes is provided in the `ndk/sample` folder. + +## Building and Installation + +The `ndk` project uses the Gradle build system in combination with CMake. You can either use a suitable IDE (e.g. Android Studio) or the command line to build it. + +## Testing and consuming a local package version + +1. Set a custom `versionName` in the `ndk/gradle.properties` file +2. Publish the package locally + +```shell +cd ndk +./gradlew :sentry-native-ndk:publishToMavenLocal +``` + +3. Consume the build in your app + +``` +// usually settings.gradle +allprojects { + repositories { + mavenLocal() + } +} + +// usually app/build.gradle +android { + buildFeatures { + prefab = true + } +} + +dependencies { + implementation("io.sentry:sentry-native-ndk:") +} +``` + +4. Link the pre-built packages with your native code + +```cmake +# usually app/CMakeLists.txt + +find_package(sentry-native-ndk REQUIRED CONFIG) + +target_link_libraries( PRIVATE + ${LOG_LIB} + sentry-native-ndk::sentry-android + sentry-native-ndk::sentry +) +``` + +## Development + +Please see the [contribution guide](../CONTRIBUTING.md). diff --git a/ndk/build.gradle.kts b/ndk/build.gradle.kts new file mode 100644 index 000000000..4e9ded034 --- /dev/null +++ b/ndk/build.gradle.kts @@ -0,0 +1,207 @@ +import com.diffplug.spotless.LineEnding +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.MavenPublishPlugin +import com.vanniktech.maven.publish.MavenPublishPluginExtension +import groovy.util.Node +import io.gitlab.arturbosch.detekt.extensions.DetektExtension +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + +plugins { + `java-library` + id("com.diffplug.spotless") version "6.11.0" apply true + id("io.gitlab.arturbosch.detekt") version "1.19.0" + `maven-publish` + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.13.0" + +} + +buildscript { + repositories { + google() + } + dependencies { + classpath("com.android.tools.build:gradle:7.4.2") + classpath(kotlin("gradle-plugin", version = "1.8.0")) + classpath("com.vanniktech:gradle-maven-publish-plugin:0.18.0") + // dokka is required by gradle-maven-publish-plugin. + classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.7.10") + classpath("net.ltgt.gradle:gradle-errorprone-plugin:3.0.1") + + // legacy pre-prefab support + // https://github.com/howardpang/androidNativeBundle + classpath("io.github.howardpang:androidNativeBundle:1.1.4") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } + group = "io.sentry" + version = properties["versionName"].toString() + description = "SDK for sentry.io" + tasks { + withType { + testLogging.showStandardStreams = true + testLogging.exceptionFormat = TestExceptionFormat.FULL + testLogging.events = setOf( + TestLogEvent.SKIPPED, + TestLogEvent.PASSED, + TestLogEvent.FAILED + ) + maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 + + // Cap JVM args per test + minHeapSize = "128m" + maxHeapSize = "1g" + dependsOn("cleanTest") + } + withType { + options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Werror", "-Xlint:-classfile", "-Xlint:-processing")) + } + } +} + +subprojects { + plugins.withId("io.gitlab.arturbosch.detekt") { + configure { + buildUponDefaultConfig = true + allRules = true + config.setFrom("${rootProject.rootDir}/detekt.yml") + } + } + + if (!this.name.contains("sample")) { + apply() + + val sep = File.separator + + configure { + + this.getByName("main").contents { + // non android modules + from("build${sep}libs") + from("build${sep}publications${sep}maven") + // android modules + from("build${sep}outputs${sep}aar") { + include("*-release*") + } + from("build${sep}publications${sep}release") + } + + // craft only uses zip archives + this.forEach { dist -> + if (dist.name == DistributionPlugin.MAIN_DISTRIBUTION_NAME) { + tasks.getByName("distTar").enabled = false + } else { + tasks.getByName(dist.name + "DistTar").enabled = false + } + } + } + + tasks.named("distZip").configure { + this.dependsOn("publishToMavenLocal") + this.doLast { + val distributionFilePath = + "${this.project.buildDir}${sep}distributions${sep}${this.project.name}-${this.project.version}.zip" + val file = File(distributionFilePath) + if (!file.exists()) throw IllegalStateException("Distribution file: $distributionFilePath does not exist") + if (file.length() == 0L) throw IllegalStateException("Distribution file: $distributionFilePath is empty") + } + } + + afterEvaluate { + apply() + + configure { + // signing is done when uploading files to MC + // via gpg:sign-and-deploy-file (release.kts) + releaseSigningEnabled = false + } + + @Suppress("UnstableApiUsage") + configure { + assignAarTypes() + } + } + } +} + +spotless { + lineEndings = LineEnding.UNIX + java { + target("**/*.java") + removeUnusedImports() + googleJavaFormat() + targetExclude("**/generated/**", "**/vendor/**") + } + kotlin { + target("**/*.kt") + ktlint() + } + kotlinGradle { + target("**/*.kts") + ktlint() + } +} + +private val androidLibs = setOf( + "lib" +) + +private val androidXLibs = listOf( + "androidx.core:core" +) + +/* + * Adapted from https://github.com/androidx/androidx/blob/c799cba927a71f01ea6b421a8f83c181682633fb/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt#L524-L549 + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Workaround for https://github.com/gradle/gradle/issues/3170 +@Suppress("UnstableApiUsage") +fun MavenPublishBaseExtension.assignAarTypes() { + pom { + withXml { + val dependencies = asNode().children().find { + it is Node && it.name().toString().endsWith("dependencies") + } as Node? + + dependencies?.children()?.forEach { dep -> + if (dep !is Node) { + return@forEach + } + val group = dep.children().firstOrNull { + it is Node && it.name().toString().endsWith("groupId") + } as? Node + val groupValue = group?.children()?.firstOrNull() as? String + + val artifactId = dep.children().firstOrNull { + it is Node && it.name().toString().endsWith("artifactId") + } as? Node + val artifactIdValue = artifactId?.children()?.firstOrNull() as? String + + if (artifactIdValue in androidLibs) { + dep.appendNode("type", "aar") + } else if ("$groupValue:$artifactIdValue" in androidXLibs) { + dep.appendNode("type", "aar") + } + } + } + } +} diff --git a/ndk/debug.keystore b/ndk/debug.keystore new file mode 100644 index 000000000..7da7480dd Binary files /dev/null and b/ndk/debug.keystore differ diff --git a/ndk/gradle.properties b/ndk/gradle.properties new file mode 100644 index 000000000..0d3666df5 --- /dev/null +++ b/ndk/gradle.properties @@ -0,0 +1,53 @@ +# Daemons heap size +org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC +org.gradle.caching=true +org.gradle.parallel=true + +# AndroidX required by AGP >= 3.6.x +android.useAndroidX=true + +# Required by AGP >= 8.0.x +android.defaults.buildfeatures.buildconfig=true + +# Release information, used for maven publishing +versionName=0.7.12 + +# disable renderscript, it's enabled by default +android.defaults.buildfeatures.renderscript=false + +# disable shader compilation, it's enabled by default +android.defaults.buildfeatures.shaders=false + +# disable aidl files, it's enabled by default +android.defaults.buildfeatures.aidl=false + +# disable Resource Values generation +android.defaults.buildfeatures.resvalues=false + +# disable automatically adding Kotlin stdlib to compile dependencies +kotlin.stdlib.default.dependency=false + +# TODO: Enable Prefab https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html +# android.enablePrefab=true +# android.prefabVersion=1.0.0 + +# publication pom properties +POM_NAME=Sentry SDK +POM_DESCRIPTION=SDK for sentry.io +POM_URL=https://github.com/getsentry/sentry-native +POM_SCM_URL=https://github.com/getsentry/sentry-native +POM_SCM_CONNECTION=scm:git:git://github.com/getsentry/sentry-native.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/getsentry/sentry-native.git + +POM_LICENCE_NAME=MIT +POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php + +POM_DEVELOPER_ID=getsentry +POM_DEVELOPER_NAME=Sentry Team and Contributors +POM_DEVELOPER_URL=https://github.com/getsentry/ + +POM_ARTIFACT_ID=sentry-native-ndk + +systemProp.org.gradle.internal.http.socketTimeout=120000 + +android.nonTransitiveRClass=true diff --git a/ndk/gradle/wrapper/gradle-wrapper.jar b/ndk/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..7f93135c4 Binary files /dev/null and b/ndk/gradle/wrapper/gradle-wrapper.jar differ diff --git a/ndk/gradle/wrapper/gradle-wrapper.properties b/ndk/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..3fa8f862f --- /dev/null +++ b/ndk/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/ndk/gradlew b/ndk/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/ndk/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/ndk/gradlew.bat b/ndk/gradlew.bat new file mode 100644 index 000000000..6689b85be --- /dev/null +++ b/ndk/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ndk/lib/CMakeLists.txt b/ndk/lib/CMakeLists.txt new file mode 100644 index 000000000..ff1471b35 --- /dev/null +++ b/ndk/lib/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10) +project(Sentry-Android LANGUAGES C CXX) + +# Add sentry-android shared library +add_library(sentry-android SHARED src/main/jni/sentry.c) + +# make sure that we build it as a shared lib instead of a static lib +set(BUILD_SHARED_LIBS ON) +set(SENTRY_BUILD_SHARED_LIBS ON) + +# Adding sentry-native project +add_subdirectory(${SENTRY_NATIVE_SRC} sentry_build) + +# Link to sentry-native +target_link_libraries(sentry-android PRIVATE $) + +# Support 16KB page sizes +target_link_options(sentry-android PRIVATE "-Wl,-z,max-page-size=16384") diff --git a/ndk/lib/api/sentry-android-ndk.api b/ndk/lib/api/sentry-android-ndk.api new file mode 100644 index 000000000..e8f838ce8 --- /dev/null +++ b/ndk/lib/api/sentry-android-ndk.api @@ -0,0 +1,29 @@ +public final class io/sentry/android/ndk/BuildConfig { + public static final field BUILD_TYPE Ljava/lang/String; + public static final field DEBUG Z + public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; + public static final field VERSION_NAME Ljava/lang/String; + public fun ()V +} + +public final class io/sentry/android/ndk/DebugImagesLoader : io/sentry/android/core/IDebugImagesLoader { + public fun (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/ndk/NativeModuleListLoader;)V + public fun clearDebugImages ()V + public fun loadDebugImages ()Ljava/util/List; +} + +public final class io/sentry/android/ndk/NdkScopeObserver : io/sentry/ScopeObserverAdapter { + public fun (Lio/sentry/SentryOptions;)V + public fun addBreadcrumb (Lio/sentry/Breadcrumb;)V + public fun removeExtra (Ljava/lang/String;)V + public fun removeTag (Ljava/lang/String;)V + public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V + public fun setTag (Ljava/lang/String;Ljava/lang/String;)V + public fun setUser (Lio/sentry/protocol/User;)V +} + +public final class io/sentry/android/ndk/SentryNdk { + public static fun close ()V + public static fun init (Lio/sentry/android/core/SentryAndroidOptions;)V +} + diff --git a/ndk/lib/build.gradle.kts b/ndk/lib/build.gradle.kts new file mode 100644 index 000000000..3381b21b9 --- /dev/null +++ b/ndk/lib/build.gradle.kts @@ -0,0 +1,138 @@ +plugins { + id("com.android.library") + kotlin("android") + id("com.ydq.android.gradle.native-aar.export") +} + +var sentryNativeSrc: String = "${project.projectDir}/../.." + +android { + compileSdk = 34 + namespace = "io.sentry.ndk" + + defaultConfig { + minSdk = 19 + + externalNativeBuild { + cmake { + arguments.add(0, "-DANDROID_STL=c++_static") + arguments.add(0, "-DSENTRY_NATIVE_SRC=$sentryNativeSrc") + } + } + + ndk { + abiFilters.addAll(listOf("x86", "armeabi-v7a", "x86_64", "arm64-v8a")) + } + } + + // we use the default NDK and CMake versions based on the AGP's version + // https://developer.android.com/studio/projects/install-ndk#apply-specific-version + externalNativeBuild { + cmake { + path("CMakeLists.txt") + } + } + + buildTypes { + getByName("debug") + getByName("release") { + consumerProguardFiles("proguard-rules.pro") + } + } + + buildFeatures { + prefabPublishing = true + } + + // creates + // lib.aar/prefab/modules/sentry-android/libs//.so + // lib.aar/prefab/modules/sentry-android/include/sentry.h + prefab { + create("sentry-android") {} + create("sentry") { + headers = "../../include" + } + } + + // legacy pre-prefab support + // https://github.com/howardpang/androidNativeBundle + // creates + // lib.aar/jni//.so + // lib.aar/jni/include/sentry.h + nativeBundleExport { + headerDir = "../../include" + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + testOptions { + animationsDisabled = true + unitTests.apply { + isReturnDefaultValues = true + isIncludeAndroidResources = true + } + } + + lint { + warningsAsErrors = true + checkDependencies = true + checkReleaseBuilds = true + } + + variantFilter { + if (System.getenv("CI")?.toBoolean() == true && buildType.name == "debug") { + ignore = true + } + } + + packagingOptions { + jniLibs { + useLegacyPackaging = true + } + } +} + +dependencies { + compileOnly("org.jetbrains:annotations:23.0.0") +} + +/* + * Prefab doesn't support c++_static, so we need to change it to none. + * This should be fine, as we don't expose any conflicting symbols. + * Based on: https://github.com/bugsnag/bugsnag-android/blob/59460018551750dfcce4fd4e9f612eae7826559e/bugsnag-plugin-android-ndk/build.gradle.kts + * + * Copyright (c) 2012 Bugsnag + + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +afterEvaluate { + tasks.getByName("prefabReleasePackage") { + doLast { + project.fileTree("build/intermediates/prefab_package/") { + include("**/abi.json") + }.forEach { file -> + file.writeText(file.readText().replace("c++_static", "none")) + } + } + } +} diff --git a/ndk/lib/proguard-rules.pro b/ndk/lib/proguard-rules.pro new file mode 100644 index 000000000..a6d1d5f15 --- /dev/null +++ b/ndk/lib/proguard-rules.pro @@ -0,0 +1,22 @@ +##---------------Begin: proguard configuration for NDK ---------- + +# The Android SDK checks at runtime if this class is available via Class.forName +-keep class io.sentry.ndk.SentryNdk { *; } + +# The JNI layer uses this class through reflection +-keep class io.sentry.ndk.NdkOptions { *; } +-keep class io.sentry.ndk.DebugImage { *; } + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames,includedescriptorclasses class * { + native ; +} + +# don't warn jetbrains annotations +-dontwarn org.jetbrains.annotations.** + +# To ensure that stack traces is unambiguous +# https://developer.android.com/studio/build/shrink-code#decode-stack-trace +-keepattributes LineNumberTable,SourceFile + +##---------------End: proguard configuration for NDK ---------- diff --git a/ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java b/ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java new file mode 100644 index 000000000..e06b6e2b4 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java @@ -0,0 +1,199 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public final class DebugImage { + + /** + * The unique UUID of the image. + * + *

UUID computed from the file contents, assigned by the Java SDK. + */ + private @Nullable String uuid; + + private @Nullable String type; + /** + * Unique debug identifier of the image. + * + *

- `elf`: Debug identifier of the dynamic library or executable. If a code identifier is + * available, the debug identifier is the little-endian UUID representation of the first 16-bytes + * of that identifier. Spaces are inserted for readability, note the byte order of the first + * fields: + * + *

```text code id: f1c3bcc0 2798 65fe 3058 404b2831d9e6 4135386c debug id: + * c0bcc3f1-9827-fe65-3058-404b2831d9e6 ``` + * + *

If no code id is available, the debug id should be computed by XORing the first 4096 bytes + * of the `.text` section in 16-byte chunks, and representing it as a little-endian UUID (again + * swapping the byte order). + * + *

- `pe`: `signature` and `age` of the PDB file. Both values can be read from the CodeView + * PDB70 debug information header in the PE. The value should be represented as little-endian + * UUID, with the age appended at the end. Note that the byte order of the UUID fields must be + * swapped (spaces inserted for readability): + * + *

```text signature: f1c3bcc0 2798 65fe 3058 404b2831d9e6 age: 1 debug_id: + * c0bcc3f1-9827-fe65-3058-404b2831d9e6-1 ``` + * + *

- `macho`: Identifier of the dynamic library or executable. It is the value of the `LC_UUID` + * load command in the Mach header, formatted as UUID. + */ + private @Nullable String debugId; + + /** + * Path and name of the debug companion file. + * + *

- `elf`: Name or absolute path to the file containing stripped debug information for this + * image. This value might be _required_ to retrieve debug files from certain symbol servers. + * + *

- `pe`: Name of the PDB file containing debug information for this image. This value is + * often required to retrieve debug files from specific symbol servers. + * + *

- `macho`: Name or absolute path to the dSYM file containing debug information for this + * image. This value might be required to retrieve debug files from certain symbol servers. + */ + private @Nullable String debugFile; + /** + * Optional identifier of the code file. + * + *

- `elf`: If the program was compiled with a relatively recent compiler, this should be the + * hex representation of the `NT_GNU_BUILD_ID` program header (type `PT_NOTE`), or the value of + * the `.note.gnu.build-id` note section (type `SHT_NOTE`). Otherwise, leave this value empty. + * + *

Certain symbol servers use the code identifier to locate debug information for ELF images, + * in which case this field should be included if possible. + * + *

- `pe`: Identifier of the executable or DLL. It contains the values of the `time_date_stamp` + * from the COFF header and `size_of_image` from the optional header formatted together into a hex + * string using `%08x%X` (note that the second value is not padded): + * + *

```text time_date_stamp: 0x5ab38077 size_of_image: 0x9000 code_id: 5ab380779000 ``` + * + *

The code identifier should be provided to allow server-side stack walking of binary crash + * reports, such as Minidumps. + * + *

+ * + *

- `macho`: Identifier of the dynamic library or executable. It is the value of the `LC_UUID` + * load command in the Mach header, formatted as UUID. Can be empty for Mach images, as it is + * equivalent to the debug identifier. + */ + private @Nullable String codeId; + /** + * Path and name of the image file (required). + * + *

The absolute path to the dynamic library or executable. This helps to locate the file if it + * is missing on Sentry. + * + *

- `pe`: The code file should be provided to allow server-side stack walking of binary crash + * reports, such as Minidumps. + */ + private @Nullable String codeFile; + /** + * Starting memory address of the image (required). + * + *

Memory address, at which the image is mounted in the virtual address space of the process. + * Should be a string in hex representation prefixed with `"0x"`. + */ + private @Nullable String imageAddr; + /** + * Size of the image in bytes (required). + * + *

The size of the image in virtual memory. If missing, Sentry will assume that the image spans + * up to the next image, which might lead to invalid stack traces. + */ + private @Nullable Long imageSize; + /** + * CPU architecture target. + * + *

Architecture of the module. If missing, this will be backfilled by Sentry. + */ + private @Nullable String arch; + + @SuppressWarnings("unused") + private @Nullable Map unknown; + + public @Nullable String getUuid() { + return uuid; + } + + public void setUuid(final @Nullable String uuid) { + this.uuid = uuid; + } + + public @Nullable String getType() { + return type; + } + + public void setType(final @Nullable String type) { + this.type = type; + } + + public @Nullable String getDebugId() { + return debugId; + } + + public void setDebugId(final @Nullable String debugId) { + this.debugId = debugId; + } + + public @Nullable String getDebugFile() { + return debugFile; + } + + public void setDebugFile(final @Nullable String debugFile) { + this.debugFile = debugFile; + } + + public @Nullable String getCodeFile() { + return codeFile; + } + + public void setCodeFile(final @Nullable String codeFile) { + this.codeFile = codeFile; + } + + public @Nullable String getImageAddr() { + return imageAddr; + } + + public void setImageAddr(final @Nullable String imageAddr) { + this.imageAddr = imageAddr; + } + + public @Nullable Long getImageSize() { + return imageSize; + } + + public void setImageSize(final @Nullable Long imageSize) { + this.imageSize = imageSize; + } + + /** + * Sets the image size. + * + * @param imageSize the image size. + */ + public void setImageSize(long imageSize) { + this.imageSize = imageSize; + } + + public @Nullable String getArch() { + return arch; + } + + public void setArch(final @Nullable String arch) { + this.arch = arch; + } + + public @Nullable String getCodeId() { + return codeId; + } + + public void setCodeId(final @Nullable String codeId) { + this.codeId = codeId; + } + +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java b/ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java new file mode 100644 index 000000000..4053929b3 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java @@ -0,0 +1,18 @@ +package io.sentry.ndk; + +public interface INativeScope { + void setTag(String key, String value); + + void removeTag(String key); + + void setExtra(String key, String value); + + void removeExtra(String key); + + void setUser(String id, String email, String ipAddress, String username); + + void removeUser(); + + void addBreadcrumb( + String level, String message, String category, String type, String timestamp, String data); +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java b/ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java new file mode 100644 index 000000000..c6612cef7 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java @@ -0,0 +1,18 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.Nullable; + +public final class NativeModuleListLoader { + + public static native DebugImage[] nativeLoadModuleList(); + + public static native void nativeClearModuleList(); + + public @Nullable DebugImage[] loadModuleList() { + return nativeLoadModuleList(); + } + + public void clearModuleList() { + nativeClearModuleList(); + } +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java b/ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java new file mode 100644 index 000000000..18df418f7 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java @@ -0,0 +1,55 @@ +package io.sentry.ndk; + +public final class NativeScope implements INativeScope { + public static native void nativeSetTag(String key, String value); + + public static native void nativeRemoveTag(String key); + + public static native void nativeSetExtra(String key, String value); + + public static native void nativeRemoveExtra(String key); + + public static native void nativeSetUser( + String id, String email, String ipAddress, String username); + + public static native void nativeRemoveUser(); + + public static native void nativeAddBreadcrumb( + String level, String message, String category, String type, String timestamp, String data); + + @Override + public void setTag(String key, String value) { + nativeSetTag(key, value); + } + + @Override + public void removeTag(String key) { + nativeRemoveTag(key); + } + + @Override + public void setExtra(String key, String value) { + nativeSetExtra(key, value); + } + + @Override + public void removeExtra(String key) { + nativeRemoveExtra(key); + } + + @Override + public void setUser(String id, String email, String ipAddress, String username) { + nativeSetUser(id, email, ipAddress, username); + } + + @Override + public void removeUser() { + nativeRemoveUser(); + } + + @Override + public void addBreadcrumb( + String level, String message, String category, String type, String timestamp, String data) { + nativeAddBreadcrumb(level, message, category, type, timestamp, data); + } +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java b/ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java new file mode 100644 index 000000000..c4ed14f40 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java @@ -0,0 +1,64 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class NdkOptions { + private final @NotNull String dsn; + private final boolean isDebug; + private final @NotNull String outboxPath; + private final @Nullable String release; + private final @Nullable String environment; + private final @Nullable String dist; + private final int maxBreadcrumbs; + private final @Nullable String sdkName; + + public NdkOptions(@NotNull String dsn, boolean isDebug, @NotNull String outboxPath, @Nullable String release, @Nullable String environment, @Nullable String dist, int maxBreadcrumbs, @Nullable String sdkName) { + this.dsn = dsn; + this.isDebug = isDebug; + this.outboxPath = outboxPath; + this.release = release; + this.environment = environment; + this.dist = dist; + this.maxBreadcrumbs = maxBreadcrumbs; + this.sdkName = sdkName; + } + + @NotNull + public String getDsn() { + return dsn; + } + + public boolean isDebug() { + return isDebug; + } + + @NotNull + public String getOutboxPath() { + return outboxPath; + } + + @Nullable + public String getRelease() { + return release; + } + + @Nullable + public String getEnvironment() { + return environment; + } + + @Nullable + public String getDist() { + return dist; + } + + public int getMaxBreadcrumbs() { + return maxBreadcrumbs; + } + + @Nullable + public String getSdkName() { + return sdkName; + } +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java b/ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java new file mode 100644 index 000000000..58394188c --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java @@ -0,0 +1,42 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class SentryNdk { + + static { + // On older Android versions, it was necessary to manually call "`System.loadLibrary` on all + // transitive dependencies before loading [the] main library." + // The dependencies of `libsentry.so` are currently `lib{c,m,dl,log}.so`. + // See + // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#changes-to-library-dependency-resolution + System.loadLibrary("log"); + System.loadLibrary("sentry"); + System.loadLibrary("sentry-android"); + } + + private SentryNdk() { + } + + private static native void initSentryNative(@NotNull final NdkOptions options); + + private static native void shutdown(); + + /** + * Init the NDK integration + * + * @param options the SentryAndroidOptions + */ + public static void init(@NotNull final NdkOptions options) { + initSentryNative(options); + } + + /** + * Closes the NDK integration + */ + public static void close() { + shutdown(); + } +} diff --git a/ndk/lib/src/main/jni/sentry.c b/ndk/lib/src/main/jni/sentry.c new file mode 100644 index 000000000..ee412a01a --- /dev/null +++ b/ndk/lib/src/main/jni/sentry.c @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include + +#define ENSURE(Expr) \ + if (!(Expr)) \ + return + +#define ENSURE_OR_FAIL(Expr) \ + if (!(Expr)) \ + goto fail + +static bool get_string_into(JNIEnv *env, jstring jstr, char *buf, size_t buf_len) { + jsize utf_len = (*env)->GetStringUTFLength(env, jstr); + if ((size_t) utf_len >= buf_len) { + return false; + } + + jsize j_len = (*env)->GetStringLength(env, jstr); + + (*env)->GetStringUTFRegion(env, jstr, 0, j_len, buf); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return false; + } + + buf[utf_len] = '\0'; + return true; +} + +static char *get_string(JNIEnv *env, jstring jstr) { + char *buf = NULL; + + jsize utf_len = (*env)->GetStringUTFLength(env, jstr); + size_t buf_len = (size_t) utf_len + 1; + buf = sentry_malloc(buf_len); + ENSURE_OR_FAIL(buf); + + ENSURE_OR_FAIL(get_string_into(env, jstr, buf, buf_len)); + + return buf; + + fail: + sentry_free(buf); + + return NULL; +} + +static char *call_get_string(JNIEnv *env, jobject obj, jmethodID mid) { + jstring j_str = (jstring) (*env)->CallObjectMethod(env, obj, mid); + ENSURE_OR_FAIL(j_str); + char *str = get_string(env, j_str); + (*env)->DeleteLocalRef(env, j_str); + + return str; + + fail: + return NULL; +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeSetTag( + JNIEnv *env, + jclass cls, + jstring key, + jstring value) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + const char *charValue = (*env)->GetStringUTFChars(env, value, 0); + + sentry_set_tag(charKey, charValue); + + (*env)->ReleaseStringUTFChars(env, key, charKey); + (*env)->ReleaseStringUTFChars(env, value, charValue); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeRemoveTag(JNIEnv *env, jclass cls, jstring key) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + + sentry_remove_tag(charKey); + + (*env)->ReleaseStringUTFChars(env, key, charKey); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeSetExtra( + JNIEnv *env, + jclass cls, + jstring key, + jstring value) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + const char *charValue = (*env)->GetStringUTFChars(env, value, 0); + + sentry_value_t sentryValue = sentry_value_new_string(charValue); + sentry_set_extra(charKey, sentryValue); + + (*env)->ReleaseStringUTFChars(env, key, charKey); + (*env)->ReleaseStringUTFChars(env, value, charValue); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeRemoveExtra(JNIEnv *env, jclass cls, jstring key) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + + sentry_remove_extra(charKey); + + (*env)->ReleaseStringUTFChars(env, key, charKey); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeSetUser( + JNIEnv *env, + jclass cls, + jstring id, + jstring email, + jstring ipAddress, + jstring username) { + sentry_value_t user = sentry_value_new_object(); + if (id) { + const char *charId = (*env)->GetStringUTFChars(env, id, 0); + sentry_value_set_by_key(user, "id", sentry_value_new_string(charId)); + (*env)->ReleaseStringUTFChars(env, id, charId); + } + if (email) { + const char *charEmail = (*env)->GetStringUTFChars(env, email, 0); + sentry_value_set_by_key( + user, "email", sentry_value_new_string(charEmail)); + (*env)->ReleaseStringUTFChars(env, email, charEmail); + } + if (ipAddress) { + const char *charIpAddress = (*env)->GetStringUTFChars(env, ipAddress, 0); + sentry_value_set_by_key( + user, "ip_address", sentry_value_new_string(charIpAddress)); + (*env)->ReleaseStringUTFChars(env, ipAddress, charIpAddress); + } + if (username) { + const char *charUsername = (*env)->GetStringUTFChars(env, username, 0); + sentry_value_set_by_key( + user, "username", sentry_value_new_string(charUsername)); + (*env)->ReleaseStringUTFChars(env, username, charUsername); + } + sentry_set_user(user); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeRemoveUser(JNIEnv *env, jclass cls) { + sentry_remove_user(); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeAddBreadcrumb( + JNIEnv *env, + jclass cls, + jstring level, + jstring message, + jstring category, + jstring type, + jstring timestamp, + jstring data) { + if (!level && !message && !category && !type) { + return; + } + const char *charMessage = NULL; + if (message) { + charMessage = (*env)->GetStringUTFChars(env, message, 0); + } + const char *charType = NULL; + if (type) { + charType = (*env)->GetStringUTFChars(env, type, 0); + } + sentry_value_t crumb = sentry_value_new_breadcrumb(charType, charMessage); + + if (charMessage) { + (*env)->ReleaseStringUTFChars(env, message, charMessage); + } + if (charType) { + (*env)->ReleaseStringUTFChars(env, type, charType); + } + + if (category) { + const char *charCategory = (*env)->GetStringUTFChars(env, category, 0); + sentry_value_set_by_key( + crumb, "category", sentry_value_new_string(charCategory)); + (*env)->ReleaseStringUTFChars(env, category, charCategory); + } + if (level) { + const char *charLevel = (*env)->GetStringUTFChars(env, level, 0); + sentry_value_set_by_key( + crumb, "level", sentry_value_new_string(charLevel)); + (*env)->ReleaseStringUTFChars(env, level, charLevel); + } + + if (timestamp) { + // overwrite timestamp that is already created on sentry_value_new_breadcrumb + const char *charTimestamp = (*env)->GetStringUTFChars(env, timestamp, 0); + sentry_value_set_by_key( + crumb, "timestamp", sentry_value_new_string(charTimestamp)); + (*env)->ReleaseStringUTFChars(env, timestamp, charTimestamp); + } + + if (data) { + const char *charData = (*env)->GetStringUTFChars(env, data, 0); + + // we create an object because the Java layer parses it as a Map + sentry_value_t dataObject = sentry_value_new_object(); + sentry_value_set_by_key(dataObject, "data", sentry_value_new_string(charData)); + + sentry_value_set_by_key(crumb, "data", dataObject); + + (*env)->ReleaseStringUTFChars(env, data, charData); + } + + sentry_add_breadcrumb(crumb); +} + +static void send_envelope(sentry_envelope_t *envelope, void *data) { + const char *outbox_path = (const char *) data; + char envelope_id_str[40]; + + sentry_uuid_t envelope_id = sentry_uuid_new_v4(); + sentry_uuid_as_string(&envelope_id, envelope_id_str); + + size_t outbox_len = strlen(outbox_path); + size_t final_len = outbox_len + 42; // "/" + envelope_id_str + "\0" = 42 + char *envelope_path = sentry_malloc(final_len); + ENSURE(envelope_path); + int written = snprintf(envelope_path, final_len, "%s/%s", outbox_path, envelope_id_str); + if (written > outbox_len && written < final_len) { + sentry_envelope_write_to_file(envelope, envelope_path); + } + + sentry_free(envelope_path); + sentry_envelope_free(envelope); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_SentryNdk_initSentryNative( + JNIEnv *env, + jclass cls, + jobject sentry_ndk_options) { + jclass options_cls = (*env)->GetObjectClass(env, sentry_ndk_options); + jmethodID outbox_path_mid = (*env)->GetMethodID(env, options_cls, "getOutboxPath", + "()Ljava/lang/String;"); + jmethodID dsn_mid = (*env)->GetMethodID(env, options_cls, "getDsn", "()Ljava/lang/String;"); + jmethodID is_debug_mid = (*env)->GetMethodID(env, options_cls, "isDebug", "()Z"); + jmethodID release_mid = (*env)->GetMethodID(env, options_cls, "getRelease", + "()Ljava/lang/String;"); + jmethodID environment_mid = (*env)->GetMethodID(env, options_cls, "getEnvironment", + "()Ljava/lang/String;"); + jmethodID dist_mid = (*env)->GetMethodID(env, options_cls, "getDist", "()Ljava/lang/String;"); + jmethodID max_crumbs_mid = (*env)->GetMethodID(env, options_cls, "getMaxBreadcrumbs", "()I"); + jmethodID native_sdk_name_mid = (*env)->GetMethodID(env, options_cls, "getSdkName", + "()Ljava/lang/String;"); + + (*env)->DeleteLocalRef(env, options_cls); + + char *outbox_path = NULL; + sentry_transport_t *transport = NULL; + bool transport_owns_path = false; + sentry_options_t *options = NULL; + bool options_owns_transport = false; + char *dsn_str = NULL; + char *release_str = NULL; + char *environment_str = NULL; + char *dist_str = NULL; + char *native_sdk_name_str = NULL; + + options = sentry_options_new(); + ENSURE_OR_FAIL(options); + + // session tracking is enabled by default, but the Android SDK already handles it + sentry_options_set_auto_session_tracking(options, 0); + + jboolean debug = (jboolean) (*env)->CallBooleanMethod(env, sentry_ndk_options, is_debug_mid); + sentry_options_set_debug(options, debug); + + jint max_crumbs = (jint) (*env)->CallIntMethod(env, sentry_ndk_options, max_crumbs_mid); + sentry_options_set_max_breadcrumbs(options, max_crumbs); + + outbox_path = call_get_string(env, sentry_ndk_options, outbox_path_mid); + ENSURE_OR_FAIL(outbox_path); + + transport = sentry_transport_new(send_envelope); + ENSURE_OR_FAIL(transport); + sentry_transport_set_state(transport, outbox_path); + sentry_transport_set_free_func(transport, sentry_free); + transport_owns_path = true; + + sentry_options_set_transport(options, transport); + options_owns_transport = true; + + // give sentry-native its own database path it can work with, next to the outbox + size_t outbox_len = strlen(outbox_path); + size_t final_len = outbox_len + 15; // len(".sentry-native\0") = 15 + char *database_path = sentry_malloc(final_len); + ENSURE_OR_FAIL(database_path); + strncpy(database_path, outbox_path, final_len); + char *dir = strrchr(database_path, '/'); + if (dir) { + strncpy(dir + 1, ".sentry-native", final_len - (dir + 1 - database_path)); + } + sentry_options_set_database_path(options, database_path); + sentry_free(database_path); + + dsn_str = call_get_string(env, sentry_ndk_options, dsn_mid); + ENSURE_OR_FAIL(dsn_str); + sentry_options_set_dsn(options, dsn_str); + sentry_free(dsn_str); + + release_str = call_get_string(env, sentry_ndk_options, release_mid); + if (release_str) { + sentry_options_set_release(options, release_str); + sentry_free(release_str); + } + + environment_str = call_get_string(env, sentry_ndk_options, environment_mid); + if (environment_str) { + sentry_options_set_environment(options, environment_str); + sentry_free(environment_str); + } + + dist_str = call_get_string(env, sentry_ndk_options, dist_mid); + if (dist_str) { + sentry_options_set_dist(options, dist_str); + sentry_free(dist_str); + } + + native_sdk_name_str = call_get_string(env, sentry_ndk_options, native_sdk_name_mid); + if (native_sdk_name_str) { + sentry_options_set_sdk_name(options, native_sdk_name_str); + sentry_free(native_sdk_name_str); + } + + sentry_init(options); + return; + + fail: + if (!transport_owns_path) { + sentry_free(outbox_path); + } + if (!options_owns_transport) { + sentry_transport_free(transport); + } + sentry_options_free(options); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeModuleListLoader_nativeClearModuleList(JNIEnv *env, jclass cls) { + sentry_clear_modulecache(); +} + +JNIEXPORT jobjectArray JNICALL +Java_io_sentry_ndk_NativeModuleListLoader_nativeLoadModuleList(JNIEnv *env, jclass cls) { + sentry_value_t image_list_t = sentry_get_modules_list(); + jobjectArray image_list = NULL; + + if (sentry_value_get_type(image_list_t) == SENTRY_VALUE_TYPE_LIST) { + size_t len_t = sentry_value_get_length(image_list_t); + + jclass image_class = (*env)->FindClass(env, "io/sentry/ndk/DebugImage"); + image_list = (*env)->NewObjectArray(env, len_t, image_class, NULL); + + jmethodID image_addr_method = (*env)->GetMethodID(env, image_class, "setImageAddr", + "(Ljava/lang/String;)V"); + + jmethodID image_size_method = (*env)->GetMethodID(env, image_class, "setImageSize", + "(J)V"); + + jmethodID code_file_method = (*env)->GetMethodID(env, image_class, "setCodeFile", + "(Ljava/lang/String;)V"); + + jmethodID image_addr_ctor = (*env)->GetMethodID(env, image_class, "", + "()V"); + + jmethodID type_method = (*env)->GetMethodID(env, image_class, "setType", + "(Ljava/lang/String;)V"); + + jmethodID debug_id_method = (*env)->GetMethodID(env, image_class, "setDebugId", + "(Ljava/lang/String;)V"); + + jmethodID code_id_method = (*env)->GetMethodID(env, image_class, "setCodeId", + "(Ljava/lang/String;)V"); + + jmethodID debug_file_method = (*env)->GetMethodID(env, image_class, "setDebugFile", + "(Ljava/lang/String;)V"); + + for (size_t i = 0; i < len_t; i++) { + sentry_value_t image_t = sentry_value_get_by_index(image_list_t, i); + + if (!sentry_value_is_null(image_t)) { + jobject image = (*env)->NewObject(env, image_class, image_addr_ctor); + + sentry_value_t image_addr_t = sentry_value_get_by_key(image_t, "image_addr"); + if (!sentry_value_is_null(image_addr_t)) { + + const char *value_v = sentry_value_as_string(image_addr_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, image_addr_method, value); + + // Local refs (eg NewStringUTF) are freed automatically when the native method + // returns, but if you're iterating a large array, it's recommended to release + // manually due to allocation limits (512) on Android < 8 or OOM. + // https://developer.android.com/training/articles/perf-jni.html#local-and-global-references + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t image_size_t = sentry_value_get_by_key(image_t, "image_size"); + if (!sentry_value_is_null(image_size_t)) { + + int32_t value_v = sentry_value_as_int32(image_size_t); + jlong value = (jlong) value_v; + + (*env)->CallVoidMethod(env, image, image_size_method, value); + } + + sentry_value_t code_file_t = sentry_value_get_by_key(image_t, "code_file"); + if (!sentry_value_is_null(code_file_t)) { + + const char *value_v = sentry_value_as_string(code_file_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, code_file_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t code_type_t = sentry_value_get_by_key(image_t, "type"); + if (!sentry_value_is_null(code_type_t)) { + + const char *value_v = sentry_value_as_string(code_type_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, type_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t debug_id_t = sentry_value_get_by_key(image_t, "debug_id"); + if (!sentry_value_is_null(code_type_t)) { + + const char *value_v = sentry_value_as_string(debug_id_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, debug_id_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t code_id_t = sentry_value_get_by_key(image_t, "code_id"); + if (!sentry_value_is_null(code_id_t)) { + + const char *value_v = sentry_value_as_string(code_id_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, code_id_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + // not needed on Android, but keeping for forward compatibility + sentry_value_t debug_file_t = sentry_value_get_by_key(image_t, "debug_file"); + if (!sentry_value_is_null(debug_file_t)) { + + const char *value_v = sentry_value_as_string(debug_file_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, debug_file_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + (*env)->SetObjectArrayElement(env, image_list, i, image); + + (*env)->DeleteLocalRef(env, image); + } + } + + sentry_value_decref(image_list_t); + } + + return image_list; +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_SentryNdk_shutdown(JNIEnv *env, jclass cls) { + sentry_close(); +} diff --git a/ndk/lib/src/main/res/values/public.xml b/ndk/lib/src/main/res/values/public.xml new file mode 100644 index 000000000..788fdddc0 --- /dev/null +++ b/ndk/lib/src/main/res/values/public.xml @@ -0,0 +1,4 @@ + + + + diff --git a/ndk/sample/CMakeLists.txt b/ndk/sample/CMakeLists.txt new file mode 100644 index 000000000..10933b78c --- /dev/null +++ b/ndk/sample/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(sentry-native-ndk-sample LANGUAGES C CXX) + +set(BUILD_SHARED_LIBS ON) +set(SENTRY_BUILD_SHARED_LIBS ON) + +add_library(ndk-sample SHARED src/main/cpp/ndk-sample.cpp) + +# Adding sentry-native project +add_subdirectory(${SENTRY_NATIVE_SRC} sentry_build) + +# Android logging library +find_library(LOG_LIB log) + +target_link_libraries(ndk-sample PRIVATE + ${LOG_LIB} + $ +) +# Support 16KB page sizes +target_link_options(ndk-sample PRIVATE "-Wl,-z,max-page-size=16384") diff --git a/ndk/sample/build.gradle.kts b/ndk/sample/build.gradle.kts new file mode 100644 index 000000000..2227fe1ea --- /dev/null +++ b/ndk/sample/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + id("com.android.application") + kotlin("android") +} + +var sentryNativeSrc: String = "${project.projectDir}/../.." + +android { + compileSdk = 34 + namespace = "io.sentry.ndk.sample" + + defaultConfig { + applicationId = "io.sentry.ndk.sample" + minSdk = 19 + targetSdk = 34 + versionCode = 2 + versionName = project.version.toString() + + externalNativeBuild { + cmake { + arguments.add(0, "-DANDROID_STL=c++_shared") + arguments.add(0, "-DSENTRY_NATIVE_SRC=$sentryNativeSrc") + } + } + + ndk { + abiFilters.addAll(listOf("x86", "armeabi-v7a", "x86_64", "arm64-v8a")) + } + } + + externalNativeBuild { + cmake { + path("CMakeLists.txt") + } + } + + signingConfigs { + getByName("debug") { + storeFile = rootProject.file("debug.keystore") + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" + } + } + + buildTypes { + getByName("release") { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("debug") // to be able to run release mode + isShrinkResources = true + + addManifestPlaceholders( + mapOf( + "sentryDebug" to false, "sentryEnvironment" to "release" + ) + ) + } + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + packagingOptions { + jniLibs { + useLegacyPackaging = true + } + } + ndkVersion = "27.0.12077973" +} + +dependencies { + implementation(project(":sentry-native-ndk")) +} diff --git a/ndk/sample/proguard-rules.pro b/ndk/sample/proguard-rules.pro new file mode 100644 index 000000000..1165340c8 --- /dev/null +++ b/ndk/sample/proguard-rules.pro @@ -0,0 +1,34 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# To ensure that stack traces is unambiguous +# https://developer.android.com/studio/build/shrink-code#decode-stack-trace +-keepattributes LineNumberTable,SourceFile + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames,includedescriptorclasses class * { + native ; +} + +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.ConscryptHostnameVerifier +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE diff --git a/ndk/sample/src/main/AndroidManifest.xml b/ndk/sample/src/main/AndroidManifest.xml new file mode 100644 index 000000000..882d9c1e8 --- /dev/null +++ b/ndk/sample/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/ndk/sample/src/main/cpp/ndk-sample.cpp b/ndk/sample/src/main/cpp/ndk-sample.cpp new file mode 100644 index 000000000..d1f62bf0b --- /dev/null +++ b/ndk/sample/src/main/cpp/ndk-sample.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#define TAG "ndk-sample" + +extern "C" { + +JNIEXPORT void JNICALL Java_io_sentry_ndk_sample_NdkSample_crash(JNIEnv *env, jclass cls) { + __android_log_print(ANDROID_LOG_WARN, TAG, "About to crash."); + char *ptr = 0; + *ptr += 1; +} + +JNIEXPORT void JNICALL Java_io_sentry_ndk_sample_NdkSample_message(JNIEnv *env, jclass cls) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Sending message."); + sentry_value_t event = sentry_value_new_message_event( + /* level */ SENTRY_LEVEL_INFO, + /* logger */ "custom", + /* message */ "It works!" + ); + sentry_capture_event(event); +} + +} diff --git a/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java b/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java new file mode 100644 index 000000000..d8d42b3df --- /dev/null +++ b/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java @@ -0,0 +1,54 @@ +package io.sentry.ndk.sample; + +import android.app.Activity; +import android.os.Bundle; + +import java.io.File; + +import io.sentry.ndk.NdkOptions; +import io.sentry.ndk.SentryNdk; + +public class MainActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + findViewById(R.id.init_ndk_button).setOnClickListener(v -> initNdk()); + findViewById(R.id.trigger_native_crash_button).setOnClickListener(v -> NdkSample.crash()); + findViewById(R.id.capture_message_button).setOnClickListener(v -> NdkSample.message()); + } + + private void initNdk() { + final File outboxFolder = setupOutboxFolder(); + final NdkOptions options = new NdkOptions( + "https://1053864c67cc410aa1ffc9701bd6f93d@o447951.ingest.sentry.io/5428559", + BuildConfig.DEBUG, + outboxFolder.getAbsolutePath(), + "1.0.0", + "production", + BuildConfig.VERSION_NAME, + 100, + "sentry-native-jni" + ); + SentryNdk.init(options); + } + + private File setupOutboxFolder() { + // ensure we have a proper outbox directory + final File outboxDir = new File(getFilesDir(), "outbox"); + if (outboxDir.isFile()) { + final boolean deleteOk = outboxDir.delete(); + if (!deleteOk) { + throw new IllegalStateException("Failed to delete outbox file: " + outboxDir); + } + } + if (!outboxDir.exists()) { + final boolean mkdirOk = outboxDir.mkdirs(); + if (!mkdirOk) { + throw new IllegalStateException("Failed to create outbox directory: " + outboxDir); + } + } + return outboxDir; + } +} diff --git a/ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java b/ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java new file mode 100644 index 000000000..27fdeaf5e --- /dev/null +++ b/ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java @@ -0,0 +1,11 @@ +package io.sentry.ndk.sample; + +public class NdkSample { + static { + System.loadLibrary("ndk-sample"); + } + + public static native void crash(); + + public static native void message(); +} diff --git a/ndk/sample/src/main/res/layout/activity_main.xml b/ndk/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..8045ad259 --- /dev/null +++ b/ndk/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,36 @@ + + + + + +