├── 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 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
5 |
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 |
5 |
6 |
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 |
8 |
9 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 | [](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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | xmlns:android
15 |
16 | ^$
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | xmlns:.*
26 |
27 | ^$
28 |
29 |
30 | BY_NAME
31 |
32 |
33 |
34 |
35 |
36 |
37 | .*:id
38 |
39 | http://schemas.android.com/apk/res/android
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | .*:name
49 |
50 | http://schemas.android.com/apk/res/android
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | name
60 |
61 | ^$
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | style
71 |
72 | ^$
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | .*
82 |
83 | ^$
84 |
85 |
86 | BY_NAME
87 |
88 |
89 |
90 |
91 |
92 |
93 | .*
94 |
95 | http://schemas.android.com/apk/res/android
96 |
97 |
98 | ANDROID_ATTRIBUTE_ORDER
99 |
100 |
101 |
102 |
103 |
104 |
105 | .*
106 |
107 | .*
108 |
109 |
110 | BY_NAME
111 |
112 |
113 |
114 |
115 |
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 |
133 |
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 |
--------------------------------------------------------------------------------