Skip to content

Open Source Firebase Crashlytics for Android #1103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "firebase-crashlytics-ndk/src/third_party/breakpad"]
path = firebase-crashlytics-ndk/src/third_party/breakpad
url = https://chromium.googlesource.com/breakpad/breakpad
[submodule "firebase-crashlytics-ndk/src/third_party/lss"]
path = firebase-crashlytics-ndk/src/third_party/lss
url = https://chromium.googlesource.com/linux-syscall-support
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ dependencies:
* `firebase-abt`
* `firebase-common`
* `firebase-common-ktx`
* `firebase-crashlytics`
* `firebase-crashlytics-ndk`
* `firebase-database`
* `firebase-database-ktx`
* `firebase-database-collection`
Expand Down
16 changes: 16 additions & 0 deletions firebase-crashlytics-ndk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Firebase Crashlytics NDK Component

This component enables NDK crash reporting with Crashlytics.

Requires NDK version r17c to build.

## Building

All Gradle commands should be run from the root of this repository.

`./gradlew :firebase-crashlytics-ndk:assemble`

## Running Tests

Integration tests, requiring a running and connected device (emulator or real):
`./gradlew :firebase-crashlytics-ndk:connectedAndroidTest`
74 changes: 74 additions & 0 deletions firebase-crashlytics-ndk/firebase-crashlytics-ndk.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

plugins {
id 'firebase-library'
}

firebaseLibrary {
testLab.enabled = true
publishJavadoc = false
}

def androidVersion = 28

android {
adbOptions {
timeOutInMs 60 * 1000
}

compileSdkVersion androidVersion
defaultConfig {
minSdkVersion 16
targetSdkVersion androidVersion
versionName version

multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
androidTest {
java {
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

externalNativeBuild {
ndkBuild {
path "./src/main/jni/Android.mk"
}
}
}

dependencies {
implementation project(':firebase-common')
implementation project(':firebase-crashlytics')
implementation 'com.google.android.gms:play-services-basement:17.0.0'

testImplementation 'androidx.test:runner:1.2.0'
testImplementation 'org.robolectric:robolectric:4.2'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:1.10.19'

androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'org.mockito:mockito-core:2.25.0'
androidTestImplementation 'com.linkedin.dexmaker:dexmaker:2.25.0'
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.25.0'
androidTestImplementation 'com.google.protobuf:protobuf-java:3.6.1'
}
2 changes: 2 additions & 0 deletions firebase-crashlytics-ndk/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version=17.0.0-beta01
latestReleasedVersion=17.0.0-beta01
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.crashlytics.ndk;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import junit.framework.TestCase;

public class BreakpadControllerTest extends TestCase {

private BreakpadController controller;

private Context context;
private NativeApi mockNativeApi;
private CrashFilesManager mockFilesManager;

private File testFilesDirectory;

@Override
protected void setUp() throws Exception {
super.setUp();
context = ApplicationProvider.getApplicationContext();
mockNativeApi = mock(NativeApi.class);
mockFilesManager = mock(CrashFilesManager.class);
controller = new BreakpadController(context, mockNativeApi, mockFilesManager);
}

@Override
protected void tearDown() throws Exception {
recursiveDelete(testFilesDirectory);
super.tearDown();
}

private static void recursiveDelete(File file) {
if (file == null) {
return;
}
if (file.isDirectory()) {
for (File f : file.listFiles()) {
recursiveDelete(f);
}
}
file.delete();
}

private boolean setupTestFilesDirectory() {
// For each test case, create a new, random subdirectory to guarantee a clean slate for file
// manipulation.
testFilesDirectory = new File(context.getFilesDir(), UUID.randomUUID().toString());
return testFilesDirectory.mkdirs();
}

public void testHasCrashDataForSession() throws IOException {
assertTrue(setupTestFilesDirectory());
assertTrue(new File(testFilesDirectory, "crash.dmp").createNewFile());
final String sessionId = "test";
when(mockFilesManager.hasSessionFileDirectory(sessionId)).thenReturn(true);
when(mockFilesManager.getSessionFileDirectory(sessionId)).thenReturn(testFilesDirectory);
assertTrue(controller.hasCrashDataForSession("test"));
}

public void testHasCrashDataForSession_noCrashDataReturnsFalse() {
final String sessionId = "test";
when(mockFilesManager.hasSessionFileDirectory(sessionId)).thenReturn(false);
assertFalse(controller.hasCrashDataForSession("test"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.crashlytics.ndk;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import junit.framework.TestCase;

public class FirebaseCrashlyticsNdkTest extends TestCase {

private FirebaseCrashlyticsNdk nativeComponent;

private NativeComponentController mockController;

@Override
protected void setUp() throws Exception {
super.setUp();
mockController = mock(NativeComponentController.class);
nativeComponent = new FirebaseCrashlyticsNdk(mockController);
}

@Override
protected void tearDown() throws Exception {
super.tearDown();
}

public void testHasCrashDataForSession() {
final String sessionId = "test";
when(mockController.hasCrashDataForSession(sessionId)).thenReturn(true);
assertTrue(nativeComponent.hasCrashDataForSession("test"));
}

public void testHasCrashDataForSession_noCrashDataReturnsFalse() {
final String sessionId = "test";
when(mockController.hasCrashDataForSession(sessionId)).thenReturn(false);
assertFalse(nativeComponent.hasCrashDataForSession("test"));
}

public void testHasCrashDataForSession_nullSessionIdReturnsFalse() {
assertFalse(nativeComponent.hasCrashDataForSession(null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.crashlytics.ndk.utils;

public class TestMapEntry {
public static final String FORMAT_ENTRY = "%08x-%08x %s %08x %s %d %s";

private static final long DEFAULT_START_ADDRESS = 0L;
private static final long DEFAULT_END_ADDRESS = 64L;
private static final String DEFAULT_PERMS = "rwxp";
private static final long DEFAULT_OFFSET = 0L;
private static final String DEFAULT_DEVICE = "03:0c";
private static final int DEFAULT_INODE = 7;
private static final String DEFAULT_PATH = "/valid/path";

private String outputFormat = FORMAT_ENTRY;

public long startAddress;
public long endAddress;
public String permissions;
public long offset;
public String device;
public int inode;
public String path;

public TestMapEntry() {
this(
DEFAULT_START_ADDRESS,
DEFAULT_END_ADDRESS,
DEFAULT_PERMS,
DEFAULT_OFFSET,
DEFAULT_DEVICE,
DEFAULT_INODE,
DEFAULT_PATH);
}

public TestMapEntry(long startAddress, long size, String path) {
this(startAddress, startAddress + size, DEFAULT_PERMS, path);
}

public TestMapEntry(long startAddress, long size, String perms, String path) {
this(
startAddress,
startAddress + size,
perms,
DEFAULT_OFFSET,
DEFAULT_DEVICE,
DEFAULT_INODE,
path);
}

public TestMapEntry(
long startAddress,
long endAddress,
String permissions,
long offset,
String device,
int inode,
String path) {
this.startAddress = startAddress;
this.endAddress = endAddress;
this.permissions = permissions;
this.offset = offset;
this.device = device;
this.inode = inode;
this.path = path;
}

public void setToStringFormat(String format) {
this.outputFormat = format;
}

@Override
public String toString() {
return String.format(
outputFormat, startAddress, endAddress, permissions, offset, device, inode, path);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"pc":2934348926,"symbol":"_ZN4boom7explodeEi"}
{"si_addr":0,"sig_name":"SIGSEGV","sig_code":"SEGV_MAPERR"}
{"time":1473795194}
{"version":"1.1.3"}
{"orientation":1,"total_physical_memory":
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{"pc":2934348926,"symbol":"_ZN4boom7explodeEi"}
{"si_addr":0,"sig_name":"SIGSEGV","sig_code":"SEGV_MAPERR"}
{"time":1473795194}
{"version":"1.1.3"}
{"orientation":1,"total_physical_memory":1945096192,"total_internal_storage":28734615552,"available_physical_memory":64888832,"available_internal_storage":15769907200,"battery":94,"proximity_enabled":true}
{"unwind_impl":"libunwind"}
{"maps":["ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]","be45c000-bec5b000 rw-p 00000000 00:00 0 [stack]","be45c000-be45c000 ---p 00000000 00:00 0 ","b6f3f000-b6f40000 rw-p 00000000 00:00 0 ","b6f3e000-b6f3f000 r--p 4000 00:00 0 /system/bin/app_process32","b6f39000-b6f3e000 r-xp 00000000 00:00 0 /system/bin/app_process32","ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]","be45c000-bec5b000 rw-p 00000000 00:00 0 [stack]","be45c000-be45c000 ---p 00000000 00:00 0 ","b6f3f000-b6f40000 rw-p 00000000 00:00 0 ","b6f3e000-b6f3f000 r--p 4000 00:00 0 /system/bin/app_process32","b6f39000-b6f3e000 r-xp 00000000 00:00 0 /system/bin/app_process32","b6f37000-b6f39000 rw-p 00000000 00:00 0 ","b6f35000-b6f37000 rw-p 1d000 00:00 0 /system/bin/linker","b6f34000-b6f35000 r--p 1c000 00:00 0 /system/bin/linker","b6f17000-b6f34000 r-xp 00000000 00:00 0 /system/bin/linker","b6f15000-b6f17000 rw-p 00000000 00:00 0 [anon:thread signal stack]","b6f14000-b6f15000 ---p 00000000 00:00 0 ","b6f13000-b6f14000 r--p 00000000 00:00 0 ","b6ef3000-b6f13000 r--p 00000000 00:00 0 /dev/__properties__","b6ef2000-b6ef3000 r--p 00000000 00:00 0 [anon:linker_alloc]","b6ef1000-b6ef2000 rw-p 00000000 00:00 0 [anon:linker_alloc_32]","b6ef0000-b6ef1000 rw-p 00000000 00:00 0 [anon:linker_alloc_vector]","b6eef000-b6ef0000 rw-p 00000000 00:00 0 [anon:linker_alloc]","b6eed000-b6eef000 r--p 00000000 00:00 0 [anon:linker_alloc]","b6eec000-b6eed000 rw-p 00000000 00:00 0 [anon:linker_alloc_vector]","b6eeb000-b6eec000 rw-p f000 00:00 0 /system/lib/libcutils.so","b6eea000-b6eeb000 r--p e000 00:00 0 /system/lib/libcutils.so","b6ee9000-b6eea000 ---p 00000000 00:00 0 ","b6edb000-b6ee9000 r-xp 00000000 00:00 0 /system/lib/libcutils.so","b6eda000-b6edb000 rw-p 19000 00:00 0 /system/lib/libutils.so","b6ed9000-b6eda000 r--p 18000 00:00 0 /system/lib/libutils.so","b6ed8000-b6ed9000 ---p 00000000 00:00 0 ","b6ec0000-b6ed8000 r-xp 00000000 00:00 0 /system/lib/libutils.so","b6ebf000-b6ec0000 rw-p 00000000 00:00 0 [anon:linker_alloc]","b6ebe000-b6ebf000 rw-p 8000 00:00 0 /system/lib/liblog.so","b6ebd000-b6ebe000 r--p 7000 00:00 0 /system/lib/liblog.so","b6eb5000-b6ebd000 r-xp 00000000 00:00 0 /system/lib/liblog.so","b6eb4000-b6eb5000 rw-p 2c000 00:00 0 /system/lib/libbinder.so","b6ead000-b6eb4000 r--p 25000 00:00 0 /system/lib/libbinder.so","b6eac000-b6ead000 ---p 00000000 00:00 0 ","b6e87000-b6eac000 r-xp 00000000 00:00 0 /system/lib/libbinder.so","b6e83000-b6e87000 r--p 00000000 00:00 0 [anon:linker_alloc]","b6e82000-b6e83000 rw-p 00000000 00:00 0 [anon:linker_alloc_64]","b6e81000-b6e82000 rw-p 00000000 00:00 0 [anon:linker_alloc_vector]","b6e79000-b6e81000 rw-p d3000 00:00 0 /system/lib/libandroid_runtime.so","b6e73000-b6e79000 r--p cd000 00:00 0 /system/lib/libandroid_runtime.so","b6e72000-b6e73000 ---p 00000000 00:00 0 ","b6da5000-b6e72000 r-xp 00000000 00:00 0 /system/lib/libandroid_runtime.so","b6da4000-b6da5000 rw-p 2b000 00:00 0 /system/lib/libwilhelm.so","b6da1000-b6da4000 r--p 28000 00:00 0 /system/lib/libwilhelm.so","b6da0000-b6da1000 ---p 00000000 00:00 0 ","b6d78000-b6da0000 r-xp 00000000 00:00 0 /system/lib/libwilhelm.so","b6d77000-b6d78000 rw-p 00000000 00:00 0 ","b6d76000-b6d77000 rw-p 8c000 00:00 0 /system/lib/libc++.so”]}
{"threads":[{
32 changes: 32 additions & 0 deletions firebase-crashlytics-ndk/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ ATTRIBUTIONS:
~ libunwind - a platform-independent unwind library
~ Copyright (C) 2012 Tommi Rantala <[email protected]>
~ Copyright (C) 2013 Linaro Limited
~ Copyright (C) 2008 CodeSourcery
~
~ you may not use these libunwind except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
~
-->
<manifest package="com.crashlytics.android.ndk"
xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<service android:name="com.google.firebase.components.ComponentDiscoveryService" android:exported="false">
<meta-data android:name="com.google.firebase.components:com.google.firebase.crashlytics.ndk.CrashlyticsNdkRegistrar"
android:value="com.google.firebase.components.ComponentRegistrar" />
</service>
</application>
</manifest>
Loading