diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index 7349ff0281..0a6d7e1613 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -11,7 +11,8 @@ steps: MAVEN_VERSION: "3.6.1" MINIMAL_FIXTURE: true TEST_FIXTURE_NDK_VERSION: "17.2.4988734" - TEST_FIXTURE_NAME: "fixture-minimal.apk" + TEST_FIXTURE_NAME: "fixture-minimal" + TEST_FIXTURE_CONFIGURATION: "release" BUILD_TASK: "assembleRelease" - label: ':android: Build Example App' @@ -26,7 +27,9 @@ steps: key: "fixture-debug" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/debug/fixture-debug.apk + artifact_paths: + - "build/debug/fixture-debug.apk" + - "build/debug/fixture-debug/*" plugins: - docker-compose#v3.7.0: run: android-builder @@ -34,7 +37,8 @@ steps: MAVEN_VERSION: "3.6.1" MINIMAL_FIXTURE: false TEST_FIXTURE_NDK_VERSION: "17.2.4988734" - TEST_FIXTURE_NAME: "fixture-debug.apk" + TEST_FIXTURE_NAME: "fixture-debug" + TEST_FIXTURE_CONFIGURATION: "debug" BUILD_TASK: "assembleDebug" - label: ':android: Build Scan' @@ -70,7 +74,9 @@ steps: timeout_in_minutes: 30 plugins: artifacts#v1.2.0: - download: "build/debug/fixture-debug.apk" + download: + - "build/debug/fixture-debug.apk" + - "build/debug/fixture-debug/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -82,6 +88,8 @@ steps: - "--farm=bs" - "--device=ANDROID_9_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/debug/fixture-debug" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -93,62 +101,15 @@ steps: - docker-compose#v3.7.0: run: android-sizer - - label: ':android: Android 5 NDK r16 end-to-end tests - batch 1' - depends_on: - - "fixture-r16" - timeout_in_minutes: 90 - plugins: - artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: android-maze-runner - run: android-maze-runner - command: - - "features/smoke_tests" - - "features/full_tests" - - "--exclude=features/full_tests/[^a-m].*.feature" - - "--app=/app/build/release/fixture-r16.apk" - - "--farm=bs" - - "--device=ANDROID_5_0" - - "--fail-fast" - concurrency: 9 - concurrency_group: 'browserstack-app' - concurrency_method: eager - soft_fail: - - exit_status: "*" - - - label: ':android: Android 5 NDK r16 end-to-end tests - batch 2' - depends_on: - - "fixture-r16" - timeout_in_minutes: 90 - plugins: - artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: android-maze-runner - run: android-maze-runner - command: - - "features/full_tests" - - "--exclude=features/full_tests/[^n-z].*.feature" - - "--app=/app/build/release/fixture-r16.apk" - - "--farm=bs" - - "--device=ANDROID_5_0" - - "--fail-fast" - concurrency: 9 - concurrency_group: 'browserstack-app' - concurrency_method: eager - soft_fail: - - exit_status: "*" - - label: ':android: Android 6 NDK r16 end-to-end tests - batch 1' depends_on: - "fixture-r16" timeout_in_minutes: 90 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -163,6 +124,8 @@ steps: - "--device=ANDROID_6_0_SAMSUNG_GALAXY_S7" - "--device=ANDROID_6_0_GOOGLE_NEXUS_6" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -173,7 +136,9 @@ steps: timeout_in_minutes: 90 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -187,6 +152,8 @@ steps: - "--device=ANDROID_6_0_SAMSUNG_GALAXY_S7" - "--device=ANDROID_6_0_GOOGLE_NEXUS_6" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -197,7 +164,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -210,6 +179,8 @@ steps: - "--farm=bs" - "--device=ANDROID_7_1_SAMSUNG_GALAXY_NOTE_8" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -220,7 +191,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -232,6 +205,8 @@ steps: - "--farm=bs" - "--device=ANDROID_7_1_SAMSUNG_GALAXY_NOTE_8" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -242,7 +217,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -255,6 +232,8 @@ steps: - "--farm=bs" - "--device=ANDROID_8_1" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -265,7 +244,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -277,6 +258,8 @@ steps: - "--farm=bs" - "--device=ANDROID_8_1" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -287,7 +270,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -300,6 +285,8 @@ steps: - "features/smoke_tests" - "features/full_tests" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -310,7 +297,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -322,6 +311,8 @@ steps: - "--farm=bs" - "--device=ANDROID_9_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -332,7 +323,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -345,6 +338,8 @@ steps: - "--farm=bs" - "--device=ANDROID_10_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -355,7 +350,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -368,6 +365,8 @@ steps: - "--farm=bs" - "--device=ANDROID_10_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -382,7 +381,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -395,6 +396,8 @@ steps: - "--farm=bs" - "--device=ANDROID_11_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -405,7 +408,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -418,6 +423,8 @@ steps: - "--farm=bs" - "--device=ANDROID_11_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -432,7 +439,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -445,6 +454,8 @@ steps: - "--farm=bs" - "--device=ANDROID_12_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -455,7 +466,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -467,6 +480,8 @@ steps: - "--farm=bs" - "--device=ANDROID_12_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager diff --git a/.buildkite/pipeline.quick.yml b/.buildkite/pipeline.quick.yml index c3f65c3397..71f017dca7 100644 --- a/.buildkite/pipeline.quick.yml +++ b/.buildkite/pipeline.quick.yml @@ -1,34 +1,13 @@ steps: - - label: ':android: Android 5 NDK r16 smoke tests' - key: 'android-5-smoke' - depends_on: "fixture-r16" - timeout_in_minutes: 60 - plugins: - artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: android-maze-runner - run: android-maze-runner - command: - - "features/smoke_tests" - - "--app=/app/build/release/fixture-r16.apk" - - "--farm=bs" - - "--device=ANDROID_5_0" - - "--fail-fast" - concurrency: 9 - concurrency_group: 'browserstack-app' - concurrency_method: eager - soft_fail: - - exit_status: "*" - - label: ':android: Android 6 NDK r16 smoke tests' key: 'android-6-smoke' depends_on: "fixture-r16" timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -44,6 +23,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" - label: ':android: Android 7 NDK r19 smoke tests' key: 'android-7-smoke' @@ -51,7 +32,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -65,6 +48,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" - label: ':android: Android 8.1 NDK r19 smoke tests' key: 'android-8-1-smoke' @@ -72,7 +57,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r19.apk" + download: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -86,6 +73,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r19" - label: ':android: Android 9 NDK r21 end-to-end tests - batch 1' depends_on: @@ -93,7 +82,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -108,6 +99,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" - label: ':android: Android 9 NDK r21 end-to-end tests - batch 2' depends_on: @@ -115,7 +108,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -130,6 +125,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" # Note that the smoke tests are included in this batch to even up # the time for each batch. @@ -139,7 +136,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -155,6 +154,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" - label: ':android: Android 10 NDK r21 smoke tests' key: 'android-10-smoke' @@ -162,7 +163,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -176,6 +179,8 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" - label: ':android: Android 11 NDK r21 smoke tests' key: 'android-11-smoke' @@ -183,7 +188,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -197,3 +204,5 @@ steps: concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 1952411433..8bfcfeeaef 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -54,14 +54,17 @@ steps: key: "fixture-r16" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/release/fixture-r16.apk + artifact_paths: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" plugins: - docker-compose#v3.7.0: run: android-builder env: MAVEN_VERSION: "3.6.1" TEST_FIXTURE_NDK_VERSION: "17.2.4988734" - TEST_FIXTURE_NAME: "fixture-r16.apk" + TEST_FIXTURE_NAME: "fixture-r16" + TEST_FIXTURE_CONFIGURATION: "release" USE_LEGACY_OKHTTP: "true" BUILD_TASK: "assembleRelease" @@ -69,28 +72,34 @@ steps: key: "fixture-r19" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/release/fixture-r19.apk + artifact_paths: + - "build/release/fixture-r19.apk" + - "build/release/fixture-r19/*" plugins: - docker-compose#v3.7.0: run: android-builder env: MAVEN_VERSION: "3.6.1" TEST_FIXTURE_NDK_VERSION: "19.2.5345600" - TEST_FIXTURE_NAME: "fixture-r19.apk" + TEST_FIXTURE_NAME: "fixture-r19" + TEST_FIXTURE_CONFIGURATION: "release" BUILD_TASK: "assembleRelease" - label: ':android: Build fixture APK r21' key: "fixture-r21" depends_on: "android-ci" timeout_in_minutes: 30 - artifact_paths: build/release/fixture-r21.apk + artifact_paths: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" plugins: - docker-compose#v3.7.0: run: android-builder env: MAVEN_VERSION: "3.6.1" TEST_FIXTURE_NDK_VERSION: "21.3.6528147" - TEST_FIXTURE_NAME: "fixture-r21.apk" + TEST_FIXTURE_NAME: "fixture-r21" + TEST_FIXTURE_CONFIGURATION: "release" BUILD_TASK: "assembleRelease" - label: ':android: Lint' @@ -163,7 +172,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r16.apk" + download: + - "build/release/fixture-r16.apk" + - "build/release/fixture-r16/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -174,6 +185,8 @@ steps: - "--farm=bs" - "--device=ANDROID_4_4" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r16" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager @@ -188,7 +201,9 @@ steps: timeout_in_minutes: 60 plugins: artifacts#v1.2.0: - download: "build/release/fixture-r21.apk" + download: + - "build/release/fixture-r21.apk" + - "build/release/fixture-r21/*" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: android-maze-runner @@ -201,6 +216,8 @@ steps: - "--farm=bs" - "--device=ANDROID_12_0" - "--fail-fast" + env: + TEST_FIXTURE_SYMBOL_DIR: "build/release/fixture-r21" concurrency: 9 concurrency_group: 'browserstack-app' concurrency_method: eager diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e5b74514..703305c4fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 5.21.0 (2022-03-17) + +### Enhancements + +* Fix inconsistencies in stack trace quality for C/C++ events. Resolves a few + cases where file and line number information was not resolving to the correct + locations. This change may result in grouping changes to more correctly + highlight the root cause of an event. + [#1605](https://github.com/bugsnag/bugsnag-android/pull/1605) + [#1606](https://github.com/bugsnag/bugsnag-android/pull/1606) + +### Bug fixes + +* Fixed an issue where an uncaught exception on the main thread could in rare cases trigger an ANR. + [#1624](https://github.com/bugsnag/bugsnag-android/pull/1624) + ## 5.20.0 (2022-03-10) ### Enhancements diff --git a/Makefile b/Makefile index a5c64e2a0e..90cc7e5312 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,18 @@ test-fixtures: # Build the full test fixture @./gradlew -PTEST_FIXTURE_NDK_VERSION=$(TEST_FIXTURE_NDK_VERSION) -p=features/fixtures/mazerunner/ assembleRelease -x check @cp features/fixtures/mazerunner/app/build/outputs/apk/release/fixture.apk build/fixture.apk + # copy the unstripped scenarios libs (for stack unwinding validation) + # grabs the newest files as the likely just built ones + LIB_CXX_BUGSNAG=$$(ls -dtr features/fixtures/mazerunner/cxx-scenarios-bugsnag/build/intermediates/cxx/RelWithDebInfo/* | head -n 1) && \ + LIB_CXX=$$(ls -dtr features/fixtures/mazerunner/cxx-scenarios/build/intermediates/cxx/RelWithDebInfo/* | head -n 1) && \ + cp $$LIB_CXX/obj/x86_64/libcxx-scenarios.so build/libcxx-scenarios-x86_64.so && \ + cp $$LIB_CXX/obj/x86/libcxx-scenarios.so build/libcxx-scenarios-x86.so && \ + cp $$LIB_CXX/obj/arm64-v8a/libcxx-scenarios.so build/libcxx-scenarios-arm64.so && \ + cp $$LIB_CXX/obj/armeabi-v7a/libcxx-scenarios.so build/libcxx-scenarios-arm32.so && \ + cp $$LIB_CXX_BUGSNAG/obj/x86_64/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-x86_64.so && \ + cp $$LIB_CXX_BUGSNAG/obj/x86/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-x86.so && \ + cp $$LIB_CXX_BUGSNAG/obj/arm64-v8a/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-arm64.so && \ + cp $$LIB_CXX_BUGSNAG/obj/armeabi-v7a/libcxx-scenarios-bugsnag.so build/libcxx-scenarios-bugsnag-arm32.so # And the minimal (no NDK or ANR plugin) test fixture @./gradlew -PMINIMAL_FIXTURE=true -PTEST_FIXTURE_NAME=fixture-minimal.apk -p=features/fixtures/mazerunner/ assembleRelease -x check @@ -48,7 +60,14 @@ ifeq ($(VERSION),) @$(error VERSION is not defined. Run with `make VERSION=number bump`) endif @echo Bumping the version number to $(VERSION) + @sed -i '' "s/bugsnag-android:.*\"/bugsnag-android:$(VERSION)\"/" examples/sdk-app-example/app/build.gradle @sed -i '' "s/VERSION_NAME=.*/VERSION_NAME=$(VERSION)/" gradle.properties @sed -i '' "s/var version: String = .*/var version: String = \"$(VERSION)\",/"\ bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt @sed -i '' "s/## TBD/## $(VERSION) ($(shell date '+%Y-%m-%d'))/" CHANGELOG.md + +.PHONY: check +check: + @./gradlew lint detekt ktlintCheck checkstyle + @./scripts/run-cpp-check.sh + @./scripts/run-clang-format-ci-check.sh diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt index c87fe1926e..b853655648 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt @@ -62,10 +62,10 @@ class AppDataCollectorForegroundTest { val appWithState = appDataCollector.generateAppWithState() assertEquals(true, appWithState.inForeground) - // allow for 20ms of error + // allow for 200ms of error assertTrue( "Unexpected durationInForeground: ${appWithState.durationInForeground}", - appWithState.durationInForeground in 1000L..1020L + appWithState.durationInForeground in 1000L..1200L ) verify(sessionTracker, times(1)).isInForeground diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt index 4e763633f0..c171c23d50 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/BackgroundTaskService.kt @@ -158,18 +158,13 @@ internal class BackgroundTaskService( internalReportExecutor.shutdownNow() defaultExecutor.shutdownNow() - // shutdown the error/session executors first, waiting for existing tasks to complete. - // If a request fails it may perform IO to persist the payload for delivery next launch, - // which would submit tasks to the IO executor - therefore it's critical to - // shutdown the IO executor last. + // Wait a little while for these ones to shut down errorExecutor.shutdown() sessionExecutor.shutdown() + ioExecutor.shutdown() errorExecutor.awaitTerminationSafe() sessionExecutor.awaitTerminationSafe() - - // shutdown the IO executor last, waiting for any existing tasks to complete - ioExecutor.shutdown() ioExecutor.awaitTerminationSafe() } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt index 7851ffb4c3..bfa3ed5d7f 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt @@ -7,7 +7,7 @@ import java.io.IOException */ class Notifier @JvmOverloads constructor( var name: String = "Android Bugsnag Notifier", - var version: String = "5.20.0", + var version: String = "5.21.0", var url: String = "https://bugsnag.com" ) : JsonStream.Streamable { diff --git a/bugsnag-android-core/src/main/jni/root_detection.c b/bugsnag-android-core/src/main/jni/root_detection.c index 5ba5b9360a..6152285f29 100644 --- a/bugsnag-android-core/src/main/jni/root_detection.c +++ b/bugsnag-android-core/src/main/jni/root_detection.c @@ -59,7 +59,7 @@ static inline bool is_path_writable(const char* path) { static inline bool can_create_file(const char* path) { unlink(path); - const int fd = open(path, O_CREAT); + const int fd = open(path, O_CREAT, O_RDONLY); if (fd < 0) { return false; } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt index bafc1ab351..3978dcb8e6 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt @@ -140,7 +140,7 @@ class LaunchCrashDeliveryTest { payload: EventPayload, deliveryParams: DeliveryParams ): DeliveryStatus { - Thread.sleep(3000) + Thread.sleep(2000) count.getAndIncrement() return DeliveryStatus.DELIVERED } diff --git a/bugsnag-plugin-android-ndk/CMakeLists.txt b/bugsnag-plugin-android-ndk/CMakeLists.txt index cd96ddfd6a..f16af6162d 100644 --- a/bugsnag-plugin-android-ndk/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.4.1) +cmake_minimum_required(VERSION 3.8.0) project(TEST) add_subdirectory(src/main) diff --git a/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt new file mode 100644 index 0000000000..5d89d0df4d --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/androidTest/java/com/bugsnag/android/ndk/UnwindTest.kt @@ -0,0 +1,160 @@ +package com.bugsnag.android.ndk + +import android.os.Build +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Test +import kotlin.math.abs + +class UnwindTest { + + @Test + fun testUnwindForNotify() { + assertFramesMatchExpected( + unwindForNotify(), + "Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify", + ) + } + + @Test + fun testUnwindForCrash() { + assertFramesMatchExpected( + unwindForCrash(), + "Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash", + ) + } + + // find a frame matching a method, returning the index, or fail the test + fun findIndexByMethod(items: List>, method: String): Int { + val index = items.indices.find { items[it]["method"] == method } + if (index == null) { + fail("did not find method '$method' in list: $items") + } + return index!! + } + + // verify that a stack trace contains the entries in EXPECTED_FUNCTIONS and + // is followed by the expected JNI function + fun assertFramesMatchExpected(frames: List>, jniFunction: String) { + // the top of the stack includes the unwinder itself, etc, so first + // scan for the expected contents, which must be a sequence + val offset = findIndexByMethod(frames, EXPECTED_FUNCTIONS[0]) + + // check if frames have enough remaining room for expected contents + assertTrue( + "missing expected stack frames in $frames", + frames.size - offset > EXPECTED_FUNCTIONS.size + 1 + ) + + EXPECTED_FUNCTIONS.forEachIndexed { index, name -> + assertFrameMatches(index, name, frames[offset + index]) + } + + assertFrameMatches( // JNI function must immediately follow + EXPECTED_FUNCTIONS.size, + jniFunction, + frames[offset + EXPECTED_FUNCTIONS.size] + ) + } + + fun assertFrameMatches(frameIndex: Int, expectedFunctionName: String, frame: Map) { + val method = frame["method"] as String + val file = frame["file"] as String + val symbolAddress = frame["symbolAddress"] as Long + val lineNumber = frame["lineNumber"] as Long + val loadAddress = frame["loadAddress"] as Long + + assertEquals( + """ + expected function name '$expectedFunctionName' at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + expectedFunctionName, + method + ) + + assertTrue( + """ + expected file suffix '$EXPECTED_NATIVE_LIB_NAME' at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + file.endsWith(EXPECTED_NATIVE_LIB_NAME) + ) + + // these are smol functions. the symbol and return addresses should be + // close to the local representation, with there being a bit of a larger + // gap on x86 than the other three ABIs, which are near exact. + val addressDelta = wordsize * 4 + + assertAlmostEquals( + """ + expected function address to nearly equal ${nativeInfo[expectedFunctionName]} at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + nativeInfo[expectedFunctionName]!!, + symbolAddress, + addressDelta + ) + + // sanity check that the line number doesn't include load addr or + // vice versa + assertTrue( + """ + expected line number and load address to be > 0 at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + loadAddress > 0 && lineNumber > 0 + ) + + assertAlmostEquals( + """ + expected line number + load address to nearly equal ${nativeInfo[expectedFunctionName]} at index $frameIndex + actual frame: $frame + native function addresses: $nativeInfo + """, + nativeInfo[expectedFunctionName]!!, + lineNumber + loadAddress, + addressDelta + ) + } + + // assert two values are equal within an acceptable delta + fun assertAlmostEquals(message: String, expectedValue: Long, actualValue: Long, delta: Long) { + val diff = abs(expectedValue - actualValue) + assertTrue(message + " \ndiff: $diff delta: $delta", diff <= delta) + } + + // unwind a known set of functions and return the stack frame contents + external fun unwindForNotify(): List> + external fun unwindForCrash(): List> + + companion object BuildInfo { + // these are the names and ordering of the native functions expected to + // be in the detected stack traces (see UnwindTest.cpp) + val EXPECTED_FUNCTIONS = listOf( + "unwind_func_four", + "unwind_func_three", + "unwind_func_two", + "unwind_func_one" + ) + + // name of the library containing the EXPECTED_FUNCTIONS + const val EXPECTED_NATIVE_LIB_NAME = "libbugsnag-ndk-test.so" + + // get expected frame info. returns a map of frame names to function addresses + external fun getNativeFunctionInfo(): Map + + val nativeInfo = getNativeFunctionInfo() + + val is64bit = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + Build.SUPPORTED_64_BIT_ABIS.size > 0 + else + false + val wordsize: Long = if (is64bit) 64 else 32 + } +} diff --git a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt index ca95c1d525..c3c7f84a84 100644 --- a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt @@ -20,11 +20,7 @@ add_library( # Specifies the name of the library. jni/utils/serializer/event_reader.c jni/utils/serializer/event_writer.c jni/utils/serializer/json_writer.c - jni/utils/stack_unwinder.c - jni/utils/stack_unwinder_libunwindstack.cpp - jni/utils/stack_unwinder_libcorkscrew.c - jni/utils/stack_unwinder_libunwind.c - jni/utils/stack_unwinder_simple.c + jni/utils/stack_unwinder.cpp jni/utils/serializer.c jni/utils/string.c jni/utils/threads.c @@ -34,7 +30,6 @@ add_library( # Specifies the name of the library. include_directories( jni jni/deps - jni/external/libunwind/include jni/external/libunwindstack-ndk/include ) @@ -45,16 +40,16 @@ target_link_libraries( # Specifies the target library. # Links the log library to the target library. log) +# Avoid exporting symbols in release mode to keep internals private +# More symbols are exported in debug mode for the sake of unit testing +set(EXTRA_LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/exported_native_symbols-${CMAKE_BUILD_TYPE}.txt") + set_target_properties(bugsnag-ndk PROPERTIES - COMPILE_OPTIONS - -Werror -Wall -pedantic) + COMPILE_OPTIONS -Werror -Wall -pedantic + LINK_FLAGS "${EXTRA_LINK_FLAGS}" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES) add_subdirectory(jni/external/libunwindstack-ndk/cmake) target_link_libraries(bugsnag-ndk unwindstack) -if(${ANDROID_ABI} STREQUAL "armeabi" OR ${ANDROID_ABI} STREQUAL "armeabi-v7a") - add_library(libunwind STATIC IMPORTED) - set_target_properties(libunwind PROPERTIES IMPORTED_LOCATION - ${ANDROID_NDK}/sources/cxx-stl/llvm-libc++/libs/${ANDROID_ABI}/libunwind.a) - target_link_libraries(bugsnag-ndk libunwind) -endif() diff --git a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt new file mode 100644 index 0000000000..90a7ad9ab0 --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-Debug.txt @@ -0,0 +1,13 @@ +LIBBUGSNAG_NDK { +global: + bugsnag_*; + Java_*; + bsg_*; + json_*; + BSG_MIGRATOR_CURRENT_VERSION; + migrate_app_v2; + __gxx_*; + __cxa_*; +local: + *; +}; diff --git a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt new file mode 100644 index 0000000000..94204bd8fa --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt @@ -0,0 +1,25 @@ +LIBBUGSNAG_NDK { +global: + bugsnag_*; + __cxa_*; + __dynamic_cast; + __emutls_get_address; + __gxx_personality_v0; + Java_*; + extern "C++" { + "std::get_terminate()"; + "std::set_terminate(void (*)())"; + "std::set_unexpected(void (*)())"; + "std::get_new_handler()"; + "std::set_new_handler(void (*)())"; + "std::rethrow_exception(std::exception_ptr)"; + "std::__throw_bad_alloc()"; + "std::uncaught_exception()"; + "std::uncaught_exceptions()"; + "std::nothrow"; + "std::terminate()"; + }; + +local: + *; +}; diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c index da58918c6c..8046e7ee97 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag.c @@ -136,8 +136,7 @@ void bugsnag_notify_env(JNIEnv *env, const char *name, const char *message, bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX]; memset(stacktrace, 0, sizeof(stacktrace)); - ssize_t frame_count = - bsg_unwind_stack(bsg_configured_unwind_style(), stacktrace, NULL, NULL); + ssize_t frame_count = bsg_unwind_concurrent_stack(stacktrace, NULL, NULL); // create StackTraceElement array jtrace = bsg_safe_new_object_array(env, frame_count, diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c index 5bb8a749f7..648a533a73 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -38,24 +38,6 @@ static void release_env_write_lock(void) { pthread_mutex_unlock(&bsg_global_env_write_mutex); } -bsg_unwinder bsg_configured_unwind_style() { - if (bsg_global_env != NULL) - return bsg_global_env->unwind_style; - - return BSG_CUSTOM_UNWIND; -} - -/** - * Get the configured unwind style for async-safe environments such as signal - * handlers. - */ -bsg_unwinder bsg_configured_signal_unwind_style() { - if (bsg_global_env != NULL) - return bsg_global_env->signal_unwind_style; - - return BSG_CUSTOM_UNWIND; -} - void bugsnag_add_on_error(bsg_on_error on_error) { if (bsg_global_env != NULL) { bsg_global_env->on_error = on_error; @@ -155,9 +137,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( } bsg_environment *bugsnag_env = calloc(1, sizeof(bsg_environment)); - bsg_set_unwind_types((int)_api_level, (bool)is32bit, - &bugsnag_env->signal_unwind_style, - &bugsnag_env->unwind_style); + bsg_unwinder_init(); bugsnag_env->report_header.big_endian = htonl(47) == 47; // potentially too clever, see man 3 htonl bugsnag_env->report_header.version = BUGSNAG_EVENT_VERSION; @@ -721,20 +701,10 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata( release_env_write_lock(); } -// Unwind the stack using the configured unwind style for signal handlers. -// This function gets exposed via -// Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction() -static ssize_t -bsg_unwind_stack_signal(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) __asyncsafe { - return bsg_unwind_stack(bsg_configured_signal_unwind_style(), stacktrace, - info, user_context); -} - JNIEXPORT jlong JNICALL Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction( JNIEnv *env, jobject thiz) { - return (jlong)bsg_unwind_stack_signal; + return (jlong)bsg_unwind_crash_stack; } JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag( diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h index 50fccf5f81..66506884d4 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.h @@ -16,14 +16,6 @@ extern "C" { #endif typedef struct { - /** - * Unwinding style used for signal-safe handling - */ - bsg_unwinder signal_unwind_style; - /** - * Preferred unwinding style - */ - bsg_unwinder unwind_style; /** * Records the version of the bugsnag NDK report being serialized to disk. */ @@ -76,12 +68,6 @@ typedef struct { bsg_thread_send_policy send_threads; } bsg_environment; -/** - * Get the configured unwind style for non-async-safe environments. - * DO NOT USE THIS IN A SIGNAL HANDLER! - */ -bsg_unwinder bsg_configured_unwind_style(); - /** * Invokes the user-supplied on_error callback, if it has been set. This allows * users to mutate the bugsnag_event payload before it is persisted to disk, and diff --git a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h deleted file mode 100644 index bc4e696c0c..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/__libunwind_config.h +++ /dev/null @@ -1,55 +0,0 @@ -//===------------------------- __libunwind_config.h -----------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -#ifndef ____LIBUNWIND_CONFIG_H__ -#define ____LIBUNWIND_CONFIG_H__ -#if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && \ - !defined(__ARM_DWARF_EH__) -#define _LIBUNWIND_ARM_EHABI 1 -#else -#define _LIBUNWIND_ARM_EHABI 0 -#endif -#if defined(_LIBUNWIND_IS_NATIVE_ONLY) -# if defined(__i386__) -# define _LIBUNWIND_TARGET_I386 1 -# define _LIBUNWIND_CONTEXT_SIZE 8 -# define _LIBUNWIND_CURSOR_SIZE 19 -# elif defined(__x86_64__) -# define _LIBUNWIND_TARGET_X86_64 1 -# define _LIBUNWIND_CONTEXT_SIZE 21 -# define _LIBUNWIND_CURSOR_SIZE 33 -# elif defined(__ppc__) -# define _LIBUNWIND_TARGET_PPC 1 -# define _LIBUNWIND_CONTEXT_SIZE 117 -# define _LIBUNWIND_CURSOR_SIZE 128 -# elif defined(__aarch64__) -# define _LIBUNWIND_TARGET_AARCH64 1 -# define _LIBUNWIND_CONTEXT_SIZE 66 -# define _LIBUNWIND_CURSOR_SIZE 78 -# elif defined(__arm__) -# define _LIBUNWIND_TARGET_ARM 1 -# define _LIBUNWIND_CONTEXT_SIZE 60 -# define _LIBUNWIND_CURSOR_SIZE 67 -# elif defined(__or1k__) -# define _LIBUNWIND_TARGET_OR1K 1 -# define _LIBUNWIND_CONTEXT_SIZE 16 -# define _LIBUNWIND_CURSOR_SIZE 28 -# else -# error "Unsupported architecture." -# endif -#else // !_LIBUNWIND_IS_NATIVE_ONLY -# define _LIBUNWIND_TARGET_I386 1 -# define _LIBUNWIND_TARGET_X86_64 1 -# define _LIBUNWIND_TARGET_PPC 1 -# define _LIBUNWIND_TARGET_AARCH64 1 -# define _LIBUNWIND_TARGET_ARM 1 -# define _LIBUNWIND_TARGET_OR1K 1 -# define _LIBUNWIND_CONTEXT_SIZE 128 -# define _LIBUNWIND_CURSOR_SIZE 140 -#endif // _LIBUNWIND_IS_NATIVE_ONLY -#endif // ____LIBUNWIND_CONFIG_H__ \ No newline at end of file diff --git a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h deleted file mode 100644 index 29917662a8..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwind/include/libunwind.h +++ /dev/null @@ -1,508 +0,0 @@ -//===---------------------------- libunwind.h -----------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -// -// Compatible with libuwind API documented at: -// http://www.nongnu.org/libunwind/man/libunwind(3).html -// -//===----------------------------------------------------------------------===// -#ifndef __LIBUNWIND__ -#define __LIBUNWIND__ -#include "__libunwind_config.h" -#include -#include -#ifdef __APPLE__ - #include - #ifdef __arm__ - #define LIBUNWIND_AVAIL __attribute__((unavailable)) - #else - #define LIBUNWIND_AVAIL __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0) - #endif -#else - #define LIBUNWIND_AVAIL -#endif -/* error codes */ -enum { - UNW_ESUCCESS = 0, /* no error */ - UNW_EUNSPEC = -6540, /* unspecified (general) error */ - UNW_ENOMEM = -6541, /* out of memory */ - UNW_EBADREG = -6542, /* bad register number */ - UNW_EREADONLYREG = -6543, /* attempt to write read-only register */ - UNW_ESTOPUNWIND = -6544, /* stop unwinding */ - UNW_EINVALIDIP = -6545, /* invalid IP */ - UNW_EBADFRAME = -6546, /* bad frame */ - UNW_EINVAL = -6547, /* unsupported operation or bad value */ - UNW_EBADVERSION = -6548, /* unwind info has unsupported version */ - UNW_ENOINFO = -6549 /* no unwind info found */ -}; -struct unw_context_t { - uint64_t data[_LIBUNWIND_CONTEXT_SIZE]; -}; -typedef struct unw_context_t unw_context_t; -struct unw_cursor_t { - uint64_t data[_LIBUNWIND_CURSOR_SIZE]; -}; -typedef struct unw_cursor_t unw_cursor_t; -typedef struct unw_addr_space *unw_addr_space_t; -typedef int unw_regnum_t; -#if _LIBUNWIND_ARM_EHABI -typedef uint32_t unw_word_t; -typedef uint64_t unw_fpreg_t; -#else -typedef uint64_t unw_word_t; -typedef double unw_fpreg_t; -#endif -struct unw_proc_info_t { - unw_word_t start_ip; /* start address of function */ - unw_word_t end_ip; /* address after end of function */ - unw_word_t lsda; /* address of language specific data area, */ - /* or zero if not used */ - unw_word_t handler; /* personality routine, or zero if not used */ - unw_word_t gp; /* not used */ - unw_word_t flags; /* not used */ - uint32_t format; /* compact unwind encoding, or zero if none */ - uint32_t unwind_info_size; /* size of dwarf unwind info, or zero if none */ - unw_word_t unwind_info; /* address of dwarf unwind info, or zero */ - unw_word_t extra; /* mach_header of mach-o image containing func */ -}; -typedef struct unw_proc_info_t unw_proc_info_t; -#ifdef __cplusplus -extern "C" { -#endif -extern int unw_getcontext(unw_context_t *) LIBUNWIND_AVAIL; -extern int unw_init_local(unw_cursor_t *, unw_context_t *) LIBUNWIND_AVAIL; -extern int unw_step(unw_cursor_t *) LIBUNWIND_AVAIL; -extern int unw_get_reg(unw_cursor_t *, unw_regnum_t, unw_word_t *) LIBUNWIND_AVAIL; -extern int unw_get_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t *) LIBUNWIND_AVAIL; -extern int unw_set_reg(unw_cursor_t *, unw_regnum_t, unw_word_t) LIBUNWIND_AVAIL; -extern int unw_set_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t) LIBUNWIND_AVAIL; -extern int unw_resume(unw_cursor_t *) LIBUNWIND_AVAIL; -#ifdef __arm__ -/* Save VFP registers in FSTMX format (instead of FSTMD). */ -extern void unw_save_vfp_as_X(unw_cursor_t *) LIBUNWIND_AVAIL; -#endif -extern const char *unw_regname(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; -extern int unw_get_proc_info(unw_cursor_t *, unw_proc_info_t *) LIBUNWIND_AVAIL; -extern int unw_is_fpreg(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; -extern int unw_is_signal_frame(unw_cursor_t *) LIBUNWIND_AVAIL; -extern int unw_get_proc_name(unw_cursor_t *, char *, size_t, unw_word_t *) LIBUNWIND_AVAIL; -//extern int unw_get_save_loc(unw_cursor_t*, int, unw_save_loc_t*); -extern unw_addr_space_t unw_local_addr_space; -#ifdef UNW_REMOTE -/* - * Mac OS X "remote" API for unwinding other processes on same machine - * - */ -extern unw_addr_space_t unw_create_addr_space_for_task(task_t); -extern void unw_destroy_addr_space(unw_addr_space_t); -extern int unw_init_remote_thread(unw_cursor_t *, unw_addr_space_t, thread_t *); -#endif /* UNW_REMOTE */ -/* - * traditional libuwind "remote" API - * NOT IMPLEMENTED on Mac OS X - * - * extern int unw_init_remote(unw_cursor_t*, unw_addr_space_t, - * thread_t*); - * extern unw_accessors_t unw_get_accessors(unw_addr_space_t); - * extern unw_addr_space_t unw_create_addr_space(unw_accessors_t, int); - * extern void unw_flush_cache(unw_addr_space_t, unw_word_t, - * unw_word_t); - * extern int unw_set_caching_policy(unw_addr_space_t, - * unw_caching_policy_t); - * extern void _U_dyn_register(unw_dyn_info_t*); - * extern void _U_dyn_cancel(unw_dyn_info_t*); - */ -#ifdef __cplusplus -} -#endif -// architecture independent register numbers -enum { - UNW_REG_IP = -1, // instruction pointer - UNW_REG_SP = -2, // stack pointer -}; -// 32-bit x86 registers -enum { - UNW_X86_EAX = 0, - UNW_X86_ECX = 1, - UNW_X86_EDX = 2, - UNW_X86_EBX = 3, - UNW_X86_EBP = 4, - UNW_X86_ESP = 5, - UNW_X86_ESI = 6, - UNW_X86_EDI = 7 -}; -// 64-bit x86_64 registers -enum { - UNW_X86_64_RAX = 0, - UNW_X86_64_RDX = 1, - UNW_X86_64_RCX = 2, - UNW_X86_64_RBX = 3, - UNW_X86_64_RSI = 4, - UNW_X86_64_RDI = 5, - UNW_X86_64_RBP = 6, - UNW_X86_64_RSP = 7, - UNW_X86_64_R8 = 8, - UNW_X86_64_R9 = 9, - UNW_X86_64_R10 = 10, - UNW_X86_64_R11 = 11, - UNW_X86_64_R12 = 12, - UNW_X86_64_R13 = 13, - UNW_X86_64_R14 = 14, - UNW_X86_64_R15 = 15 -}; -// 32-bit ppc register numbers -enum { - UNW_PPC_R0 = 0, - UNW_PPC_R1 = 1, - UNW_PPC_R2 = 2, - UNW_PPC_R3 = 3, - UNW_PPC_R4 = 4, - UNW_PPC_R5 = 5, - UNW_PPC_R6 = 6, - UNW_PPC_R7 = 7, - UNW_PPC_R8 = 8, - UNW_PPC_R9 = 9, - UNW_PPC_R10 = 10, - UNW_PPC_R11 = 11, - UNW_PPC_R12 = 12, - UNW_PPC_R13 = 13, - UNW_PPC_R14 = 14, - UNW_PPC_R15 = 15, - UNW_PPC_R16 = 16, - UNW_PPC_R17 = 17, - UNW_PPC_R18 = 18, - UNW_PPC_R19 = 19, - UNW_PPC_R20 = 20, - UNW_PPC_R21 = 21, - UNW_PPC_R22 = 22, - UNW_PPC_R23 = 23, - UNW_PPC_R24 = 24, - UNW_PPC_R25 = 25, - UNW_PPC_R26 = 26, - UNW_PPC_R27 = 27, - UNW_PPC_R28 = 28, - UNW_PPC_R29 = 29, - UNW_PPC_R30 = 30, - UNW_PPC_R31 = 31, - UNW_PPC_F0 = 32, - UNW_PPC_F1 = 33, - UNW_PPC_F2 = 34, - UNW_PPC_F3 = 35, - UNW_PPC_F4 = 36, - UNW_PPC_F5 = 37, - UNW_PPC_F6 = 38, - UNW_PPC_F7 = 39, - UNW_PPC_F8 = 40, - UNW_PPC_F9 = 41, - UNW_PPC_F10 = 42, - UNW_PPC_F11 = 43, - UNW_PPC_F12 = 44, - UNW_PPC_F13 = 45, - UNW_PPC_F14 = 46, - UNW_PPC_F15 = 47, - UNW_PPC_F16 = 48, - UNW_PPC_F17 = 49, - UNW_PPC_F18 = 50, - UNW_PPC_F19 = 51, - UNW_PPC_F20 = 52, - UNW_PPC_F21 = 53, - UNW_PPC_F22 = 54, - UNW_PPC_F23 = 55, - UNW_PPC_F24 = 56, - UNW_PPC_F25 = 57, - UNW_PPC_F26 = 58, - UNW_PPC_F27 = 59, - UNW_PPC_F28 = 60, - UNW_PPC_F29 = 61, - UNW_PPC_F30 = 62, - UNW_PPC_F31 = 63, - UNW_PPC_MQ = 64, - UNW_PPC_LR = 65, - UNW_PPC_CTR = 66, - UNW_PPC_AP = 67, - UNW_PPC_CR0 = 68, - UNW_PPC_CR1 = 69, - UNW_PPC_CR2 = 70, - UNW_PPC_CR3 = 71, - UNW_PPC_CR4 = 72, - UNW_PPC_CR5 = 73, - UNW_PPC_CR6 = 74, - UNW_PPC_CR7 = 75, - UNW_PPC_XER = 76, - UNW_PPC_V0 = 77, - UNW_PPC_V1 = 78, - UNW_PPC_V2 = 79, - UNW_PPC_V3 = 80, - UNW_PPC_V4 = 81, - UNW_PPC_V5 = 82, - UNW_PPC_V6 = 83, - UNW_PPC_V7 = 84, - UNW_PPC_V8 = 85, - UNW_PPC_V9 = 86, - UNW_PPC_V10 = 87, - UNW_PPC_V11 = 88, - UNW_PPC_V12 = 89, - UNW_PPC_V13 = 90, - UNW_PPC_V14 = 91, - UNW_PPC_V15 = 92, - UNW_PPC_V16 = 93, - UNW_PPC_V17 = 94, - UNW_PPC_V18 = 95, - UNW_PPC_V19 = 96, - UNW_PPC_V20 = 97, - UNW_PPC_V21 = 98, - UNW_PPC_V22 = 99, - UNW_PPC_V23 = 100, - UNW_PPC_V24 = 101, - UNW_PPC_V25 = 102, - UNW_PPC_V26 = 103, - UNW_PPC_V27 = 104, - UNW_PPC_V28 = 105, - UNW_PPC_V29 = 106, - UNW_PPC_V30 = 107, - UNW_PPC_V31 = 108, - UNW_PPC_VRSAVE = 109, - UNW_PPC_VSCR = 110, - UNW_PPC_SPE_ACC = 111, - UNW_PPC_SPEFSCR = 112 -}; -// 64-bit ARM64 registers -enum { - UNW_ARM64_X0 = 0, - UNW_ARM64_X1 = 1, - UNW_ARM64_X2 = 2, - UNW_ARM64_X3 = 3, - UNW_ARM64_X4 = 4, - UNW_ARM64_X5 = 5, - UNW_ARM64_X6 = 6, - UNW_ARM64_X7 = 7, - UNW_ARM64_X8 = 8, - UNW_ARM64_X9 = 9, - UNW_ARM64_X10 = 10, - UNW_ARM64_X11 = 11, - UNW_ARM64_X12 = 12, - UNW_ARM64_X13 = 13, - UNW_ARM64_X14 = 14, - UNW_ARM64_X15 = 15, - UNW_ARM64_X16 = 16, - UNW_ARM64_X17 = 17, - UNW_ARM64_X18 = 18, - UNW_ARM64_X19 = 19, - UNW_ARM64_X20 = 20, - UNW_ARM64_X21 = 21, - UNW_ARM64_X22 = 22, - UNW_ARM64_X23 = 23, - UNW_ARM64_X24 = 24, - UNW_ARM64_X25 = 25, - UNW_ARM64_X26 = 26, - UNW_ARM64_X27 = 27, - UNW_ARM64_X28 = 28, - UNW_ARM64_X29 = 29, - UNW_ARM64_FP = 29, - UNW_ARM64_X30 = 30, - UNW_ARM64_LR = 30, - UNW_ARM64_X31 = 31, - UNW_ARM64_SP = 31, - // reserved block - UNW_ARM64_D0 = 64, - UNW_ARM64_D1 = 65, - UNW_ARM64_D2 = 66, - UNW_ARM64_D3 = 67, - UNW_ARM64_D4 = 68, - UNW_ARM64_D5 = 69, - UNW_ARM64_D6 = 70, - UNW_ARM64_D7 = 71, - UNW_ARM64_D8 = 72, - UNW_ARM64_D9 = 73, - UNW_ARM64_D10 = 74, - UNW_ARM64_D11 = 75, - UNW_ARM64_D12 = 76, - UNW_ARM64_D13 = 77, - UNW_ARM64_D14 = 78, - UNW_ARM64_D15 = 79, - UNW_ARM64_D16 = 80, - UNW_ARM64_D17 = 81, - UNW_ARM64_D18 = 82, - UNW_ARM64_D19 = 83, - UNW_ARM64_D20 = 84, - UNW_ARM64_D21 = 85, - UNW_ARM64_D22 = 86, - UNW_ARM64_D23 = 87, - UNW_ARM64_D24 = 88, - UNW_ARM64_D25 = 89, - UNW_ARM64_D26 = 90, - UNW_ARM64_D27 = 91, - UNW_ARM64_D28 = 92, - UNW_ARM64_D29 = 93, - UNW_ARM64_D30 = 94, - UNW_ARM64_D31 = 95, -}; -// 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1. -// Naming scheme uses recommendations given in Note 4 for VFP-v2 and VFP-v3. -// In this scheme, even though the 64-bit floating point registers D0-D31 -// overlap physically with the 32-bit floating pointer registers S0-S31, -// they are given a non-overlapping range of register numbers. -// -// Commented out ranges are not preserved during unwinding. -enum { - UNW_ARM_R0 = 0, - UNW_ARM_R1 = 1, - UNW_ARM_R2 = 2, - UNW_ARM_R3 = 3, - UNW_ARM_R4 = 4, - UNW_ARM_R5 = 5, - UNW_ARM_R6 = 6, - UNW_ARM_R7 = 7, - UNW_ARM_R8 = 8, - UNW_ARM_R9 = 9, - UNW_ARM_R10 = 10, - UNW_ARM_R11 = 11, - UNW_ARM_R12 = 12, - UNW_ARM_SP = 13, // Logical alias for UNW_REG_SP - UNW_ARM_R13 = 13, - UNW_ARM_LR = 14, - UNW_ARM_R14 = 14, - UNW_ARM_IP = 15, // Logical alias for UNW_REG_IP - UNW_ARM_R15 = 15, - // 16-63 -- OBSOLETE. Used in VFP1 to represent both S0-S31 and D0-D31. - UNW_ARM_S0 = 64, - UNW_ARM_S1 = 65, - UNW_ARM_S2 = 66, - UNW_ARM_S3 = 67, - UNW_ARM_S4 = 68, - UNW_ARM_S5 = 69, - UNW_ARM_S6 = 70, - UNW_ARM_S7 = 71, - UNW_ARM_S8 = 72, - UNW_ARM_S9 = 73, - UNW_ARM_S10 = 74, - UNW_ARM_S11 = 75, - UNW_ARM_S12 = 76, - UNW_ARM_S13 = 77, - UNW_ARM_S14 = 78, - UNW_ARM_S15 = 79, - UNW_ARM_S16 = 80, - UNW_ARM_S17 = 81, - UNW_ARM_S18 = 82, - UNW_ARM_S19 = 83, - UNW_ARM_S20 = 84, - UNW_ARM_S21 = 85, - UNW_ARM_S22 = 86, - UNW_ARM_S23 = 87, - UNW_ARM_S24 = 88, - UNW_ARM_S25 = 89, - UNW_ARM_S26 = 90, - UNW_ARM_S27 = 91, - UNW_ARM_S28 = 92, - UNW_ARM_S29 = 93, - UNW_ARM_S30 = 94, - UNW_ARM_S31 = 95, - // 96-103 -- OBSOLETE. F0-F7. Used by the FPA system. Superseded by VFP. - // 104-111 -- wCGR0-wCGR7, ACC0-ACC7 (Intel wireless MMX) - UNW_ARM_WR0 = 112, - UNW_ARM_WR1 = 113, - UNW_ARM_WR2 = 114, - UNW_ARM_WR3 = 115, - UNW_ARM_WR4 = 116, - UNW_ARM_WR5 = 117, - UNW_ARM_WR6 = 118, - UNW_ARM_WR7 = 119, - UNW_ARM_WR8 = 120, - UNW_ARM_WR9 = 121, - UNW_ARM_WR10 = 122, - UNW_ARM_WR11 = 123, - UNW_ARM_WR12 = 124, - UNW_ARM_WR13 = 125, - UNW_ARM_WR14 = 126, - UNW_ARM_WR15 = 127, - // 128-133 -- SPSR, SPSR_{FIQ|IRQ|ABT|UND|SVC} - // 134-143 -- Reserved - // 144-150 -- R8_USR-R14_USR - // 151-157 -- R8_FIQ-R14_FIQ - // 158-159 -- R13_IRQ-R14_IRQ - // 160-161 -- R13_ABT-R14_ABT - // 162-163 -- R13_UND-R14_UND - // 164-165 -- R13_SVC-R14_SVC - // 166-191 -- Reserved - UNW_ARM_WC0 = 192, - UNW_ARM_WC1 = 193, - UNW_ARM_WC2 = 194, - UNW_ARM_WC3 = 195, - // 196-199 -- wC4-wC7 (Intel wireless MMX control) - // 200-255 -- Reserved - UNW_ARM_D0 = 256, - UNW_ARM_D1 = 257, - UNW_ARM_D2 = 258, - UNW_ARM_D3 = 259, - UNW_ARM_D4 = 260, - UNW_ARM_D5 = 261, - UNW_ARM_D6 = 262, - UNW_ARM_D7 = 263, - UNW_ARM_D8 = 264, - UNW_ARM_D9 = 265, - UNW_ARM_D10 = 266, - UNW_ARM_D11 = 267, - UNW_ARM_D12 = 268, - UNW_ARM_D13 = 269, - UNW_ARM_D14 = 270, - UNW_ARM_D15 = 271, - UNW_ARM_D16 = 272, - UNW_ARM_D17 = 273, - UNW_ARM_D18 = 274, - UNW_ARM_D19 = 275, - UNW_ARM_D20 = 276, - UNW_ARM_D21 = 277, - UNW_ARM_D22 = 278, - UNW_ARM_D23 = 279, - UNW_ARM_D24 = 280, - UNW_ARM_D25 = 281, - UNW_ARM_D26 = 282, - UNW_ARM_D27 = 283, - UNW_ARM_D28 = 284, - UNW_ARM_D29 = 285, - UNW_ARM_D30 = 286, - UNW_ARM_D31 = 287, - // 288-319 -- Reserved for VFP/Neon - // 320-8191 -- Reserved - // 8192-16383 -- Unspecified vendor co-processor register. -}; -// OpenRISC1000 register numbers -enum { - UNW_OR1K_R0 = 0, - UNW_OR1K_R1 = 1, - UNW_OR1K_R2 = 2, - UNW_OR1K_R3 = 3, - UNW_OR1K_R4 = 4, - UNW_OR1K_R5 = 5, - UNW_OR1K_R6 = 6, - UNW_OR1K_R7 = 7, - UNW_OR1K_R8 = 8, - UNW_OR1K_R9 = 9, - UNW_OR1K_R10 = 10, - UNW_OR1K_R11 = 11, - UNW_OR1K_R12 = 12, - UNW_OR1K_R13 = 13, - UNW_OR1K_R14 = 14, - UNW_OR1K_R15 = 15, - UNW_OR1K_R16 = 16, - UNW_OR1K_R17 = 17, - UNW_OR1K_R18 = 18, - UNW_OR1K_R19 = 19, - UNW_OR1K_R20 = 20, - UNW_OR1K_R21 = 21, - UNW_OR1K_R22 = 22, - UNW_OR1K_R23 = 23, - UNW_OR1K_R24 = 24, - UNW_OR1K_R25 = 25, - UNW_OR1K_R26 = 26, - UNW_OR1K_R27 = 27, - UNW_OR1K_R28 = 28, - UNW_OR1K_R29 = 29, - UNW_OR1K_R30 = 30, - UNW_OR1K_R31 = 31, -}; -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk index d781d7b325..81b3598c5c 160000 --- a/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk +++ b/bugsnag-plugin-android-ndk/src/main/jni/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit d781d7b325a6f64e4f63fc4e269a19944c7fe688 +Subproject commit 81b3598c5c60044ed8a58bc8ce32257f2da0704d diff --git a/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp b/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp index 59098b082c..61b1c80646 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp +++ b/bugsnag-plugin-android-ndk/src/main/jni/handlers/cpp_handler.cpp @@ -43,48 +43,6 @@ void bsg_handler_uninstall_cpp() { bsg_global_env = NULL; } -void bsg_write_current_exception_message(char *message, size_t length) { - try { - throw; - } catch (std::exception &exc) { - bsg_strncpy(message, (char *)exc.what(), length); - } catch (std::exception *exc) { - bsg_strncpy(message, (char *)exc->what(), length); - } catch (std::string obj) { - bsg_strncpy(message, (char *)obj.c_str(), length); - } catch (char *obj) { - snprintf(message, length, "%s", obj); - } catch (char obj) { - snprintf(message, length, "%c", obj); - } catch (short obj) { - snprintf(message, length, "%d", obj); - } catch (int obj) { - snprintf(message, length, "%d", obj); - } catch (long obj) { - snprintf(message, length, "%ld", obj); - } catch (long long obj) { - snprintf(message, length, "%lld", obj); - } catch (long double obj) { - snprintf(message, length, "%Lf", obj); - } catch (double obj) { - snprintf(message, length, "%f", obj); - } catch (float obj) { - snprintf(message, length, "%f", obj); - } catch (unsigned char obj) { - snprintf(message, length, "%u", obj); - } catch (unsigned short obj) { - snprintf(message, length, "%u", obj); - } catch (unsigned int obj) { - snprintf(message, length, "%u", obj); - } catch (unsigned long obj) { - snprintf(message, length, "%lu", obj); - } catch (unsigned long long obj) { - snprintf(message, length, "%llu", obj); - } catch (...) { - // no way to describe what this is - } -} - void bsg_handle_cpp_terminate() { if (bsg_global_env == NULL || bsg_global_env->handling_crash) return; @@ -92,9 +50,8 @@ void bsg_handle_cpp_terminate() { bsg_global_env->handling_crash = true; bsg_populate_event_as(bsg_global_env); bsg_global_env->next_event.unhandled = true; - bsg_global_env->next_event.error.frame_count = - bsg_unwind_stack(bsg_global_env->unwind_style, - bsg_global_env->next_event.error.stacktrace, NULL, NULL); + bsg_global_env->next_event.error.frame_count = bsg_unwind_crash_stack( + bsg_global_env->next_event.error.stacktrace, NULL, NULL); if (bsg_global_env->send_threads != SEND_THREADS_NEVER) { bsg_global_env->next_event.thread_count = bsg_capture_thread_states( @@ -109,11 +66,6 @@ void bsg_handle_cpp_terminate() { (char *)tinfo->name(), sizeof(bsg_global_env->next_event.error.errorClass)); } - size_t message_length = sizeof(bsg_global_env->next_event.error.errorMessage); - char message[message_length]; - bsg_write_current_exception_message(message, message_length); - bsg_strncpy(bsg_global_env->next_event.error.errorMessage, (char *)message, - message_length); if (bsg_run_on_error()) { bsg_increment_unhandled_count(&bsg_global_env->next_event); diff --git a/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c b/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c index 5f53554c6a..5c4f0cc42b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/handlers/signal_handler.c @@ -182,8 +182,7 @@ void bsg_handle_signal(int signum, siginfo_t *info, bsg_global_env->handling_crash = true; bsg_global_env->next_event.unhandled = true; bsg_populate_event_as(bsg_global_env); - bsg_global_env->next_event.error.frame_count = bsg_unwind_stack( - bsg_global_env->signal_unwind_style, + bsg_global_env->next_event.error.frame_count = bsg_unwind_crash_stack( bsg_global_env->next_event.error.stacktrace, info, user_context); if (bsg_global_env->send_threads != SEND_THREADS_NEVER) { @@ -212,4 +211,4 @@ void bsg_handle_signal(int signum, siginfo_t *info, } bsg_handler_uninstall_signal(); bsg_invoke_previous_signal_handler(signum, info, user_context); -} \ No newline at end of file +} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c deleted file mode 100644 index 1ee8e67b24..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "stack_unwinder.h" -#include "stack_unwinder_libcorkscrew.h" -#include "stack_unwinder_libunwind.h" -#include "stack_unwinder_libunwindstack.h" -#include "stack_unwinder_simple.h" -#include "string.h" -#include -#include -#include -#include - -#define BSG_LIBUNWIND_LEVEL 21 -#define BSG_LIBUNWINDSTACK_LEVEL 15 -#define BSG_LIBUNWIND_LEVEL_ARM32 16 -#define BSG_LIBCORKSCREW_MIN_LEVEL 16 -#define BSG_LIBCORKSCREW_MAX_LEVEL 19 - -void bsg_set_unwind_types(int apiLevel, bool is32bit, bsg_unwinder *signal_type, - bsg_unwinder *other_type) { -#if defined(__arm__) - if (apiLevel >= BSG_LIBUNWIND_LEVEL_ARM32 && is32bit && - bsg_configure_libunwind(is32bit)) { - if (apiLevel >= BSG_LIBUNWIND_LEVEL) { - *signal_type = BSG_LIBUNWIND; - } else if (bsg_configure_libcorkscrew()) { - *signal_type = BSG_LIBCORKSCREW; - } else { - *signal_type = BSG_CUSTOM_UNWIND; - } - *other_type = BSG_LIBUNWIND; - return; - } -#endif - if (apiLevel >= BSG_LIBUNWINDSTACK_LEVEL) { - bsg_configure_libunwind(is32bit); - *signal_type = BSG_LIBUNWINDSTACK; - *other_type = BSG_LIBUNWIND; - } else { - *signal_type = BSG_CUSTOM_UNWIND; - *other_type = BSG_CUSTOM_UNWIND; - } -} - -void bsg_insert_fileinfo(ssize_t frame_count, - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX]) { - static Dl_info info; - for (int i = 0; i < frame_count; ++i) { - if (dladdr((void *)stacktrace[i].frame_address, &info) != 0) { - stacktrace[i].load_address = (uintptr_t)info.dli_fbase; - stacktrace[i].symbol_address = (uintptr_t)info.dli_saddr; - stacktrace[i].line_number = - stacktrace[i].frame_address - stacktrace[i].load_address; - if (info.dli_fname != NULL) { - bsg_strncpy(stacktrace[i].filename, (char *)info.dli_fname, - sizeof(stacktrace[i].filename)); - } - if (info.dli_sname != NULL) { - bsg_strncpy(stacktrace[i].method, (char *)info.dli_sname, - sizeof(stacktrace[i].method)); - } - } - } -} - -ssize_t bsg_unwind_stack(bsg_unwinder unwind_style, - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { - ssize_t frame_count = 0; - if (unwind_style == BSG_LIBUNWINDSTACK) { - frame_count = - bsg_unwind_stack_libunwindstack(stacktrace, info, user_context); - } else if (unwind_style == BSG_LIBUNWIND) { - frame_count = bsg_unwind_stack_libunwind(stacktrace, info, user_context); - } else if (unwind_style == BSG_LIBCORKSCREW) { - frame_count = bsg_unwind_stack_libcorkscrew(stacktrace, info, user_context); - } else { - frame_count = bsg_unwind_stack_simple(stacktrace, info, user_context); - } - bsg_insert_fileinfo(frame_count, - stacktrace); // none of this is safe ¯\_(ツ)_/¯ - - return frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp new file mode 100644 index 0000000000..1a488c2182 --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp @@ -0,0 +1,106 @@ +#include "stack_unwinder.h" + +#include "string.h" + +#include +#include +#include +#include +#include +#include + +// unwinder intended for a potentially terminating context +static unwindstack::Unwinder *crash_time_unwinder; +// soft lock for using the crash time unwinder - if active, return without +// attempting to unwind. This isn't a "real" lock to avoid deadlocking in the +// event of a crash while handling an ANR or the reverse. +static bool unwinding_crash_stack; + +// Thread-safe, reusable unwinder - uses thread-specific memory caches +static unwindstack::LocalUnwinder *current_time_unwinder; + +static bool attempted_init; + +void bsg_unwinder_init() { + if (attempted_init) { + // already initialized or failed to init, cannot be done more than once + return; + } + attempted_init = true; + + auto crash_time_maps = new unwindstack::LocalMaps(); + if (crash_time_maps->Parse()) { + std::shared_ptr crash_time_memory( + new unwindstack::MemoryLocal); + crash_time_unwinder = new unwindstack::Unwinder( + BUGSNAG_FRAMES_MAX, crash_time_maps, + unwindstack::Regs::CreateFromLocal(), crash_time_memory); + auto arch = unwindstack::Regs::CurrentArch(); + auto dexfiles_ptr = unwindstack::CreateDexFiles(arch, crash_time_memory); + crash_time_unwinder->SetDexFiles(dexfiles_ptr.get()); + } + + current_time_unwinder = new unwindstack::LocalUnwinder(); + if (!current_time_unwinder->Init()) { + delete current_time_unwinder; + current_time_unwinder = nullptr; + } +} + +ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context) { + if (crash_time_unwinder == nullptr || unwinding_crash_stack) { + return 0; + } + unwinding_crash_stack = true; + if (user_context) { + crash_time_unwinder->SetRegs(unwindstack::Regs::CreateFromUcontext( + unwindstack::Regs::CurrentArch(), user_context)); + } else { + auto regs = unwindstack::Regs::CreateFromLocal(); + unwindstack::RegsGetLocal(regs); + crash_time_unwinder->SetRegs(regs); + } + + crash_time_unwinder->Unwind(); + int frame_count = 0; + for (auto &frame : crash_time_unwinder->frames()) { + stack[frame_count].frame_address = frame.pc; + stack[frame_count].line_number = frame.rel_pc; + stack[frame_count].load_address = frame.map_start; + stack[frame_count].symbol_address = frame.pc - frame.function_offset; + bsg_strncpy(stack[frame_count].filename, frame.map_name.c_str(), + sizeof(stack[frame_count].filename)); + bsg_strncpy(stack[frame_count].method, frame.function_name.c_str(), + sizeof(stack[frame_count].method)); + frame_count++; + } + unwinding_crash_stack = false; + return frame_count; +} + +ssize_t +bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *_info, void *_context) { + if (current_time_unwinder == nullptr) { + return 0; + } + + std::vector frames; + current_time_unwinder->Unwind(&frames, BUGSNAG_FRAMES_MAX); + int frame_count = 0; + for (auto &frame : frames) { + stack[frame_count].frame_address = frame.pc; + if (frame.map_info != nullptr) { + stack[frame_count].line_number = frame.rel_pc; + stack[frame_count].load_address = frame.map_info->start(); + stack[frame_count].symbol_address = frame.pc - frame.map_info->offset(); + bsg_strncpy(stack[frame_count].filename, frame.map_info->name().c_str(), + sizeof(stack[frame_count].filename)); + } + bsg_strncpy(stack[frame_count].method, frame.function_name.c_str(), + sizeof(stack[frame_count].method)); + frame_count++; + } + return frame_count; +} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h index 161290370d..25078e898b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.h @@ -1,5 +1,4 @@ -#ifndef BSG_STACK_UNWINDER_H -#define BSG_STACK_UNWINDER_H +#pragma once #include "build.h" #include @@ -9,36 +8,40 @@ extern "C" { #endif -typedef enum { - BSG_LIBUNWIND, - BSG_LIBUNWINDSTACK, - BSG_LIBCORKSCREW, - BSG_CUSTOM_UNWIND, -} bsg_unwinder; +/** + * Initialize the stack unwinder. Must be called prior to initial use. + */ +void bsg_unwinder_init(void); /** - * Based on the current environment, determine what unwinding library to use. + * Unwind a stack in a terminating context. If info and a user context pointer + * are provided, the exception stack will be walked. Otherwise, the current + * stack will be walked. The results will populate the stack. * - * Android API level 21+: libunwind - * Android API level 16-19: libunwind, unless in a signal handler. Then - * libcorkscrew. - * Everything else: custom unwinding logic + * @param stack buffer to contain the frame contents + * @param info signal info or null if none + * @param user_context crash context or null if none + * + * @return the number of frames */ -void bsg_set_unwind_types(int apiLevel, bool is32bit, bsg_unwinder *signal_type, - bsg_unwinder *other_type); +ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context) __asyncsafe; /** - * Unwind the stack using the preferred tool/style. If info and a user - * context pointer are provided, the exception stack will be walked. Otherwise, - * the current stack will be walked instead. The results will populate the - * stacktrace + * Unwind a stack in a thread-safe context. If info and a user context pointer + * are provided, the exception stack will be walked. Otherwise, the current + * stack will be walked. The results will populate the stack. + * + * @param stack buffer to contain the frame contents + * @param info IGNORED - provided for signature compatibility + * @param user_context IGNORED - provided for signature compatibility + * * @return the number of frames */ -ssize_t bsg_unwind_stack(bsg_unwinder unwind_style, - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) __asyncsafe; +ssize_t +bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); #ifdef __cplusplus } #endif -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c deleted file mode 100644 index ecf59c03d0..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "stack_unwinder_libcorkscrew.h" -#include -#include -#include -#include -#include - -#include "string.h" - -typedef struct { - uintptr_t absolute_pc; - uintptr_t stack_top; - size_t stack_size; -} backtrace_frame_t; - -typedef struct { - uintptr_t relative_pc; - uintptr_t relative_symbol_addr; - char *map_name; - char *symbol_name; - char *demangled_name; -} backtrace_symbol_t; - -/* Extracted from Android's include/corkscrew/backtrace.h */ -typedef struct map_info_t map_info_t; - -struct bsg_unwind_config { - void *cork_unwind_backtrace_signal_arch; - void *cork_unwind_backtrace_thread; - void *cork_acquire_my_map_info_list; - void *cork_release_my_map_info_list; - void *cork_get_backtrace_symbols; - void *cork_free_backtrace_symbols; -}; - -static struct bsg_unwind_config *bsg_global_unwind_cfg; - -bool bsg_libcorkscrew_configured() { - return bsg_global_unwind_cfg->cork_unwind_backtrace_signal_arch != NULL && - bsg_global_unwind_cfg->cork_unwind_backtrace_thread != NULL && - bsg_global_unwind_cfg->cork_acquire_my_map_info_list != NULL && - bsg_global_unwind_cfg->cork_release_my_map_info_list != NULL && - bsg_global_unwind_cfg->cork_get_backtrace_symbols != NULL && - bsg_global_unwind_cfg->cork_free_backtrace_symbols != NULL; -} - -bool bsg_configure_libcorkscrew(void) { - bsg_global_unwind_cfg = calloc(1, sizeof(struct bsg_unwind_config)); - void *libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL); - if (libcorkscrew != NULL) { - bsg_global_unwind_cfg->cork_unwind_backtrace_signal_arch = - dlsym(libcorkscrew, "unwind_backtrace_signal_arch"); - bsg_global_unwind_cfg->cork_acquire_my_map_info_list = - dlsym(libcorkscrew, "acquire_my_map_info_list"); - bsg_global_unwind_cfg->cork_release_my_map_info_list = - dlsym(libcorkscrew, "release_my_map_info_list"); - bsg_global_unwind_cfg->cork_get_backtrace_symbols = - dlsym(libcorkscrew, "get_backtrace_symbols"); - bsg_global_unwind_cfg->cork_free_backtrace_symbols = - dlsym(libcorkscrew, "free_backtrace_symbols"); - bsg_global_unwind_cfg->cork_unwind_backtrace_thread = - dlsym(libcorkscrew, "unwind_backtrace_thread"); - } - - return bsg_libcorkscrew_configured(); -} - -ssize_t -bsg_unwind_stack_libcorkscrew(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { - backtrace_frame_t frames[BUGSNAG_FRAMES_MAX]; - backtrace_symbol_t symbols[BUGSNAG_FRAMES_MAX]; - map_info_t *(*acquire_my_map_info_list)(void) = - bsg_global_unwind_cfg->cork_acquire_my_map_info_list; - ssize_t (*unwind_backtrace_signal_arch)( - siginfo_t *, void *, const map_info_t *, backtrace_frame_t *, size_t, - size_t) = bsg_global_unwind_cfg->cork_unwind_backtrace_signal_arch; - ssize_t (*unwind_backtrace_thread)(pid_t, backtrace_frame_t *, size_t, - size_t) = - bsg_global_unwind_cfg->cork_unwind_backtrace_thread; - void (*release_my_map_info_list)(map_info_t *) = - bsg_global_unwind_cfg->cork_release_my_map_info_list; - void (*get_backtrace_symbols)(const backtrace_frame_t *, size_t, - backtrace_symbol_t *) = - bsg_global_unwind_cfg->cork_get_backtrace_symbols; - void (*free_backtrace_symbols)(backtrace_symbol_t *, size_t) = - bsg_global_unwind_cfg->cork_free_backtrace_symbols; - - ssize_t size; - if (user_context != NULL) { - map_info_t *const info_list = acquire_my_map_info_list(); - size = unwind_backtrace_signal_arch(info, user_context, info_list, frames, - 0, (size_t)BUGSNAG_FRAMES_MAX); - release_my_map_info_list(info_list); - } else { - size = unwind_backtrace_thread(getpid(), frames, 0, - (size_t)BUGSNAG_FRAMES_MAX); - } - - get_backtrace_symbols(frames, (size_t)size, symbols); - int frame_count = 0; - for (int i = 0; i < size; i++) { - backtrace_frame_t backtrace_frame = frames[i]; - backtrace_symbol_t backtrace_symbol = symbols[i]; - - if ((void *)backtrace_frame.absolute_pc == NULL) { - continue; // nobody's home - } - if (frame_count > 0 && backtrace_frame.absolute_pc == - stacktrace[frame_count - 1].frame_address) { - continue; // already seen this - } - if (backtrace_symbol.symbol_name != NULL) { - bsg_strncpy(stacktrace[frame_count].method, backtrace_symbol.symbol_name, - sizeof(stacktrace[frame_count].method)); - } - - stacktrace[frame_count].frame_address = backtrace_frame.absolute_pc; - frame_count++; - } - free_backtrace_symbols(symbols, (size_t)size); - - return frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h deleted file mode 100644 index 8d8a8b7eea..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libcorkscrew.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_LIBCORKSCREW_H -#define BUGSNAG_UTILS_STACK_UNWINDER_LIBCORKSCREW_H - -#include "../event.h" -#include - -bool bsg_configure_libcorkscrew(void); - -ssize_t -bsg_unwind_stack_libcorkscrew(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c deleted file mode 100644 index 4828469586..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "stack_unwinder_libunwind.h" -#include "build.h" -#include -#include -#include - -#if defined(__arm__) -#include -#endif - -typedef struct { - size_t frame_count; - uintptr_t frame_addresses[BUGSNAG_FRAMES_MAX]; -} bsg_libunwind_state; - -bsg_libunwind_state *bsg_global_libunwind_state; -bool bsg_libunwind_global_is32bit = false; - -bool bsg_configure_libunwind(bool is32bit) { - bsg_global_libunwind_state = calloc(1, sizeof(bsg_libunwind_state)); - bsg_libunwind_global_is32bit = is32bit; - return true; -} - -static _Unwind_Reason_Code -bsg_libunwind_callback(struct _Unwind_Context *context, void *arg) __asyncsafe { - bsg_libunwind_state *state = (bsg_libunwind_state *)arg; - - uintptr_t ip = _Unwind_GetIP(context); - - if (state->frame_count >= BUGSNAG_FRAMES_MAX) { - return _URC_END_OF_STACK; - } else if (state->frame_count > 0 && (void *)ip == NULL) { // nobody's home - return _URC_NO_REASON; - } - state->frame_addresses[state->frame_count] = ip; - state->frame_count++; - - return _URC_NO_REASON; -} - -#if defined(__arm__) -ssize_t bsg_unwind_stack_libunwind_arm32( - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], siginfo_t *info, - void *user_context) __asyncsafe { - unw_cursor_t cursor; - unw_context_t uc; - int index = 0; - - unw_getcontext(&uc); - unw_init_local(&cursor, &uc); - // Initialize cursor state with register data, if any - if (user_context != NULL) { - /** - * Set the registers and initial frame to the values from the signal - * handler user context. - * - * When a signal is raised on 32-bit ARM, the current context is the signal - * stack rather than the crash stack. To work around this, set the register - * state before unwinding the first frame (using the program counter as the - * first frame). Then the stack can be unwound normally. - */ - const ucontext_t *signal_ucontext = (const ucontext_t *)user_context; - const struct sigcontext *signal_mcontext = &(signal_ucontext->uc_mcontext); - unw_set_reg(&cursor, UNW_ARM_R0, signal_mcontext->arm_r0); - unw_set_reg(&cursor, UNW_ARM_R1, signal_mcontext->arm_r1); - unw_set_reg(&cursor, UNW_ARM_R2, signal_mcontext->arm_r2); - unw_set_reg(&cursor, UNW_ARM_R3, signal_mcontext->arm_r3); - unw_set_reg(&cursor, UNW_ARM_R4, signal_mcontext->arm_r4); - unw_set_reg(&cursor, UNW_ARM_R5, signal_mcontext->arm_r5); - unw_set_reg(&cursor, UNW_ARM_R6, signal_mcontext->arm_r6); - unw_set_reg(&cursor, UNW_ARM_R7, signal_mcontext->arm_r7); - unw_set_reg(&cursor, UNW_ARM_R8, signal_mcontext->arm_r8); - unw_set_reg(&cursor, UNW_ARM_R9, signal_mcontext->arm_r9); - unw_set_reg(&cursor, UNW_ARM_R10, signal_mcontext->arm_r10); - unw_set_reg(&cursor, UNW_ARM_R11, signal_mcontext->arm_fp); - unw_set_reg(&cursor, UNW_ARM_R12, signal_mcontext->arm_ip); - unw_set_reg(&cursor, UNW_ARM_R13, signal_mcontext->arm_sp); - unw_set_reg(&cursor, UNW_ARM_R14, signal_mcontext->arm_lr); - unw_set_reg(&cursor, UNW_ARM_R15, signal_mcontext->arm_pc); - unw_set_reg(&cursor, UNW_REG_IP, signal_mcontext->arm_pc); - unw_set_reg(&cursor, UNW_REG_SP, signal_mcontext->arm_sp); - // Manually insert first frame to avoid being skipped in step() - stacktrace[index++].frame_address = signal_mcontext->arm_pc; - } - - while (unw_step(&cursor) > 0 && index < BUGSNAG_FRAMES_MAX) { - unw_word_t ip = 0; - unw_get_reg(&cursor, UNW_REG_IP, &ip); - stacktrace[index++].frame_address = ip; - } - - return index; -} -#endif -ssize_t -bsg_unwind_stack_libunwind(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { -#if defined(__arm__) - if (bsg_libunwind_global_is32bit) { // avoid this code path if a 64-bit device - // is running 32-bit - return bsg_unwind_stack_libunwind_arm32(stacktrace, info, user_context); - } -#endif - if (bsg_global_libunwind_state == NULL) { - return 0; - } - bsg_global_libunwind_state->frame_count = 0; - // The return value of _Unwind_Backtrace sits on a throne of lies - _Unwind_Backtrace(bsg_libunwind_callback, bsg_global_libunwind_state); - for (int i = 0; i < bsg_global_libunwind_state->frame_count; ++i) { - stacktrace[i].frame_address = - bsg_global_libunwind_state->frame_addresses[i]; - } - return bsg_global_libunwind_state->frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h deleted file mode 100644 index 961d363671..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwind.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWIND_H -#define BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWIND_H - -#include "../event.h" -#include - -bool bsg_configure_libunwind(bool is32bit); - -ssize_t -bsg_unwind_stack_libunwind(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); - -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp deleted file mode 100644 index 30b5180230..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "stack_unwinder_libunwindstack.h" -#include "string.h" -#include -#include -#include -#include -#include -#include -#include - -ssize_t bsg_unwind_stack_libunwindstack( - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], siginfo_t *info, - void *user_context) { - if (user_context == NULL) { - return 0; // only handle unwinding from signals - } - - // Fetch register values from signal context. To get registers without - // a context, use unwindstack::Regs::CreateFromLocal() - const std::unique_ptr regs( - unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), - user_context)); - - std::string unw_function_name; - unwindstack::LocalMaps maps; - - if (!maps.Parse()) { - stacktrace[0].frame_address = regs->pc(); // only known frame - return 1; - } - - const std::shared_ptr memory( - new unwindstack::MemoryLocal); - - int frame_count = 0; - for (int i = 0; i < BUGSNAG_FRAMES_MAX; i++) { - stacktrace[frame_count++].frame_address = regs->pc(); - unwindstack::MapInfo *const map_info = maps.Find(regs->pc()); - if (!map_info) { - break; - } - unwindstack::Elf *const elf = map_info->GetElf(memory, false); - if (!elf) { - break; - } - - // Getting value of program counter relative module where a function is - // located. - const uint64_t rel_pc = elf->GetRelPc(regs->pc(), map_info); - uint64_t adjusted_rel_pc = rel_pc; - if (frame_count != 0) { - // If it's not a first frame we need to rewind program counter value to - // previous instruction. For the first frame pc from ucontext points - // exactly to a failed instruction, for other frames rel_pc will contain - // return address after function call instruction. - adjusted_rel_pc -= regs->GetPcAdjustment(rel_pc, elf); - } - bool finished = false; - if (!elf->Step(rel_pc, adjusted_rel_pc, map_info->elf_offset, regs.get(), - memory.get(), &finished)) { - break; - } - } - return frame_count; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h deleted file mode 100644 index 6f6aa884a2..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_libunwindstack.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWINDSTACK_H -#define BUGSNAG_UTILS_STACK_UNWINDER_LIBUNWINDSTACK_H - -#include "../event.h" -#include - -#ifdef __cplusplus -extern "C" -#endif - ssize_t - bsg_unwind_stack_libunwindstack( - bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], siginfo_t *info, - void *user_context); -#endif diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c deleted file mode 100644 index 99c566719c..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "stack_unwinder_simple.h" -#include "string.h" -#include -#include - -ssize_t -bsg_unwind_stack_simple(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context) { - if (user_context != NULL) { - // program counter / instruction pointer - uintptr_t ip = 0; - ucontext_t *ctx = (ucontext_t *)user_context; -#if defined(__i386__) - ip = (uintptr_t)ctx->uc_mcontext.gregs[REG_EIP]; -#elif defined(__x86_64__) - ip = (uintptr_t)ctx->uc_mcontext.gregs[REG_RIP]; -#elif defined(__arm__) - ip = (uintptr_t)ctx->uc_mcontext.arm_ip; -#elif defined(__aarch64__) - ip = (uintptr_t)ctx->uc_mcontext.regs[15]; -#else - ip = (uintptr_t)info->si_addr; -#endif - if (ip != 0) { - stacktrace[0].frame_address = ip; - return 1; - } - } - - return 0; -} diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h deleted file mode 100644 index 528e786bf7..0000000000 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder_simple.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef BUGSNAG_UTILS_STACK_UNWINDER_SIMPLE_H -#define BUGSNAG_UTILS_STACK_UNWINDER_SIMPLE_H - -#include "../event.h" -#include - -ssize_t -bsg_unwind_stack_simple(bugsnag_stackframe stacktrace[BUGSNAG_FRAMES_MAX], - siginfo_t *info, void *user_context); -#endif diff --git a/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt index fb883f598b..b8688cfb1a 100644 --- a/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt +++ b/bugsnag-plugin-android-ndk/src/test/CMakeLists.txt @@ -17,5 +17,6 @@ add_library(bugsnag-ndk-test SHARED cpp/migrations/EventMigrationV6Tests.cpp cpp/migrations/EventMigrationV7Tests.cpp cpp/migrations/EventMigrationV8Tests.cpp + cpp/UnwindTest.cpp ) target_link_libraries(bugsnag-ndk-test bugsnag-ndk) diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp b/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp new file mode 100644 index 0000000000..f07d11bae9 --- /dev/null +++ b/bugsnag-plugin-android-ndk/src/test/cpp/UnwindTest.cpp @@ -0,0 +1,134 @@ +#include +#include +#include + +#include + +extern "C" { + +typedef ssize_t (*unwinder)(bugsnag_stackframe *, siginfo *, void *); + +ssize_t bsg_unwind_crash_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); + +ssize_t +bsg_unwind_concurrent_stack(bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX], + siginfo_t *info, void *user_context); + +// the following functions are marked as optnone to make stack contents +// assertions uniform across architectures, etc +// https://clang.llvm.org/docs/AttributeReference.html#optnone + +jobject __attribute__((optnone)) unwind_func_four(JNIEnv *env, unwinder func) { + bugsnag_stackframe stack[BUGSNAG_FRAMES_MAX]; + ssize_t count = func(stack, nullptr, nullptr); + + // find JNI references + auto ArrayList = (*env).FindClass("java/util/ArrayList"); + auto HashMap = (*env).FindClass("java/util/HashMap"); + auto Long = (*env).FindClass("java/lang/Long"); + auto listInit = (*env).GetMethodID(ArrayList, "", "()V"); + auto listAdd = (*env).GetMethodID(ArrayList, "add", "(Ljava/lang/Object;)Z"); + auto longInit = (*env).GetMethodID(Long, "", "(J)V"); + auto mapInit = (*env).GetMethodID(HashMap, "", "()V"); + auto mapPut = (*env).GetMethodID( + HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + // make list instance + auto frames = (*env).NewObject(ArrayList, listInit); + + for (int index = 0; index < count; index++) { + auto frame = (*env).NewObject(HashMap, mapInit); + + // using empty string as a sentinel if method or file is null + auto file = stack[index].filename[0] ? stack[index].filename : ""; + auto method = stack[index].method[0] ? stack[index].method : ""; + jlong lineNumber = stack[index].line_number; + jlong symbolAddress = stack[index].symbol_address; + jlong frameAddress = stack[index].frame_address; + jlong loadAddress = stack[index].load_address; + + (*env).CallObjectMethod(frame, mapPut, (*env).NewStringUTF("file"), + (*env).NewStringUTF(file)); + (*env).CallObjectMethod(frame, mapPut, (*env).NewStringUTF("method"), + (*env).NewStringUTF(method)); + + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("lineNumber"), + (*env).NewObject(Long, longInit, lineNumber)); + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("symbolAddress"), + (*env).NewObject(Long, longInit, symbolAddress)); + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("frameAddress"), + (*env).NewObject(Long, longInit, frameAddress)); + (*env).CallObjectMethod( + frame, mapPut, (*env).NewStringUTF("loadAddress"), + (*env).NewObject(Long, longInit, loadAddress)); + + (*env).CallBooleanMethod(frames, listAdd, frame); + } + + return frames; +} + +jobject __attribute__((optnone)) unwind_func_three(JNIEnv *env, unwinder func) { + return unwind_func_four(env, func); +} + +jobject __attribute__((optnone)) unwind_func_two(JNIEnv *env, unwinder func) { + return unwind_func_three(env, func); +} + +jobject __attribute__((optnone)) unwind_func_one(JNIEnv *env, unwinder func) { + return unwind_func_two(env, func); +} + +JNIEXPORT jobject JNICALL +Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify(JNIEnv *env, + jobject _this) { + bsg_unwinder_init(); + return unwind_func_one(env, bsg_unwind_concurrent_stack); +} + +JNIEXPORT jobject JNICALL +Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash(JNIEnv *env, + jobject _this) { + bsg_unwinder_init(); + return unwind_func_one(env, bsg_unwind_crash_stack); +} + +JNIEXPORT jobject JNICALL +Java_com_bugsnag_android_ndk_UnwindTest_00024BuildInfo_getNativeFunctionInfo( + JNIEnv *env, jobject _this) { + // find JNI references + auto HashMap = (*env).FindClass("java/util/HashMap"); + auto Long = (*env).FindClass("java/lang/Long"); + auto longInit = (*env).GetMethodID(Long, "", "(J)V"); + auto mapInit = (*env).GetMethodID(HashMap, "", "()V"); + auto put = (*env).GetMethodID( + HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + // insert each function as a mapping between function name and address +#define add_function_info(map, func) \ + (*env).CallObjectMethod(map, put, (*env).NewStringUTF(#func), \ + (*env).NewObject(Long, longInit, (jlong)&func)) + + // make list of addresses + auto items = (*env).NewObject(HashMap, mapInit); + add_function_info(items, unwind_func_four); + add_function_info(items, unwind_func_three); + add_function_info(items, unwind_func_two); + add_function_info(items, unwind_func_one); + add_function_info(items, + Java_com_bugsnag_android_ndk_UnwindTest_unwindForCrash); + add_function_info(items, + Java_com_bugsnag_android_ndk_UnwindTest_unwindForNotify); + +#undef add_function_info + + return items; +} +} diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt index 194ee5237a..5a5ac1c4a4 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt @@ -9,7 +9,7 @@ object Versions { // Note minSdkVersion must be >=21 for 64 bit architectures val minSdkVersion = 14 val compileSdkVersion = 31 - val ndk = "17.2.4988734" + val ndk = "23.1.7779620" val java = JavaVersion.VERSION_1_7 val kotlin = "1.4.32" diff --git a/docker-compose.yml b/docker-compose.yml index 7534f7f0f8..4dc34d47c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: MINIMAL_FIXTURE: TEST_FIXTURE_NDK_VERSION: TEST_FIXTURE_NAME: + TEST_FIXTURE_CONFIGURATION: USE_LEGACY_OKHTTP: BUILD_TASK: volumes: @@ -78,6 +79,7 @@ services: BROWSER_STACK_ACCESS_KEY: SAUCE_LABS_USERNAME: SAUCE_LABS_ACCESS_KEY: + TEST_FIXTURE_SYMBOL_DIR: volumes: - ./build:/app/build - ./features/:/app/features/ diff --git a/dockerfiles/Dockerfile.android-builder b/dockerfiles/Dockerfile.android-builder index d61c23e89e..72cd0dbb80 100644 --- a/dockerfiles/Dockerfile.android-builder +++ b/dockerfiles/Dockerfile.android-builder @@ -10,5 +10,12 @@ CMD ./gradlew -p=${FIXTURE_PROJECT} ${BUILD_TASK} \ -PUSE_LEGACY_OKHTTP=${USE_LEGACY_OKHTTP} \ -PMINIMAL_FIXTURE=${MINIMAL_FIXTURE} \ -PTEST_FIXTURE_NDK_VERSION=${TEST_FIXTURE_NDK_VERSION} \ - -PTEST_FIXTURE_NAME=${TEST_FIXTURE_NAME} \ - && cp -R ${FIXTURE_PROJECT}/app/build/outputs/apk/* /app/build + -PTEST_FIXTURE_NAME=${TEST_FIXTURE_NAME}.apk \ + && FIXTURE_SYMBOL_DIR=/app/build/${TEST_FIXTURE_CONFIGURATION}/${TEST_FIXTURE_NAME} \ + && mkdir -p $FIXTURE_SYMBOL_DIR \ + && cp -R ${FIXTURE_PROJECT}/app/build/outputs/apk/* /app/build \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios-bugsnag/build/intermediates/cxx/*/*/obj/arm64-v8a/libcxx-scenarios-bugsnag.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-bugsnag-arm64.so \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios-bugsnag/build/intermediates/cxx/*/*/obj/armeabi-v7a/libcxx-scenarios-bugsnag.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-bugsnag-arm32.so \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios/build/intermediates/cxx/*/*/obj/arm64-v8a/libcxx-scenarios.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-arm64.so \ + && cp -R ${FIXTURE_PROJECT}/cxx-scenarios/build/intermediates/cxx/*/*/obj/armeabi-v7a/libcxx-scenarios.so ${FIXTURE_SYMBOL_DIR}/libcxx-scenarios-arm32.so + diff --git a/docs/TESTING.md b/docs/TESTING.md index 6e269890d3..ee7aebc2f4 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -20,7 +20,7 @@ Instrumentation tests require an Android emulator or device to run, and they can ## Static analysis -Several static analysis checks are run against bugsnag-android to maintain the quality of the codebase. `./gradlew check` runs them all at once. +Several static analysis and code style checks are run against bugsnag-android to maintain the quality of the codebase. `make check` runs them all at once. ### Android Lint diff --git a/examples/sdk-app-example/app/build.gradle b/examples/sdk-app-example/app/build.gradle index 8c09f20689..7ee14b7135 100644 --- a/examples/sdk-app-example/app/build.gradle +++ b/examples/sdk-app-example/app/build.gradle @@ -38,7 +38,7 @@ android { } dependencies { - implementation "com.bugsnag:bugsnag-android:5.17.0" + implementation "com.bugsnag:bugsnag-android:5.20.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.appcompat:appcompat:1.4.0" implementation "com.google.android.material:material:1.4.0" diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt index 8389ceb556..577809f3fd 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required(VERSION 3.4.1) add_library(cxx-scenarios-bugsnag SHARED - src/main/cpp/cxx-scenarios-bugsnag.cpp) + src/main/cpp/cxx-scenarios-bugsnag.cpp + src/main/cpp/CXXExceptionSmokeScenario.cpp) add_library(lib_bugsnag SHARED IMPORTED) set(BUGSNAG_LIB_DIR diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp new file mode 100644 index 0000000000..977ccc90d7 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/CXXExceptionSmokeScenario.cpp @@ -0,0 +1,29 @@ +#include +#include + +namespace magicstacks { + +class FatalProblem : std::runtime_error { + using std::runtime_error::runtime_error; + + virtual ~FatalProblem() {} +}; + +void __attribute__((optnone)) top() { + throw new FatalProblem("well there it is!"); +} + +void __attribute__((optnone)) middle() { top(); } + +void __attribute__((optnone)) start() { middle(); } +} // namespace magicstacks + +extern "C" { +JNIEXPORT int JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash( + JNIEnv *env, jobject instance) { + magicstacks::start(); + + return 90; +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp index 6c1ad586a4..72fc6b6581 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp @@ -153,16 +153,6 @@ Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionOnErrorFalseScenario_c return 22; } -JNIEXPORT int JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash( - JNIEnv *env, - jobject instance) { - int x = 61; - printf("This one here: %ld\n", (long) f_trigger_an_exception(x > 0)); - printf("This one here: %ld\n", (long) f_throw_an_object(x > 0, x)); - return 55; -} - JNIEXPORT void JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXStartScenario_activate( JNIEnv *env, diff --git a/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt b/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt index 295556880b..38eb9315fa 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt +++ b/features/fixtures/mazerunner/cxx-scenarios/CMakeLists.txt @@ -2,6 +2,16 @@ cmake_minimum_required(VERSION 3.4.1) add_library(cxx-scenarios SHARED src/main/cpp/cxx-scenarios.cpp + src/main/cpp/CXXAbortScenario.cpp + src/main/cpp/CXXCallNullFunctionPointerScenario.cpp + src/main/cpp/CXXDereferenceNullScenario.cpp + src/main/cpp/CXXExternalStackElementScenario.cpp + src/main/cpp/CXXImproperTypecastScenario.cpp + src/main/cpp/CXXInvalidRethrow.cpp + src/main/cpp/CXXStackoverflowScenario.cpp + src/main/cpp/CXXThrowFromNoexcept.cpp + src/main/cpp/CXXTrapScenario.cpp + src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp src/main/cpp/bugsnag-java-scenarios.cpp) add_library(lib_monochrome SHARED IMPORTED) diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp new file mode 100644 index 0000000000..68313e00e7 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXAbortScenario.cpp @@ -0,0 +1,15 @@ +#include +#include + +namespace evictor { +void __attribute__((optnone)) exit_with_style() { abort(); } +} // namespace evictor + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXAbortScenario_crash( + JNIEnv *env, jobject instance) { + // added namespaces for variety between tests + evictor::exit_with_style(); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp new file mode 100644 index 0000000000..848916240a --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXCallNullFunctionPointerScenario.cpp @@ -0,0 +1,20 @@ +#include + +void (*definitely_valid_func)(jobject) = 0; + +namespace dispatch { +class Handler { +public: + static void __attribute__((optnone)) handle(jobject obj) { + definitely_valid_func(obj); + } +}; +} // namespace dispatch + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXCallNullFunctionPointerScenario_crash( + JNIEnv *env, jobject instance) { + dispatch::Handler::handle(instance); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp new file mode 100644 index 0000000000..637fabfd45 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXDereferenceNullScenario.cpp @@ -0,0 +1,16 @@ +#include + +static volatile int *the_value; + +int __attribute__((optnone)) get_the_null_value() { + // assume this function is very interesting + return *the_value; +} + +extern "C" { +JNIEXPORT jint JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXDereferenceNullScenario_crash( + JNIEnv *env, jobject instance) { + return get_the_null_value(); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp new file mode 100644 index 0000000000..d1d18ed26e --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXExternalStackElementScenario.cpp @@ -0,0 +1,19 @@ +#include +#include + +extern "C" { +// defined in libs/[ABI]/libmonochrome.so +int something_innocuous(int input); + +JNIEXPORT int JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash( + JNIEnv *env, jobject instance, jint counter) { + printf("Captain, why are we out here chasing comets?\n%d\n", counter); + int value = counter * 4; + if (counter > 0) { + value = something_innocuous(counter); + } + printf("Something innocuous this way comes: %d\n", value); + return value; +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp new file mode 100644 index 0000000000..b7c06f4d70 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXImproperTypecastScenario.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +typedef struct { + char *text; +} weird_obj_t; + +char *__attribute__((optnone)) crash_improper_cast(void *counter) { + weird_obj_t *obj = (weird_obj_t *)counter; + + return obj->text; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash( + JNIEnv *env, jobject instance) { + printf("This one here: %s\n", crash_improper_cast((void *)39)); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp new file mode 100644 index 0000000000..d35244f242 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXInvalidRethrow.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +void __attribute__((optnone)) print_last_exception() { + try { + throw; + } catch (std::exception *ex) { + // should never get here, since there is no exception. + printf("ex: %p", ex); + } +} + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXInvalidRethrow_crash( + JNIEnv *env, jobject instance) { + print_last_exception(); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp new file mode 100644 index 0000000000..fbe5fce359 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXStackoverflowScenario.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +extern "C" { +int __attribute__((optnone)) __attribute__((noinline)) crash_stack_overflow(int counter, char *input) { + char stack[7]; + + strcpy(stack, input); + + return 4 / counter; +} + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXStackoverflowScenario_crash( + JNIEnv *env, jobject instance, jint counter, jstring text_) { + char *text = (char *)(*env).GetStringUTFChars(text_, 0); + printf("This one here: %ld\n", + (long)crash_stack_overflow((int)counter, text)); + (*env).ReleaseStringUTFChars(text_, text); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp new file mode 100644 index 0000000000..f0dfa28a46 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXThrowFromNoexcept.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +class FunkError : public std::runtime_error { + using std::runtime_error::runtime_error; + + const char *toss_an_exception() const { + throw new FunkError("you done it now!"); + } + +public: + virtual const char *what() const noexcept { return toss_an_exception(); } +}; + +extern "C" { +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXThrowFromNoexcept_crash( + JNIEnv *env, jobject instance) { + auto err = new FunkError("mistakes were made"); + printf("what? %s", err->what()); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp new file mode 100644 index 0000000000..439b3e9466 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXTrapScenario.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +int trap_it() { + time_t now; + now = time(&now); + if (now > 0) + __builtin_trap(); + + return 0; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapScenario_crash( + JNIEnv *env, jobject instance) { + printf("This one here: %ld\n", (long)trap_it()); +} +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp new file mode 100644 index 0000000000..618ec74761 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/CXXWriteReadOnlyMemoryScenario.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +static char * __attribute__((used)) somefakefunc(void) { + return NULL; +}; + +int crash_write_read_only_mem(int counter) { + if (counter > 2) { + int *pointer = (int *)&somefakefunc; + *pointer = counter; + } + return counter / 14; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_crash(JNIEnv *env, + jobject instance) { + printf("This one here: %d\n", crash_write_read_only_mem(42)); +} + +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp index 020393b2ea..ec1bafa36e 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp @@ -23,15 +23,6 @@ bool __attribute__((noinline)) run_back(int value, int boundary) { extern "C" { -static char * __attribute__((used)) somefakefunc(void) { - return NULL; -}; - -typedef struct { - int field1; - char *field2; -} reporter_t; - int crash_abort(bool route) { if (route) abort(); @@ -73,14 +64,6 @@ int crash_anr(bool route) { return 7; } -int crash_write_read_only_mem(int counter) { - if (counter > 2) { - int *pointer = (int *)&somefakefunc; - *pointer = counter; - } - return counter / 14; -} - int __attribute__((noinline)) throw_an_object(bool value, int boundary) { if (value) { printf("Now we know what they mean by 'advanced' tactical training: %d", boundary); @@ -97,27 +80,6 @@ int __attribute__((noinline)) trigger_an_exception(bool value) { return 405; } -char *crash_improper_cast(intptr_t counter) { - reporter_t *report = (reporter_t *)counter; - - return report->field2; -} - -int crash_double_free(int counter) { - for (int i = 0; i < 30; i++) { - reporter_t *reporter = (reporter_t *)malloc(sizeof(reporter_t)); - reporter->field1 = 22 + counter; - char *field2 = reporter->field2; - strcpy(field2, "Indeed"); - printf("%d field1 is: %d", i, reporter->field1); - printf("%d field2 is: %s", i, field2); - free(field2); - free(reporter); - } - - return counter / -8; -} - int crash_trap() { time_t now; now = time(&now); @@ -127,14 +89,6 @@ int crash_trap() { return 0; } -int crash_stack_overflow(int counter, char *input) { - char stack[7]; - - strcpy(stack, input); - - return 4 / counter; -} - JNIEXPORT int JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXSigtrapScenario_crash(JNIEnv *env, jobject instance, @@ -275,68 +229,12 @@ Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapOutsideReleaseStagesScenari printf("This one here: %ld\n", (long) crash_trap()); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXStackoverflowScenario_crash(JNIEnv *env, - jobject instance, - jint counter, - jstring text_) { - char *text = (char *)(*env).GetStringUTFChars(text_, 0); - printf("This one here: %ld\n", (long) crash_stack_overflow((int)counter, text)); - (*env).ReleaseStringUTFChars(text_, text); -} - JNIEXPORT void JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapLaterDisabledScenario_crash(JNIEnv *env, jobject instance) { printf("This one here: %ld\n", (long) crash_trap()); } -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXTrapScenario_crash(JNIEnv *env, jobject instance) { - printf("This one here: %ld\n", (long) crash_trap()); -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXDoubleFreeScenario_crash(JNIEnv *env, - jobject instance) { - printf("This one here: %d\n", crash_double_free(42)); -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_crash(JNIEnv *env, - jobject instance) { - printf("This one here: %d\n", crash_write_read_only_mem(42)); -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash(JNIEnv *env, - jobject instance) { - printf("This one here: %s\n", crash_improper_cast(39)); -} - -// defined in libs/[ABI]/libmonochrome.so -int something_innocuous(int input); - -JNIEXPORT int JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash(JNIEnv *env, - jobject instance, - jint counter) { - printf("Captain, why are we out here chasing comets?\n%d\n", counter); - int value = counter * 4; - if (counter > 0) { - value = something_innocuous(counter); - } - printf("Something innocuous this way comes: %d\n", value); - return value; -} - -JNIEXPORT void JNICALL -Java_com_bugsnag_android_mazerunner_scenarios_CXXAbortScenario_crash(JNIEnv *env, - jobject instance) { - int x = 47; - printf("This one here: %ld\n", (long) crash_abort(x > 0)); -} - JNIEXPORT void JNICALL Java_com_bugsnag_android_mazerunner_scenarios_CXXNaughtyStringsScenario_crash(JNIEnv *env, jobject instance) { abort(); diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt new file mode 100644 index 0000000000..8e778bce64 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCallNullFunctionPointerScenario.kt @@ -0,0 +1,24 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration + +class CXXCallNullFunctionPointerScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + companion object { + init { + System.loadLibrary("cxx-scenarios") + } + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + crash() + } +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDoubleFreeScenario.java b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDereferenceNullScenario.java similarity index 59% rename from features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDoubleFreeScenario.java rename to features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDereferenceNullScenario.java index b6c4cb003f..21f22838b2 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDoubleFreeScenario.java +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXDereferenceNullScenario.java @@ -7,17 +7,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -public class CXXDoubleFreeScenario extends Scenario { +public class CXXDereferenceNullScenario extends Scenario { static { System.loadLibrary("cxx-scenarios"); } - public native void crash(); + public native int crash(); - public CXXDoubleFreeScenario(@NonNull Configuration config, - @NonNull Context context, - @Nullable String eventMetadata) { + public CXXDereferenceNullScenario(@NonNull Configuration config, + @NonNull Context context, + @Nullable String eventMetadata) { super(config, context, eventMetadata); } diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt new file mode 100644 index 0000000000..329325f555 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXInvalidRethrow.kt @@ -0,0 +1,24 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration + +class CXXInvalidRethrow( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + companion object { + init { + System.loadLibrary("cxx-scenarios") + } + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + crash() + } +} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java deleted file mode 100644 index 1208fe46c5..0000000000 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXNullPointerScenario.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.bugsnag.android.mazerunner.scenarios; - -import com.bugsnag.android.Configuration; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class CXXNullPointerScenario extends Scenario { - - static { - System.loadLibrary("cxx-scenarios"); - } - - public native void crash(); - - public CXXNullPointerScenario(@NonNull Configuration config, - @NonNull Context context, - @Nullable String eventMetadata) { - super(config, context, eventMetadata); - } - - @Override - public void startScenario() { - super.startScenario(); - crash(); - } -} diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt new file mode 100644 index 0000000000..7c6001a12d --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXThrowFromNoexcept.kt @@ -0,0 +1,24 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration + +class CXXThrowFromNoexcept( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + companion object { + init { + System.loadLibrary("cxx-scenarios") + } + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + crash() + } +} diff --git a/features/fixtures/minimalapp/Dangerfile b/features/fixtures/minimalapp/Dangerfile index d941401f86..6661078b0f 100644 --- a/features/fixtures/minimalapp/Dangerfile +++ b/features/fixtures/minimalapp/Dangerfile @@ -9,7 +9,6 @@ UNBUNDLED_RELEASE = "app-release.apks" STANDALONE_DIR = BUNDLE_DIR + "standalones/" STANDALONE_HDPI_BASE = "standalone-hdpi.apk" STANDALONE_ARM64_V8A = "standalone-arm64_v8a_hdpi.apk" -STANDALONE_ARMEABI = "standalone-armeabi_hdpi.apk" STANDALONE_ARMEABI_V7A = "standalone-armeabi_v7a_hdpi.apk" STANDALONE_X86_64 = "standalone-x86_64_hdpi.apk" STANDALONE_X86 = "standalone-x86_hdpi.apk" @@ -48,7 +47,6 @@ buildOutputs(bugsnag: true, minified: false) apk_bugsnag_size = `stat --printf="%s" #{RELEASE_APK}`.to_i arm64_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARM64_V8A}`.to_i -armeabi_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI}`.to_i armeabi_v7a_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI_V7A}`.to_i x86_64_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86_64}`.to_i x86_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86}`.to_i @@ -57,20 +55,17 @@ buildOutputs(bugsnag: true, minified: true) min_apk_bugsnag_size = `stat --printf="%s" #{RELEASE_APK}`.to_i min_arm64_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARM64_V8A}`.to_i -min_armeabi_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI}`.to_i min_armeabi_v7a_bugsnag_size = `stat --printf "%s" #{STANDALONE_DIR + STANDALONE_ARMEABI_V7A}`.to_i min_x86_64_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86_64}`.to_i min_x86_bugsnag_size = `stat --print "%s" #{STANDALONE_DIR + STANDALONE_X86}`.to_i calculated_sizes = { :arm64 => arm64_bugsnag_size - aab_size, - :armeabi => armeabi_bugsnag_size - aab_size, :armeabi_v7a => armeabi_v7a_bugsnag_size - aab_size, :x86_64 => x86_64_bugsnag_size - aab_size, :x86 => x86_bugsnag_size - aab_size, :apk => apk_bugsnag_size - apk_size, :min_arm64 => min_arm64_bugsnag_size - min_aab_size, - :min_armeabi => min_armeabi_bugsnag_size - min_aab_size, :min_armeabi_v7a => min_armeabi_v7a_bugsnag_size - min_aab_size, :min_x86_64 => min_x86_64_bugsnag_size - min_aab_size, :min_x86 => min_x86_bugsnag_size - min_aab_size, @@ -88,7 +83,6 @@ markdown(%Q{ |-------------|-----------------------------------------------|---------------------------------------------------| | APK | #{format_kbs(calculated_sizes[:apk])} | #{format_kbs(calculated_sizes[:min_apk])} | | arm64_v8a | #{format_kbs(calculated_sizes[:arm64])} | #{format_kbs(calculated_sizes[:min_arm64])} | - | armeabi | #{format_kbs(calculated_sizes[:armeabi])} | #{format_kbs(calculated_sizes[:min_armeabi])} | | armeabi_v7a | #{format_kbs(calculated_sizes[:armeabi_v7a])} | #{format_kbs(calculated_sizes[:min_armeabi_v7a])} | | x86 | #{format_kbs(calculated_sizes[:x86])} | #{format_kbs(calculated_sizes[:min_x86])} | | x86_64 | #{format_kbs(calculated_sizes[:x86_64])} | #{format_kbs(calculated_sizes[:min_x86_64])} | diff --git a/features/full_tests/native_crash_handling.feature b/features/full_tests/native_crash_handling.feature index 2cb254d8da..a591be2f3c 100644 --- a/features/full_tests/native_crash_handling.feature +++ b/features/full_tests/native_crash_handling.feature @@ -1,124 +1,146 @@ Feature: Native crash reporting -Scenario: Dereference a null pointer - When I run "CXXNullPointerScenario" and relaunch the app - And I configure Bugsnag for "CXXNullPointerScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception "errorClass" equals "SIGSEGV" - And the exception "message" equals "Segmentation violation (invalid memory reference)" - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true - And the event "app.binaryArch" is not null - And the event "device.totalMemory" is not null - And the error payload field "events.0.device.cpuAbi" is a non-empty array - And the error payload field "events.0.metaData.app.memoryLimit" is greater than 0 + Scenario: Dereference a null pointer + When I run "CXXDereferenceNullScenario" and relaunch the app + And I configure Bugsnag for "CXXDereferenceNullScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the event "app.binaryArch" is not null + And the event "device.totalMemory" is not null + And the error payload field "events.0.device.cpuAbi" is a non-empty array + And the error payload field "events.0.metaData.app.memoryLimit" is greater than 0 + And the first significant stack frames match: + | get_the_null_value() | CXXDereferenceNullScenario.cpp | 7 | - # This scenario will not pass on API levels < 18, as stack corruption - # is handled without calling atexit handlers, etc. - # In the device logs you will see: - # system/bin/app_process([proc id]): stack corruption detected: aborted - # Original code here: - # https://android.googlesource.com/platform/bionic/+/d0f2b7e7e65f19f978c59abcbb522c08e76b1508/libc/bionic/ssp.c - # Refactored here to use abort() on newer versions: - # https://android.googlesource.com/platform/bionic/+/fb7eb5e07f43587c2bedf2aaa53b21fa002417bb - @skip_below_api18 - Scenario: Stack buffer overflow - When I run "CXXStackoverflowScenario" and relaunch the app - And I configure Bugsnag for "CXXStackoverflowScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception reflects a signal was raised - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + # This scenario will not pass on API levels < 18, as stack corruption + # is handled without calling atexit handlers, etc. + # In the device logs you will see: + # system/bin/app_process([proc id]): stack corruption detected: aborted + # Original code here: + # https://android.googlesource.com/platform/bionic/+/d0f2b7e7e65f19f978c59abcbb522c08e76b1508/libc/bionic/ssp.c + # Refactored here to use abort() on newer versions: + # https://android.googlesource.com/platform/bionic/+/fb7eb5e07f43587c2bedf2aaa53b21fa002417bb + @skip_below_api18 + Scenario: Stack buffer overflow + When I run "CXXStackoverflowScenario" and relaunch the app + And I configure Bugsnag for "CXXStackoverflowScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception reflects a signal was raised + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | crash_stack_overflow | CXXStackoverflowScenario.cpp | - Scenario: Program trap() - When I run "CXXTrapScenario" and relaunch the app - And I configure Bugsnag for "CXXTrapScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception "errorClass" equals one of: - | SIGILL | - | SIGTRAP | - And the exception "message" equals one of: - | Illegal instruction | - | Trace/breakpoint trap | - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Program trap() + When I run "CXXTrapScenario" and relaunch the app + And I configure Bugsnag for "CXXTrapScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals one of: + | SIGILL | + | SIGTRAP | + And the exception "message" equals one of: + | Illegal instruction | + | Trace/breakpoint trap | + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | trap_it() | CXXTrapScenario.cpp | 10 | - Scenario: Write to read-only memory - When I run "CXXWriteReadOnlyMemoryScenario" and relaunch the app - And I configure Bugsnag for "CXXWriteReadOnlyMemoryScenario" - And I wait to receive an error - And the exception "errorClass" equals "SIGSEGV" - And the exception "message" equals "Segmentation violation (invalid memory reference)" - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Write to read-only memory + When I run "CXXWriteReadOnlyMemoryScenario" and relaunch the app + And I configure Bugsnag for "CXXWriteReadOnlyMemoryScenario" + And I wait to receive an error + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | crash_write_read_only_mem(int) | CXXWriteReadOnlyMemoryScenario.cpp | 12 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXWriteReadOnlyMemoryScenario_crash | CXXWriteReadOnlyMemoryScenario.cpp | 22 | - Scenario: Double free() allocated memory - When I run "CXXDoubleFreeScenario" and relaunch the app - And I configure Bugsnag for "CXXDoubleFreeScenario" - And I wait to receive an error - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true - # Fix as part of PLAT-5643 - # And the exception "errorClass" equals "SIGSEGV" - # And the exception "message" equals "Segmentation violation (invalid memory reference)" + Scenario: Improper object type cast + When I run "CXXImproperTypecastScenario" and relaunch the app + And I configure Bugsnag for "CXXImproperTypecastScenario" + And I wait to receive an error + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | crash_improper_cast(void*) | CXXImproperTypecastScenario.cpp | 12 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXImproperTypecastScenario_crash | CXXImproperTypecastScenario.cpp | 20 | - Scenario: Improper object type cast - When I run "CXXImproperTypecastScenario" and relaunch the app - And I configure Bugsnag for "CXXImproperTypecastScenario" - And I wait to receive an error - And the exception "errorClass" equals "SIGSEGV" - And the exception "message" equals "Segmentation violation (invalid memory reference)" - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Program abort() + When I run "CXXAbortScenario" and relaunch the app + And I configure Bugsnag for "CXXAbortScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals one of: + | SIGABRT | + | SIGSEGV | + And the exception "message" equals one of: + | Abort program | + | Segmentation violation (invalid memory reference) | + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | evictor::exit_with_style() | CXXAbortScenario.cpp | 5 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXAbortScenario_crash | CXXAbortScenario.cpp | 13 | - Scenario: Program abort() - When I run "CXXAbortScenario" and relaunch the app - And I configure Bugsnag for "CXXAbortScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the exception "errorClass" equals one of: - | SIGABRT | - | SIGSEGV | - And the exception "message" equals one of: - | Abort program | - | Segmentation violation (invalid memory reference) | - And the exception "type" equals "c" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Undefined JNI method + When I run "UnsatisfiedLinkErrorScenario" and relaunch the app + And I configure Bugsnag for "UnsatisfiedLinkErrorScenario" + And I wait to receive an error + And the report contains the required fields + And the exception "errorClass" equals "java.lang.UnsatisfiedLinkError" + And the exception "type" equals "android" + And the event "severity" equals "error" + And the event "unhandled" is true - Scenario: Undefined JNI method - When I run "UnsatisfiedLinkErrorScenario" and relaunch the app - And I configure Bugsnag for "UnsatisfiedLinkErrorScenario" - And I wait to receive an error - And the report contains the required fields - And the exception "errorClass" equals "java.lang.UnsatisfiedLinkError" - And the exception "type" equals "android" - And the event "severity" equals "error" - And the event "unhandled" is true + Scenario: Causing a crash in a separate library + When I run "CXXExternalStackElementScenario" and relaunch the app + And I configure Bugsnag for "CXXExternalStackElementScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the event "severity" equals "error" + And the event "unhandled" is true + And the exception "errorClass" equals one of: + | SIGILL | + | SIGTRAP | + And the exception "message" equals one of: + | Illegal instruction | + | Trace/breakpoint trap | + And the exception "type" equals "c" + And the first significant stack frames match: + | something_innocuous | libmonochrome.so | (ignore) | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash | CXXExternalStackElementScenario.cpp | 14 | - Scenario: Causing a crash in a separate library - When I run "CXXExternalStackElementScenario" and relaunch the app - And I configure Bugsnag for "CXXExternalStackElementScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the event "severity" equals "error" - And the event "unhandled" is true - And the exception "errorClass" equals one of: - | SIGILL | - | SIGTRAP | - And the exception "message" equals one of: - | Illegal instruction | - | Trace/breakpoint trap | - And the exception "type" equals "c" - And the first significant stack frame methods and files should match: - | something_innocuous | | libmonochrome.so | - | Java_com_bugsnag_android_mazerunner_scenarios_CXXExternalStackElementScenario_crash | | libcxx-scenarios.so | + Scenario: Call null function pointer + A null pointer should be the first element of a stack trace, + followed by the calling function + + When I run "CXXCallNullFunctionPointerScenario" and relaunch the app + And I configure Bugsnag for "CXXCallNullFunctionPointerScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGSEGV" + And the exception "message" equals "Segmentation violation (invalid memory reference)" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the "method" of stack frame 0 equals "0x0" + And the "lineNumber" of stack frame 0 equals 0 + And the first significant stack frames match: + | dispatch::Handler::handle(_jobject*) | CXXCallNullFunctionPointerScenario.cpp | 9 | diff --git a/features/full_tests/native_throw_crash.feature b/features/full_tests/native_throw_crash.feature index 4c72ea1874..4c5ba720f6 100644 --- a/features/full_tests/native_throw_crash.feature +++ b/features/full_tests/native_throw_crash.feature @@ -1,21 +1,47 @@ Feature: Native crash reporting with thrown objects - Scenario: Throwing an exception in C++ - When I run "CXXExceptionScenario" and relaunch the app - And I configure Bugsnag for "CXXExceptionScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the event "severity" equals "error" - And the event "unhandled" is true - And the exception "errorClass" equals "SIGABRT" - And the exception "message" equals "Abort program" + Scenario: Throwing an exception in C++ + When I run "CXXExceptionScenario" and relaunch the app + And I configure Bugsnag for "CXXExceptionScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the event "severity" equals "error" + And the event "unhandled" is true + And the exception "errorClass" equals "SIGABRT" + And the exception "message" equals "Abort program" + + Scenario: Throwing an object in C++ + When I run "CXXThrowSomethingScenario" and relaunch the app + And I configure Bugsnag for "CXXThrowSomethingScenario" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the event "severity" equals "error" + And the event "unhandled" is true + And the exception "errorClass" equals "SIGABRT" + And the exception "message" equals "Abort program" + + Scenario: Rethrow in C++ without initial exception + When I run "CXXInvalidRethrow" and relaunch the app + And I configure Bugsnag for "CXXInvalidRethrow" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGABRT" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And the first significant stack frames match: + | print_last_exception() | CXXInvalidRethrow.cpp | 7 | + + Scenario: Throw from C++ noexcept function + When I run "CXXThrowFromNoexcept" and relaunch the app + And I configure Bugsnag for "CXXThrowFromNoexcept" + And I wait to receive an error + And the error payload contains a completed unhandled native report + And the exception "errorClass" equals "SIGABRT" + And the exception "type" equals "c" + And the event "severity" equals "error" + And the event "unhandled" is true + And on arm64, the first significant stack frames match: + | FunkError::what() const | CXXThrowFromNoexcept.cpp | 13 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXThrowFromNoexcept_crash | CXXThrowFromNoexcept.cpp | 21 | - Scenario: Throwing an object in C++ - When I run "CXXThrowSomethingScenario" and relaunch the app - And I configure Bugsnag for "CXXThrowSomethingScenario" - And I wait to receive an error - And the error payload contains a completed unhandled native report - And the event "severity" equals "error" - And the event "unhandled" is true - And the exception "errorClass" equals "SIGABRT" - And the exception "message" equals "Abort program" diff --git a/features/smoke_tests/unhandled.feature b/features/smoke_tests/unhandled.feature index 4d06fdfbde..72a3d5d884 100644 --- a/features/smoke_tests/unhandled.feature +++ b/features/smoke_tests/unhandled.feature @@ -190,7 +190,7 @@ Scenario: C++ exception thrown with overwritten config # Exception details And the error payload field "events" is an array with 1 elements - And the exception "message" equals "How about NO" + And the exception "errorClass" demangles to "magicstacks::FatalProblem*" And the exception "type" equals "c" And the event "unhandled" is true And the event "severity" equals "error" @@ -202,12 +202,11 @@ Scenario: C++ exception thrown with overwritten config # Stacktrace validation And the error payload field "events.0.exceptions.0.stacktrace" is a non-empty array And the event stacktrace identifies the program counter - And the event "exceptions.0.stacktrace.0.method" is not null - And the event "exceptions.0.stacktrace.0.file" is not null - And the error payload field "events.0.exceptions.0.stacktrace.0.frameAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.symbolAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.loadAddress" is greater than 0 - And the error payload field "events.0.exceptions.0.stacktrace.0.lineNumber" is greater than 0 + And the first significant stack frames match: + | magicstacks::top() | CXXExceptionSmokeScenario.cpp | 13 | + | magicstacks::middle() | CXXExceptionSmokeScenario.cpp | 16 | + | magicstacks::start() | CXXExceptionSmokeScenario.cpp | 18 | + | Java_com_bugsnag_android_mazerunner_scenarios_CXXExceptionSmokeScenario_crash | CXXExceptionSmokeScenario.cpp | 25 | # App data And the event binary arch field is valid diff --git a/features/steps/android_steps.rb b/features/steps/android_steps.rb index c89ae15da2..ae95d9a8ed 100644 --- a/features/steps/android_steps.rb +++ b/features/steps/android_steps.rb @@ -5,8 +5,7 @@ When('any dialog is cleared and the element {string} is present') do |element_id| count = 0 present = false - # Give Android 5 more time to find elements in an attempt to combat flakes - timeout = (Maze.config.os_version == 5 ? 15 : 3) + timeout = 3 until present || count > 5 present = Maze.driver.wait_for_element(element_id, timeout = timeout) break if present @@ -96,39 +95,6 @@ Maze.check.include(possible_values.raw.flatten, value) end -# Checks whether the first significant frames match several given frames -# -# @param expected_values [Array] A table dictating the expected files and methods of the frames -# The first two entries are methods (enabling flexibility across SDKs), the third is the file name -Then("the first significant stack frame methods and files should match:") do |expected_values| - stacktrace = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.0.exceptions.0.stacktrace") - expected_frame_values = expected_values.raw - significant_frames = stacktrace.each_with_index.map do |frame, index| - method = `c++filt -_ _#{frame["method"]}`.chomp - method = frame["method"] if method == "_#{frame["method"]}" - insignificant = method.start_with?("bsg_") || - method.start_with?("std::") || - method.start_with?("__cxx") || - frame["file"].start_with?("/system/") || - frame["file"].end_with?("libbugsnag-ndk.so") - { :index => index, :method => method, :file => frame["file"] } unless insignificant - end - significant_frames.select! { |frame| frame } - expected_frame_values.each_with_index do |expected_frame, index| - test_frame = significant_frames[index] - method_match_a = expected_frame[0] == test_frame[:method] - method_match_b = expected_frame[1] == test_frame[:method] - Maze.check.true( - method_match_a || method_match_b, - "'#{test_frame[:method]}' in frame #{test_frame[:index]} is not equal to '#{expected_frame[0]}' or '#{expected_frame[1]}'" - ) - Maze.check.true( - test_frame[:file].end_with?(expected_frame[2]), - "'#{test_frame[:file]}' in frame #{test_frame[:index]} does not end with '#{expected_frame[2]}'" - ) - end -end - Then("the report contains the required fields") do steps %Q{ And the error payload field "notifier.name" is not null diff --git a/features/steps/symbol_steps.rb b/features/steps/symbol_steps.rb new file mode 100644 index 0000000000..4fecae4666 --- /dev/null +++ b/features/steps/symbol_steps.rb @@ -0,0 +1,116 @@ +# Steps for validating native stack trace contents. +# Depends on the the following binaries being available in $PATH: +# +# * c++filt +# * addr2line + +# Sentinel value which can be used in stack frame matching to ignore location info +IGNORED_VALUE = '(ignore)' +# Location on disk of native symbol files +SYMBOL_DIR = ENV['TEST_FIXTURE_SYMBOL_DIR'] || 'build' + +# Checks whether the first significant frames in an event match provided frames +# +# @param expected_values [Array] A table dictating the expected files and methods of the frames +# The table is formatted as any of: +# | method | +# or +# | method | binary filename | +# or +# | method | source file name | line number | +# or +# | method | (ignore) | +# or +# | method | (ignore) | (ignore) | +Then("the first significant stack frames match:") do |expected_values| + body = Maze::Server.errors.current[:body] + arch = Maze::Helper.read_key_path(body, "events.0.app.binaryArch") + stacktrace = Maze::Helper.read_key_path(body, "events.0.exceptions.0.stacktrace") + + significant_frames = stacktrace.map { |frame| symbolicate(arch, frame) }.flatten.compact + + expected_values.raw.each_with_index do |expected_frame, index| + raise "No matching significant frame at index #{index}" if significant_frames.length <= index + + test_frame = significant_frames[index] + Maze.check.equal( + expected_frame[0], test_frame[:method], + "'#{test_frame[:method]}' in frame #{index} is not equal to '#{expected_frame[0]}'" + ) + if expected_frame.length > 1 && expected_frame[1] != IGNORED_VALUE + Maze.check.true( + test_frame[:file].end_with?(expected_frame[1]), + "'#{test_frame[:file]}' in frame #{index} does not end with '#{expected_frame[1]}'" + ) + end + if expected_frame.length > 2 && expected_frame[2] != IGNORED_VALUE + Maze.check.equal(test_frame[:lineNumber], expected_frame[2], + "line number #{test_frame[:lineNumber]} in frame #{index} does not equal #{expected_frame[2]}" + ) + end + end +end + +# skip step unless on specified architecture +Then(/^on (arm32|arm64|x86|x86_64), (.+)$/) do |arch, step_text, table| + body = Maze::Server.errors.current[:body] + actual_arch = Maze::Helper.read_key_path(body, "events.0.app.binaryArch") + step(step_text, table) if arch == actual_arch +end + +Then("the exception {string} demangles to {string}") do |keypath, expected_value| + body = Maze::Server.errors.current[:body] + actual_value = Maze::Helper.read_key_path(body, "events.0.exceptions.0.#{keypath}") + demangled_value = demangle(actual_value) + Maze.check.equal(demangled_value, expected_value, + "expected '#{actual_value}' to demangle to '#{expected_value}' but was '#{demangled_value}'") +end + +def is_out_of_project? file, method + # no binary was found to match the address + file.nil? || + # in native functions from bugsnag-plugin-android-ndk + method.start_with?("bsg_") || file.end_with?("libbugsnag-ndk.so") || + # c++ standard library + hooks (__cxx_*, __cxa_*, etc) + method.start_with?("std::") || method.start_with?("__cx") || + # failed to resolve a symbol location + method.start_with?("0x") || + # sneaky libc functions + method.start_with?("str") || method.start_with?("abort") || + # android built-in libraries + file.start_with?("/system/") || file.start_with?("/apex/") +end + +def demangle symbol + `c++filt --types --no-strip-underscore #{symbol}`.chomp +end + +def lookup_address binary, address + info = `addr2line --exe '#{binary}' --inlines --basenames --functions --demangle 0x#{address.to_s(16)}`.chomp + return nil if info.start_with? '??' # failed to resolve + # can return multiple if there are inlined frames + frames = info.split("\n").each_slice(2).map do |function_name, location| + file, line = location.split(':') + { file: file, lineNumber: line, method: function_name } + end.reject { |frame| is_out_of_project?(frame[:file], frame[:method]) } + + return nil if frames.length == 0 + + frames +end + +# Resolve file and method name for in-project contents, returning nil +# if the frame is not in project. +def symbolicate arch, frame + method = demangle(frame["method"]) + binary_file = frame["file"] + + return nil if is_out_of_project?(binary_file, method) + + symbol_file = File.join(SYMBOL_DIR, "#{File.basename(binary_file, '.so')}-#{arch}.so") + + if File.exist?(symbol_file) and sym_info = lookup_address(symbol_file, frame["lineNumber"]) + return sym_info + end + [{ :method => method, :file => binary_file }] +end diff --git a/gradle.properties b/gradle.properties index 13efc4cbdf..1e1329ff98 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx4096m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel=true -VERSION_NAME=5.20.0 +VERSION_NAME=5.21.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git