├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── font-v26 │ │ │ ├── jetbrains_mono_variable_weight.ttf │ │ │ └── jetbrains_mono.xml │ │ ├── layout │ │ │ ├── bad_layout.xml │ │ │ ├── activity_main.xml │ │ │ ├── fragment_logs.xml │ │ │ ├── fragment_settings.xml │ │ │ └── fragment_testing_crash.xml │ │ ├── values-night │ │ │ ├── themes.xml │ │ │ └── colors.xml │ │ ├── values-v31 │ │ │ └── colors.xml │ │ ├── values-night-v31 │ │ │ └── colors.xml │ │ ├── values-v23 │ │ │ └── themes.xml │ │ ├── values-night-v23 │ │ │ └── themes.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values-v27 │ │ │ └── themes.xml │ │ ├── values-night-v27 │ │ │ └── themes.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ └── com │ │ │ └── kyant │ │ │ └── fishnet │ │ │ └── demo │ │ │ ├── FishnetApp.kt │ │ │ ├── ui │ │ │ ├── MonospacedText.kt │ │ │ ├── MaterialSpinnerContainer.kt │ │ │ └── MaterialButton.kt │ │ │ ├── MainActivity.kt │ │ │ ├── LoggingConfig.kt │ │ │ ├── TestingCrashFragment.kt │ │ │ ├── SettingsFragment.kt │ │ │ └── LogsFragment.kt │ │ ├── cpp │ │ ├── CMakeLists.txt │ │ └── lib.c │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle.kts ├── fishnet ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── cpp │ │ ├── version.h │ │ ├── root.h │ │ ├── clock.h │ │ ├── signal_handler.h │ │ ├── command_line.h │ │ ├── fds.h │ │ ├── log_header.h │ │ ├── tasks.h │ │ ├── logcat.h │ │ ├── human_readable.h │ │ ├── property.h │ │ ├── backtrace.h │ │ ├── cause.h │ │ ├── registers.h │ │ ├── debugger_thread_info.h │ │ ├── dump.h │ │ ├── anr_signal_handler.h │ │ ├── backtrace.cpp │ │ ├── process.h │ │ ├── property.cpp │ │ ├── clock.cpp │ │ ├── abort_message.h │ │ ├── root.cpp │ │ ├── duration.h │ │ ├── external │ │ │ ├── _libc │ │ │ │ ├── async_safe │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Android.bp │ │ │ │ │ └── include │ │ │ │ │ │ └── async_safe │ │ │ │ │ │ ├── CHECK.h │ │ │ │ │ │ └── log.h │ │ │ │ ├── private │ │ │ │ │ └── ErrnoRestorer.h │ │ │ │ └── platform │ │ │ │ │ └── bionic │ │ │ │ │ ├── pac.h │ │ │ │ │ └── mte.h │ │ │ └── libiberty │ │ │ │ ├── rustc_demangle.h │ │ │ │ ├── safe-ctype.h │ │ │ │ └── cp-demangle.h │ │ ├── memory.h │ │ ├── thread.h │ │ ├── logcat.cpp │ │ ├── command_line.cpp │ │ ├── abi.h │ │ ├── log.h │ │ ├── fishnet.cpp │ │ ├── fds.cpp │ │ ├── registers.cpp │ │ ├── log_header.cpp │ │ ├── abort_message.cpp │ │ ├── signal_handler.cpp │ │ ├── process.cpp │ │ ├── cause.cpp │ │ ├── log.cpp │ │ ├── anr_signal_handler.cpp │ │ ├── dump.cpp │ │ ├── human_readable.cpp │ │ └── thread.cpp │ │ └── java │ │ └── com │ │ └── kyant │ │ └── fishnet │ │ ├── Fishnet.java │ │ ├── NativeSignalHandler.java │ │ └── JavaExceptionHandler.java ├── consumer-rules.pro ├── proguard-rules.pro └── build.gradle.kts ├── .idea ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── kotlinc.xml ├── AndroidProjectSystem.xml ├── migrations.xml ├── markdown.xml ├── gradle.xml ├── vcs.xml ├── runConfigurations.xml └── misc.xml ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── fix_libziparchive_branch.sh ├── .gitignore ├── settings.gradle.kts ├── .gitmodules ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /fishnet/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /fishnet/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | /deploymentTargetSelector.xml 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Fishnet 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /fix_libziparchive_branch.sh: -------------------------------------------------------------------------------- 1 | cd fishnet/src/main/cpp/external/libziparchive 2 | git fetch --depth=1 origin refs/heads/main:refs/heads/main 3 | git checkout main 4 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/version.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_VERSION_H 2 | #define FISHNET_VERSION_H 3 | 4 | #define FISHNET_VERSION "v1.1.0" 5 | 6 | #endif //FISHNET_VERSION_H 7 | -------------------------------------------------------------------------------- /app/src/main/res/font-v26/jetbrains_mono_variable_weight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyant0/Fishnet/HEAD/app/src/main/res/font-v26/jetbrains_mono_variable_weight.ttf -------------------------------------------------------------------------------- /fishnet/src/main/cpp/root.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_ROOT_H 2 | #define FISHNET_ROOT_H 3 | 4 | #include 5 | 6 | bool is_rooted(); 7 | 8 | #endif //FISHNET_ROOT_H 9 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/clock.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_CLOCK_H 2 | #define FISHNET_CLOCK_H 3 | 4 | #include 5 | 6 | std::string get_timestamp(); 7 | 8 | #endif //FISHNET_CLOCK_H 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/signal_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_SIGNAL_HANDLER_H 2 | #define FISHNET_SIGNAL_HANDLER_H 3 | 4 | void init_signal_handler(); 5 | 6 | void deinit_signal_handler(); 7 | 8 | #endif //FISHNET_SIGNAL_HANDLER_H 9 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/command_line.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_COMMAND_LINE_H 2 | #define FISHNET_COMMAND_LINE_H 3 | 4 | #include 5 | 6 | std::vector get_command_line(pid_t pid); 7 | 8 | #endif //FISHNET_COMMAND_LINE_H 9 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/fds.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_FDS_H 2 | #define FISHNET_FDS_H 3 | 4 | #include 5 | 6 | #include "log.h" 7 | 8 | void dump_open_fds(LogRecord &record, pid_t pid); 9 | 10 | #endif //FISHNET_FDS_H 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bad_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/log_header.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_LOG_HEADER_H 2 | #define FISHNET_LOG_HEADER_H 3 | 4 | #include "log.h" 5 | 6 | void print_log_header(LogRecord &record, const LogType &type, pid_t pid); 7 | 8 | #endif //FISHNET_LOG_HEADER_H 9 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/tasks.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_TASKS_H 2 | #define FISHNET_TASKS_H 3 | 4 | #include 5 | 6 | #include "log.h" 7 | 8 | void print_tasks(LogRecord &record, pid_t pid); 9 | 10 | #endif //FISHNET_TASKS_H 11 | -------------------------------------------------------------------------------- /.idea/AndroidProjectSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/logcat.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_LOGCAT_H 2 | #define FISHNET_LOGCAT_H 3 | 4 | #include 5 | 6 | #include "log.h" 7 | 8 | void dump_logcat(pid_t pid); 9 | 10 | void print_logs(LogRecord &record); 11 | 12 | #endif //FISHNET_LOGCAT_H 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/black 4 | #475D92 5 | #FEFBFF 6 | #D9E2FF 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/white 4 | #7A90C8 5 | #1A1B20 6 | #475D92 7 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/human_readable.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_HUMAN_READABLE_H 2 | #define FISHNET_HUMAN_READABLE_H 3 | 4 | #include 5 | 6 | const char *get_signame(const siginfo_t *info); 7 | 8 | const char *get_sigcode(const siginfo_t *info); 9 | 10 | #endif //FISHNET_HUMAN_READABLE_H 11 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/property.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_PROPERTY_H 2 | #define FISHNET_PROPERTY_H 3 | 4 | #include 5 | 6 | std::string get_property(const char *key, const char *default_value); 7 | 8 | bool get_bool_property(const char *key, bool default_value); 9 | 10 | #endif //FISHNET_PROPERTY_H 11 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_600 4 | @android:color/system_neutral1_10 5 | @android:color/system_accent1_100 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /app/release 17 | -------------------------------------------------------------------------------- /app/src/main/res/font-v26/jetbrains_mono.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-night-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_400 4 | @android:color/system_neutral1_900 5 | @android:color/system_accent1_600 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/FishnetApp.kt: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet.demo 2 | 3 | import android.app.Application 4 | 5 | class FishnetApp : Application() { 6 | override fun onCreate() { 7 | super.onCreate() 8 | LoggingConfig.init(this) 9 | System.loadLibrary("fishnet_demo") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-night-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/backtrace.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_BACKTRACE_H 2 | #define FISHNET_BACKTRACE_H 3 | 4 | #include "unwindstack/Unwinder.h" 5 | 6 | #include "log.h" 7 | 8 | void print_backtrace(LogRecord &record, unwindstack::ArchEnum arch, 9 | const std::vector &frames); 10 | 11 | #endif //FISHNET_BACKTRACE_H 12 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.13.1" 3 | kotlin = "2.2.21" 4 | 5 | [libraries] 6 | 7 | [plugins] 8 | android-application = { id = "com.android.application", version.ref = "agp" } 9 | android-library = { id = "com.android.library", version.ref = "agp" } 10 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 11 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/cause.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_CAUSE_H 2 | #define FISHNET_CAUSE_H 3 | 4 | #include "unwindstack/Unwinder.h" 5 | 6 | #include "log.h" 7 | 8 | void dump_probable_cause(LogRecord &record, const siginfo_t *info, unwindstack::Maps *maps, 9 | const std::unique_ptr ®s); 10 | 11 | #endif //FISHNET_CAUSE_H 12 | -------------------------------------------------------------------------------- /fishnet/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keepclassmembers class com.kyant.fishnet.Fishnet { 2 | public *; 3 | } 4 | -keepnames class com.kyant.fishnet.JavaExceptionHandler { 5 | private java.lang.String dumpJavaThreads(); 6 | } 7 | -keepclassmembers class com.kyant.fishnet.JavaExceptionHandler { 8 | public *; 9 | private java.lang.String dumpJavaThreads(); 10 | } 11 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/registers.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_REGISTERS_H 2 | #define FISHNET_REGISTERS_H 3 | 4 | #include "unwindstack/Regs.h" 5 | 6 | #include "log.h" 7 | 8 | void print_thread_registers(LogRecord &record, unwindstack::ArchEnum arch, int word_size, 9 | const std::unique_ptr ®s); 10 | 11 | #endif //FISHNET_REGISTERS_H 12 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/debugger_thread_info.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_DEBUGGER_THREAD_INFO_H 2 | #define FISHNET_DEBUGGER_THREAD_INFO_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct DebuggerThreadInfo { 8 | pid_t crashing_tid; 9 | siginfo_t *siginfo; 10 | void *ucontext; 11 | } DebuggerThreadInfo; 12 | 13 | #endif //FISHNET_DEBUGGER_THREAD_INFO_H 14 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/dump.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_DUMP_H 2 | #define FISHNET_DUMP_H 3 | 4 | #include "debugger_thread_info.h" 5 | 6 | void fishnet_dump_native(const DebuggerThreadInfo *info); 7 | 8 | void fishnet_dump_java(const char *java_stack_traces); 9 | 10 | void fishnet_dump_anr(const char *java_stack_traces, const DebuggerThreadInfo *info); 11 | 12 | #endif //FISHNET_DUMP_H 13 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/anr_signal_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_ANR_SIGNAL_HANDLER_H 2 | #define FISHNET_ANR_SIGNAL_HANDLER_H 3 | 4 | #include 5 | #include 6 | 7 | void init_anr_signal_handler(JavaVM *vm, JNIEnv *env); 8 | 9 | void deinit_anr_signal_handler(); 10 | 11 | void anr_signal_handler(int signal_number, siginfo_t *info, void *context); 12 | 13 | #endif //FISHNET_ANR_SIGNAL_HANDLER_H 14 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/backtrace.cpp: -------------------------------------------------------------------------------- 1 | #include "backtrace.h" 2 | 3 | void print_backtrace(LogRecord &record, unwindstack::ArchEnum arch, 4 | const std::vector &frames) { 5 | LOG_FISHNET(""); 6 | LOG_FISHNET("backtrace:"); 7 | for (const auto &frame: frames) { 8 | LOG_FISHNET(" %s", unwindstack::Unwinder::FormatFrame(arch, frame, false).c_str()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/process.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_PROCESS_H 2 | #define FISHNET_PROCESS_H 3 | 4 | #include 5 | 6 | #include "log.h" 7 | 8 | std::string get_process_name(pid_t pid); 9 | 10 | uint64_t get_process_uptime(pid_t pid); 11 | 12 | void get_process_tids(pid_t pid, std::vector &tids); 13 | 14 | void print_process_status(LogRecord &record, pid_t pid); 15 | 16 | void print_memory_info(LogRecord &record); 17 | 18 | #endif //FISHNET_PROCESS_H 19 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/property.cpp: -------------------------------------------------------------------------------- 1 | #include "property.h" 2 | 3 | #include 4 | 5 | std::string get_property(const char *key, const char *default_value) { 6 | char value[PROP_VALUE_MAX]; 7 | if (__system_property_get(key, value) > 0) { 8 | return value; 9 | } 10 | return default_value; 11 | } 12 | 13 | bool get_bool_property(const char *key, bool default_value) { 14 | return strcmp(get_property(key, default_value ? "1" : "0").c_str(), "1") == 0; 15 | } 16 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/clock.cpp: -------------------------------------------------------------------------------- 1 | #include "clock.h" 2 | 3 | std::string get_timestamp() { 4 | timespec ts{}; 5 | clock_gettime(CLOCK_REALTIME, &ts); 6 | 7 | tm tm{}; 8 | localtime_r(&ts.tv_sec, &tm); 9 | 10 | // 2024-11-19 21:55:28.269116356+0800 11 | char buf[35]; 12 | char *s = buf; 13 | size_t sz = sizeof(buf), n; 14 | n = strftime(s, sz, "%F %H:%M", &tm), s += n, sz -= n; 15 | n = snprintf(s, sz, ":%02d.%09ld", tm.tm_sec, ts.tv_nsec), s += n, sz -= n; 16 | strftime(s, sz, "%z", &tm); 17 | return buf; 18 | } 19 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/abort_message.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_ABORT_MESSAGE_H 2 | #define FISHNET_ABORT_MESSAGE_H 3 | 4 | #include "unwindstack/Unwinder.h" 5 | 6 | #include "log.h" 7 | 8 | void set_aborter(); 9 | 10 | void try_read_abort_message_from_logcat(pid_t pid); 11 | 12 | void get_scudo_message_if_needed(const unwindstack::ArchEnum &arch, const std::unique_ptr ®s, 13 | const std::vector &frames); 14 | 15 | void dump_abort_message(LogRecord &record); 16 | 17 | #endif //FISHNET_ABORT_MESSAGE_H 18 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.name = "Fishnet" 23 | include(":app") 24 | include(":fishnet") 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/ui/MonospacedText.kt: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet.demo.ui 2 | 3 | import android.content.Context 4 | import android.graphics.Typeface 5 | import android.os.Build 6 | import android.util.AttributeSet 7 | import android.widget.TextView 8 | import com.kyant.fishnet.demo.R 9 | 10 | class MonospacedText @JvmOverloads constructor( 11 | context: Context, attrs: AttributeSet? = null 12 | ) : TextView(context, attrs) { 13 | init { 14 | typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 15 | resources.getFont(R.font.jetbrains_mono) 16 | } else { 17 | Typeface.MONOSPACE 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/root.cpp: -------------------------------------------------------------------------------- 1 | #include "root.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "log.h" 10 | 11 | // See https://github.com/vvb2060/MagiskDetector/blob/1570969c1070b372b63aa18c6ff30a3f897a1983/app/src/main/jni/vvb2060.c#L97 12 | bool is_rooted() { 13 | char *path = getenv("PATH"); 14 | const char *p = strtok(path, ":"); 15 | char su_path[256]; 16 | do { 17 | snprintf(su_path, sizeof(su_path), "%s/su", p); 18 | if (access(su_path, F_OK) == 0) { 19 | return true; 20 | } 21 | } while ((p = strtok(nullptr, ":")) != nullptr); 22 | return false; 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/values-v27/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-night-v27/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/duration.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_DURATION_H 2 | #define FISHNET_DURATION_H 3 | 4 | #include 5 | 6 | static std::string seconds_to_human_readable_time(uint64_t total_seconds) { 7 | uint64_t hours = total_seconds / 3600; 8 | uint64_t minutes = (total_seconds % 3600) / 60; 9 | uint64_t seconds = total_seconds % 60; 10 | 11 | std::string result; 12 | 13 | if (hours > 0) { 14 | result += (hours < 10 ? "0" : "") + std::to_string(hours) + ":"; 15 | } 16 | 17 | result += (minutes < 10 ? "0" : "") + std::to_string(minutes) + "."; 18 | 19 | result += (seconds < 10 ? "0" : "") + std::to_string(seconds); 20 | 21 | return result; 22 | } 23 | 24 | #endif //FISHNET_DURATION_H 25 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/async_safe/README.md: -------------------------------------------------------------------------------- 1 | # async_safe logging 2 | 3 | This library provides an async_safe implementation for formatting and writing log messages to logd. 4 | 5 | Note that the liblog implementation connects a single socket to logd and uses a RWLock to manage 6 | it among threads, whereas these functions connect to liblog for each log message. While it's 7 | beneficial to have this lock-free and therefore async_safe mechanism to write to logd, connecting 8 | a socket for each message does not scale well under load. It was also determined to be too 9 | costly to connect a socket for each thread as some processes, such as system_server, have over 100 10 | threads. Therefore, we maintain these two separate mechanisms. 11 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_MEMORY_H 2 | #define FISHNET_MEMORY_H 3 | 4 | #include "unwindstack/Maps.h" 5 | #include "unwindstack/Regs.h" 6 | 7 | #include "log.h" 8 | 9 | void print_tag_dump(LogRecord &record, uint64_t fault_addr, unwindstack::ArchEnum arch, 10 | std::shared_ptr &process_memory); 11 | 12 | void print_thread_memory_dump(LogRecord &record, int word_size, const std::unique_ptr ®s, 13 | unwindstack::Maps *maps, unwindstack::Memory *memory); 14 | 15 | void print_memory_maps(LogRecord &record, uint64_t fault_addr, int word_size, unwindstack::Maps *maps, 16 | std::shared_ptr &process_memory); 17 | 18 | #endif //FISHNET_MEMORY_H 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /fishnet/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 4.0.2) 2 | 3 | project(fishnet_demo) 4 | 5 | set(CMAKE_C_STANDARD 23) 6 | 7 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -flto -fvisibility=hidden -fdata-sections -ffunction-sections -fomit-frame-pointer") 8 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -flto -Wl,--exclude-libs,ALL -Wl,--gc-sections -Wl,-s -Wl,--pack-dyn-relocs=relr -Wl,--build-id=none") 9 | set(CMAKE_VISIBILITY_INLINES_HIDDEN YES) 10 | 11 | add_library(${CMAKE_PROJECT_NAME} SHARED 12 | lib.c) 13 | 14 | add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD 15 | COMMAND ${CMAKE_OBJCOPY} 16 | --remove-section .comment 17 | --remove-section .note 18 | --strip-debug $) 19 | 20 | target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE 21 | android) 22 | -------------------------------------------------------------------------------- /fishnet/src/main/java/com/kyant/fishnet/Fishnet.java: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Fishnet is an Android library that dumps Java and native crashes. 9 | * 10 | * @see Fishnet#init(Context, String) 11 | */ 12 | public final class Fishnet { 13 | private Fishnet() { 14 | } 15 | 16 | /** 17 | * Initialize Fishnet. 18 | * 19 | * @param context Application context 20 | * @param path Absolute path to store crash logs 21 | */ 22 | public static void init(Context context, String path) { 23 | Objects.requireNonNull(context, "context must not be null"); 24 | Objects.requireNonNull(path, "path must not be null"); 25 | 26 | NativeSignalHandler.init(context, path); 27 | JavaExceptionHandler.init(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 21 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_THREAD_H 2 | #define FISHNET_THREAD_H 3 | 4 | #include "unwindstack/AndroidUnwinder.h" 5 | 6 | #include "log.h" 7 | 8 | void print_main_thread_header(LogRecord &record, pid_t pid, pid_t tid, uid_t uid); 9 | 10 | void print_main_thread(LogRecord &record, pid_t pid, pid_t tid, uid_t uid, const siginfo_t *info, int word_size, 11 | const unwindstack::ArchEnum &arch, unwindstack::AndroidUnwinder *unwinder, 12 | const std::unique_ptr ®s, 13 | const std::vector &frames, 14 | bool dump_memory, bool dump_memory_maps); 15 | 16 | void print_thread(LogRecord &record, pid_t tid, int word_size, const unwindstack::ArchEnum &arch, 17 | unwindstack::ThreadUnwinder *unwinder, bool dump_memory); 18 | 19 | #endif //FISHNET_THREAD_H 20 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/logcat.cpp: -------------------------------------------------------------------------------- 1 | #include "logcat.h" 2 | 3 | #include 4 | 5 | static std::vector logs{}; 6 | 7 | void dump_logcat(pid_t pid) { 8 | char command[28]; 9 | snprintf(command, sizeof(command), "logcat -t 100 --pid=%d", pid); 10 | FILE *fp = popen(command, "r"); 11 | if (fp == nullptr) return; 12 | 13 | char line[1024]; 14 | if (fp) { 15 | fgets(line, sizeof(line), fp); // skip the first line 16 | // 11-19 22:37:54.578 10167 10167 D Com... 17 | while (fgets(line, sizeof(line), fp) != nullptr) { 18 | logs.emplace_back(line); 19 | } 20 | pclose(fp); 21 | } 22 | } 23 | 24 | void print_logs(LogRecord &record) { 25 | LOG_FISHNET(""); 26 | LOG_FISHNET("log main:"); 27 | 28 | const size_t size = logs.size(); 29 | 30 | for (size_t i = 0; i < size; ++i) { 31 | LOG_FISHNET_LN(" %s", logs[i].c_str()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/ui/MaterialSpinnerContainer.kt: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet.demo.ui 2 | 3 | import android.content.Context 4 | import android.graphics.Outline 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import android.view.ViewOutlineProvider 8 | import android.widget.FrameLayout 9 | import com.kyant.fishnet.demo.R 10 | 11 | class MaterialSpinnerContainer @JvmOverloads constructor( 12 | context: Context, attrs: AttributeSet? = null 13 | ) : FrameLayout(context, attrs) { 14 | init { 15 | background = resources.getDrawable(R.color.button, context.theme) 16 | val cornerRadiusPx = 18 * resources.displayMetrics.density 17 | outlineProvider = object : ViewOutlineProvider() { 18 | override fun getOutline(view: View, outline: Outline) { 19 | outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx) 20 | } 21 | } 22 | clipToOutline = true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/command_line.cpp: -------------------------------------------------------------------------------- 1 | #include "command_line.h" 2 | 3 | std::vector get_command_line(pid_t pid) { 4 | std::vector result; 5 | 6 | char process_name[256]; 7 | char path[22]; 8 | 9 | snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); 10 | FILE *file = fopen(path, "r"); 11 | if (!file) { 12 | result.emplace_back(""); 13 | return result; 14 | } 15 | if (fgets(process_name, sizeof(process_name), file) == nullptr) { 16 | fclose(file); 17 | result.emplace_back(""); 18 | return result; 19 | } 20 | 21 | std::string cmdline(process_name); 22 | auto it = cmdline.cbegin(); 23 | while (it != cmdline.cend()) { 24 | auto terminator = std::find(it, cmdline.cend(), '\0'); 25 | result.emplace_back(it, terminator); 26 | it = std::find_if(terminator, cmdline.cend(), [](char c) { return c != '\0'; }); 27 | } 28 | if (result.empty()) { 29 | result.emplace_back(""); 30 | } 31 | 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/libiberty/rustc_demangle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef RUSTC_DEMANGLE_H_ 18 | #define RUSTC_DEMANGLE_H_ 19 | 20 | #include 21 | 22 | #include "demangle.h" 23 | 24 | // This is just an empty stub for the rustc-demangle-capi rust crate. 25 | // It is used to build libunwindstack in the perfetto standalone build. 26 | 27 | static inline char* rustc_demangle(const char *mangled, char*, size_t*, int*) { 28 | return rust_demangle(mangled, DMGL_PARAMS); 29 | } 30 | 31 | #endif // RUSTC_DEMANGLE_H_ 32 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/private/ErrnoRestorer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | class ErrnoRestorer { 22 | public: 23 | explicit ErrnoRestorer() : saved_errno_(errno) { 24 | } 25 | 26 | ~ErrnoRestorer() { 27 | errno = saved_errno_; 28 | } 29 | 30 | void override(int new_errno) { 31 | saved_errno_ = new_errno; 32 | } 33 | 34 | private: 35 | int saved_errno_; 36 | 37 | ErrnoRestorer(const ErrnoRestorer &) = delete; 38 | 39 | void operator=(const ErrnoRestorer &) = delete; 40 | }; 41 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 12 | 13 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/abi.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_ABI_H 2 | #define FISHNET_ABI_H 3 | 4 | #include "unwindstack/Arch.h" 5 | 6 | #if defined(__arm__) 7 | #define CURRENT_ARCH unwindstack::ArchEnum::ARCH_ARM 8 | #define ABI_STRING "arm" 9 | #elif defined(__aarch64__) 10 | #define CURRENT_ARCH unwindstack::ArchEnum::ARCH_ARM64 11 | #define ABI_STRING "arm64" 12 | #elif defined(__i386__) 13 | #define CURRENT_ARCH unwindstack::ArchEnum::ARCH_X86; 14 | #define ABI_STRING "x86" 15 | #elif defined(__x86_64__) 16 | #define CURRENT_ARCH unwindstack::ArchEnum::ARCH_X86_64; 17 | #define ABI_STRING "x86_64" 18 | #elif defined(__riscv) 19 | #define CURRENT_ARCH unwindstack::ArchEnum::ARCH_RISCV64; 20 | #define ABI_STRING "riscv64" 21 | #else 22 | #define CURRENT_ARCH unwindstack::ArchEnum::ARCH_UNKNOWN; 23 | #define ABI_STRING "" 24 | #endif 25 | 26 | constexpr static inline uintptr_t untag_address(uintptr_t p) { 27 | #if defined(__aarch64__) 28 | return p & ((1ULL << 56) - 1); 29 | #else 30 | return p; 31 | #endif 32 | } 33 | 34 | consteval static inline unwindstack::ArchEnum current_arch() { 35 | return CURRENT_ARCH; 36 | } 37 | 38 | consteval static inline int pointer_width() { 39 | #if defined(__arm__) || defined(__i386__) 40 | return 4; 41 | #else 42 | return 8; 43 | #endif 44 | } 45 | 46 | consteval static inline const char *abi_string() { 47 | return ABI_STRING; 48 | } 49 | 50 | #endif //FISHNET_ABI_H 51 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fishnet/src/main/cpp/external/art"] 2 | path = fishnet/src/main/cpp/external/art 3 | url = https://android.googlesource.com/platform/art 4 | shallow = true 5 | [submodule "fishnet/src/main/cpp/external/libbase"] 6 | path = fishnet/src/main/cpp/external/libbase 7 | url = https://android.googlesource.com/platform/system/libbase 8 | shallow = true 9 | [submodule "fishnet/src/main/cpp/external/libprocinfo"] 10 | path = fishnet/src/main/cpp/external/libprocinfo 11 | url = https://android.googlesource.com/platform/system/libprocinfo 12 | shallow = true 13 | [submodule "fishnet/src/main/cpp/external/libziparchive"] 14 | path = fishnet/src/main/cpp/external/libziparchive 15 | url = https://android.googlesource.com/platform/system/libziparchive 16 | shallow = true 17 | [submodule "fishnet/src/main/cpp/external/logging"] 18 | path = fishnet/src/main/cpp/external/logging 19 | url = https://android.googlesource.com/platform/system/logging 20 | shallow = true 21 | [submodule "fishnet/src/main/cpp/external/lzma"] 22 | path = fishnet/src/main/cpp/external/lzma 23 | url = https://android.googlesource.com/platform/external/lzma 24 | shallow = true 25 | [submodule "fishnet/src/main/cpp/external/unwinding"] 26 | path = fishnet/src/main/cpp/external/unwinding 27 | url = https://android.googlesource.com/platform/system/unwinding 28 | shallow = true 29 | [submodule "fishnet/src/main/cpp/external/zstd"] 30 | path = fishnet/src/main/cpp/external/zstd 31 | url = https://android.googlesource.com/platform/external/zstd 32 | shallow = true 33 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/log.h: -------------------------------------------------------------------------------- 1 | #ifndef FISHNET_LOG_H 2 | #define FISHNET_LOG_H 3 | 4 | #include 5 | #include 6 | 7 | #define LOG_TAG "Fishnet" 8 | 9 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 10 | 11 | #define LOG_FISHNET(...) log_fishnet(record, true, __VA_ARGS__) 12 | 13 | #define LOG_FISHNET_LN(...) log_fishnet(record, false, __VA_ARGS__) 14 | 15 | #define FISHNET_RECORD(type) LogRecord record = start_recording(type) 16 | 17 | #define FISHNET_WRITE() write_log(record) 18 | 19 | struct ApkInfo { 20 | const char *package_name; 21 | const char *version_name; 22 | uint64_t version_code; 23 | const char *cert; 24 | }; 25 | 26 | enum LogType { 27 | None, 28 | Native, 29 | Java, 30 | ANR, 31 | }; 32 | 33 | struct LogRecord { 34 | LogType type; 35 | std::string timestamp; 36 | std::string content; 37 | }; 38 | 39 | void set_log_path(const char *path); 40 | 41 | void set_apk_info(const ApkInfo &info); 42 | 43 | const ApkInfo &get_apk_info(); 44 | 45 | const char *log_type_to_string(LogType type); 46 | 47 | LogRecord start_recording(LogType type); 48 | 49 | void write_log(const LogRecord &record); 50 | 51 | __attribute__((__format__(printf, 1, 2))) 52 | std::string StringPrintf(const char *fmt, ...); 53 | 54 | __attribute__((__format__(printf, 2, 3))) 55 | void StringAppendF(std::string *dst, const char *format, ...); 56 | 57 | __attribute__((__format__(printf, 3, 4))) 58 | void log_fishnet(LogRecord &record, bool linebreak, const char *fmt, ...); 59 | 60 | #endif //FISHNET_LOG_H 61 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/async_safe/Android.bp: -------------------------------------------------------------------------------- 1 | // ======================================================== 2 | // libasync_safe.a 3 | // ======================================================== 4 | package { 5 | // See: http://go/android-license-faq 6 | // A large-scale-change added 'default_applicable_licenses' to import 7 | // all of the 'license_kinds' from "bionic_libc_license" 8 | // to get the below license kinds: 9 | // SPDX-license-identifier-BSD 10 | default_applicable_licenses: ["bionic_libc_license"], 11 | } 12 | 13 | cc_library_static { 14 | defaults: ["libc_defaults"], 15 | srcs: [ 16 | "async_safe_log.cpp", 17 | ], 18 | 19 | name: "libasync_safe", 20 | vendor_available: true, 21 | product_available: true, 22 | recovery_available: true, 23 | native_bridge_supported: true, 24 | 25 | include_dirs: ["bionic/libc"], 26 | header_libs: [ 27 | "libc_headers", 28 | "liblog_headers", 29 | ], 30 | 31 | export_include_dirs: ["include"], 32 | export_header_lib_headers: ["liblog_headers"], 33 | stl: "none", 34 | 35 | apex_available: [ 36 | "//apex_available:anyapex", 37 | "//apex_available:platform", 38 | ], 39 | min_sdk_version: "apex_inherit", 40 | } 41 | 42 | cc_library_headers { 43 | name: "libasync_safe_headers", 44 | ramdisk_available: true, 45 | vendor_ramdisk_available: true, 46 | recovery_available: true, 47 | native_bridge_supported: true, 48 | defaults: ["linux_bionic_supported"], 49 | 50 | export_include_dirs: ["include"], 51 | 52 | system_shared_libs: [], 53 | stl: "none", 54 | 55 | apex_available: [ 56 | "//apex_available:platform", 57 | "com.android.runtime", 58 | ], 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/platform/bionic/pac.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Android Open Source Project 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in 12 | * the documentation and/or other materials provided with the 13 | * distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 22 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | inline uintptr_t __bionic_clear_pac_bits(uintptr_t ptr) { 34 | #if defined(__aarch64__) 35 | register uintptr_t x30 __asm("x30") = ptr; 36 | // This is a NOP on pre-Armv8.3-A architectures. 37 | asm("xpaclri" : "+r"(x30)); 38 | return x30; 39 | #else 40 | return ptr; 41 | #endif 42 | } 43 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | # Enable the build cache to save time by reusing outputs produced by other successful builds. 25 | # https://docs.gradle.org/current/userguide/build_cache.html 26 | org.gradle.caching=true 27 | # Enable the configuration cache to reuse the build configuration and enable parallel task execution. 28 | # (Note that some plugins may not yet be compatible with the configuration cache.) 29 | # https://docs.gradle.org/current/userguide/configuration_cache.html 30 | org.gradle.configuration-cache=true 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") 2 | 3 | package com.kyant.fishnet.demo 4 | 5 | import android.app.ActionBar 6 | import android.app.Activity 7 | import android.app.Fragment 8 | import android.app.FragmentTransaction 9 | import android.os.Bundle 10 | 11 | class MainActivity : Activity() { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_main) 15 | 16 | checkNotNull(actionBar).apply { 17 | navigationMode = ActionBar.NAVIGATION_MODE_TABS 18 | newTab().apply { 19 | text = "Test" 20 | setTabListener(TabListener(TestingCrashFragment())) 21 | addTab(this) 22 | } 23 | newTab().apply { 24 | text = "Logs" 25 | setTabListener(TabListener(LogsFragment())) 26 | addTab(this) 27 | } 28 | newTab().apply { 29 | text = "Settings" 30 | setTabListener(TabListener(SettingsFragment())) 31 | addTab(this) 32 | } 33 | } 34 | } 35 | 36 | private inner class TabListener(private val fragment: Fragment) : ActionBar.TabListener { 37 | override fun onTabSelected(tab: ActionBar.Tab?, ft: FragmentTransaction?) { 38 | fragmentManager.beginTransaction() 39 | .replace(R.id.content, fragment) 40 | .commit() 41 | } 42 | 43 | override fun onTabUnselected(tab: ActionBar.Tab?, ft: FragmentTransaction?) { 44 | } 45 | 46 | override fun onTabReselected(tab: ActionBar.Tab?, ft: FragmentTransaction?) { 47 | fragmentManager.beginTransaction() 48 | .replace(R.id.content, fragment) 49 | .commit() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fishnet 2 | 3 | Fishnet is an Android library that dumps Java, native and ANR crashes. 4 | The format of the log file is almost the same as the one generated by Android. 5 | 6 | ## Compatibility 7 | 8 | - Android: 6.0 and above 9 | - Architectures: arm64-v8a, armeabi-v7a, x86, x86_64 10 | - Log types: Java, Native, ANR 11 | 12 | ### Additional abort message detection 13 | 14 | - [ ] [FORTIFY](https://android-developers.googleblog.com/2017/04/fortify-in-android.html) 15 | - [ ] [fdsan](https://android.googlesource.com/platform/bionic/+/master/docs/fdsan.md) (Android 10+) 16 | - [x] [Scudo error](https://source.android.com/docs/security/test/scudo) (Android 11+) 17 | - [ ] [GWP-ASan](https://developer.android.com/ndk/guides/gwp-asan) (Android 14 +) 18 | - [ ] [MTE](https://developer.android.com/ndk/guides/arm-mte) (Android 14 QPR3 +) 19 | 20 | ## Installation 21 | 22 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.kyant0/fishnet)](https://central.sonatype.com/artifact/io.github.kyant0/fishnet) 23 | 24 | In build.gradle.kts, add 25 | 26 | ```kotlin 27 | implementation("io.github.kyant0:fishnet:") 28 | ``` 29 | 30 | ## Usage 31 | 32 | In the `Application` class, add the following code, 33 | 34 | ```kotlin 35 | import com.kyant.fishnet.Fishnet 36 | 37 | class App : Application() { 38 | override fun onCreate() { 39 | super.onCreate() 40 | val logPath = File(filesDir, "logs").apply { mkdirs() }.absolutePath 41 | Fishnet.init(this, logPath) 42 | } 43 | } 44 | ``` 45 | 46 | Run application and make a testing crash, the log file will be generated in the path you specified. 47 | 48 | ## Demo 49 | 50 | See the `app` module, 51 | the pre-built APK can be found in the [GitHub releases](https://github.com/Kyant0/Fishnet/releases). 52 | 53 | ## Build 54 | 55 | ### Clone the repository 56 | 57 | ```shell 58 | git clone https://github.com/Kyant0/Fishnet.git 59 | cd Fishnet 60 | git submodule init 61 | git submodule update 62 | git apply fishnet_external.patch 63 | ``` 64 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/fishnet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "log.h" 4 | #include "signal_handler.h" 5 | #include "dump.h" 6 | #include "anr_signal_handler.h" 7 | 8 | extern "C" { 9 | 10 | JNIEXPORT void JNICALL 11 | Java_com_kyant_fishnet_NativeSignalHandler_nativeInit(JNIEnv *env, jclass, jstring path, 12 | jstring packageName, jstring versionName, jlong versionCode, 13 | jstring cert) { 14 | const char *log_path = env->GetStringUTFChars(path, nullptr); 15 | set_log_path(log_path); 16 | env->ReleaseStringUTFChars(path, log_path); 17 | 18 | const char *package_name = env->GetStringUTFChars(packageName, nullptr); 19 | const char *version_name = env->GetStringUTFChars(versionName, nullptr); 20 | const char *cert_str = env->GetStringUTFChars(cert, nullptr); 21 | set_apk_info({ 22 | strdup(package_name), 23 | strdup(version_name), 24 | (uint64_t) versionCode, 25 | strdup(cert_str), 26 | }); 27 | 28 | env->ReleaseStringUTFChars(packageName, package_name); 29 | env->ReleaseStringUTFChars(versionName, version_name); 30 | env->ReleaseStringUTFChars(cert, cert_str); 31 | } 32 | 33 | JNIEXPORT void JNICALL 34 | Java_com_kyant_fishnet_NativeSignalHandler_nativeDumpJavaCrash(JNIEnv *env, jclass, jstring java_stack_traces) { 35 | const char *stack_traces = env->GetStringUTFChars(java_stack_traces, nullptr); 36 | fishnet_dump_java(stack_traces); 37 | env->ReleaseStringUTFChars(java_stack_traces, stack_traces); 38 | } 39 | 40 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) { 41 | JNIEnv *env; 42 | if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { 43 | return JNI_ERR; 44 | } 45 | 46 | init_signal_handler(); 47 | init_anr_signal_handler(vm, env); 48 | 49 | return JNI_VERSION_1_6; 50 | } 51 | 52 | JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *) { 53 | JNIEnv *env; 54 | if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { 55 | return; 56 | } 57 | 58 | deinit_signal_handler(); 59 | deinit_anr_signal_handler(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /fishnet/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | id("com.vanniktech.maven.publish") 4 | } 5 | 6 | android { 7 | namespace = "com.kyant.fishnet" 8 | compileSdk { 9 | version = release(36) 10 | } 11 | buildToolsVersion = "36.1.0" 12 | ndkVersion = "29.0.14206865" 13 | 14 | defaultConfig { 15 | minSdk = 23 16 | 17 | ndk { 18 | abiFilters += arrayOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86") 19 | } 20 | consumerProguardFiles("consumer-rules.pro") 21 | } 22 | 23 | buildTypes { 24 | release { 25 | isMinifyEnabled = false 26 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 27 | } 28 | } 29 | externalNativeBuild { 30 | cmake { 31 | path("src/main/cpp/CMakeLists.txt") 32 | version = "4.1.2" 33 | } 34 | } 35 | compileOptions { 36 | sourceCompatibility = JavaVersion.VERSION_21 37 | targetCompatibility = JavaVersion.VERSION_21 38 | } 39 | lint { 40 | checkReleaseBuilds = false 41 | } 42 | } 43 | 44 | mavenPublishing { 45 | publishToMavenCentral() 46 | signAllPublications() 47 | 48 | coordinates("io.github.kyant0", "fishnet", "1.1.0") 49 | 50 | pom { 51 | name.set("Fishnet") 52 | description.set("Dump Java, native and ANR crashes") 53 | inceptionYear.set("2025") 54 | url.set("https://github.com/Kyant0/Fishnet") 55 | licenses { 56 | license { 57 | name.set("The Apache License, Version 2.0") 58 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 59 | distribution.set("repo") 60 | } 61 | } 62 | developers { 63 | developer { 64 | id.set("Kyant0") 65 | name.set("Kyant") 66 | url.set("https://github.com/Kyant0") 67 | } 68 | } 69 | scm { 70 | url.set("https://github.com/Kyant0/Fishnet") 71 | connection.set("scm:git:git://github.com/Kyant0/Fishnet.git") 72 | developerConnection.set("scm:git:ssh://git@github.com/Kyant0/Fishnet.git") 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/LoggingConfig.kt: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet.demo 2 | 3 | import android.content.Context 4 | import android.os.Environment 5 | import com.kyant.fishnet.Fishnet 6 | import java.io.File 7 | 8 | object LoggingConfig { 9 | lateinit var logPath: File 10 | private set 11 | private lateinit var savingLocationFile: File 12 | var savingLocation: SavingLocation = SavingLocation.Internal 13 | private set 14 | 15 | fun init(context: Context) { 16 | savingLocationFile = File(context.filesDir, "savingLocation").apply { 17 | if (createNewFile()) { 18 | writeBytes(byteArrayOf(0)) 19 | } 20 | } 21 | savingLocation = SavingLocation.entries[savingLocationFile.readBytes().first().toInt()] 22 | changeSavingLocation(context, savingLocation) 23 | } 24 | 25 | fun getSavingPath(context: Context, savingLocation: SavingLocation): File { 26 | return when (savingLocation) { 27 | SavingLocation.Internal -> File(context.filesDir, "logs") 28 | SavingLocation.External -> File(context.getExternalFilesDir(null), "logs") 29 | SavingLocation.Downloads -> File( 30 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), 31 | "Fishnet" 32 | ) 33 | } 34 | } 35 | 36 | fun changeSavingLocation(context: Context, savingLocation: SavingLocation) { 37 | savingLocationFile = File(context.filesDir, "savingLocation").apply { 38 | createNewFile() 39 | writeBytes(byteArrayOf(savingLocation.ordinal.toByte())) 40 | } 41 | logPath = getSavingPath(context, savingLocation).apply { mkdirs() } 42 | Fishnet.init(context, logPath.absolutePath) 43 | this.savingLocation = savingLocation 44 | } 45 | 46 | fun getLogs(): List { 47 | return try { 48 | logPath.listFiles() 49 | ?.filter { it.extension == "log" } 50 | ?.reversed() 51 | .orEmpty() 52 | } catch (_: Exception) { 53 | emptyList() 54 | } 55 | } 56 | 57 | enum class SavingLocation { 58 | Internal, 59 | External, 60 | Downloads, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/ui/MaterialButton.kt: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet.demo.ui 2 | 3 | import android.content.Context 4 | import android.graphics.Outline 5 | import android.graphics.Typeface 6 | import android.os.Build 7 | import android.util.AttributeSet 8 | import android.util.TypedValue 9 | import android.view.Gravity 10 | import android.view.View 11 | import android.view.ViewOutlineProvider 12 | import android.widget.TextView 13 | import com.kyant.fishnet.demo.R 14 | import kotlin.math.roundToInt 15 | 16 | class MaterialButton @JvmOverloads constructor( 17 | context: Context, attrs: AttributeSet? = null 18 | ) : TextView(context, attrs) { 19 | init { 20 | background = resources.getDrawable(R.color.button, context.theme) 21 | val value = TypedValue() 22 | context.theme.resolveAttribute(android.R.attr.selectableItemBackground, value, true) 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 24 | foreground = resources.getDrawable(value.resourceId, context.theme) 25 | } 26 | val horizontalPadding = (16 * resources.displayMetrics.density).roundToInt() 27 | val verticalPadding = (10 * resources.displayMetrics.density).roundToInt() 28 | setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding) 29 | gravity = Gravity.CENTER 30 | context.theme.resolveAttribute(android.R.attr.textColorPrimary, value, true) 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 32 | setTextColor(resources.getColor(value.resourceId, context.theme)) 33 | } else { 34 | @Suppress("DEPRECATION") 35 | setTextColor(resources.getColor(value.resourceId)) 36 | } 37 | textSize = 15f 38 | typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 39 | Typeface.create(Typeface.DEFAULT_BOLD, 500, false) 40 | } else { 41 | Typeface.DEFAULT_BOLD 42 | } 43 | val cornerRadiusPx = 12 * resources.displayMetrics.density 44 | outlineProvider = object : ViewOutlineProvider() { 45 | override fun getOutline(view: View, outline: Outline) { 46 | outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx) 47 | } 48 | } 49 | clipToOutline = true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.kyant.fishnet.demo" 8 | compileSdk { 9 | version = release(36) 10 | } 11 | buildToolsVersion = "36.1.0" 12 | ndkVersion = "29.0.14206865" 13 | 14 | defaultConfig { 15 | applicationId = "com.kyant.fishnet.demo" 16 | minSdk = 23 17 | targetSdk = 36 18 | versionCode = 1 19 | versionName = "1.0.0" 20 | 21 | ndk { 22 | abiFilters += arrayOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64") 23 | } 24 | } 25 | buildTypes { 26 | release { 27 | isMinifyEnabled = true 28 | isShrinkResources = true 29 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 30 | signingConfigs { 31 | create("release") { 32 | enableV1Signing = true 33 | enableV2Signing = true 34 | enableV3Signing = true 35 | enableV4Signing = false 36 | } 37 | } 38 | signingConfig = signingConfigs.getByName("release") 39 | vcsInfo { 40 | include = false 41 | } 42 | } 43 | } 44 | externalNativeBuild { 45 | cmake { 46 | path("src/main/cpp/CMakeLists.txt") 47 | version = "4.1.2" 48 | } 49 | } 50 | packaging { 51 | resources { 52 | excludes += arrayOf( 53 | "DebugProbesKt.bin", 54 | "kotlin/**", 55 | "META-INF/*.version", 56 | "META-INF/**/LICENSE.txt", 57 | "*.properties", 58 | "kotlin-tooling-metadata.json", 59 | ) 60 | } 61 | dex { 62 | useLegacyPackaging = true 63 | } 64 | jniLibs { 65 | useLegacyPackaging = true 66 | } 67 | } 68 | dependenciesInfo { 69 | includeInApk = false 70 | includeInBundle = false 71 | } 72 | lint { 73 | checkReleaseBuilds = false 74 | } 75 | } 76 | kotlin { 77 | jvmToolchain(21) 78 | } 79 | 80 | dependencies { 81 | implementation(project(":fishnet")) 82 | } 83 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/fds.cpp: -------------------------------------------------------------------------------- 1 | #include "fds.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct FD { 10 | int32_t fd; 11 | std::string path; 12 | std::string owner; 13 | uint64_t tag; 14 | }; 15 | 16 | void dump_open_fds(LogRecord &record, pid_t pid) { 17 | char fd_dir[17]; 18 | snprintf(fd_dir, sizeof(fd_dir), "/proc/%d/fd", pid); 19 | 20 | DIR *dir = opendir(fd_dir); 21 | if (!dir) return; 22 | 23 | std::vector open_fds; 24 | 25 | struct dirent *entry; 26 | char fd_path[24], path[PATH_MAX]; 27 | while ((entry = readdir(dir)) != nullptr) { 28 | if (entry->d_name[0] == '.') continue; 29 | 30 | const int fd = atoi(entry->d_name); 31 | FD fd_info = {.fd = fd}; 32 | 33 | snprintf(fd_path, sizeof(fd_path), "%s/%s", fd_dir, entry->d_name); 34 | ssize_t len = readlink(fd_path, path, sizeof(path) - 1); 35 | if (len) { 36 | path[len] = '\0'; 37 | fd_info.path = path; 38 | } else { 39 | fd_info.path = StringPrintf("??? (%s)", strerror(errno)); 40 | } 41 | 42 | if (__builtin_available(android 29, *)) { 43 | const uint64_t tag = android_fdsan_get_owner_tag(fd); 44 | fd_info.tag = android_fdsan_get_tag_value(tag); 45 | if (fd_info.tag) { 46 | fd_info.owner = android_fdsan_get_tag_type(tag); 47 | } 48 | } 49 | 50 | open_fds.emplace_back(fd_info); 51 | } 52 | 53 | closedir(dir); 54 | 55 | if (!open_fds.empty()) { 56 | LOG_FISHNET(""); 57 | LOG_FISHNET("open files:"); 58 | if (__builtin_available(android 29, *)) { 59 | for (const FD &fd: open_fds) { 60 | if (!fd.owner.empty()) { 61 | LOG_FISHNET(" fd %d: %s (owned by %s 0x%" PRIx64 ")", 62 | fd.fd, fd.path.c_str(), fd.owner.c_str(), fd.tag); 63 | } else { 64 | LOG_FISHNET(" fd %d: %s (unowned)", fd.fd, fd.path.c_str()); 65 | } 66 | } 67 | } else { 68 | for (const FD &fd: open_fds) { 69 | LOG_FISHNET(" fd %d: %s", fd.fd, fd.path.c_str()); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/async_safe/include/async_safe/CHECK.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in 12 | * the documentation and/or other materials provided with the 13 | * distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 22 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | __BEGIN_DECLS 36 | 37 | // TODO: replace this with something more like 's family of macros. 38 | 39 | #define CHECK(predicate) \ 40 | do { \ 41 | if (!(predicate)) { \ 42 | async_safe_fatal("%s:%d: %s CHECK '%s' failed", __FILE__, __LINE__, __FUNCTION__, \ 43 | #predicate); \ 44 | } \ 45 | } while (0) 46 | 47 | __END_DECLS 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_logs.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 16 | 17 | 22 | 23 | 24 | 25 | 29 | 30 | 35 | 36 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 59 | 60 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/registers.cpp: -------------------------------------------------------------------------------- 1 | #include "registers.h" 2 | 3 | #include 4 | #include 5 | 6 | static void print_register_row(LogRecord &record, int word_size, 7 | const std::vector> &row) { 8 | std::string output = " "; 9 | for (const auto &[name, value]: row) { 10 | output += StringPrintf(" %-3s %0*" PRIx64, name.c_str(), 2 * word_size, (uint64_t) value); 11 | } 12 | LOG_FISHNET("%s", output.c_str()); 13 | } 14 | 15 | void print_thread_registers(LogRecord &record, unwindstack::ArchEnum arch, int word_size, 16 | const std::unique_ptr ®s) { 17 | static constexpr size_t column_count = 4; 18 | std::vector> current_row; 19 | std::vector> special_row; 20 | std::unordered_set special_registers; 21 | 22 | switch (arch) { 23 | case unwindstack::ArchEnum::ARCH_ARM: 24 | case unwindstack::ArchEnum::ARCH_ARM64: 25 | special_registers = {"ip", "lr", "sp", "pc", "pst"}; 26 | break; 27 | 28 | case unwindstack::ArchEnum::ARCH_RISCV64: 29 | special_registers = {"ra", "sp", "pc"}; 30 | break; 31 | 32 | case unwindstack::ArchEnum::ARCH_X86: 33 | special_registers = {"ebp", "esp", "eip"}; 34 | break; 35 | 36 | case unwindstack::ArchEnum::ARCH_X86_64: 37 | special_registers = {"rbp", "rsp", "rip"}; 38 | break; 39 | 40 | default: 41 | LOG_FISHNET("Unknown architecture %d printing thread registers", arch); 42 | return; 43 | } 44 | 45 | LOG_FISHNET(""); 46 | LOG_FISHNET("registers:"); 47 | 48 | regs->IterateRegisters([&](const char *name, uint64_t value) { 49 | auto row = ¤t_row; 50 | if (special_registers.count(name) == 1) { 51 | row = &special_row; 52 | } 53 | 54 | row->emplace_back(name, value); 55 | if (current_row.size() == column_count) { 56 | print_register_row(record, word_size, current_row); 57 | current_row.clear(); 58 | } 59 | }); 60 | 61 | if (!current_row.empty()) { 62 | print_register_row(record, word_size, current_row); 63 | } 64 | 65 | print_register_row(record, word_size, special_row); 66 | } 67 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/log_header.cpp: -------------------------------------------------------------------------------- 1 | #include "log_header.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "version.h" 9 | #include "property.h" 10 | #include "abi.h" 11 | #include "root.h" 12 | #include "duration.h" 13 | #include "process.h" 14 | 15 | void print_log_header(LogRecord &record, const LogType &type, pid_t pid) { 16 | const ApkInfo log_info = get_apk_info(); 17 | 18 | struct utsname name_buffer{}; 19 | uname(&name_buffer); 20 | 21 | std::string kernel_version = name_buffer.release; 22 | kernel_version += ' '; 23 | kernel_version += name_buffer.version; 24 | 25 | struct sysinfo s_info{}; 26 | sysinfo(&s_info); 27 | 28 | LOG_FISHNET("****** Fishnet crash report %s ******", FISHNET_VERSION); 29 | LOG_FISHNET(""); 30 | LOG_FISHNET("Log type: %s", log_type_to_string(type)); 31 | LOG_FISHNET(""); 32 | LOG_FISHNET("APK info:"); 33 | LOG_FISHNET(" Package: '%s'", log_info.package_name); 34 | LOG_FISHNET(" Version: '%s' (%" PRId64 ")", log_info.version_name, log_info.version_code); 35 | LOG_FISHNET(" Cert: '%s'", log_info.cert); 36 | LOG_FISHNET(""); 37 | LOG_FISHNET("Device info:"); 38 | LOG_FISHNET(" Build fingerprint: '%s'", get_property("ro.build.fingerprint", "unknown").c_str()); 39 | LOG_FISHNET(" Revision: '%s'", get_property("ro.revision", "unknown").c_str()); 40 | LOG_FISHNET(" Security patch: '%s'", get_property("ro.build.version.security_patch", "unknown").c_str()); 41 | LOG_FISHNET(" Build date: '%s'", get_property("ro.system.build.date", "unknown").c_str()); 42 | LOG_FISHNET(" Kernel version: '%s'", kernel_version.c_str()); 43 | LOG_FISHNET(" SDK: %s", get_property("ro.build.version.sdk", "'unknown'").c_str()); 44 | LOG_FISHNET(" ABI: '%s'", abi_string()); 45 | LOG_FISHNET(" Locale: '%s'", get_property("ro.product.locale", "unknown").c_str()); 46 | LOG_FISHNET(" Debuggable: %s", get_bool_property("ro.debuggable", false) ? "yes" : "no"); 47 | LOG_FISHNET(" Rooted (guessed): %s", is_rooted() ? "yes" : "no"); 48 | LOG_FISHNET(" System uptime: %s", seconds_to_human_readable_time(s_info.uptime).c_str()); 49 | LOG_FISHNET(""); 50 | LOG_FISHNET("Timestamp: %s", record.timestamp.c_str()); 51 | LOG_FISHNET("Process uptime: %s", seconds_to_human_readable_time(get_process_uptime(pid)).c_str()); 52 | 53 | // only print this info if the page size is not 4k or has been in 16k mode 54 | const size_t page_size = getpagesize(); 55 | const bool has_been_16kb_mode = get_bool_property("ro.misctrl.16kb_before", false); 56 | if (page_size != 4096) { 57 | LOG_FISHNET("Page size: %zu bytes", page_size); 58 | } else if (has_been_16kb_mode) { 59 | LOG_FISHNET("Has been in 16kb mode: yes"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/TestingCrashFragment.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") 2 | 3 | package com.kyant.fishnet.demo 4 | 5 | import android.app.Fragment 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | 12 | class TestingCrashFragment : Fragment() { 13 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { 14 | return inflater?.inflate(R.layout.fragment_testing_crash, container, false) 15 | } 16 | 17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 18 | view.findViewById(R.id.btn_test_java_crash).setOnClickListener { 19 | javaCrash() 20 | } 21 | view.findViewById(R.id.btn_test_java_thread_crash).setOnClickListener { 22 | javaThreadCrash() 23 | } 24 | view.findViewById(R.id.btn_test_java_deep_exception).setOnClickListener { 25 | javaDeepException() 26 | } 27 | view.findViewById(R.id.btn_test_anr).setOnClickListener { 28 | Thread.sleep(20_000) 29 | } 30 | 31 | view.findViewById(R.id.btn_test_native_nullptr).setOnClickListener { 32 | nativeCrash("nullptr") 33 | } 34 | view.findViewById(R.id.btn_test_native_jni_error).setOnClickListener { 35 | nativeCrash("jni") 36 | } 37 | view.findViewById(R.id.btn_test_native_deadlock).setOnClickListener { 38 | nativeCrash("deadlock") 39 | } 40 | view.findViewById(R.id.btn_test_native_too_many_open_files).setOnClickListener { 41 | nativeCrash("too_many_open_files") 42 | } 43 | view.findViewById(R.id.btn_test_native_buffer_overflow).setOnClickListener { 44 | nativeCrash("buffer_overflow") 45 | } 46 | view.findViewById(R.id.btn_test_native_scudo_error).setOnClickListener { 47 | nativeCrash("scudo_error") 48 | } 49 | view.findViewById(R.id.btn_test_native_fdsan).setOnClickListener { 50 | nativeFdsanCrash() 51 | } 52 | } 53 | 54 | private fun javaCrash() { 55 | throw RuntimeException("Java crash") 56 | } 57 | 58 | private fun javaThreadCrash() { 59 | val thread = object : Thread() { 60 | override fun run() { 61 | throw RuntimeException("Java thread crash") 62 | } 63 | } 64 | thread.start() 65 | } 66 | 67 | private fun javaDeepException() { 68 | val context = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) context else activity 69 | LayoutInflater.from(context).inflate(R.layout.bad_layout, null) 70 | } 71 | 72 | private external fun nativeCrash(type: String) 73 | 74 | private external fun nativeFdsanCrash() 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /fishnet/src/main/java/com/kyant/fishnet/NativeSignalHandler.java: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.Signature; 7 | import android.content.pm.SigningInfo; 8 | import android.os.Build; 9 | 10 | final class NativeSignalHandler { 11 | private NativeSignalHandler() { 12 | } 13 | 14 | static void init(Context context, String path) { 15 | String packageName = context.getPackageName(); 16 | 17 | PackageInfo packageInfo; 18 | int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 19 | ? PackageManager.GET_SIGNING_CERTIFICATES 20 | : PackageManager.GET_SIGNATURES; 21 | try { 22 | packageInfo = context.getPackageManager().getPackageInfo(packageName, flags); 23 | } catch (PackageManager.NameNotFoundException e) { 24 | packageInfo = null; 25 | } 26 | 27 | if (packageInfo == null) { 28 | nativeInit(path, packageName, "???", 0, "???"); 29 | return; 30 | } 31 | 32 | String versionName = packageInfo.versionName != null ? packageInfo.versionName : "???"; 33 | 34 | long versionCode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 35 | ? packageInfo.getLongVersionCode() 36 | : packageInfo.versionCode; 37 | 38 | Signature[] signatures = null; 39 | String cert = "???"; 40 | 41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 42 | SigningInfo signingInfo = packageInfo.signingInfo; 43 | if (signingInfo != null) { 44 | if (!signingInfo.hasMultipleSigners()) { 45 | signatures = signingInfo.getSigningCertificateHistory(); 46 | } else { 47 | signatures = signingInfo.getApkContentsSigners(); 48 | } 49 | } 50 | } else { 51 | signatures = packageInfo.signatures; 52 | } 53 | 54 | if (signatures != null) { 55 | if (signatures.length == 1) { 56 | cert = signatures[0].toCharsString().substring(0, 8); 57 | } else { 58 | StringBuilder sb = new StringBuilder(signatures.length * 9); 59 | for (Signature signature : signatures) { 60 | sb.append(signature.toCharsString().substring(0, 8)).append(' '); 61 | } 62 | int len = sb.length(); 63 | if (len > 0) { 64 | sb.deleteCharAt(sb.length() - 1); 65 | } 66 | cert = sb.toString(); 67 | } 68 | } 69 | 70 | nativeInit(path, packageName, versionName, versionCode, cert); 71 | } 72 | 73 | static void dumpJavaCrash(String javaStackTraces) { 74 | nativeDumpJavaCrash(javaStackTraces); 75 | } 76 | 77 | private static native void nativeInit( 78 | String path, 79 | String packageName, 80 | String versionName, 81 | long versionCode, 82 | String cert 83 | ); 84 | 85 | private static native void nativeDumpJavaCrash(String javaStackTraces); 86 | 87 | static { 88 | System.loadLibrary("fishnet"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/abort_message.cpp: -------------------------------------------------------------------------------- 1 | #include "abort_message.h" 2 | 3 | #include "android-base/logging.h" 4 | 5 | static char *abort_message = nullptr; 6 | 7 | void set_aborter() { 8 | android::base::SetAborter([](const char *abort_msg) { 9 | abort_message = strdup(abort_msg); 10 | }); 11 | } 12 | 13 | void try_read_abort_message_from_logcat(pid_t pid) { 14 | // TODO: check 15 | // if (abort_message != nullptr) return; 16 | 17 | char command[32]; 18 | snprintf(command, sizeof(command), "logcat -d -s libc --pid=%d", pid); 19 | FILE *fp = popen(command, "r"); 20 | if (fp == nullptr) return; 21 | 22 | char buffer[1024]; 23 | constexpr char divider[] = ": "; 24 | 25 | while (fgets(buffer, sizeof(buffer), fp) != nullptr) { 26 | // remove line break 27 | size_t len = strlen(buffer); 28 | if (len > 0 && buffer[len - 1] == '\n') { 29 | buffer[len - 1] = '\0'; 30 | } 31 | // 11-18 20:46:27.497 31300 31300 F libc : FORTIFY: strcpy: prevented 10-byte write into 8-byte buffer 32 | const char *pos = strstr(buffer, divider); 33 | if (pos) { 34 | abort_message = strdup(pos + 2); 35 | } else { 36 | abort_message = strdup(buffer); 37 | } 38 | } 39 | 40 | pclose(fp); 41 | } 42 | 43 | void get_scudo_message_if_needed(const unwindstack::ArchEnum &arch, const std::unique_ptr ®s, 44 | const std::vector &frames) { 45 | if (abort_message != nullptr) return; 46 | 47 | if (frames.size() < 2) return; 48 | 49 | constexpr char scudo_die[] = "_ZN5scudo3dieEv"; 50 | if (frames[1].function_name != scudo_die) return; 51 | 52 | uint64_t fp; 53 | switch (arch) { 54 | case unwindstack::ARCH_ARM: 55 | fp = ((const uint32_t *) regs->RawData())[7]; // r7 56 | break; 57 | case unwindstack::ARCH_ARM64: 58 | fp = ((const uint64_t *) regs->RawData())[29]; // x29 59 | break; 60 | case unwindstack::ARCH_X86: 61 | fp = ((const uint32_t *) regs->RawData())[5]; // ebp 62 | break; 63 | case unwindstack::ARCH_X86_64: 64 | fp = ((const uint64_t *) regs->RawData())[6]; // rbp 65 | break; 66 | case unwindstack::ARCH_RISCV64: 67 | fp = ((const uint64_t *) regs->RawData())[8]; // s0 (x8) 68 | break; 69 | default: 70 | return; 71 | } 72 | 73 | const char *scudo_block = (const char *) fp; 74 | size_t offset = 0x68; 75 | constexpr size_t max_offset = 0x100; 76 | constexpr char scudo_error[] = "Scudo ERROR"; 77 | 78 | // Scudo ERROR: corrupted chunk header at address 0x73485fd60910 79 | while (offset <= max_offset) { 80 | if (memcmp(&scudo_block[offset], scudo_error, 11) == 0) { 81 | abort_message = strdup(&scudo_block[offset]); 82 | const size_t len = strlen(abort_message); 83 | if (len > 0 && abort_message[len - 1] == '\n') { 84 | abort_message[len - 1] = '\0'; 85 | } 86 | break; 87 | } 88 | offset++; 89 | } 90 | } 91 | 92 | void dump_abort_message(LogRecord &record) { 93 | if (abort_message) { 94 | LOG_FISHNET("Abort message: '%s'", abort_message); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/cpp/lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void deadlock(); 13 | 14 | void fdsanCrash(); 15 | 16 | __attribute__((optnone)) 17 | JNIEXPORT void JNICALL 18 | Java_com_kyant_fishnet_demo_TestingCrashFragment_nativeCrash(JNIEnv *env, jobject obj, jstring type) { 19 | const char *type_str = (*env)->GetStringUTFChars(env, type, nullptr); 20 | 21 | if (strcmp(type_str, "nullptr") == 0) { 22 | char *ptr = (char *) 0; 23 | *ptr = 0; 24 | } else if (strcmp(type_str, "deadlock") == 0) { 25 | deadlock(); 26 | } else if (strcmp(type_str, "too_many_open_files") == 0) { 27 | for (int i = 0; i < 100000; ++i) { 28 | dup(STDOUT_FILENO); 29 | } 30 | } else if (strcmp(type_str, "buffer_overflow") == 0) { 31 | char buffer[8]; 32 | const char *input = "Overflow!"; 33 | strcpy(buffer, input); 34 | } else if (strcmp(type_str, "scudo_error") == 0) { // Scudo ERROR 35 | jbyteArray array = (*env)->NewByteArray(env, 10); 36 | jbyte *elements = (*env)->GetByteArrayElements(env, array, nullptr); 37 | elements[20] = 42; // Access out of bounds; will likely crash 38 | (*env)->ReleaseByteArrayElements(env, array, elements, 0); 39 | (*env)->NewStringUTF(env, (const char *) elements); 40 | } else if (strcmp(type_str, "jni") == 0) { 41 | char invalid_utf8[] = {(char) 0xC3, (char) 0x28, (char) 0x00}; // 0xC3 followed by 0x28 is not valid UTF-8 42 | (*env)->NewStringUTF(env, invalid_utf8); 43 | } 44 | 45 | (*env)->ReleaseStringUTFChars(env, type, type_str); 46 | } 47 | 48 | JNIEXPORT void JNICALL 49 | Java_com_kyant_fishnet_demo_TestingCrashFragment_nativeFdsanCrash(JNIEnv *env, jobject obj) { 50 | fdsanCrash(); 51 | } 52 | 53 | 54 | void *deadlock_thread_function(void *) { 55 | pthread_mutex_t mutex; 56 | pthread_mutex_init(&mutex, nullptr); 57 | pthread_mutex_lock(&mutex); 58 | pthread_mutex_lock(&mutex); 59 | return nullptr; 60 | } 61 | 62 | void deadlock() { 63 | pthread_t thread; 64 | pthread_create(&thread, nullptr, deadlock_thread_function, nullptr); 65 | pthread_join(thread, nullptr); 66 | } 67 | 68 | 69 | void victim() { 70 | usleep(300000); // 300ms 71 | int fd = dup(STDOUT_FILENO); 72 | usleep(200000); // 200ms 73 | ssize_t rc = write(fd, "good\n", 5); 74 | if (rc == -1) { 75 | err(1, "good failed to write?!"); 76 | } 77 | close(fd); 78 | } 79 | 80 | void bystander() { 81 | usleep(100000); // 100ms 82 | int fd = dup(STDOUT_FILENO); 83 | usleep(300000); // 300ms 84 | close(fd); 85 | } 86 | 87 | void offender() { 88 | int fd = dup(STDOUT_FILENO); 89 | close(fd); 90 | usleep(200000); // 200ms 91 | close(fd); 92 | } 93 | 94 | void *thread_function(void *arg) { 95 | void (*function)() = (void (*)()) arg; 96 | function(); 97 | return nullptr; 98 | } 99 | 100 | void fdsanCrash() { 101 | pthread_t threads[3]; 102 | void (*functions[3])() = {victim, bystander, offender}; 103 | 104 | for (int i = 0; i < 3; i++) { 105 | pthread_create(&threads[i], nullptr, thread_function, functions[i]); 106 | } 107 | for (int i = 0; i < 3; i++) { 108 | pthread_join(threads[i], nullptr); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/async_safe/include/async_safe/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in 12 | * the documentation and/or other materials provided with the 13 | * distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 22 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | // This file is an alternative to , but reuses 38 | // `android_LogPriority` and should not have conflicting identifiers. 39 | #include 40 | 41 | // These functions do not allocate memory to send data to the log. 42 | 43 | __BEGIN_DECLS 44 | 45 | // Formats a message to the log (priority 'fatal'), then aborts. 46 | // Implemented as a macro so that async_safe_fatal isn't on the stack when we crash: 47 | // we appear to go straight from the caller to abort, saving an uninteresting stack 48 | // frame. 49 | #define async_safe_fatal(...) \ 50 | do { \ 51 | async_safe_fatal_no_abort(__VA_ARGS__); \ 52 | abort(); \ 53 | } while (0) \ 54 | 55 | 56 | // These functions do return, so callers that want to abort, must do so themselves, 57 | // or use the macro above. 58 | void async_safe_fatal_no_abort(const char* fmt, ...) __printflike(1, 2); 59 | void async_safe_fatal_va_list(const char* prefix, const char* fmt, va_list args); 60 | 61 | // 62 | // Formatting routines for the C library's internal debugging. 63 | // Unlike the usual alternatives, these don't allocate, and they don't drag in all of stdio. 64 | // These are async signal safe, so they can be called from signal handlers. 65 | // 66 | 67 | int async_safe_format_buffer(char* buf, size_t size, const char* fmt, ...) __printflike(3, 4); 68 | int async_safe_format_buffer_va_list(char* buffer, size_t buffer_size, const char* format, va_list args); 69 | 70 | int async_safe_format_fd(int fd, const char* format , ...) __printflike(2, 3); 71 | int async_safe_format_fd_va_list(int fd, const char* format, va_list args); 72 | int async_safe_format_log(int priority, const char* tag, const char* fmt, ...) __printflike(3, 4); 73 | int async_safe_format_log_va_list(int priority, const char* tag, const char* fmt, va_list ap); 74 | int async_safe_write_log(int priority, const char* tag, const char* msg); 75 | 76 | __END_DECLS 77 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/signal_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "signal_handler.h" 2 | 3 | #include 4 | 5 | #include "abort_message.h" 6 | #include "human_readable.h" 7 | #include "dump.h" 8 | 9 | static struct sigaction old_actions[NSIG]; 10 | 11 | static void *thread_stack = nullptr; 12 | 13 | static void *fishnet_dispatch_thread(void *arg) { 14 | const auto *info = (const DebuggerThreadInfo *) arg; 15 | fishnet_dump_native(info); 16 | return nullptr; 17 | } 18 | 19 | static bool is_entered = false; 20 | 21 | static void fishnet_signal_handler(int signal_number, siginfo_t *info, void *context) { 22 | if (is_entered) { 23 | return; 24 | } 25 | is_entered = true; 26 | 27 | LOGE("Received signal %s, code %s", get_signame(info), get_sigcode(info)); 28 | 29 | DebuggerThreadInfo thread_info = { 30 | .crashing_tid = gettid(), 31 | .siginfo = info, 32 | .ucontext = context, 33 | }; 34 | 35 | pthread_t thread_id; 36 | pthread_attr_t thread_attr; 37 | if (pthread_attr_init(&thread_attr) != 0) { 38 | goto resend; 39 | } 40 | if (pthread_attr_setstack(&thread_attr, thread_stack, 8 * getpagesize()) != 0) { 41 | LOGE("pthread_attr_setstack failed"); 42 | munmap(thread_stack, 10 * getpagesize()); 43 | goto resend; 44 | } 45 | if (pthread_create(&thread_id, &thread_attr, fishnet_dispatch_thread, &thread_info) != 0) { 46 | goto resend; 47 | } 48 | pthread_join(thread_id, nullptr); 49 | munmap(thread_stack, 10 * getpagesize()); 50 | 51 | resend: 52 | const struct sigaction old_action = old_actions[signal_number]; 53 | if (old_action.sa_flags & SA_SIGINFO) { 54 | if (old_action.sa_sigaction) { 55 | old_action.sa_sigaction(signal_number, info, context); 56 | } 57 | } else { 58 | if (old_action.sa_handler) { 59 | old_action.sa_handler(signal_number); 60 | } 61 | } 62 | } 63 | 64 | static void register_handlers(struct sigaction *action) { 65 | sigaction(SIGABRT, action, old_actions + SIGABRT); 66 | sigaction(SIGBUS, action, old_actions + SIGBUS); 67 | sigaction(SIGFPE, action, old_actions + SIGFPE); 68 | sigaction(SIGILL, action, old_actions + SIGILL); 69 | sigaction(SIGSEGV, action, old_actions + SIGSEGV); 70 | sigaction(SIGSTKFLT, action, old_actions + SIGSTKFLT); 71 | sigaction(SIGSYS, action, old_actions + SIGSYS); 72 | sigaction(SIGTRAP, action, old_actions + SIGTRAP); 73 | } 74 | 75 | void init_signal_handler() { 76 | const size_t thread_stack_pages = 8; 77 | const void *thread_stack_allocation = mmap(nullptr, getpagesize() * (thread_stack_pages + 2), 78 | PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 79 | if (thread_stack_allocation == MAP_FAILED) { 80 | LOGE("Failed to allocate thread stack"); 81 | } 82 | 83 | char *stack = (char *) thread_stack_allocation + getpagesize(); 84 | if (mprotect(stack, getpagesize() * thread_stack_pages, PROT_READ | PROT_WRITE) != 0) { 85 | LOGE("Failed to mprotect thread stack"); 86 | } 87 | thread_stack = stack; 88 | 89 | struct sigaction action{}; 90 | memset(&action, 0, sizeof(action)); 91 | sigfillset(&action.sa_mask); 92 | action.sa_sigaction = fishnet_signal_handler; 93 | action.sa_flags = SA_SIGINFO | SA_ONSTACK; 94 | register_handlers(&action); 95 | 96 | set_aborter(); 97 | } 98 | 99 | void deinit_signal_handler() { 100 | for (int i = 0; i < NSIG; i++) { 101 | sigaction(i, old_actions + i, nullptr); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 116 | 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") 2 | 3 | package com.kyant.fishnet.demo 4 | 5 | import android.Manifest 6 | import android.app.Fragment 7 | import android.content.Intent 8 | import android.content.pm.PackageManager 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.os.Environment 13 | import android.provider.Settings 14 | import android.view.LayoutInflater 15 | import android.view.View 16 | import android.view.ViewGroup 17 | import android.widget.Switch 18 | 19 | class SettingsFragment : Fragment() { 20 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { 21 | return inflater?.inflate(R.layout.fragment_settings, container, false) 22 | } 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | val context = view.context 26 | 27 | val internalStorageSwitch = view.findViewById(R.id.switch_internal_storage) 28 | val externalStorageSwitch = view.findViewById(R.id.switch_external_storage) 29 | val downloadsSwitch = view.findViewById(R.id.switch_downloads) 30 | 31 | internalStorageSwitch.isChecked = LoggingConfig.savingLocation == LoggingConfig.SavingLocation.Internal 32 | externalStorageSwitch.isChecked = LoggingConfig.savingLocation == LoggingConfig.SavingLocation.External 33 | downloadsSwitch.isChecked = LoggingConfig.savingLocation == LoggingConfig.SavingLocation.Downloads 34 | 35 | internalStorageSwitch.setOnClickListener { 36 | LoggingConfig.changeSavingLocation(context, LoggingConfig.SavingLocation.Internal) 37 | internalStorageSwitch.isChecked = true 38 | externalStorageSwitch.isChecked = false 39 | downloadsSwitch.isChecked = false 40 | } 41 | externalStorageSwitch.setOnClickListener { 42 | LoggingConfig.changeSavingLocation(context, LoggingConfig.SavingLocation.External) 43 | internalStorageSwitch.isChecked = false 44 | externalStorageSwitch.isChecked = true 45 | downloadsSwitch.isChecked = false 46 | } 47 | downloadsSwitch.setOnClickListener { 48 | LoggingConfig.changeSavingLocation(context, LoggingConfig.SavingLocation.Downloads) 49 | internalStorageSwitch.isChecked = false 50 | externalStorageSwitch.isChecked = false 51 | downloadsSwitch.isChecked = true 52 | checkPermissions() 53 | } 54 | 55 | view.findViewById(R.id.btn_delete_all_logs).setOnClickListener { 56 | LoggingConfig.SavingLocation.entries.forEach { 57 | try { 58 | LoggingConfig.getSavingPath(context, it).apply { 59 | deleteRecursively() 60 | mkdirs() 61 | } 62 | } catch (_: Exception) { 63 | } 64 | } 65 | } 66 | } 67 | 68 | private fun checkPermissions() { 69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 70 | if (!Environment.isExternalStorageManager()) { 71 | try { 72 | startActivity( 73 | Intent( 74 | Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, 75 | Uri.parse("package:${context.packageName}") 76 | ) 77 | ) 78 | } catch (_: Exception) { 79 | } 80 | } 81 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 82 | val permission = Manifest.permission.WRITE_EXTERNAL_STORAGE 83 | if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { 84 | requestPermissions(arrayOf(permission), 0) 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/process.cpp: -------------------------------------------------------------------------------- 1 | #include "process.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | std::string get_process_name(pid_t pid) { 9 | char process_name[256]; 10 | char path[22]; 11 | 12 | snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); 13 | FILE *file = fopen(path, "r"); 14 | if (!file) { 15 | return ""; 16 | } 17 | if (fgets(process_name, sizeof(process_name), file) == nullptr) { 18 | fclose(file); 19 | return ""; 20 | } 21 | fclose(file); 22 | 23 | process_name[sizeof(process_name) - 1] = '\0'; 24 | 25 | return process_name; 26 | } 27 | 28 | uint64_t get_process_start_time(pid_t pid) { 29 | char path[19]; 30 | snprintf(path, sizeof(path), "/proc/%d/stat", pid); 31 | FILE *file = fopen(path, "r"); 32 | if (!file) return -1; 33 | 34 | static constexpr const char *pattern = 35 | "%c " // state 36 | "%d " // ppid 37 | "%*d " // pgrp 38 | "%*d " // session 39 | "%*d " // tty_nr 40 | "%*d " // tpgid 41 | "%*u " // flags 42 | "%*lu " // minflt 43 | "%*lu " // cminflt 44 | "%*lu " // majflt 45 | "%*lu " // cmajflt 46 | "%*lu " // utime 47 | "%*lu " // stime 48 | "%*ld " // cutime 49 | "%*ld " // cstime 50 | "%*ld " // priority 51 | "%*ld " // nice 52 | "%*ld " // num_threads 53 | "%*ld " // itrealvalue 54 | "%llu " // starttime 55 | ; 56 | 57 | char stat[512]; 58 | if (fgets(stat, sizeof(stat), file) == nullptr) { 59 | fclose(file); 60 | return -1; 61 | } 62 | fclose(file); 63 | 64 | char state = '\0'; 65 | int ppid = 0; 66 | unsigned long long start_time = 0; 67 | const char *end_of_comm = strrchr(stat, ')'); 68 | const int rc = sscanf(end_of_comm + 2, pattern, &state, &ppid, &start_time); 69 | return rc == 3 ? start_time : -1; 70 | } 71 | 72 | uint64_t get_process_uptime(pid_t pid) { 73 | struct sysinfo si{}; 74 | sysinfo(&si); 75 | const long uptime = si.uptime; 76 | if (uptime == -1) return -1; 77 | 78 | const long clock_ticks = sysconf(_SC_CLK_TCK); 79 | const uint64_t start_time = get_process_start_time(pid); 80 | if (start_time == -1) return -1; 81 | 82 | return uptime - (start_time / clock_ticks); 83 | } 84 | 85 | void get_process_tids(pid_t pid, std::vector &tids) { 86 | char path[22]; 87 | snprintf(path, sizeof(path), "/proc/%d/task", pid); 88 | DIR *dir = opendir(path); 89 | if (!dir) return; 90 | 91 | struct dirent *entry; 92 | while ((entry = readdir(dir))) { 93 | if (entry->d_name[0] == '.') continue; 94 | tids.emplace_back(atoi(entry->d_name)); 95 | } 96 | 97 | closedir(dir); 98 | } 99 | 100 | void print_process_status(LogRecord &record, pid_t pid) { 101 | char path[21]; 102 | snprintf(path, sizeof(path), "/proc/%d/status", pid); 103 | FILE *file = fopen(path, "r"); 104 | if (!file) return; 105 | 106 | LOG_FISHNET("Process status for pid %d:", pid); 107 | std::string status; 108 | char line[256]; 109 | while (fgets(line, sizeof(line), file) != nullptr) { 110 | status += " "; 111 | status += line; 112 | } 113 | fclose(file); 114 | 115 | LOG_FISHNET("%s", status.c_str()); 116 | } 117 | 118 | void print_memory_info(LogRecord &record) { 119 | FILE *file = fopen("/proc/meminfo", "r"); 120 | if (!file) return; 121 | 122 | LOG_FISHNET("Memory info:"); 123 | std::string status; 124 | char line[256]; 125 | while (fgets(line, sizeof(line), file) != nullptr) { 126 | status += " "; 127 | status += line; 128 | } 129 | fclose(file); 130 | 131 | LOG_FISHNET("%s", status.c_str()); 132 | } 133 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Android 10 | 11 | 12 | Android Lint: Correctness 13 | 14 | 15 | Android Lint: Performance 16 | 17 | 18 | Android Lint: Security 19 | 20 | 21 | ClassNaming conventionsJava 22 | 23 | 24 | Code style issuesJava 25 | 26 | 27 | CodePlugin DevKit 28 | 29 | 30 | Compiler issuesJava 31 | 32 | 33 | Error handlingJava 34 | 35 | 36 | General 37 | 38 | 39 | Google Cloud Endpoints 40 | 41 | 42 | Groovy 43 | 44 | 45 | InternationalizationJava 46 | 47 | 48 | JSON and JSON5 49 | 50 | 51 | JVM languages 52 | 53 | 54 | Java 55 | 56 | 57 | Java 15Java language level migration aidsJava 58 | 59 | 60 | Java 16Java language level migration aidsJava 61 | 62 | 63 | Java language level migration aidsJava 64 | 65 | 66 | JavadocJava 67 | 68 | 69 | Kotlin 70 | 71 | 72 | LintAndroid 73 | 74 | 75 | Manifest 76 | 77 | 78 | Naming conventionsJava 79 | 80 | 81 | Numeric issuesJava 82 | 83 | 84 | PerformanceJava 85 | 86 | 87 | Plugin DevKit 88 | 89 | 90 | PortabilityJava 91 | 92 | 93 | Potentially confusing code constructsGroovy 94 | 95 | 96 | Probable bugsJava 97 | 98 | 99 | Probable bugsKotlin 100 | 101 | 102 | Properties files 103 | 104 | 105 | Redundant constructsKotlin 106 | 107 | 108 | Resource managementJava 109 | 110 | 111 | Screen sizesLintAndroid 112 | 113 | 114 | Style issuesKotlin 115 | 116 | 117 | Test frameworksJVM languages 118 | 119 | 120 | 121 | 122 | User defined 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 134 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/cause.cpp: -------------------------------------------------------------------------------- 1 | #include "cause.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "abi.h" 7 | 8 | static std::optional get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp, unwindstack::Maps *maps) { 9 | // Under stack MTE the stack pointer and/or the fault address can be tagged. 10 | // In order to calculate deltas between them, strip off the tags off both 11 | // addresses. 12 | fault_addr = untag_address(fault_addr); 13 | sp = untag_address(sp); 14 | static constexpr uint64_t kMaxDifferenceBytes = 256; 15 | uint64_t difference; 16 | if (sp >= fault_addr) { 17 | difference = sp - fault_addr; 18 | } else { 19 | difference = fault_addr - sp; 20 | } 21 | if (difference <= kMaxDifferenceBytes) { 22 | // The faulting address is close to the current sp, check if the sp 23 | // indicates a stack overflow. 24 | // On arm, the sp does not get updated when the instruction faults. 25 | // In this case, the sp will still be in a valid map, which is the 26 | // last case below. 27 | // On aarch64, the sp does get updated when the instruction faults. 28 | // In this case, the sp will be in either an invalid map if triggered 29 | // on the main thread, or in a guard map if in another thread, which 30 | // will be the first case or second case from below. 31 | std::shared_ptr map_info = maps->Find(sp); 32 | if (map_info == nullptr) { 33 | return "stack pointer is in a non-existent map; likely due to stack overflow."; 34 | } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) { 35 | return "stack pointer is not in a rw map; likely due to stack overflow."; 36 | } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) { 37 | return "stack pointer is close to top of stack; likely stack overflow."; 38 | } 39 | } 40 | return {}; 41 | } 42 | 43 | void dump_probable_cause(LogRecord &record, const siginfo_t *info, unwindstack::Maps *maps, 44 | const std::unique_ptr ®s) { 45 | const auto fault_addr = (const uint64_t) (info->si_addr); 46 | 47 | std::optional cause; 48 | if (info->si_signo == SIGSEGV && info->si_code == SEGV_MAPERR) { 49 | if (fault_addr < 4096) { 50 | cause = "null pointer dereference"; 51 | } else if (fault_addr == 0xffff0ffc) { 52 | cause = "call to kuser_helper_version"; 53 | } else if (fault_addr == 0xffff0fe0) { 54 | cause = "call to kuser_get_tls"; 55 | } else if (fault_addr == 0xffff0fc0) { 56 | cause = "call to kuser_cmpxchg"; 57 | } else if (fault_addr == 0xffff0fa0) { 58 | cause = "call to kuser_memory_barrier"; 59 | } else if (fault_addr == 0xffff0f60) { 60 | cause = "call to kuser_cmpxchg64"; 61 | } else { 62 | cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps); 63 | } 64 | } else if (info->si_signo == SIGSEGV && info->si_code == SEGV_ACCERR) { 65 | auto map_info = maps->Find(fault_addr); 66 | if (map_info != nullptr && map_info->flags() == PROT_EXEC) { 67 | cause = "execute-only (no-read) memory access error; likely due to data in .text."; 68 | } else if (fault_addr == regs->pc() && map_info != nullptr && (map_info->flags() & PROT_EXEC) == 0) { 69 | cause = "trying to execute non-executable memory."; 70 | } else { 71 | cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps); 72 | } 73 | } 74 | #if defined(__aarch64__) && defined(SEGV_MTESERR) 75 | else if (info->si_signo == SIGSEGV && info->si_code == SEGV_MTESERR) { 76 | // If this was a heap MTE crash, it would have been handled by scudo. Checking whether it 77 | // is a stack one. 78 | // cause = maybe_stack_mte_cause(tombstone, unwinder, target_thread, threads, fault_addr); 79 | } 80 | #endif 81 | else if (info->si_signo == SIGSYS && info->si_code == SYS_SECCOMP) { 82 | cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING, info->si_syscall); 83 | } 84 | 85 | if (cause) { 86 | LOG_FISHNET("Cause: %s", cause->c_str()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "clock.h" 8 | 9 | #define ENABLE_LOG 0 10 | 11 | static std::string log_path{}; 12 | 13 | static ApkInfo apk_info{}; 14 | 15 | void set_log_path(const char *path) { 16 | log_path = path; 17 | } 18 | 19 | void set_apk_info(const ApkInfo &info) { 20 | apk_info = info; 21 | } 22 | 23 | const ApkInfo &get_apk_info() { 24 | return apk_info; 25 | } 26 | 27 | const char *log_type_to_string(LogType type) { 28 | switch (type) { 29 | case None: 30 | return "None"; 31 | case Native: 32 | return "Native"; 33 | case Java: 34 | return "Java"; 35 | case ANR: 36 | return "ANR"; 37 | } 38 | return "Unknown"; 39 | } 40 | 41 | static std::string log_type_to_string_lowercase(LogType type) { 42 | switch (type) { 43 | case None: 44 | return "none"; 45 | case Native: 46 | return "native"; 47 | case Java: 48 | return "java"; 49 | case ANR: 50 | return "anr"; 51 | } 52 | return "unknown"; 53 | } 54 | 55 | LogRecord start_recording(LogType type) { 56 | return { 57 | .type = type, 58 | .timestamp = get_timestamp(), 59 | }; 60 | } 61 | 62 | void write_log(const LogRecord &record) { 63 | if (log_path.empty()) { 64 | return; 65 | } 66 | std::string timestamp = record.timestamp; 67 | std::replace(timestamp.begin(), timestamp.end(), ' ', '_'); 68 | std::replace(timestamp.begin(), timestamp.end(), ':', '-'); 69 | std::string path = log_path + '/' + log_type_to_string_lowercase(record.type) + '_' + timestamp + ".log"; 70 | FILE *file = fopen(path.c_str(), "w"); 71 | if (file == nullptr) { 72 | LOGE("Failed to open %s, %s", path.c_str(), strerror(errno)); 73 | return; 74 | } 75 | LOGE("Write log to %s", path.c_str()); 76 | fwrite(record.content.c_str(), 1, record.content.size(), file); 77 | fclose(file); 78 | } 79 | 80 | void StringAppendV(std::string *dst, const char *format, va_list ap) { 81 | // First try with a small fixed size buffer 82 | char space[256] __attribute__((__uninitialized__)); 83 | 84 | // It's possible for methods that use a va_list to invalidate 85 | // the data in it upon use. The fix is to make a copy 86 | // of the structure before using it and use that copy instead. 87 | va_list backup_ap; 88 | va_copy(backup_ap, ap); 89 | int result = vsnprintf(space, sizeof(space), format, backup_ap); 90 | va_end(backup_ap); 91 | 92 | if (result < sizeof(space)) { 93 | if (result >= 0) { 94 | // Normal case -- everything fit. 95 | dst->append(space, result); 96 | return; 97 | } 98 | 99 | // Just an error. 100 | return; 101 | } 102 | 103 | // Increase the buffer size to the size requested by vsnprintf, 104 | // plus one for the closing \0. 105 | const int length = result + 1; 106 | char *buf = new char[length]; 107 | 108 | // Restore the va_list before we use it again 109 | va_copy(backup_ap, ap); 110 | result = vsnprintf(buf, length, format, backup_ap); 111 | va_end(backup_ap); 112 | 113 | if (result >= 0 && result < length) { 114 | // It fit 115 | dst->append(buf, result); 116 | } 117 | delete[] buf; 118 | } 119 | 120 | std::string StringPrintf(const char *fmt, ...) { 121 | va_list ap; 122 | va_start(ap, fmt); 123 | std::string result; 124 | StringAppendV(&result, fmt, ap); 125 | va_end(ap); 126 | return result; 127 | } 128 | 129 | void StringAppendF(std::string *dst, const char *format, ...) { 130 | va_list ap; 131 | va_start(ap, format); 132 | StringAppendV(dst, format, ap); 133 | va_end(ap); 134 | } 135 | 136 | void log_fishnet(LogRecord &record, bool linebreak, const char *fmt, ...) { 137 | va_list args; 138 | va_start(args, fmt); 139 | #if ENABLE_LOG 140 | const size_t next_start = record.buffer.size(); 141 | #endif 142 | StringAppendV(&record.content, strdup(fmt), args); 143 | va_end(args); 144 | #if ENABLE_LOG 145 | __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s", record.buffer.c_str() + next_start); 146 | #endif 147 | if (linebreak) { 148 | record.content += '\n'; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/kyant/fishnet/demo/LogsFragment.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") 2 | 3 | package com.kyant.fishnet.demo 4 | 5 | import android.app.Fragment 6 | import android.content.Context 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.text.PrecomputedText 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import android.widget.AdapterView 14 | import android.widget.ArrayAdapter 15 | import android.widget.Spinner 16 | import android.widget.TextView 17 | import kotlin.concurrent.thread 18 | import kotlin.math.roundToInt 19 | 20 | class LogsFragment : Fragment() { 21 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { 22 | return inflater?.inflate(R.layout.fragment_logs, container, false) 23 | } 24 | 25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 26 | val context = view.context 27 | 28 | val progressBar = view.findViewById(R.id.progress_bar) 29 | val spinner = view.findViewById(R.id.spinner_logs) 30 | val scrollView = view.findViewById(R.id.scroll_view) 31 | val horizontalScrollView = view.findViewById(R.id.horizontal_scroll_view) 32 | val logTextView = view.findViewById(R.id.tv_log) 33 | 34 | val logs = LoggingConfig.getLogs() 35 | if (logs.isNotEmpty()) { 36 | spinner.dropDownVerticalOffset = (42 * resources.displayMetrics.density).roundToInt() 37 | spinner.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> 38 | (spinner.getChildAt(0) as TextView).apply { 39 | val paddingStart = (16 * resources.displayMetrics.density).roundToInt() 40 | if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { 41 | setPadding(0, 0, paddingStart, 0) 42 | } else { 43 | setPadding(paddingStart, 0, 0, 0) 44 | } 45 | textSize = 13f 46 | } 47 | spinner.dropDownWidth = spinner.width 48 | } 49 | spinner.adapter = LogAdapter(context, android.R.layout.simple_spinner_dropdown_item).apply { 50 | addAll(logs.map { it.name }) 51 | } 52 | spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { 53 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { 54 | progressBar.visibility = View.VISIBLE 55 | logTextView.visibility = View.INVISIBLE 56 | thread { 57 | val log = try { 58 | logs[position].readText() 59 | } catch (e: Exception) { 60 | "Failed to read log file: ${e.message}" 61 | } 62 | val precomputedText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 63 | PrecomputedText.create(log, logTextView.textMetricsParams) 64 | } else { 65 | log 66 | } 67 | logTextView.post { 68 | progressBar.visibility = View.GONE 69 | logTextView.text = precomputedText 70 | scrollView.scrollTo(0, 0) 71 | horizontalScrollView.scrollTo(0, 0) 72 | logTextView.visibility = View.VISIBLE 73 | } 74 | } 75 | } 76 | 77 | override fun onNothingSelected(parent: AdapterView<*>?) { 78 | } 79 | } 80 | } else { 81 | progressBar.visibility = View.GONE 82 | spinner.visibility = View.GONE 83 | logTextView.text = "No logs found." 84 | } 85 | } 86 | 87 | private class LogAdapter(context: Context, resource: Int) : ArrayAdapter(context, resource) { 88 | override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { 89 | return super.getDropDownView(position, convertView, parent).apply { 90 | findViewById(android.R.id.text1).apply { 91 | text = getItem(position) 92 | textSize = 14f 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/anr_signal_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "anr_signal_handler.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "log.h" 12 | #include "dump.h" 13 | 14 | static sigset_t old_set; 15 | static struct sigaction old_action; 16 | 17 | static JavaVM *g_vm = nullptr; 18 | static jclass exception_handler_class = nullptr; 19 | 20 | void init_anr_signal_handler(JavaVM *vm, JNIEnv *env) { 21 | sigset_t sig_sets; 22 | sigemptyset(&sig_sets); 23 | sigaddset(&sig_sets, SIGQUIT); 24 | pthread_sigmask(SIG_UNBLOCK, &sig_sets, &old_set); 25 | 26 | struct sigaction action{}; 27 | sigfillset(&action.sa_mask); 28 | action.sa_flags = SA_RESTART | SA_ONSTACK | SA_SIGINFO; 29 | action.sa_sigaction = anr_signal_handler; 30 | sigaction(SIGQUIT, &action, &old_action); 31 | 32 | g_vm = vm; 33 | 34 | jclass _exception_handler_class = env->FindClass("com/kyant/fishnet/JavaExceptionHandler"); 35 | exception_handler_class = (jclass) env->NewGlobalRef(_exception_handler_class); 36 | env->DeleteLocalRef(_exception_handler_class); 37 | } 38 | 39 | void deinit_anr_signal_handler() { 40 | pthread_sigmask(SIG_SETMASK, &old_set, nullptr); 41 | sigaction(SIGQUIT, &old_action, nullptr); 42 | 43 | JNIEnv *env; 44 | if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { 45 | g_vm = nullptr; 46 | return; 47 | } 48 | 49 | env->DeleteGlobalRef(exception_handler_class); 50 | 51 | g_vm = nullptr; 52 | } 53 | 54 | static int get_signal_catcher_tid(pid_t myPid) { 55 | char task[19]; 56 | snprintf(task, sizeof(task), "/proc/%d/task", myPid); 57 | DIR *processDir = opendir(task); 58 | if (!processDir) { 59 | return -1; 60 | } 61 | 62 | int tid = -1; 63 | char path[32]; 64 | char name[16]; 65 | const dirent *child; 66 | constexpr char signal_catcher[] = "Signal Catcher"; 67 | while ((child = readdir(processDir)) != nullptr) { 68 | snprintf(path, sizeof(path), "%s/%s/comm", task, child->d_name); 69 | const int fd = open(path, O_RDONLY); 70 | ssize_t size = read(fd, name, sizeof(name)); 71 | close(fd); 72 | if (size <= 0) { 73 | continue; 74 | } 75 | 76 | name[size - 1] = '\0'; 77 | if (strcmp(name, signal_catcher) == 0) { 78 | tid = atoi(child->d_name); 79 | break; 80 | } 81 | } 82 | closedir(processDir); 83 | return tid; 84 | } 85 | 86 | static void *fishnet_anr_dispatch_thread(void *arg) { 87 | const auto *info = (const DebuggerThreadInfo *) arg; 88 | 89 | JNIEnv *env; 90 | jint status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_6); 91 | 92 | if (status == JNI_EDETACHED) { 93 | status = g_vm->AttachCurrentThread(&env, nullptr); 94 | if (status != JNI_OK) { 95 | return nullptr; 96 | } 97 | } else if (status != JNI_OK) { 98 | return nullptr; 99 | } 100 | 101 | jmethodID dump_method = env->GetStaticMethodID(exception_handler_class, "dumpJavaThreads", 102 | "()Ljava/lang/String;"); 103 | auto thread_dump = (jstring) env->CallStaticObjectMethod(exception_handler_class, dump_method); 104 | 105 | const char *thread_dump_chars = env->GetStringUTFChars(thread_dump, nullptr); 106 | fishnet_dump_anr(thread_dump_chars, info); 107 | env->ReleaseStringUTFChars(thread_dump, thread_dump_chars); 108 | 109 | g_vm->DetachCurrentThread(); 110 | 111 | return nullptr; 112 | } 113 | 114 | void anr_signal_handler(int signal_number, siginfo_t *info, void *context) { 115 | const int from_pid_1 = info->_si_pad[3]; 116 | const int from_pid_2 = info->_si_pad[4]; 117 | const int pid = getpid(); 118 | 119 | LOGE("Received ANR signal from_pid_1: %d, from_pid_2: %d, pid: %d", from_pid_1, from_pid_2, pid); 120 | 121 | if (from_pid_1 != pid && from_pid_2 != pid) { 122 | DebuggerThreadInfo thread_info = { 123 | .crashing_tid = gettid(), 124 | .siginfo = info, 125 | .ucontext = context, 126 | }; 127 | 128 | pthread_t thread_id; 129 | if (pthread_create(&thread_id, nullptr, fishnet_anr_dispatch_thread, &thread_info) != 0) { 130 | goto resend; 131 | } 132 | pthread_join(thread_id, nullptr); 133 | } 134 | 135 | resend: 136 | const int sc_tid = get_signal_catcher_tid(pid); 137 | if (sc_tid != -1) { 138 | syscall(SYS_tgkill, pid, sc_tid, SIGQUIT); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /fishnet/src/main/java/com/kyant/fishnet/JavaExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.kyant.fishnet; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.Map; 6 | 7 | final class JavaExceptionHandler { 8 | private JavaExceptionHandler() { 9 | } 10 | 11 | private static final String TAG = "Fishnet"; 12 | private static final Object lock = new Object(); 13 | 14 | private static Thread.UncaughtExceptionHandler defaultHandler = null; 15 | private static Thread.UncaughtExceptionHandler handler = null; 16 | 17 | public static void init() { 18 | synchronized (lock) { 19 | if (handler == null) { 20 | defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 21 | handler = new FishnetUncaughtExceptionHandler(); 22 | } 23 | Thread.setDefaultUncaughtExceptionHandler(handler); 24 | } 25 | } 26 | 27 | private static final class FishnetUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 28 | @Override 29 | public void uncaughtException(Thread t, Throwable e) { 30 | if (defaultHandler != null) { 31 | Thread.setDefaultUncaughtExceptionHandler(defaultHandler); 32 | } 33 | 34 | try { 35 | dumpCrash(t, e); 36 | } catch (Exception exception) { 37 | Log.e(TAG, "Error while dumping Java crash", exception); 38 | } finally { 39 | if (defaultHandler != null) { 40 | defaultHandler.uncaughtException(t, e); 41 | } 42 | defaultHandler = null; 43 | handler = null; 44 | } 45 | } 46 | } 47 | 48 | private static void dumpCrash(Thread t, Throwable e) { 49 | StringBuilder sb = new StringBuilder(1024); 50 | sb.append(" 💥 ").append(e); 51 | StackTraceElement[] crashingThreadStackTrace = e.getStackTrace(); 52 | if (crashingThreadStackTrace.length > 0) { 53 | sb.append("\n ").append(getStackTraceString(e.getStackTrace())); 54 | } 55 | Throwable cause = e.getCause(); 56 | if (cause != null) { 57 | sb.append('\n'); 58 | while (cause != null) { 59 | sb.append(" 💥 Caused by: ").append(cause); 60 | StackTraceElement[] causeStackTrace = cause.getStackTrace(); 61 | if (causeStackTrace.length > 0) { 62 | sb.append("\n ").append(getStackTraceString(causeStackTrace)); 63 | } else { 64 | sb.append('\n'); 65 | } 66 | cause = cause.getCause(); 67 | } 68 | } 69 | NativeSignalHandler.dumpJavaCrash( 70 | " 🧵Crashing thread: " + toLogString(t) + '\n' + sb + '\n' + 71 | getAllStackTracesExcept(t) 72 | ); 73 | } 74 | 75 | private static String getStackTraceString(StackTraceElement[] stackTrace) { 76 | if (stackTrace.length == 0) { 77 | return "(empty stack trace)\n"; 78 | } 79 | StringBuilder sb = new StringBuilder(512); 80 | for (int i = 0, stackTraceLength = stackTrace.length; i < stackTraceLength; i++) { 81 | StackTraceElement element = stackTrace[i]; 82 | sb.append("at ").append(element); 83 | if (i < stackTraceLength - 1) { 84 | sb.append("\n "); 85 | } 86 | } 87 | sb.append('\n'); 88 | return sb.toString(); 89 | } 90 | 91 | private static String getAllStackTracesExcept(Thread excludedThread) { 92 | StringBuilder sb = new StringBuilder(128); 93 | for (Map.Entry entry : Thread.getAllStackTraces().entrySet()) { 94 | Thread t = entry.getKey(); 95 | StackTraceElement[] stackTrace = entry.getValue(); 96 | if (t.getId() != excludedThread.getId()) { 97 | sb.append(" 🧵Thread: ").append(toLogString(t)).append("\n "); 98 | sb.append(getStackTraceString(stackTrace)); 99 | sb.append('\n'); 100 | } 101 | } 102 | int len = sb.length(); 103 | if (len > 0) { 104 | sb.deleteCharAt(len - 1); 105 | } 106 | return sb.toString(); 107 | } 108 | 109 | /** 110 | * @noinspection unused 111 | */ 112 | private static String dumpJavaThreads() { 113 | return getAllStackTracesExcept(Thread.currentThread()); 114 | } 115 | 116 | private static String toLogString(Thread t) { 117 | return "\"" + t.getName() + "\" " + (t.isDaemon() ? "daemon " : "") + 118 | "prio=" + t.getPriority() + " id=" + t.getId() + " state=" + t.getState(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 21 | 22 | 32 | 33 | 43 | 44 | 54 | 55 | 62 | 63 | 70 | 71 | 78 | 79 | 89 | 90 | 100 | 101 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/dump.cpp: -------------------------------------------------------------------------------- 1 | #include "dump.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "abi.h" 9 | #include "human_readable.h" 10 | #include "logcat.h" 11 | #include "process.h" 12 | #include "tasks.h" 13 | #include "thread.h" 14 | #include "backtrace.h" 15 | #include "fds.h" 16 | #include "anr_signal_handler.h" 17 | #include "log_header.h" 18 | #include "abort_message.h" 19 | 20 | void fishnet_dump_native(const DebuggerThreadInfo *info) { 21 | const pid_t pid = getpid(); 22 | const pid_t tid = info->crashing_tid; 23 | const pid_t the_tid = gettid(); 24 | const uid_t uid = getuid(); 25 | 26 | FISHNET_RECORD(Native); 27 | 28 | std::shared_ptr process_memory = unwindstack::Memory::CreateProcessMemoryCached(pid); 29 | unwindstack::AndroidLocalUnwinder unwinder(process_memory); 30 | unwindstack::ErrorData error{}; 31 | if (!unwinder.Initialize(error)) { 32 | LOGE("Failed to init unwinder: %s", unwindstack::GetErrorCodeString(error.code)); 33 | return; 34 | } 35 | 36 | constexpr unwindstack::ArchEnum arch = current_arch(); 37 | constexpr int word_size = pointer_width(); 38 | std::unique_ptr regs(unwindstack::Regs::CreateFromUcontext(arch, info->ucontext)); 39 | unwindstack::AndroidUnwinderData data{}; 40 | unwinder.Unwind(regs.get(), data); 41 | 42 | unwindstack::ThreadUnwinder thread_unwinder(128); 43 | if (!thread_unwinder.Init()) { 44 | LOGE("Failed to init thread unwinder"); 45 | return; 46 | } 47 | 48 | std::vector tids; 49 | get_process_tids(pid, tids); 50 | 51 | get_scudo_message_if_needed(arch, regs, data.frames); 52 | 53 | dump_logcat(pid); 54 | try_read_abort_message_from_logcat(pid); 55 | 56 | print_log_header(record, LogType::Native, pid); 57 | 58 | print_main_thread(record, pid, tid, uid, info->siginfo, word_size, arch, &unwinder, regs, data.frames, 59 | true, false); 60 | 61 | print_memory_info(record); 62 | 63 | print_process_status(record, pid); 64 | 65 | print_tasks(record, pid); 66 | 67 | for (const pid_t &thread_id: tids) { 68 | if (thread_id == tid || thread_id == the_tid) { 69 | continue; 70 | } 71 | LOG_FISHNET("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); 72 | print_thread(record, thread_id, word_size, arch, &thread_unwinder, false); 73 | } 74 | 75 | dump_open_fds(record, pid); 76 | 77 | print_logs(record); 78 | 79 | FISHNET_WRITE(); 80 | } 81 | 82 | void fishnet_dump_java(const char *java_stack_traces) { 83 | const pid_t pid = getpid(); 84 | const pid_t tid = gettid(); 85 | const uid_t uid = getuid(); 86 | 87 | FISHNET_RECORD(Java); 88 | 89 | constexpr unwindstack::ArchEnum arch = current_arch(); 90 | constexpr int word_size = pointer_width(); 91 | 92 | unwindstack::ThreadUnwinder thread_unwinder(128); 93 | if (!thread_unwinder.Init()) { 94 | LOGE("Failed to init thread unwinder"); 95 | return; 96 | } 97 | 98 | std::vector tids; 99 | get_process_tids(pid, tids); 100 | 101 | dump_logcat(pid); 102 | 103 | print_log_header(record, LogType::Java, pid); 104 | 105 | print_main_thread_header(record, pid, tid, uid); 106 | LOG_FISHNET(""); 107 | 108 | if (java_stack_traces != nullptr) { 109 | LOG_FISHNET("Java stack traces:"); 110 | LOG_FISHNET("%s", java_stack_traces); 111 | } 112 | 113 | print_memory_info(record); 114 | 115 | print_process_status(record, pid); 116 | 117 | print_tasks(record, pid); 118 | 119 | for (const pid_t &thread_id: tids) { 120 | if (thread_id == tid) { 121 | continue; 122 | } 123 | LOG_FISHNET("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); 124 | print_thread(record, thread_id, word_size, arch, &thread_unwinder, false); 125 | } 126 | 127 | dump_open_fds(record, pid); 128 | 129 | print_logs(record); 130 | 131 | FISHNET_WRITE(); 132 | } 133 | 134 | void fishnet_dump_anr(const char *java_stack_traces, const DebuggerThreadInfo *info) { 135 | const pid_t pid = getpid(); 136 | const pid_t tid = info->crashing_tid; 137 | const pid_t the_tid = gettid(); 138 | const uid_t uid = getuid(); 139 | 140 | FISHNET_RECORD(ANR); 141 | 142 | constexpr unwindstack::ArchEnum arch = current_arch(); 143 | constexpr int word_size = pointer_width(); 144 | 145 | unwindstack::ThreadUnwinder thread_unwinder(128); 146 | if (!thread_unwinder.Init()) { 147 | LOGE("Failed to init thread unwinder"); 148 | return; 149 | } 150 | 151 | std::vector tids; 152 | get_process_tids(pid, tids); 153 | 154 | dump_logcat(pid); 155 | 156 | print_log_header(record, LogType::ANR, pid); 157 | 158 | print_main_thread_header(record, pid, tid, uid); 159 | LOG_FISHNET(""); 160 | 161 | if (java_stack_traces != nullptr) { 162 | LOG_FISHNET("Java stack traces:"); 163 | LOG_FISHNET("%s", java_stack_traces); 164 | } 165 | 166 | print_memory_info(record); 167 | 168 | print_process_status(record, pid); 169 | 170 | print_tasks(record, pid); 171 | 172 | for (const pid_t &thread_id: tids) { 173 | if (thread_id == pid) { 174 | continue; 175 | } 176 | if (thread_id == tid || thread_id == the_tid) { 177 | continue; 178 | } 179 | LOG_FISHNET("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); 180 | print_thread(record, thread_id, word_size, arch, &thread_unwinder, false); 181 | } 182 | 183 | dump_open_fds(record, pid); 184 | 185 | print_logs(record); 186 | 187 | FISHNET_WRITE(); 188 | } 189 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/libiberty/safe-ctype.h: -------------------------------------------------------------------------------- 1 | /* replacement macros. 2 | Copyright (C) 2000-2024 Free Software Foundation, Inc. 3 | Contributed by Zack Weinberg . 4 | This file is part of the libiberty library. 5 | Libiberty is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License as published by the Free Software Foundation; either 8 | version 2 of the License, or (at your option) any later version. 9 | Libiberty is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | You should have received a copy of the GNU Library General Public 14 | License along with libiberty; see the file COPYING.LIB. If 15 | not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, 16 | Boston, MA 02110-1301, USA. */ 17 | /* This is a compatible replacement of the standard C library's 18 | with the following properties: 19 | - Implements all isxxx() macros required by C99. 20 | - Also implements some character classes useful when 21 | parsing C-like languages. 22 | - Does not change behavior depending on the current locale. 23 | - Behaves properly for all values in the range of a signed or 24 | unsigned char. 25 | To avoid conflicts, this header defines the isxxx functions in upper 26 | case, e.g. ISALPHA not isalpha. */ 27 | #ifndef SAFE_CTYPE_H 28 | #define SAFE_CTYPE_H 29 | /* Determine host character set. */ 30 | #define HOST_CHARSET_UNKNOWN 0 31 | #define HOST_CHARSET_ASCII 1 32 | #define HOST_CHARSET_EBCDIC 2 33 | #if '\n' == 0x0A && ' ' == 0x20 && '0' == 0x30 \ 34 | && 'A' == 0x41 && 'a' == 0x61 && '!' == 0x21 35 | # define HOST_CHARSET HOST_CHARSET_ASCII 36 | #else 37 | # if '\n' == 0x15 && ' ' == 0x40 && '0' == 0xF0 \ 38 | && 'A' == 0xC1 && 'a' == 0x81 && '!' == 0x5A 39 | # define HOST_CHARSET HOST_CHARSET_EBCDIC 40 | # else 41 | # define HOST_CHARSET HOST_CHARSET_UNKNOWN 42 | # endif 43 | #endif 44 | /* Categories. */ 45 | enum { 46 | /* In C99 */ 47 | _sch_isblank = 0x0001, /* space \t */ 48 | _sch_iscntrl = 0x0002, /* nonprinting characters */ 49 | _sch_isdigit = 0x0004, /* 0-9 */ 50 | _sch_islower = 0x0008, /* a-z */ 51 | _sch_isprint = 0x0010, /* any printing character including ' ' */ 52 | _sch_ispunct = 0x0020, /* all punctuation */ 53 | _sch_isspace = 0x0040, /* space \t \n \r \f \v */ 54 | _sch_isupper = 0x0080, /* A-Z */ 55 | _sch_isxdigit = 0x0100, /* 0-9A-Fa-f */ 56 | /* Extra categories useful to cpplib. */ 57 | _sch_isidst = 0x0200, /* A-Za-z_ */ 58 | _sch_isvsp = 0x0400, /* \n \r */ 59 | _sch_isnvsp = 0x0800, /* space \t \f \v \0 */ 60 | /* Combinations of the above. */ 61 | _sch_isalpha = _sch_isupper|_sch_islower, /* A-Za-z */ 62 | _sch_isalnum = _sch_isalpha|_sch_isdigit, /* A-Za-z0-9 */ 63 | _sch_isidnum = _sch_isidst|_sch_isdigit, /* A-Za-z0-9_ */ 64 | _sch_isgraph = _sch_isalnum|_sch_ispunct, /* isprint and not space */ 65 | _sch_iscppsp = _sch_isvsp|_sch_isnvsp, /* isspace + \0 */ 66 | _sch_isbasic = _sch_isprint|_sch_iscppsp /* basic charset of ISO C 67 | (plus ` and @) */ 68 | }; 69 | /* Character classification. */ 70 | extern const unsigned short _sch_istable[256]; 71 | #define _sch_test(c, bit) (_sch_istable[(c) & 0xff] & (unsigned short)(bit)) 72 | #define ISALPHA(c) _sch_test(c, _sch_isalpha) 73 | #define ISALNUM(c) _sch_test(c, _sch_isalnum) 74 | #define ISBLANK(c) _sch_test(c, _sch_isblank) 75 | #define ISCNTRL(c) _sch_test(c, _sch_iscntrl) 76 | #define ISDIGIT(c) _sch_test(c, _sch_isdigit) 77 | #define ISGRAPH(c) _sch_test(c, _sch_isgraph) 78 | #define ISLOWER(c) _sch_test(c, _sch_islower) 79 | #define ISPRINT(c) _sch_test(c, _sch_isprint) 80 | #define ISPUNCT(c) _sch_test(c, _sch_ispunct) 81 | #define ISSPACE(c) _sch_test(c, _sch_isspace) 82 | #define ISUPPER(c) _sch_test(c, _sch_isupper) 83 | #define ISXDIGIT(c) _sch_test(c, _sch_isxdigit) 84 | #define ISIDNUM(c) _sch_test(c, _sch_isidnum) 85 | #define ISIDST(c) _sch_test(c, _sch_isidst) 86 | #define IS_ISOBASIC(c) _sch_test(c, _sch_isbasic) 87 | #define IS_VSPACE(c) _sch_test(c, _sch_isvsp) 88 | #define IS_NVSPACE(c) _sch_test(c, _sch_isnvsp) 89 | #define IS_SPACE_OR_NUL(c) _sch_test(c, _sch_iscppsp) 90 | /* Character transformation. */ 91 | extern const unsigned char _sch_toupper[256]; 92 | extern const unsigned char _sch_tolower[256]; 93 | #define TOUPPER(c) _sch_toupper[(c) & 0xff] 94 | #define TOLOWER(c) _sch_tolower[(c) & 0xff] 95 | /* Prevent the users of safe-ctype.h from accidentally using the routines 96 | from ctype.h. Initially, the approach was to produce an error when 97 | detecting that ctype.h has been included. But this was causing 98 | trouble as ctype.h might get indirectly included as a result of 99 | including another system header (for instance gnulib's stdint.h). 100 | So we include ctype.h here and then immediately redefine its macros. */ 101 | #include 102 | #undef isalpha 103 | #define isalpha(c) do_not_use_isalpha_with_safe_ctype 104 | #undef isalnum 105 | #define isalnum(c) do_not_use_isalnum_with_safe_ctype 106 | #undef iscntrl 107 | #define iscntrl(c) do_not_use_iscntrl_with_safe_ctype 108 | #undef isdigit 109 | #define isdigit(c) do_not_use_isdigit_with_safe_ctype 110 | #undef isgraph 111 | #define isgraph(c) do_not_use_isgraph_with_safe_ctype 112 | #undef islower 113 | #define islower(c) do_not_use_islower_with_safe_ctype 114 | #undef isprint 115 | #define isprint(c) do_not_use_isprint_with_safe_ctype 116 | #undef ispunct 117 | #define ispunct(c) do_not_use_ispunct_with_safe_ctype 118 | #undef isspace 119 | #define isspace(c) do_not_use_isspace_with_safe_ctype 120 | #undef isupper 121 | #define isupper(c) do_not_use_isupper_with_safe_ctype 122 | #undef isxdigit 123 | #define isxdigit(c) do_not_use_isxdigit_with_safe_ctype 124 | #undef toupper 125 | #define toupper(c) do_not_use_toupper_with_safe_ctype 126 | #undef tolower 127 | #define tolower(c) do_not_use_tolower_with_safe_ctype 128 | #endif /* SAFE_CTYPE_H */ 129 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_testing_crash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 21 | 22 | 32 | 33 | 43 | 44 | 54 | 55 | 65 | 66 | 76 | 77 | 87 | 88 | 98 | 99 | 109 | 110 | 120 | 121 | 131 | 132 | 142 | 143 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/libiberty/cp-demangle.h: -------------------------------------------------------------------------------- 1 | /* Internal demangler interface for g++ V3 ABI. 2 | Copyright (C) 2003-2024 Free Software Foundation, Inc. 3 | Written by Ian Lance Taylor . 4 | This file is part of the libiberty library, which is part of GCC. 5 | This file is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | In addition to the permissions in the GNU General Public License, the 10 | Free Software Foundation gives you unlimited permission to link the 11 | compiled version of this file into combinations with other programs, 12 | and to distribute those combinations without any restriction coming 13 | from the use of this file. (The General Public License restrictions 14 | do apply in other respects; for example, they cover modification of 15 | the file, and distribution when not linked into a combined 16 | executable.) 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | You should have received a copy of the GNU General Public License 22 | along with this program; if not, write to the Free Software 23 | Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | /* This file provides some definitions shared by cp-demangle.c and 26 | cp-demint.c. It should not be included by any other files. */ 27 | /* Information we keep for operators. */ 28 | struct demangle_operator_info 29 | { 30 | /* Mangled name. */ 31 | const char *code; 32 | /* Real name. */ 33 | const char *name; 34 | /* Length of real name. */ 35 | int len; 36 | /* Number of arguments. */ 37 | int args; 38 | }; 39 | /* How to print the value of a builtin type. */ 40 | enum d_builtin_type_print 41 | { 42 | /* Print as (type)val. */ 43 | D_PRINT_DEFAULT, 44 | /* Print as integer. */ 45 | D_PRINT_INT, 46 | /* Print as unsigned integer, with trailing "u". */ 47 | D_PRINT_UNSIGNED, 48 | /* Print as long, with trailing "l". */ 49 | D_PRINT_LONG, 50 | /* Print as unsigned long, with trailing "ul". */ 51 | D_PRINT_UNSIGNED_LONG, 52 | /* Print as long long, with trailing "ll". */ 53 | D_PRINT_LONG_LONG, 54 | /* Print as unsigned long long, with trailing "ull". */ 55 | D_PRINT_UNSIGNED_LONG_LONG, 56 | /* Print as bool. */ 57 | D_PRINT_BOOL, 58 | /* Print as float--put value in square brackets. */ 59 | D_PRINT_FLOAT, 60 | /* Print in usual way, but here to detect void. */ 61 | D_PRINT_VOID 62 | }; 63 | /* Information we keep for a builtin type. */ 64 | struct demangle_builtin_type_info 65 | { 66 | /* Type name. */ 67 | const char *name; 68 | /* Length of type name. */ 69 | int len; 70 | /* Type name when using Java. */ 71 | const char *java_name; 72 | /* Length of java name. */ 73 | int java_len; 74 | /* How to print a value of this type. */ 75 | enum d_builtin_type_print print; 76 | }; 77 | /* The information structure we pass around. */ 78 | struct d_info 79 | { 80 | /* The string we are demangling. */ 81 | const char *s; 82 | /* The end of the string we are demangling. */ 83 | const char *send; 84 | /* The options passed to the demangler. */ 85 | int options; 86 | /* The next character in the string to consider. */ 87 | const char *n; 88 | /* The array of components. */ 89 | struct demangle_component *comps; 90 | /* The index of the next available component. */ 91 | int next_comp; 92 | /* The number of available component structures. */ 93 | int num_comps; 94 | /* The array of substitutions. */ 95 | struct demangle_component **subs; 96 | /* The index of the next substitution. */ 97 | int next_sub; 98 | /* The number of available entries in the subs array. */ 99 | int num_subs; 100 | /* The last name we saw, for constructors and destructors. */ 101 | struct demangle_component *last_name; 102 | /* A running total of the length of large expansions from the 103 | mangled name to the demangled name, such as standard 104 | substitutions and builtin types. */ 105 | int expansion; 106 | /* Non-zero if we are parsing an expression. */ 107 | int is_expression; 108 | /* Non-zero if we are parsing the type operand of a conversion 109 | operator, but not when in an expression. */ 110 | int is_conversion; 111 | /* 1: using new unresolved-name grammar. 112 | -1: using new unresolved-name grammar and saw an unresolved-name. 113 | 0: using old unresolved-name grammar. */ 114 | int unresolved_name_state; 115 | /* If DMGL_NO_RECURSE_LIMIT is not active then this is set to 116 | the current recursion level. */ 117 | unsigned int recursion_level; 118 | }; 119 | /* To avoid running past the ending '\0', don't: 120 | - call d_peek_next_char if d_peek_char returned '\0' 121 | - call d_advance with an 'i' that is too large 122 | - call d_check_char(di, '\0') 123 | Everything else is safe. */ 124 | #define d_peek_char(di) (*((di)->n)) 125 | #ifndef CHECK_DEMANGLER 126 | # define d_peek_next_char(di) ((di)->n[1]) 127 | # define d_advance(di, i) ((di)->n += (i)) 128 | #endif 129 | #define d_check_char(di, c) (d_peek_char(di) == c ? ((di)->n++, 1) : 0) 130 | #define d_next_char(di) (d_peek_char(di) == '\0' ? '\0' : *((di)->n++)) 131 | #define d_str(di) ((di)->n) 132 | #ifdef CHECK_DEMANGLER 133 | static inline char 134 | d_peek_next_char (const struct d_info *di) 135 | { 136 | if (!di->n[0]) 137 | abort (); 138 | return di->n[1]; 139 | } 140 | static inline void 141 | d_advance (struct d_info *di, int i) 142 | { 143 | if (i < 0) 144 | abort (); 145 | while (i--) 146 | { 147 | if (!di->n[0]) 148 | abort (); 149 | di->n++; 150 | } 151 | } 152 | #endif 153 | /* Functions and arrays in cp-demangle.c which are referenced by 154 | functions in cp-demint.c. */ 155 | #ifdef IN_GLIBCPP_V3 156 | #define CP_STATIC_IF_GLIBCPP_V3 static 157 | #else 158 | #define CP_STATIC_IF_GLIBCPP_V3 extern 159 | #endif 160 | #ifndef IN_GLIBCPP_V3 161 | extern const struct demangle_operator_info cplus_demangle_operators[]; 162 | #endif 163 | #define D_BUILTIN_TYPE_COUNT (36) 164 | CP_STATIC_IF_GLIBCPP_V3 165 | const struct demangle_builtin_type_info 166 | cplus_demangle_builtin_types[D_BUILTIN_TYPE_COUNT]; 167 | CP_STATIC_IF_GLIBCPP_V3 168 | struct demangle_component * 169 | cplus_demangle_mangled_name (struct d_info *, int); 170 | CP_STATIC_IF_GLIBCPP_V3 171 | struct demangle_component * 172 | cplus_demangle_type (struct d_info *); 173 | extern void 174 | cplus_demangle_init_info (const char *, int, size_t, struct d_info *); 175 | /* cp-demangle.c needs to define this a little differently */ 176 | #undef CP_STATIC_IF_GLIBCPP_V3 177 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/external/_libc/platform/bionic/mte.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Android Open Source Project 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in 12 | * the documentation and/or other materials provided with the 13 | * distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 22 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | // Note: Most PR_MTE_* constants come from the upstream kernel. This tag mask 38 | // allows for the hardware to provision any nonzero tag. Zero tags are reserved 39 | // for scudo to use for the chunk headers in order to prevent linear heap 40 | // overflow/underflow. 41 | #define PR_MTE_TAG_SET_NONZERO (0xfffeUL << PR_MTE_TAG_SHIFT) 42 | 43 | inline bool mte_supported() { 44 | #if defined(__aarch64__) 45 | static bool supported = getauxval(AT_HWCAP2) & HWCAP2_MTE; 46 | #else 47 | static bool supported = false; 48 | #endif 49 | return supported; 50 | } 51 | 52 | inline void* get_tagged_address(const void* ptr) { 53 | #if defined(__aarch64__) 54 | if (mte_supported()) { 55 | __asm__ __volatile__(".arch_extension mte; ldg %0, [%0]" : "+r"(ptr)); 56 | } 57 | #endif // aarch64 58 | return const_cast(ptr); 59 | } 60 | 61 | // Inserts a random tag tag to `ptr`, using any of the set lower 16 bits in 62 | // `mask` to exclude the corresponding tag from being generated. Note: This does 63 | // not tag memory. This generates a pointer to be used with set_memory_tag. 64 | inline void* insert_random_tag(const void* ptr, __attribute__((unused)) uint64_t mask = 0) { 65 | #if defined(__aarch64__) 66 | if (mte_supported() && ptr) { 67 | __asm__ __volatile__(".arch_extension mte; irg %0, %0, %1" : "+r"(ptr) : "r"(mask)); 68 | } 69 | #endif // aarch64 70 | return const_cast(ptr); 71 | } 72 | 73 | // Stores the address tag in `ptr` to memory, at `ptr`. 74 | inline void set_memory_tag(__attribute__((unused)) void* ptr) { 75 | #if defined(__aarch64__) 76 | if (mte_supported()) { 77 | __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : "+r"(ptr)); 78 | } 79 | #endif // aarch64 80 | } 81 | 82 | #ifdef __aarch64__ 83 | class ScopedDisableMTE { 84 | size_t prev_tco_; 85 | 86 | public: 87 | ScopedDisableMTE() { 88 | if (mte_supported()) { 89 | __asm__ __volatile__(".arch_extension mte; mrs %0, tco; msr tco, #1" : "=r"(prev_tco_)); 90 | } 91 | } 92 | 93 | ~ScopedDisableMTE() { 94 | if (mte_supported()) { 95 | __asm__ __volatile__(".arch_extension mte; msr tco, %0" : : "r"(prev_tco_)); 96 | } 97 | } 98 | }; 99 | 100 | // N.B. that this is NOT the pagesize, but 4096. This is hardcoded in the codegen. 101 | // See 102 | // https://github.com/search?q=repo%3Allvm/llvm-project%20AArch64StackTagging%3A%3AinsertBaseTaggedPointer&type=code 103 | constexpr size_t kStackMteRingbufferSizeMultiplier = 4096; 104 | 105 | inline size_t stack_mte_ringbuffer_size(uintptr_t size_cls) { 106 | return kStackMteRingbufferSizeMultiplier * (1 << size_cls); 107 | } 108 | 109 | inline size_t stack_mte_ringbuffer_size_from_pointer(uintptr_t ptr) { 110 | // The size in the top byte is not the size_cls, but the number of "pages" (not OS pages, but 111 | // kStackMteRingbufferSizeMultiplier). 112 | return kStackMteRingbufferSizeMultiplier * (ptr >> 56ULL); 113 | } 114 | 115 | inline uintptr_t stack_mte_ringbuffer_size_add_to_pointer(uintptr_t ptr, uintptr_t size_cls) { 116 | return ptr | ((1ULL << size_cls) << 56ULL); 117 | } 118 | 119 | inline void stack_mte_free_ringbuffer(uintptr_t stack_mte_tls) { 120 | size_t size = stack_mte_ringbuffer_size_from_pointer(stack_mte_tls); 121 | void* ptr = reinterpret_cast(stack_mte_tls & ((1ULL << 56ULL) - 1ULL)); 122 | munmap(ptr, size); 123 | } 124 | 125 | inline void* stack_mte_ringbuffer_allocate(size_t n, const char* name) { 126 | if (n > 7) return nullptr; 127 | // Allocation needs to be aligned to 2*size to make the fancy code-gen work. 128 | // So we allocate 3*size - pagesz bytes, which will always contain size bytes 129 | // aligned to 2*size, and unmap the unneeded part. 130 | // See 131 | // https://github.com/search?q=repo%3Allvm/llvm-project%20AArch64StackTagging%3A%3AinsertBaseTaggedPointer&type=code 132 | // 133 | // In the worst case, we get an allocation that is one page past the properly 134 | // aligned address, in which case we have to unmap the previous 135 | // 2*size - pagesz bytes. In that case, we still have size properly aligned 136 | // bytes left. 137 | size_t size = stack_mte_ringbuffer_size(n); 138 | size_t pgsize = getpagesize(); 139 | 140 | size_t alloc_size = __BIONIC_ALIGN(3 * size - pgsize, pgsize); 141 | void* allocation_ptr = 142 | mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 143 | if (allocation_ptr == MAP_FAILED) 144 | return nullptr; 145 | uintptr_t allocation = reinterpret_cast(allocation_ptr); 146 | 147 | size_t alignment = 2 * size; 148 | uintptr_t aligned_allocation = __BIONIC_ALIGN(allocation, alignment); 149 | if (allocation != aligned_allocation) { 150 | munmap(reinterpret_cast(allocation), aligned_allocation - allocation); 151 | } 152 | if (aligned_allocation + size != allocation + alloc_size) { 153 | munmap(reinterpret_cast(aligned_allocation + size), 154 | (allocation + alloc_size) - (aligned_allocation + size)); 155 | } 156 | 157 | if (name) { 158 | prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, reinterpret_cast(aligned_allocation), size, name); 159 | } 160 | 161 | // We store the size in the top byte of the pointer (which is ignored) 162 | return reinterpret_cast(stack_mte_ringbuffer_size_add_to_pointer(aligned_allocation, n)); 163 | } 164 | #else 165 | struct ScopedDisableMTE { 166 | // Silence unused variable warnings in non-aarch64 builds. 167 | ScopedDisableMTE() {} 168 | }; 169 | #endif 170 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/human_readable.cpp: -------------------------------------------------------------------------------- 1 | #include "human_readable.h" 2 | 3 | #include 4 | 5 | const char *get_signame(const siginfo_t *info) { 6 | switch (info->si_signo) { 7 | case SIGABRT: 8 | return "SIGABRT"; 9 | case SIGBUS: 10 | return "SIGBUS"; 11 | case SIGFPE: 12 | return "SIGFPE"; 13 | case SIGILL: 14 | return "SIGILL"; 15 | case SIGSEGV: 16 | return "SIGSEGV"; 17 | case SIGSTKFLT: 18 | return "SIGSTKFLT"; 19 | case SIGSTOP: 20 | return "SIGSTOP"; 21 | case SIGSYS: 22 | return "SIGSYS"; 23 | case SIGTRAP: 24 | return "SIGTRAP"; 25 | default: 26 | return "?"; 27 | } 28 | } 29 | 30 | const char *get_sigcode(const siginfo_t *info) { 31 | // Try the signal-specific codes... 32 | switch (info->si_signo) { 33 | case SIGILL: 34 | switch (info->si_code) { 35 | case ILL_ILLOPC: 36 | return "ILL_ILLOPC"; 37 | case ILL_ILLOPN: 38 | return "ILL_ILLOPN"; 39 | case ILL_ILLADR: 40 | return "ILL_ILLADR"; 41 | case ILL_ILLTRP: 42 | return "ILL_ILLTRP"; 43 | case ILL_PRVOPC: 44 | return "ILL_PRVOPC"; 45 | case ILL_PRVREG: 46 | return "ILL_PRVREG"; 47 | case ILL_COPROC: 48 | return "ILL_COPROC"; 49 | case ILL_BADSTK: 50 | return "ILL_BADSTK"; 51 | case ILL_BADIADDR: 52 | return "ILL_BADIADDR"; 53 | case __ILL_BREAK: 54 | return "ILL_BREAK"; 55 | case __ILL_BNDMOD: 56 | return "ILL_BNDMOD"; 57 | } 58 | static_assert(NSIGILL == __ILL_BNDMOD, "missing ILL_* si_code"); 59 | break; 60 | case SIGBUS: 61 | switch (info->si_code) { 62 | case BUS_ADRALN: 63 | return "BUS_ADRALN"; 64 | case BUS_ADRERR: 65 | return "BUS_ADRERR"; 66 | case BUS_OBJERR: 67 | return "BUS_OBJERR"; 68 | case BUS_MCEERR_AR: 69 | return "BUS_MCEERR_AR"; 70 | case BUS_MCEERR_AO: 71 | return "BUS_MCEERR_AO"; 72 | } 73 | static_assert(NSIGBUS == BUS_MCEERR_AO, "missing BUS_* si_code"); 74 | break; 75 | case SIGFPE: 76 | switch (info->si_code) { 77 | case FPE_INTDIV: 78 | return "FPE_INTDIV"; 79 | case FPE_INTOVF: 80 | return "FPE_INTOVF"; 81 | case FPE_FLTDIV: 82 | return "FPE_FLTDIV"; 83 | case FPE_FLTOVF: 84 | return "FPE_FLTOVF"; 85 | case FPE_FLTUND: 86 | return "FPE_FLTUND"; 87 | case FPE_FLTRES: 88 | return "FPE_FLTRES"; 89 | case FPE_FLTINV: 90 | return "FPE_FLTINV"; 91 | case FPE_FLTSUB: 92 | return "FPE_FLTSUB"; 93 | case __FPE_DECOVF: 94 | return "FPE_DECOVF"; 95 | case __FPE_DECDIV: 96 | return "FPE_DECDIV"; 97 | case __FPE_DECERR: 98 | return "FPE_DECERR"; 99 | case __FPE_INVASC: 100 | return "FPE_INVASC"; 101 | case __FPE_INVDEC: 102 | return "FPE_INVDEC"; 103 | case FPE_FLTUNK: 104 | return "FPE_FLTUNK"; 105 | case FPE_CONDTRAP: 106 | return "FPE_CONDTRAP"; 107 | } 108 | static_assert(NSIGFPE == FPE_CONDTRAP, "missing FPE_* si_code"); 109 | break; 110 | case SIGSEGV: 111 | switch (info->si_code) { 112 | case SEGV_MAPERR: 113 | return "SEGV_MAPERR"; 114 | case SEGV_ACCERR: 115 | return "SEGV_ACCERR"; 116 | case SEGV_BNDERR: 117 | return "SEGV_BNDERR"; 118 | case SEGV_PKUERR: 119 | return "SEGV_PKUERR"; 120 | case SEGV_ACCADI: 121 | return "SEGV_ACCADI"; 122 | case SEGV_ADIDERR: 123 | return "SEGV_ADIDERR"; 124 | case SEGV_ADIPERR: 125 | return "SEGV_ADIPERR"; 126 | case SEGV_MTEAERR: 127 | return "SEGV_MTEAERR"; 128 | case SEGV_MTESERR: 129 | return "SEGV_MTESERR"; 130 | case SEGV_CPERR: 131 | return "SEGV_CPERR"; 132 | } 133 | static_assert(NSIGSEGV == SEGV_CPERR, "missing SEGV_* si_code"); 134 | break; 135 | case SIGSYS: 136 | switch (info->si_code) { 137 | case SYS_SECCOMP: 138 | return "SYS_SECCOMP"; 139 | case SYS_USER_DISPATCH: 140 | return "SYS_USER_DISPATCH"; 141 | } 142 | static_assert(NSIGSYS == SYS_USER_DISPATCH, "missing SYS_* si_code"); 143 | break; 144 | case SIGTRAP: 145 | switch (info->si_code) { 146 | case TRAP_BRKPT: 147 | return "TRAP_BRKPT"; 148 | case TRAP_TRACE: 149 | return "TRAP_TRACE"; 150 | case TRAP_BRANCH: 151 | return "TRAP_BRANCH"; 152 | case TRAP_HWBKPT: 153 | return "TRAP_HWBKPT"; 154 | case TRAP_UNK: 155 | return "TRAP_UNDIAGNOSED"; 156 | case TRAP_PERF: 157 | return "TRAP_PERF"; 158 | } 159 | if ((info->si_code & 0xff) == SIGTRAP) { 160 | switch ((info->si_code >> 8) & 0xff) { 161 | case PTRACE_EVENT_FORK: 162 | return "PTRACE_EVENT_FORK"; 163 | case PTRACE_EVENT_VFORK: 164 | return "PTRACE_EVENT_VFORK"; 165 | case PTRACE_EVENT_CLONE: 166 | return "PTRACE_EVENT_CLONE"; 167 | case PTRACE_EVENT_EXEC: 168 | return "PTRACE_EVENT_EXEC"; 169 | case PTRACE_EVENT_VFORK_DONE: 170 | return "PTRACE_EVENT_VFORK_DONE"; 171 | case PTRACE_EVENT_EXIT: 172 | return "PTRACE_EVENT_EXIT"; 173 | case PTRACE_EVENT_SECCOMP: 174 | return "PTRACE_EVENT_SECCOMP"; 175 | case PTRACE_EVENT_STOP: 176 | return "PTRACE_EVENT_STOP"; 177 | } 178 | } 179 | static_assert(NSIGTRAP == TRAP_PERF, "missing TRAP_* si_code"); 180 | break; 181 | } 182 | // Then the other codes... 183 | switch (info->si_code) { 184 | case SI_USER: 185 | return "SI_USER"; 186 | case SI_KERNEL: 187 | return "SI_KERNEL"; 188 | case SI_QUEUE: 189 | return "SI_QUEUE"; 190 | case SI_TIMER: 191 | return "SI_TIMER"; 192 | case SI_MESGQ: 193 | return "SI_MESGQ"; 194 | case SI_ASYNCIO: 195 | return "SI_ASYNCIO"; 196 | case SI_SIGIO: 197 | return "SI_SIGIO"; 198 | case SI_TKILL: 199 | return "SI_TKILL"; 200 | case SI_DETHREAD: 201 | return "SI_DETHREAD"; 202 | } 203 | // Then give up... 204 | return "?"; 205 | } 206 | -------------------------------------------------------------------------------- /fishnet/src/main/cpp/thread.cpp: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "abi.h" 9 | #include "human_readable.h" 10 | #include "cause.h" 11 | #include "abort_message.h" 12 | #include "command_line.h" 13 | #include "process.h" 14 | #include "thread.h" 15 | #include "property.h" 16 | #include "registers.h" 17 | #include "backtrace.h" 18 | #include "memory.h" 19 | 20 | static bool signal_has_si_addr(const siginfo_t *info) { 21 | // Manually sent signals won't have si_addr. 22 | if (info->si_code == SI_USER || info->si_code == SI_QUEUE || info->si_code == SI_TKILL) { 23 | return false; 24 | } 25 | 26 | switch (info->si_signo) { 27 | case SIGBUS: 28 | case SIGFPE: 29 | case SIGILL: 30 | case SIGTRAP: 31 | return true; 32 | case SIGSEGV: 33 | return info->si_code != SEGV_MTEAERR; 34 | default: 35 | return false; 36 | } 37 | } 38 | 39 | static bool signal_has_sender(const siginfo_t *info, pid_t caller_pid) { 40 | return SI_FROMUSER(info) && (info->si_pid != 0) && (info->si_pid != caller_pid); 41 | } 42 | 43 | static std::string get_thread_name(pid_t tid) { 44 | char thread_name[16]; 45 | char path[19]; 46 | 47 | snprintf(path, sizeof(path), "/proc/%d/comm", tid); 48 | FILE *file = fopen(path, "r"); 49 | if (!file) { 50 | return ""; 51 | } 52 | if (fgets(thread_name, sizeof(thread_name), file) == nullptr) { 53 | fclose(file); 54 | return ""; 55 | } 56 | fclose(file); 57 | 58 | size_t len = strlen(thread_name); 59 | if (len > 0 && thread_name[len - 1] == '\n') { 60 | thread_name[len - 1] = '\0'; 61 | } 62 | 63 | return thread_name; 64 | } 65 | 66 | #define DESCRIBE_FLAG(flag) \ 67 | if (value & flag) { \ 68 | desc += ", "; \ 69 | desc += #flag; \ 70 | value &= ~flag; \ 71 | } 72 | 73 | static std::string describe_end(long value, std::string &desc) { 74 | if (value) { 75 | desc += StringPrintf(", unknown 0x%lx", value); 76 | } 77 | return desc.empty() ? "" : " (" + desc.substr(2) + ")"; 78 | } 79 | 80 | static std::string describe_tagged_addr_ctrl(long value) { 81 | std::string desc; 82 | DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE) 83 | DESCRIBE_FLAG(PR_MTE_TCF_SYNC) 84 | DESCRIBE_FLAG(PR_MTE_TCF_ASYNC) 85 | if (value & PR_MTE_TAG_MASK) { 86 | desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT); 87 | value &= ~PR_MTE_TAG_MASK; 88 | } 89 | return describe_end(value, desc); 90 | } 91 | 92 | static std::string describe_pac_enabled_keys(long value) { 93 | std::string desc; 94 | DESCRIBE_FLAG(PR_PAC_APIAKEY) 95 | DESCRIBE_FLAG(PR_PAC_APIBKEY) 96 | DESCRIBE_FLAG(PR_PAC_APDAKEY) 97 | DESCRIBE_FLAG(PR_PAC_APDBKEY) 98 | DESCRIBE_FLAG(PR_PAC_APGAKEY) 99 | return describe_end(value, desc); 100 | } 101 | 102 | void print_main_thread_header(LogRecord &record, pid_t pid, pid_t tid, uid_t uid) { 103 | const std::vector command_line = get_command_line(pid); 104 | if (!command_line.empty()) { 105 | if (command_line.size() == 1) { 106 | LOG_FISHNET("Cmdline: %s", command_line[0].c_str()); 107 | } else { 108 | const std::string command_line_string = std::accumulate( 109 | std::next(command_line.begin()), command_line.end(), command_line[0], 110 | [](const std::string &a, const std::string &b) { 111 | return a + ' ' + b; 112 | }); 113 | LOG_FISHNET("Cmdline: %s", command_line_string.c_str()); 114 | } 115 | } else { 116 | LOG_FISHNET("Cmdline: "); 117 | } 118 | const std::string process_name = get_process_name(pid); 119 | const std::string thread_name = get_thread_name(tid); 120 | LOG_FISHNET("💥 pid: %d, tid: %d, uid: %d, name: %s >>> %s <<<", 121 | pid, tid, uid, thread_name.c_str(), process_name.c_str()); 122 | // Only supported on aarch64 for now. 123 | #if defined(__aarch64__) 124 | const long tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); 125 | const long pac_enabled_keys = prctl(PR_PAC_GET_ENABLED_KEYS, 0, 0, 0, 0); 126 | if (tagged_addr_ctrl != -1) { 127 | LOG_FISHNET("tagged_addr_ctrl: %016" PRIx64 "%s", tagged_addr_ctrl, 128 | describe_tagged_addr_ctrl(tagged_addr_ctrl).c_str()); 129 | } 130 | if (pac_enabled_keys != -1) { 131 | LOG_FISHNET("pac_enabled_keys: %016" PRIx64 "%s", pac_enabled_keys, 132 | describe_pac_enabled_keys(pac_enabled_keys).c_str()); 133 | } 134 | #endif 135 | } 136 | 137 | void print_main_thread(LogRecord &record, pid_t pid, pid_t tid, uid_t uid, const siginfo_t *info, int word_size, 138 | const unwindstack::ArchEnum &arch, unwindstack::AndroidUnwinder *unwinder, 139 | const std::unique_ptr ®s, 140 | const std::vector &frames, 141 | bool dump_memory, bool dump_memory_maps) { 142 | const bool has_fault_addr = signal_has_si_addr(info); 143 | const auto fault_addr = (const uint64_t) info->si_addr; 144 | print_main_thread_header(record, pid, tid, uid); 145 | 146 | std::string sender_desc; 147 | if (signal_has_sender(info, pid)) { 148 | sender_desc = StringPrintf(" from pid %d, uid %d", info->si_pid, info->si_uid); 149 | } 150 | 151 | bool is_async_mte_crash = false; 152 | bool is_mte_crash = false; 153 | std::string fault_addr_desc; 154 | if (has_fault_addr) { 155 | fault_addr_desc = StringPrintf("0x%0*" PRIx64, 2 * word_size, fault_addr); 156 | } else { 157 | fault_addr_desc = "--------"; 158 | } 159 | 160 | LOG_FISHNET("signal %d (%s), code %d (%s%s), fault addr %s", 161 | info->si_signo, get_signame(info), 162 | info->si_code, get_sigcode(info), sender_desc.c_str(), 163 | fault_addr_desc.c_str()); 164 | #ifdef SEGV_MTEAERR 165 | is_async_mte_crash = info->si_signo == SIGSEGV && info->si_code == SEGV_MTEAERR; 166 | is_mte_crash = is_async_mte_crash || (info->si_signo == SIGSEGV && info->si_code == SEGV_MTESERR); 167 | #endif 168 | 169 | dump_probable_cause(record, info, unwinder->GetMaps(), regs); 170 | 171 | dump_abort_message(record); 172 | 173 | print_thread_registers(record, arch, word_size, regs); 174 | if (is_async_mte_crash) { 175 | LOG_FISHNET("Note: This crash is a delayed async MTE crash. Memory corruption has occurred"); 176 | LOG_FISHNET(" in this process. The stack trace below is the first system call or context"); 177 | LOG_FISHNET(" switch that was executed after the memory corruption happened."); 178 | } 179 | print_backtrace(record, arch, frames); 180 | 181 | if (has_fault_addr) { 182 | print_tag_dump(record, fault_addr, arch, unwinder->GetProcessMemory()); 183 | } 184 | 185 | if (is_mte_crash) { 186 | LOG_FISHNET(""); 187 | LOG_FISHNET("Learn more about MTE reports: " 188 | "https://source.android.com/docs/security/test/memory-safety/mte-reports"); 189 | } 190 | 191 | if (dump_memory) { 192 | unwindstack::Maps *maps = unwinder->GetMaps(); 193 | unwindstack::Memory *memory = unwinder->GetProcessMemory().get(); 194 | print_thread_memory_dump(record, word_size, regs, maps, memory); 195 | 196 | LOG_FISHNET(""); 197 | } 198 | 199 | if (dump_memory_maps) { 200 | // No memory maps to print. 201 | if (unwinder->GetMaps()->Total() > 0) { 202 | print_memory_maps(record, fault_addr, word_size, unwinder->GetMaps(), unwinder->GetProcessMemory()); 203 | } else { 204 | LOG_FISHNET("No memory maps found"); 205 | } 206 | } 207 | } 208 | 209 | void print_thread(LogRecord &record, pid_t tid, int word_size, const unwindstack::ArchEnum &arch, 210 | unwindstack::ThreadUnwinder *unwinder, bool dump_memory) { 211 | std::unique_ptr regs; 212 | unwinder->UnwindWithSignal(SIGRTMIN, tid, ®s); 213 | const std::string thread_name = get_thread_name(tid); 214 | LOG_FISHNET("🧵 tid: %d, name: %s", tid, thread_name.c_str()); 215 | if (regs) { 216 | print_thread_registers(record, arch, word_size, regs); 217 | } 218 | print_backtrace(record, arch, unwinder->ConsumeFrames()); 219 | if (dump_memory) { 220 | unwindstack::Maps *maps = unwinder->GetMaps(); 221 | unwindstack::Memory *memory = unwinder->GetProcessMemory().get(); 222 | if (regs) { 223 | print_thread_memory_dump(record, word_size, regs, maps, memory); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | --------------------------------------------------------------------------------