#include #include #include #include #include #include #include "logcat.h" constexpr size_t kMaxLogSize = 32 * 1024 * 1024; constexpr std::array kLogChar = { /*ANDROID_LOG_UNKNOWN*/'?', /*ANDROID_LOG_DEFAULT*/ '?', /*ANDROID_LOG_VERBOSE*/ 'V', /*ANDROID_LOG_DEBUG*/ 'D', /*ANDROID_LOG_INFO*/'I', /*ANDROID_LOG_WARN*/'W', /*ANDROID_LOG_ERROR*/ 'E', /*ANDROID_LOG_FATAL*/ 'F', /*ANDROID_LOG_SILENT*/ 'S', }; class UniqueFile : public std::unique_ptr> { inline static deleter_type deleter = [](auto f) { f && f != stdout && fclose(f); }; public: explicit UniqueFile(FILE *f) : std::unique_ptr>(f, deleter) {} UniqueFile(int fd, const char *mode) : UniqueFile(fd > 0 ? fdopen(fd, mode) : stdout) {}; UniqueFile() : UniqueFile(stdout) {}; }; class Logcat { public: explicit Logcat(JNIEnv *env, jobject thiz, jmethodID method, jlong logger_id) : env_(env), thiz_(thiz), refresh_fd_method_(method), logger_id_(logger_id), stop_verbose_inst_("!!stop_verbose!!" + std::to_string(logger_id_)), start_verbose_inst_("!!start_verbose!!" + std::to_string(logger_id_)) {} [[noreturn]] void Run(); private: inline void RefreshFd(bool is_verbose); void ProcessBuffer(struct log_msg *buf); static int PrintLogLine(const AndroidLogEntry &entry, FILE *out); JNIEnv *env_; jobject thiz_; jmethodID refresh_fd_method_; jlong logger_id_; UniqueFile modules_file_{}; size_t modules_file_part_ = 0; size_t modules_print_count_ = 0; UniqueFile verbose_file_{}; size_t verbose_file_part_ = 0; size_t verbose_print_count_ = 0; bool verbose_ = false; const std::string stop_verbose_inst_; const std::string start_verbose_inst_; }; int Logcat::PrintLogLine(const AndroidLogEntry &entry, FILE *out) { if (!out) return 0; constexpr static size_t kMaxTimeBuff = 64; struct tm tm{}; std::array time_buff; auto now = entry.tv_sec; auto nsec = entry.tv_nsec; auto message_len = entry.messageLen; const auto *message = entry.message; if (now < 0) { nsec = NS_PER_SEC - nsec; } if (message_len >= 1 && message[message_len - 1] == '\n') { --message_len; } localtime_r(&now, &tm); strftime(time_buff.data(), time_buff.size(), "%Y-%m-%dT%H:%M:%S", &tm); return fprintf(out, "[ %s.%03ld %8d:%6d:%6d %c/%-15.*s ] %.*s\n", time_buff.data(), nsec / MS_PER_NSEC, entry.uid, entry.pid, entry.tid, kLogChar[entry.priority], static_cast(entry.tagLen), entry.tag, static_cast(message_len), message); } void Logcat::RefreshFd(bool is_verbose) { constexpr const char *start_info = "----%" PRId64 "-%zu start----\n"; constexpr const char *stop_info = "----%" PRId64 "-%zu end----\n"; if (is_verbose) { verbose_print_count_ = 0; fprintf(verbose_file_.get(), stop_info, logger_id_, verbose_file_part_); verbose_file_ = UniqueFile(env_->CallIntMethod(thiz_, refresh_fd_method_, JNI_TRUE), "w"); verbose_file_part_++; fprintf(verbose_file_.get(), start_info, logger_id_, verbose_file_part_); } else { modules_print_count_ = 0; fprintf(modules_file_.get(), stop_info, logger_id_, modules_file_part_); modules_file_ = UniqueFile(env_->CallIntMethod(thiz_, refresh_fd_method_, JNI_FALSE), "w"); modules_file_part_++; fprintf(modules_file_.get(), start_info, logger_id_, modules_file_part_); } } void Logcat::ProcessBuffer(struct log_msg *buf) { AndroidLogEntry entry; if (android_log_processLogBuffer(&buf->entry, &entry) < 0) return; std::string_view tag(entry.tag); bool shortcut = false; if (tag == "LSPosed-Bridge" || tag == "XSharedPreferences") [[unlikely]] { modules_print_count_ += PrintLogLine(entry, modules_file_.get()); shortcut = true; } if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH || tag == "Magisk" || tag.starts_with("Riru") || tag.starts_with("LSPosed"))) [[unlikely]] { verbose_print_count_ += PrintLogLine(entry, verbose_file_.get()); } if (entry.pid == getpid() && tag == "LSPosedLogcat") [[unlikely]] { if (std::string_view(entry.message) == stop_verbose_inst_) { verbose_ = false; } else if (std::string_view(entry.message) == start_verbose_inst_) { RefreshFd(true); verbose_ = true; } } } void Logcat::Run() { constexpr size_t tail_after_crash = 10U; size_t tail = 0; RefreshFd(false); while (true) { std::unique_ptr logger_list{ android_logger_list_alloc(0, tail, 0), &android_logger_list_free}; tail = tail_after_crash; for (log_id id:{LOG_ID_MAIN, LOG_ID_CRASH}) { auto *logger = android_logger_open(logger_list.get(), id); if (logger == nullptr) continue; android_logger_set_log_size(logger, kMaxLogSize); } struct log_msg msg{}; while (true) { if (android_logger_list_read(logger_list.get(), &msg) <= 0) [[unlikely]] break; ProcessBuffer(&msg); fflush(verbose_file_.get()); fflush(modules_file_.get()); if (verbose_print_count_ >= kMaxLogSize) [[unlikely]] RefreshFd(true); if (modules_print_count_ >= kMaxLogSize) [[unlikely]] RefreshFd(false); } fprintf(verbose_file_.get(), "\nLogd maybe crashed, retrying in 1s...\n"); fprintf(modules_file_.get(), "\nLogd maybe crashed, retrying in 1s...\n"); sleep(1); } } extern "C" JNIEXPORT void JNICALL // NOLINTNEXTLINE Java_org_lsposed_lspd_service_LogcatService_runLogcat(JNIEnv *env, jobject thiz, jlong logger_id) { jclass clazz = env->GetObjectClass(thiz); jmethodID method = env->GetMethodID(clazz, "refreshFd", "(Z)I"); Logcat logcat(env, thiz, method, logger_id); logcat.Run(); }