Save crash to file (#2460)

This commit is contained in:
南宫雪珊 2023-07-10 13:20:57 +08:00 committed by GitHub
parent 4190bfb9e6
commit 8792e62df8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 291 deletions

View File

@ -56,9 +56,6 @@
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".ui.activity.CrashReportActivity"
android:process=":error" />
<provider
android:name="androidx.startup.InitializationProvider"

View File

@ -20,18 +20,20 @@
package org.lsposed.manager;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.provider.MediaStore;
import android.provider.Settings;
import android.system.Os;
import android.text.TextUtils;
@ -43,8 +45,8 @@ import androidx.preference.PreferenceManager;
import org.lsposed.hiddenapibypass.HiddenApiBypass;
import org.lsposed.manager.adapters.AppHelper;
import org.lsposed.manager.receivers.LSPManagerServiceHolder;
import org.lsposed.manager.repo.RepoLoader;
import org.lsposed.manager.ui.activity.CrashReportActivity;
import org.lsposed.manager.util.CloudflareDNS;
import org.lsposed.manager.util.ModuleUtil;
import org.lsposed.manager.util.Telemetry;
@ -55,8 +57,8 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
@ -96,23 +98,12 @@ public class App extends Application {
var list = AppHelper.getAppList(false);
var pm = App.getInstance().getPackageManager();
list.parallelStream().forEach(i -> AppHelper.getAppLabel(i, pm));
AppHelper.getDenyList(false);
ModuleUtil.getInstance();
RepoLoader.getInstance();
});
return false;
});
Looper.myQueue().addIdleHandler(() -> {
if (App.getInstance() == null || App.getExecutorService() == null) return true;
App.getExecutorService().submit(() -> AppHelper.getDenyList(false));
return false;
});
Looper.myQueue().addIdleHandler(() -> {
if (App.getInstance() == null || App.getExecutorService() == null) return true;
App.getExecutorService().submit(ModuleUtil::getInstance);
return false;
});
Looper.myQueue().addIdleHandler(() -> {
if (App.getInstance() == null || App.getExecutorService() == null) return true;
App.getExecutorService().submit(RepoLoader::getInstance);
App.getExecutorService().submit(HTML_TEMPLATE);
App.getExecutorService().submit(HTML_TEMPLATE_DARK);
return false;
});
}
@ -169,41 +160,47 @@ public class App extends Application {
}
}
@SuppressLint("WrongConstant")
private void setCrashReport() {
var handler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
String stackTraceString = sw.toString();
//Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
//The limit is 1MB on Android but some devices seem to have it lower.
//See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
//And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
if (stackTraceString.length() > 131071) {
String disclaimer = " [stack trace too large]";
stackTraceString = stackTraceString.substring(0, 131071 - disclaimer.length()) + disclaimer;
var time = OffsetDateTime.now();
var dir = new File(getCacheDir(), "crash");
//noinspection ResultOfMethodCallIgnored
dir.mkdir();
var file = new File(dir, time.toEpochSecond() + ".log");
try (var pw = new PrintWriter(file)) {
pw.println(BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")");
pw.println(time);
pw.println("pid: " + Os.getpid() + " uid: " + Os.getuid());
throwable.printStackTrace(pw);
} catch (IOException ignored) {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
var table = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
var values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, "LSPosed_crash_report" + time.toEpochSecond() + ".zip");
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS);
var cr = getContentResolver();
var uri = cr.insert(table, values);
if (uri == null) return;
try (var zipFd = cr.openFileDescriptor(uri, "wt")) {
LSPManagerServiceHolder.getService().getLogs(zipFd);
} catch (Exception ignored) {
cr.delete(uri, null, null);
}
}
if (handler != null) {
handler.uncaughtException(thread, throwable);
}
Intent intent = new Intent(App.this, CrashReportActivity.class);
intent.putExtra(BuildConfig.APPLICATION_ID + ".EXTRA_STACK_TRACE", stackTraceString);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
App.this.startActivity(intent);
System.exit(10);
Process.killProcess(Os.getpid());
});
}
@Override
public void onCreate() {
super.onCreate();
if (!BuildConfig.DEBUG && !isParasitic) {
setCrashReport();
}
instance = this;
setCrashReport();
pref = PreferenceManager.getDefaultSharedPreferences(this);
if (!pref.contains("doh")) {
var name = "private_dns_mode";
@ -256,17 +253,14 @@ public class App extends Application {
}, intentFilter, Context.RECEIVER_NOT_EXPORTED);
UpdateUtil.loadRemoteVersion();
executorService.submit(HTML_TEMPLATE);
executorService.submit(HTML_TEMPLATE_DARK);
}
@NonNull
public static OkHttpClient getOkHttpClient() {
if (okHttpClient != null) return okHttpClient;
var builder = new OkHttpClient.Builder()
.cache(getOkHttpCache())
.dns(new CloudflareDNS());
.cache(getOkHttpCache())
.dns(new CloudflareDNS());
if (BuildConfig.DEBUG) {
var log = new HttpLoggingInterceptor();
log.setLevel(HttpLoggingInterceptor.Level.HEADERS);

View File

@ -1,131 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package org.lsposed.manager.ui.activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.color.DynamicColors;
import org.lsposed.manager.BuildConfig;
import org.lsposed.manager.R;
import org.lsposed.manager.databinding.ActivityCrashReportBinding;
import org.lsposed.manager.util.NavUtil;
import java.time.LocalDateTime;
import rikka.material.app.LocaleDelegate;
import rikka.material.app.MaterialActivity;
public class CrashReportActivity extends MaterialActivity {
ActivityCrashReportBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
binding = ActivityCrashReportBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.sendLogs.setOnClickListener(view -> {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, getAllErrorDetailsFromIntent(getIntent()));
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, null));
});
binding.reportIssue.setOnClickListener(view -> {
var clipboard = getSystemService(ClipboardManager.class);
//Are there any devices without clipboard...?
if (clipboard != null) {
ClipData clip = ClipData.newPlainText("LSPManagerCrashInfo",
getAllErrorDetailsFromIntent(getIntent()));
clipboard.setPrimaryClip(clip);
Toast.makeText(this, R.string.crash_info_copied, Toast.LENGTH_LONG).show();
}
NavUtil.startURL(this, "https://github.com/LSPosed/LSPosed/issues");
});
}
@Override
public void onApplyUserThemeResource(@NonNull Resources.Theme theme, boolean isDecorView) {
if (!DynamicColors.isDynamicColorAvailable())
theme.applyStyle(R.style.ThemeOverlay_MaterialBlue, true);
}
public String getAllErrorDetailsFromIntent(@NonNull Intent intent) {
String versionName = String.format(LocaleDelegate.getDefaultLocale(), "%s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);
return "Build version: " + versionName + " \n" +
"Current date: " + LocalDateTime.now() + " \n" +
"Device: " + getDeviceModelName() + " \n" +
"Fingerprint: " + getFingerprint() + " \n \n" +
"SDK: " + Build.VERSION.SDK_INT + " \n \n" +
"Stack trace: \n" +
getStackTraceFromIntent(intent);
}
private String getFingerprint() {
return Build.BRAND + '/' +
Build.PRODUCT + '/' +
Build.DEVICE + ':' +
Build.VERSION.RELEASE + '/' +
Build.ID + '/' +
Build.VERSION.INCREMENTAL + ':' +
Build.TYPE + '/' +
Build.TAGS;
}
private String getDeviceModelName() {
String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL;
if (model.startsWith(manufacturer)) {
return capitalize(model);
} else {
return capitalize(manufacturer) + " " + model;
}
}
private String capitalize(@Nullable String s) {
if (s == null || s.length() == 0) {
return "";
}
char first = s.charAt(0);
if (Character.isUpperCase(first)) {
return s;
} else {
return Character.toUpperCase(first) + s.substring(1);
}
}
public String getStackTraceFromIntent(@NonNull Intent intent) {
return intent.getStringExtra(BuildConfig.APPLICATION_ID + ".EXTRA_STACK_TRACE");
}
}

View File

@ -1,109 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of LSPosed.
~
~ LSPosed is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ LSPosed is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
~
~ Copyright (C) 2020 EdXposed Contributors
~ Copyright (C) 2021 LSPosed Contributors
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false">
<com.google.android.material.card.MaterialCardView
style="?materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="16dp"
android:minHeight="148dp"
android:outlineAmbientShadowColor="@color/home_secondary_outline_ambient_shadow_color"
android:outlineSpotShadowColor="@color/home_secondary_outline_spot_shadow_color"
app:cardElevation="@dimen/home_secondary_elevation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:paddingBottom="8dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1.0"
android:orientation="vertical"
android:paddingTop="8dp">
<com.google.android.material.textview.MaterialTextView
style="?attr/textAppearanceHeadline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manager_crashed" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginStart="8dp"
android:background="@android:color/transparent"
android:tint="@color/material_amber_500"
app:srcCompat="@drawable/ic_round_warning_24" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/send_logs"
style="?attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:minWidth="0dp"
android:text="@string/send_crash_info"
android:textAlignment="center"
android:tooltipText="@string/send_crash_info"
app:icon="@drawable/ic_round_send_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/report_issue"
style="?attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:minWidth="0dp"
android:text="@string/report_issue"
android:textAlignment="center"
android:tooltipText="@string/report_issue"
app:icon="@drawable/ic_round_bug_report_24" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View File

@ -24,6 +24,7 @@ import static org.lsposed.lspd.service.ServiceManager.toGlobalNamespace;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
@ -69,6 +70,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@ -248,6 +250,11 @@ public class ConfigFileManager {
zipAddDir(os, oldLogDirPath);
zipAddDir(os, Paths.get("/data/tombstones"));
zipAddDir(os, Paths.get("/data/anr"));
var data = Paths.get("/data/data");
var app1 = data.resolve(BuildConfig.MANAGER_INJECTED_PKG_NAME + "/cache/crash");
var app2 = data.resolve(BuildConfig.DEFAULT_MANAGER_PACKAGE_NAME + "/cache/crash");
zipAddDir(os, app1);
zipAddDir(os, app2);
zipAddProcOutput(os, "full.log", "logcat", "-b", "all", "-d");
zipAddProcOutput(os, "dmesg.log", "dmesg");
var magiskDataDir = Paths.get("/data/adb");
@ -261,6 +268,14 @@ public class ConfigFileManager {
zipAddFile(os, p.resolve("sepolicy.rule"), magiskDataDir);
});
}
var proc = Paths.get("/proc");
for (var pid : new String[]{"self", String.valueOf(Binder.getCallingPid())}) {
var pidPath = proc.resolve(pid);
zipAddFile(os, pidPath.resolve("maps"), proc);
zipAddFile(os, pidPath.resolve("mountinfo"), proc);
zipAddFile(os, pidPath.resolve("status"), proc);
}
zipAddFile(os, dbPath.toPath(), configDirPath);
ConfigManager.getInstance().exportScopes(os);
} catch (Throwable e) {
Log.w(TAG, "get log", e);
@ -299,6 +314,7 @@ public class ConfigFileManager {
}
private static void zipAddDir(ZipOutputStream os, Path path) throws IOException {
if (!Files.isDirectory(path)) return;
Files.walkFileTree(path, new SimpleFileVisitor<>() {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (Files.isRegularFile(file)) {