fix(lifecycle): collect Big App on EXEC before running query

This commit is contained in:
aydencharles committed 2026-09-11 22:47:42 +08:00
1 parent 3fedbf4a03
commit 6950517ad8
7 files changed
+488 -19

No files matched your search

+2 -2
View File
@@ -303,7 +303,7 @@ shellui / homebrew
homebrew (app jailbreak)
└─► sandbox file …/download0/etahen_jailbreak|onionhen_jailbreak
(SceSysCore 进程生命周期事件 + 沙盒目录 vnode 事件 + 白名单 TID)
(SceSysCore 进程事件触发 Running Big App 补扫 + 沙盒 vnode + 白名单 TID)
```
### 3.1 消息格式
@@ -387,7 +387,7 @@ struct IPCMessage {
- 提权与分区 remount
- 阻止系统更新(unmount `/update`)
- **kstuff**:fself / fpkg 相关内核能力(通常 ≥ 3.00)
- App jailbreak(按设置启停;SceSysCore 生命周期 + 沙盒 vnode 事件 + 白名单 TID,无常驻轮询)
- App jailbreak(按设置启停;SceSysCore 生命周期事件触发 Running Big App 补扫,再跟沙盒 vnode 与白名单 TID。EXEC 身份未就绪或 NOTE_TRACK 跟丢时短重试,无沙盒常驻扫描)
- 休息后 Toolbox 恢复(kstuff-lite:新 ShellUI PID + trophy sprx,再按 `rest_mode.resume_reinject_delay_seconds` 等待)
- 休息后监听套接字重绑(ps5-payload-manager:IPC / TCP `accept` 失败自愈)
- 双守护进程架构(util 可被 daemon 拉起)
+144 -17
View File
@@ -9,12 +9,18 @@
* Accepted names:
* - etahen_jailbreak
* - onionhen_jailbreak
* 3. SceSysCore NOTE_EXEC/NOTE_EXIT events identify app lifetime. Vnode
* events then follow sandbox -> slot -> download0 -> request file.
* 3. SceSysCore process events identify app lifetime. NOTE_TRACK does
* deliver the game child's NOTE_EXEC, and GetAppInfo is often already
* filled from SysCore appParam. sceSystemServiceGetAppIdOfRunningBigApp
* is later (after ResArbitrator). A usable EXEC identity publishes
* immediately; otherwise the running Big App query (same path FPS uses)
* reconciles Cdlg EXEC / SysCore FORK, with short oneshot retries and a
* 2s fallback. Vnode events then follow sandbox -> slot -> download0 ->
* request file.
*
* There is no periodic foreground-app query or sandbox scan. The only scan of
* the 51 possible slots happens when an allowed app starts, or when the
* sandbox root reports that one of its direct children changed.
* There is no periodic sandbox scan. The only scan of the 51 possible slots
* happens when an allowed app is collected, or when the sandbox root reports
* that one of its direct children changed.
*/
#include "daemon_ops.hpp"
@@ -23,6 +29,7 @@
#include <onion/app_jailbreak_policy.hpp>
#include <onion/app_lifecycle.hpp>
#include <onion/big_app_collect_policy.hpp>
#include <onion/platform.h>
#include <onion/proc_query.h>
#include <onion/settings.hpp>
@@ -131,6 +138,8 @@ constexpr const char *kJailbreakReqNames[] = {
};
constexpr const char *kShellUiTitleId = "NPXS40087";
constexpr const char *kShellUiProcessName = "SceShellUI";
constexpr uintptr_t kReconcileBurstTimerIdent = 1;
constexpr uintptr_t kReconcileFallbackTimerIdent = 2;
struct ExecIdentity {
char process_name[64] {};
@@ -318,25 +327,76 @@ struct TrackedBigApp {
/** Identifies foreground Big Apps and only publishes lifecycle events. */
class SceSysCoreAppLifecycleCollector {
public:
bool on_exec(pid_t pid, const ExecIdentity &id) {
if (id.app_info_rc != 0 || id.tid.empty() || big_apps_.count(pid) != 0)
return false;
bool has_tracked() const { return !big_apps_.empty(); }
bool consider(const char *source, pid_t exec_pid, const ExecIdentity *id) {
std::string current_title_id;
int current_app_id = -1;
if (!Get_Running_App_TID(current_title_id, current_app_id) ||
current_app_id < 0 ||
id.info.app_id != static_cast<uint32_t>(current_app_id) ||
id.tid != current_title_id || get_game_pid() != pid)
const bool query_ok = Get_Running_App_TID(current_title_id, current_app_id);
const pid_t running_pid = query_ok ? get_game_pid() : -1;
onion::lifecycle::ExecSnapshot exec;
exec.pid = exec_pid;
if (id != nullptr) {
exec.app_info_rc = id->app_info_rc;
exec.app_id = id->info.app_id;
exec.title_id = id->tid;
exec.already_tracked = exec_pid > 1 && big_apps_.count(exec_pid) != 0;
}
onion::lifecycle::RunningBigAppSnapshot running;
running.query_ok = query_ok;
running.pid = running_pid;
running.app_id = current_app_id;
running.title_id = current_title_id;
const bool running_tracked =
running_pid > 1 && big_apps_.count(running_pid) != 0;
const onion::lifecycle::BigAppCollectDecision decision =
onion::lifecycle::decide_big_app_started(exec, running,
running_tracked);
if (decision.action !=
onion::lifecycle::BigAppCollectAction::PublishStarted) {
const bool quiet =
std::strcmp(source, "timer") == 0 &&
(decision.reason ==
onion::lifecycle::BigAppCollectReason::RunningQueryNotReady ||
decision.reason ==
onion::lifecycle::BigAppCollectReason::AlreadyTracked);
if (quiet) {
LOG_TRACE("[lifecycle] collect skip source=%s reason=%s exec_pid=%d "
"running_pid=%d",
source,
onion::lifecycle::big_app_collect_reason_name(
decision.reason),
static_cast<int>(exec_pid), static_cast<int>(running_pid));
} else {
LOG_DEBUG("[lifecycle] collect skip source=%s reason=%s exec_pid=%d "
"exec_tid=%s running_pid=%d running_tid=%s query_ok=%d",
source,
onion::lifecycle::big_app_collect_reason_name(
decision.reason),
static_cast<int>(exec_pid),
id != nullptr ? id->tid.c_str() : "-",
static_cast<int>(running_pid),
current_title_id.empty() ? "-" : current_title_id.c_str(),
query_ok ? 1 : 0);
}
return false;
}
if (!onion::daemon::app_lifecycle::publish_big_app_started(
pid, id.info.app_id, id.tid))
decision.pid, decision.app_id, decision.title_id))
return false;
big_apps_[pid] = {id.info.app_id, id.tid};
LOG_INFO("[lifecycle] Big App started pid=%d appid=%u tid=%s",
static_cast<int>(pid), id.info.app_id, id.tid.c_str());
const std::string started_tid(decision.title_id);
big_apps_[decision.pid] = {decision.app_id, started_tid};
LOG_INFO("[lifecycle] Big App started pid=%d appid=%u tid=%s via=%s "
"source=%s",
static_cast<int>(decision.pid), decision.app_id,
started_tid.c_str(),
onion::lifecycle::big_app_collect_reason_name(decision.reason),
source);
return true;
}
@@ -402,6 +462,10 @@ public:
static_cast<long long>(event.data));
continue;
}
if (event.filter == EVFILT_TIMER) {
handle_reconcile_timer(event);
continue;
}
if (event.filter == EVFILT_PROC && !handle_process_event(event))
return;
}
@@ -441,6 +505,14 @@ private:
LOG_INFO("[lifecycle] SceSysCore listener active pid=%d",
static_cast<int>(syscore_pid_));
EV_SET(&event, kReconcileFallbackTimerIdent, EVFILT_TIMER,
EV_ADD | EV_ENABLE, 0, onion::lifecycle::kBigAppReconcileFallbackMs,
nullptr);
if (kevent(kq_, &event, 1, nullptr, 0, nullptr) != 0) {
LOG_WARN("[lifecycle] Big App fallback timer failed: %s",
strerror(errno));
}
return true;
}
@@ -452,6 +524,12 @@ private:
bool handle_process_event(const struct kevent &event) {
const pid_t pid = static_cast<pid_t>(event.ident);
if (event.fflags & NOTE_TRACKERR) {
LOG_WARN("[lifecycle] NOTE_TRACKERR pid=%d child=%lld",
static_cast<int>(pid), static_cast<long long>(event.data));
}
bool spawn_activity = false;
if (event.fflags & NOTE_EXEC) {
const ExecIdentity id = read_exec_identity(pid);
if (identity_is_shellui(id)) {
@@ -459,8 +537,15 @@ private:
static_cast<int>(pid));
toolbox_on_new_shellui(pid);
}
(void)collector_.on_exec(pid, id);
spawn_activity = true;
if (collector_.consider("exec", pid, &id))
disarm_burst_timer();
} else if (event.fflags & (NOTE_FORK | NOTE_CHILD)) {
spawn_activity = true;
if (collector_.consider("fork", pid, nullptr))
disarm_burst_timer();
}
if (event.fflags & NOTE_EXIT) {
if (onion::lifecycle::ProcessExitPolicy::should_stop_listener(
syscore_pid_, pid)) {
@@ -469,12 +554,54 @@ private:
}
(void)collector_.on_exit(pid);
}
if (spawn_activity && !collector_.has_tracked())
arm_burst_timer(/*restart=*/true);
return true;
}
void handle_reconcile_timer(const struct kevent &event) {
if (collector_.consider("timer", -1, nullptr)) {
disarm_burst_timer();
return;
}
if (event.ident == kReconcileBurstTimerIdent)
arm_burst_timer(/*restart=*/false);
}
void arm_burst_timer(bool restart) {
if (kq_ < 0)
return;
if (restart)
burst_index_ = 0;
if (burst_index_ >= onion::lifecycle::kBigAppReconcileBurstCount)
return;
struct kevent event;
EV_SET(&event, kReconcileBurstTimerIdent, EVFILT_TIMER,
EV_ADD | EV_ENABLE | EV_ONESHOT, 0,
onion::lifecycle::kBigAppReconcileBurstMs[burst_index_], nullptr);
if (kevent(kq_, &event, 1, nullptr, 0, nullptr) != 0) {
LOG_WARN("[lifecycle] Big App burst timer failed: %s", strerror(errno));
return;
}
++burst_index_;
}
void disarm_burst_timer() {
burst_index_ = onion::lifecycle::kBigAppReconcileBurstCount;
if (kq_ < 0)
return;
struct kevent event;
EV_SET(&event, kReconcileBurstTimerIdent, EVFILT_TIMER, EV_DELETE, 0, 0,
nullptr);
(void)kevent(kq_, &event, 1, nullptr, 0, nullptr);
}
int control_read_fd_ = -1;
int kq_ = -1;
pid_t syscore_pid_ = -1;
int burst_index_ = onion::lifecycle::kBigAppReconcileBurstCount;
SceSysCoreAppLifecycleCollector collector_;
};
@@ -0,0 +1,151 @@
/* Copyright (C) 2026 OnionHEN / LightningMods
*
* Decide when a SysCore process event should publish BigAppStarted.
*
* NOTE_TRACK does deliver the game child's NOTE_EXEC. GetAppInfo is often
* already filled from SysCore appParam at that instant, but
* sceSystemServiceGetAppIdOfRunningBigApp is not: ResArbitrator registers
* the Big App after EXEC. Prefer a usable running-Big-App query when it is
* ready (Cdlg EXEC, SysCore FORK). Otherwise publish from a non-system EXEC
* identity instead of waiting for the reconcile timer.
*/
#pragma once
#include <cstdint>
#include <string_view>
#include <sys/types.h>
namespace onion::lifecycle {
enum class BigAppCollectAction {
Ignore,
PublishStarted,
};
enum class BigAppCollectReason {
AlreadyTracked,
ExecNotReady,
ExecNotRunningBigApp,
RunningQueryNotReady,
RunningPidInvalid,
PublishFromExec,
PublishFromRunning,
};
struct ExecSnapshot {
pid_t pid = -1;
int app_info_rc = -1;
uint32_t app_id = 0;
std::string_view title_id;
bool already_tracked = false;
};
struct RunningBigAppSnapshot {
bool query_ok = false;
pid_t pid = -1;
int app_id = -1;
std::string_view title_id;
};
struct BigAppCollectDecision {
BigAppCollectAction action = BigAppCollectAction::Ignore;
BigAppCollectReason reason = BigAppCollectReason::RunningQueryNotReady;
pid_t pid = -1;
uint32_t app_id = 0;
std::string_view title_id;
};
inline constexpr int kBigAppReconcileBurstMs[] = {100, 300, 1000};
inline constexpr int kBigAppReconcileBurstCount =
static_cast<int>(sizeof(kBigAppReconcileBurstMs) /
sizeof(kBigAppReconcileBurstMs[0]));
inline constexpr int kBigAppReconcileFallbackMs = 2000;
inline bool running_snapshot_usable(const RunningBigAppSnapshot &running) {
return running.query_ok && running.pid > 1 && running.app_id >= 0 &&
!running.title_id.empty();
}
inline bool exec_snapshot_usable(const ExecSnapshot &exec) {
return exec.pid > 1 && exec.app_info_rc == 0 && !exec.title_id.empty() &&
!exec.already_tracked;
}
inline bool is_system_app_title(std::string_view title_id) {
return title_id.size() >= 4 && title_id.compare(0, 4, "NPXS") == 0;
}
inline const char *big_app_collect_reason_name(BigAppCollectReason reason) {
switch (reason) {
case BigAppCollectReason::AlreadyTracked:
return "already-tracked";
case BigAppCollectReason::ExecNotReady:
return "exec-not-ready";
case BigAppCollectReason::ExecNotRunningBigApp:
return "exec-not-running-big-app";
case BigAppCollectReason::RunningQueryNotReady:
return "running-query-not-ready";
case BigAppCollectReason::RunningPidInvalid:
return "running-pid-invalid";
case BigAppCollectReason::PublishFromExec:
return "exec";
case BigAppCollectReason::PublishFromRunning:
return "running";
}
return "unknown";
}
inline BigAppCollectDecision
decide_big_app_started(const ExecSnapshot &exec,
const RunningBigAppSnapshot &running,
bool running_pid_already_tracked) {
BigAppCollectDecision decision;
if (running_snapshot_usable(running)) {
decision.pid = running.pid;
decision.app_id = static_cast<uint32_t>(running.app_id);
decision.title_id = running.title_id;
if (running_pid_already_tracked) {
decision.action = BigAppCollectAction::Ignore;
decision.reason = BigAppCollectReason::AlreadyTracked;
return decision;
}
decision.action = BigAppCollectAction::PublishStarted;
const bool exec_matches =
exec.pid == running.pid && exec.app_info_rc == 0 &&
!exec.title_id.empty() && exec.title_id == running.title_id &&
exec.app_id == static_cast<uint32_t>(running.app_id);
decision.reason = exec_matches ? BigAppCollectReason::PublishFromExec
: BigAppCollectReason::PublishFromRunning;
return decision;
}
if (exec_snapshot_usable(exec) && !is_system_app_title(exec.title_id)) {
decision.action = BigAppCollectAction::PublishStarted;
decision.reason = BigAppCollectReason::PublishFromExec;
decision.pid = exec.pid;
decision.app_id = exec.app_id;
decision.title_id = exec.title_id;
return decision;
}
decision.pid = exec.pid;
decision.app_id = exec.app_id;
decision.title_id = exec.title_id;
decision.action = BigAppCollectAction::Ignore;
if (exec.already_tracked) {
decision.reason = BigAppCollectReason::AlreadyTracked;
} else if (exec_snapshot_usable(exec) &&
is_system_app_title(exec.title_id)) {
decision.reason = BigAppCollectReason::ExecNotRunningBigApp;
} else if (!running.query_ok) {
decision.reason = BigAppCollectReason::RunningQueryNotReady;
} else if (running.pid <= 1) {
decision.reason = BigAppCollectReason::RunningPidInvalid;
} else {
decision.reason = BigAppCollectReason::ExecNotReady;
}
return decision;
}
} // namespace onion::lifecycle
+1
View File
@@ -138,6 +138,7 @@ TEST_CXX_SRCS := \
test_settings.cpp \
test_msg_protocol.cpp \
test_app_jailbreak_policy.cpp \
test_big_app_collect_policy.cpp \
test_debug_settings_route_policy.cpp \
test_ipc_harden.cpp \
test_ps5_settings_ui.cpp \
+1
View File
@@ -56,6 +56,7 @@ Binary: `source/util/build/host-tests/onion-host-tests`
| `test_msg_protocol` | IPC paths, magic, command ordinals, `IPC_Ret`, message POD, reply JSON body |
| `test_util_language` | system-language cache/refresh and notification language selection |
| `test_app_jailbreak_policy` | configurable app-jailbreak exact/prefix Title ID allowlist, including the Itemzflow compatibility ID `ITEM00001` |
| `test_big_app_collect_policy` | Big App start collection: EXEC identity can publish before Running Big App is registered; Cdlg EXEC reconciles the homebrew pid |
| `test_ps5_settings_ui` | fluent XML builder + escaping |
| `test_toolbox_route` | resource → page routing + dynamic cheat enabled-state and Kstuff descriptors |
| `test_toolbox_i18n` | locale-key parity for toolbox groups (zh-Hans, en) |
@@ -0,0 +1,187 @@
#include "test_harness.h"
#include <onion/big_app_collect_policy.hpp>
namespace {
using onion::lifecycle::BigAppCollectAction;
using onion::lifecycle::BigAppCollectReason;
using onion::lifecycle::decide_big_app_started;
using onion::lifecycle::ExecSnapshot;
using onion::lifecycle::RunningBigAppSnapshot;
int test_publish_when_exec_is_running_big_app(void) {
ExecSnapshot exec;
exec.pid = 179;
exec.app_info_rc = 0;
exec.app_id = 32792;
exec.title_id = "LAPY20011";
RunningBigAppSnapshot running;
running.query_ok = true;
running.pid = 179;
running.app_id = 32792;
running.title_id = "LAPY20011";
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::PublishStarted);
TEST_ASSERT_TRUE(decision.reason == BigAppCollectReason::PublishFromExec);
TEST_ASSERT_EQ_INT(179, static_cast<int>(decision.pid));
TEST_ASSERT_EQ_INT(32792, static_cast<int>(decision.app_id));
TEST_ASSERT_TRUE(decision.title_id == "LAPY20011");
return 0;
}
int test_reconcile_cdlg_exec_to_running_homebrew(void) {
ExecSnapshot exec;
exec.pid = 180;
exec.app_info_rc = 0;
exec.app_id = 32793;
exec.title_id = "NPXS40093";
RunningBigAppSnapshot running;
running.query_ok = true;
running.pid = 179;
running.app_id = 32792;
running.title_id = "LAPY20011";
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::PublishStarted);
TEST_ASSERT_TRUE(decision.reason == BigAppCollectReason::PublishFromRunning);
TEST_ASSERT_EQ_INT(179, static_cast<int>(decision.pid));
TEST_ASSERT_TRUE(decision.title_id == "LAPY20011");
return 0;
}
int test_reconcile_when_exec_identity_not_ready(void) {
ExecSnapshot exec;
exec.pid = 179;
exec.app_info_rc = -1;
RunningBigAppSnapshot running;
running.query_ok = true;
running.pid = 179;
running.app_id = 32792;
running.title_id = "LAPY20011";
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::PublishStarted);
TEST_ASSERT_TRUE(decision.reason == BigAppCollectReason::PublishFromRunning);
TEST_ASSERT_EQ_INT(179, static_cast<int>(decision.pid));
return 0;
}
int test_ignore_already_tracked_running_pid(void) {
ExecSnapshot exec;
exec.pid = 179;
exec.app_info_rc = 0;
exec.app_id = 32792;
exec.title_id = "LAPY20011";
RunningBigAppSnapshot running;
running.query_ok = true;
running.pid = 179;
running.app_id = 32792;
running.title_id = "LAPY20011";
const auto decision = decide_big_app_started(exec, running, true);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::Ignore);
TEST_ASSERT_TRUE(decision.reason == BigAppCollectReason::AlreadyTracked);
TEST_ASSERT_EQ_INT(179, static_cast<int>(decision.pid));
return 0;
}
int test_publish_exec_when_running_query_not_ready(void) {
ExecSnapshot exec;
exec.pid = 99;
exec.app_info_rc = 0;
exec.app_id = 24;
exec.title_id = "LAPY20011";
RunningBigAppSnapshot running;
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::PublishStarted);
TEST_ASSERT_TRUE(decision.reason == BigAppCollectReason::PublishFromExec);
TEST_ASSERT_EQ_INT(99, static_cast<int>(decision.pid));
TEST_ASSERT_TRUE(decision.title_id == "LAPY20011");
return 0;
}
int test_skip_fork_until_identity_or_running_query(void) {
ExecSnapshot exec;
exec.pid = 99;
RunningBigAppSnapshot running;
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::Ignore);
TEST_ASSERT_TRUE(decision.reason ==
BigAppCollectReason::RunningQueryNotReady);
return 0;
}
int test_skip_system_exec_until_running_query(void) {
ExecSnapshot exec;
exec.pid = 100;
exec.app_info_rc = 0;
exec.app_id = 25;
exec.title_id = "NPXS40093";
RunningBigAppSnapshot running;
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::Ignore);
TEST_ASSERT_TRUE(decision.reason ==
BigAppCollectReason::ExecNotRunningBigApp);
return 0;
}
int test_skip_when_running_pid_invalid(void) {
ExecSnapshot exec;
RunningBigAppSnapshot running;
running.query_ok = true;
running.pid = -1;
running.app_id = 32792;
running.title_id = "LAPY20011";
const auto decision = decide_big_app_started(exec, running, false);
TEST_ASSERT_TRUE(decision.action == BigAppCollectAction::Ignore);
TEST_ASSERT_TRUE(decision.reason == BigAppCollectReason::RunningPidInvalid);
return 0;
}
int test_reconcile_retry_delays(void) {
TEST_ASSERT_EQ_INT(3, onion::lifecycle::kBigAppReconcileBurstCount);
TEST_ASSERT_EQ_INT(100, onion::lifecycle::kBigAppReconcileBurstMs[0]);
TEST_ASSERT_EQ_INT(300, onion::lifecycle::kBigAppReconcileBurstMs[1]);
TEST_ASSERT_EQ_INT(1000, onion::lifecycle::kBigAppReconcileBurstMs[2]);
TEST_ASSERT_EQ_INT(2000, onion::lifecycle::kBigAppReconcileFallbackMs);
TEST_ASSERT_STREQ("running", onion::lifecycle::big_app_collect_reason_name(
BigAppCollectReason::PublishFromRunning));
TEST_ASSERT_STREQ("exec", onion::lifecycle::big_app_collect_reason_name(
BigAppCollectReason::PublishFromExec));
return 0;
}
} // namespace
extern "C" int test_big_app_collect_policy_suite(void) {
int failures = 0;
failures += onion_test_run("big_app_collect.exec_matches_running",
test_publish_when_exec_is_running_big_app);
failures += onion_test_run("big_app_collect.cdlg_reconciles_homebrew",
test_reconcile_cdlg_exec_to_running_homebrew);
failures += onion_test_run("big_app_collect.exec_identity_not_ready",
test_reconcile_when_exec_identity_not_ready);
failures += onion_test_run("big_app_collect.already_tracked",
test_ignore_already_tracked_running_pid);
failures += onion_test_run("big_app_collect.exec_before_running_query",
test_publish_exec_when_running_query_not_ready);
failures += onion_test_run("big_app_collect.fork_without_identity",
test_skip_fork_until_identity_or_running_query);
failures += onion_test_run("big_app_collect.system_exec_without_running",
test_skip_system_exec_until_running_query);
failures += onion_test_run("big_app_collect.running_pid_invalid",
test_skip_when_running_pid_invalid);
failures += onion_test_run("big_app_collect.retry_delays",
test_reconcile_retry_delays);
return failures;
}
+2
View File
@@ -26,6 +26,7 @@ extern "C" int test_platform_notify_suite(void);
extern "C" int test_util_language_suite(void);
extern "C" int test_msg_protocol_suite(void);
extern "C" int test_app_jailbreak_policy_suite(void);
extern "C" int test_big_app_collect_policy_suite(void);
extern "C" int test_debug_settings_route_policy_suite(void);
extern "C" int test_ipc_harden_suite(void);
extern "C" int test_toolbox_injection_suite(void);
@@ -80,6 +81,7 @@ int main() {
failures += test_util_language_suite();
failures += test_msg_protocol_suite();
failures += test_app_jailbreak_policy_suite();
failures += test_big_app_collect_policy_suite();
failures += test_debug_settings_route_policy_suite();
failures += test_ipc_harden_suite();
failures += test_toolbox_injection_suite();