אפשר להשתמש ב-watchdog של המכונית כדי לנפות באגים ב-VHAL. הטיימר המפקח (watchdog) של המכונית עוקב אחרי תהליכים לא תקינים ומפסיק אותם. כדי שהתהליך יהיה במעקב על ידי הטיימר המפקח (watchdog) של הרכב, צריך לרשום את התהליך גם במפקח (watchdog) של הרכב. כשה-car watchdog מפסיק תהליכים לא תקינים, הוא רושם את הסטטוס של התהליכים ב-data/anr
, כמו בדפים אחרים של נתונים שנוצרים כשהאפליקציה לא מגיבה (ANR). כך קל יותר לנפות באגים.
המאמר הזה מתאר איך בקשות HAL ושירותים של ספקים יכולים לרשום תהליך באמצעות הטיימר המפקח (watchdog) של הרכב.
HAL של הספק
בדרך כלל, HAL של הספק משתמש במאגר חוטים ל-hwbinder
. עם זאת, הלקוח של הטיימר המפקח (watchdog) של הרכב מתקשר עם הדימון של הטיימר המפקח (watchdog) של הרכב דרך binder
, ששונה מ-hwbinder
. לכן נעשה שימוש במאגר אחר של שרשורים ב-binder
.
ציון הכלי עזר המפקח (watchdog) של מכונית ב-makefile
- כוללים את
carwatchdog_aidl_interface-ndk_platform
ב-shared_libs
:Android.bp
:cc_defaults { name: "vhal_v2_0_defaults", shared_libs: [ "libbinder_ndk", "libhidlbase", "liblog", "libutils", "android.hardware.automotive.vehicle@2.0", "carwatchdog_aidl_interface-ndk_platform", ], cflags: [ "-Wall", "-Wextra", "-Werror", ], }
הוספת מדיניות SELinux
-
מאשרים ל-
system_server
להרוג את ה-HAL. אם אין לכםsystem_server.te
, צריך ליצור אותו. מומלץ מאוד להוסיף מדיניות SELinux לכל מכשיר. -
מתן הרשאה ל-HAL של הספק להשתמש ב-binder (מאקרו
binder_use
) ולהוסיף את ה-HAL של הספק לדומיין הלקוחcarwatchdog
(פקודת מאקרוcarwatchdog_client_domain
). ראו את הקוד הבא עבורsystemserver.te
ו-vehicle_default.te
:system_server.te
# Allow system_server to kill vehicle HAL allow system_server hal_vehicle_server:process sigkill;
hal_vehicle_default.te
# Configuration for register VHAL to car watchdog carwatchdog_client_domain(hal_vehicle_default) binder_use(hal_vehicle_default)
הטמעת סוג לקוח באמצעות ירושה מ-BnCarWatchdogClient
-
ב-
checkIfAlive
, מבצעים בדיקת תקינות. לדוגמה, פרסום ב-handler של לולאת השרשור. אם הוא תקין, צריך להתקשר למספרICarWatchdog::tellClientAlive
. הקוד שלמטה מיועד ל-WatchogClient.h
ול-WatchogClient.cpp
:WatchogClient.h
class WatchdogClient : public aidl::android::automotive::watchdog::BnCarWatchdogClient { public: explicit WatchdogClient(const ::android::sp<::android::Looper>& handlerLooper, VehicleHalManager* vhalManager);
ndk::ScopedAStatus checkIfAlive(int32_t sessionId, aidl::android::automotive::watchdog::TimeoutLength timeout) override; ndk::ScopedAStatus prepareProcessTermination() override; };WatchogClient.cpp
ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength /*timeout*/) { // Implement or call your health check logic here return ndk::ScopedAStatus::ok(); }
התחלת השרשור של ה-binder ורישום הלקוח
- יצירת מאגר של חוטים לתקשורת של Binder. אם HAL של הספק משתמש ב-hwbinder למטרות משלו, צריך ליצור מאגר משימות נוסף לתקשורת של car watchdog binder).
-
מחפשים את הדימון לפי השם שלו ומפעילים את
ICarWatchdog::registerClient
. שם הממשק של הדימון של טיימר המפקח (watchdog) של המכונית הואandroid.automotive.watchdog.ICarWatchdog/default
. -
בהתאם למהירות התגובה של השירות, בוחרים באחד משלושת סוגי הזמן הקצוב לתפוגה הבאים שנתמכים על ידי ה-watchdog ברכב, ולאחר מכן מעבירים את הזמן הקצוב לתפוגה בקריאה ל-
ICarWatchdog::registerClient
:- critical(3s)
- בינוני(5 שניות)
- normal(10s)
VehicleService.cpp
ו-WatchogClient.cpp
:VehicleService.cpp
int main(int /* argc */, char* /* argv */ []) { // Set up thread pool for hwbinder configureRpcThreadpool(4, false /* callerWillJoin */); ALOGI("Registering as service..."); status_t status = service->registerAsService(); if (status != OK) { ALOGE("Unable to register vehicle service (%d)", status); return 1; } // Setup a binder thread pool to be a car watchdog client. ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); sp<Looper> looper(Looper::prepare(0 /* opts */)); std::shared_ptr<WatchdogClient> watchdogClient = ndk::SharedRefBase::make<WatchdogClient>(looper, service.get()); // The current health check is done in the main thread, so it falls short of capturing the real // situation. Checking through HAL binder thread should be considered. if (!watchdogClient->initialize()) { ALOGE("Failed to initialize car watchdog client"); return 1; } ALOGI("Ready"); while (true) { looper->pollAll(-1 /* timeoutMillis */); } return 1; }
WatchogClient.cpp
bool WatchdogClient::initialize() { ndk::SpAIBinder binder(AServiceManager_getService("android.automotive.watchdog.ICarWatchdog/default")); if (binder.get() == nullptr) { ALOGE("Failed to get carwatchdog daemon"); return false; } std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder); if (server == nullptr) { ALOGE("Failed to connect to carwatchdog daemon"); return false; } mWatchdogServer = server; binder = this->asBinder(); if (binder.get() == nullptr) { ALOGE("Failed to get car watchdog client binder object"); return false; } std::shared_ptr<ICarWatchdogClient> client = ICarWatchdogClient::fromBinder(binder); if (client == nullptr) { ALOGE("Failed to get ICarWatchdogClient from binder"); return false; } mTestClient = client; mWatchdogServer->registerClient(client, TimeoutLength::TIMEOUT_NORMAL); ALOGI("Successfully registered the client to car watchdog server"); return true; }
שירותי ספק (מקורי)
ציון קובץ ה-makefile של aidl של הטיימר המפקח (watchdog) של המכונית
- כוללים את
carwatchdog_aidl_interface-ndk_platform
ב-shared_libs
.Android.bp
cc_binary { name: "sample_native_client", srcs: [ "src/*.cpp" ], shared_libs: [ "carwatchdog_aidl_interface-ndk_platform", "libbinder_ndk", ], vendor: true, }
הוספת מדיניות SELinux
- כדי להוסיף מדיניות SELinux, צריך לאפשר לדומיין השירות של הספק להשתמש ב-binder (מאקרו
binder_use
) ולהוסיף את דומיין השירות של הספק לדומיין הלקוחcarwatchdog
(במאקרוcarwatchdog_client_domain
). הקוד שלמטה מיועד ל-sample_client.te
ול-file_contexts
:sample_client.te
type sample_client, domain; type sample_client_exec, exec_type, file_type, vendor_file_type; carwatchdog_client_domain(sample_client) init_daemon_domain(sample_client) binder_use(sample_client)
file_contexts
/vendor/bin/sample_native_client u:object_r:sample_client_exec:s0
הטמעת סוג לקוח באמצעות ירושה מ-BnCarWatchdogClient
- ב-
checkIfAlive
, מבצעים בדיקת תקינות. אפשרות אחת היא לפרסם את האירוע בטיפול בלולאת השרשור. אם הוא תקין, צריך להתקשר למספרICarWatchdog::tellClientAlive
. הקוד שלמטה מיועד ל-SampleNativeClient.h
ול-SampleNativeClient.cpp
:SampleNativeClient.h
class SampleNativeClient : public BnCarWatchdogClient { public: ndk::ScopedAStatus checkIfAlive(int32_t sessionId, TimeoutLength timeout) override; ndk::ScopedAStatus prepareProcessTermination() override; void initialize(); private: void respondToDaemon(); private: ::android::sp<::android::Looper> mHandlerLooper; std::shared_ptr<ICarWatchdog> mWatchdogServer; std::shared_ptr<ICarWatchdogClient> mClient; int32_t mSessionId; };
SampleNativeClient.cpp
ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength timeout) { mHandlerLooper->removeMessages(mMessageHandler, WHAT_CHECK_ALIVE); mSessionId = sessionId; mHandlerLooper->sendMessage(mMessageHandler, Message(WHAT_CHECK_ALIVE)); return ndk::ScopedAStatus::ok(); } // WHAT_CHECK_ALIVE triggers respondToDaemon from thread handler void WatchdogClient::respondToDaemon() { // your health checking method here ndk::ScopedAStatus status = mWatchdogServer->tellClientAlive(mClient, mSessionId); }
התחלת שרשור ב-binder ורישום הלקוח
שם הממשק של הטיימר המפקח (watchdog) של המכונית הוא android.automotive.watchdog.ICarWatchdog/default
.
- מחפשים את הדימון בעל השם ומתקשרים אל
ICarWatchdog::registerClient
. הקוד שלמטה מיועד ל-main.cpp
ול-SampleNativeClient.cpp
:main.cpp
int main(int argc, char** argv) { sp<Looper> looper(Looper::prepare(/*opts=*/0)); ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); std::shared_ptr<SampleNativeClient> client = ndk::SharedRefBase::make<SampleNatvieClient>(looper); // The client is registered in initialize() client->initialize(); ... }
SampleNativeClient.cpp
void SampleNativeClient::initialize() { ndk::SpAIBinder binder(AServiceManager_getService( "android.automotive.watchdog.ICarWatchdog/default")); std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder); mWatchdogServer = server; ndk::SpAIBinder binder = this->asBinder(); std::shared_ptr<ICarWatchdogClient> client = ICarWatchdogClient::fromBinder(binder) mClient = client; server->registerClient(client, TimeoutLength::TIMEOUT_NORMAL); }
שירותי ספקים (Android)
הטמעת לקוח באמצעות ירושה מ-CarWatchdogClientCallback
- עורכים את הקובץ החדש באופן הבא:
private final CarWatchdogClientCallback mClientCallback = new CarWatchdogClientCallback() { @Override public boolean onCheckHealthStatus(int sessionId, int timeout) { // Your health check logic here // Returning true implies the client is healthy // If false is returned, the client should call // CarWatchdogManager.tellClientAlive after health check is // completed } @Override public void onPrepareProcessTermination() {} };
רישום הלקוח
- התקשרות אל
CarWatchdogManager.registerClient()
:private void startClient() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager( Car.CAR_WATCHDOG_SERVICE); // Choose a proper executor according to your health check method ExecutorService executor = Executors.newFixedThreadPool(1); manager.registerClient(executor, mClientCallback, CarWatchdogManager.TIMEOUT_NORMAL); }
ביטול הרישום של הלקוח
- קוראים לפונקציה
CarWatchdogManager.unregisterClient()
כשהשירות יסתיים:private void finishClient() { CarWatchdogManager manager = (CarWatchdogManager) car.getCarManager( Car.CAR_WATCHDOG_SERVICE); manager.unregisterClient(mClientCallback); }
זיהוי תהליכים שהסתיימו על ידי הטיימר המפקח (watchdog) של הרכב
Car watchdog מאחסן או משמיד תהליכים (HAL של ספק, שירותים מקומיים של ספק, שירותי Android של ספק) שמתועדים ב-Car watchdog כשהם תקועים ולא מגיבים. כדי לזהות דיווחים כאלה, בודקים את ה-logcats. הטיימר המפקח (watchdog) של הרכב יוצר יומן carwatchdog killed process_name (pid:process_id)
כשתהליך בעייתי מבוטל או מבוטל. לכן:
$ adb logcat -s CarServiceHelper | fgrep "carwatchdog killed"
היומנים הרלוונטיים מתועדים. לדוגמה, אם אפליקציית KitchenSink (לקוח של Watchdog לרכב) נתקעת, שורה כמו זו שבהמשך תירשם ביומן:
05-01 09:50:19.683 578 5777 W CarServiceHelper: carwatchdog killed com.google.android.car.kitchensink (pid: 5574)
כדי לקבוע למה או איפה האפליקציה KitchenSink נתקעה, משתמשים ב-dump של התהליך שנשמר ב-/data/anr
בדיוק כמו שמשתמשים במקרים של ANR ב-Activity.
$ adb root $ adb shell grep -Hn "pid process_pid" /data/anr/*
הפלט לדוגמה הבא הוא ספציפי לאפליקציית KitchenSink:
$ adb shell su root grep -Hn "pid 5574" /data/anr/*. /data/anr/anr_2020-05-01-09-50-18-290:3:----- pid 5574 at 2020-05-01 09:50:18 ----- /data/anr/anr_2020-05-01-09-50-18-290:285:----- Waiting Channels: pid 5574 at 2020-05-01 09:50:18 -----
מחפשים את קובץ הדמפ (לדוגמה, /data/anr/anr_2020-05-01-09-50-18-290
בדוגמה שלמעלה) ומתחילים את הניתוח.