Controllore dell'auto

Utilizza il watchdog dell'auto per facilitare il debug del VHAL. Il watchdog di Car monitora l'integrità e termina i processi non integri. Affinché un processo sia monitorato dal watchdog dell'auto, questo deve essere registrato presso il watchdog dell'auto. Quando il watchdog dell'auto termina i processi non corretti, scrive lo stato dei processi in data/anr come per altri dump ANR (Application Not Responding). In questo modo viene gestito il processo di debug.

Questo articolo descrive come i servizi e gli HAL del fornitore possono registrare un processo con il watchdog dell'auto.

HAL del fornitore

In genere, l'HAL del fornitore utilizza un pool di thread per hwbinder. Tuttavia, il client watchdog dell'auto comunica con il daemon del watchdog dell'auto tramite binder, che è diverso da hwbinder. Di conseguenza, è in uso un altro pool di thread per binder.

Specifica l'aidl per il watchdog dell'auto nel file makefile

  1. Includi carwatchdog_aidl_interface-ndk_platform in 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",
        ],
    }
    

Aggiungere un criterio SELinux

  1. Consenti a system_server di uccidere l'HAL. Se non hai system_server.te, creane uno. Ti consigliamo vivamente di aggiungere un criterio SELinux a ogni dispositivo.
  2. Consenti all'HAL del fornitore di utilizzare il binder (macro binder_use) e aggiungi l'HAL del fornitore al dominio client carwatchdog (macro carwatchdog_client_domain). Controlla il codice riportato di seguito per systemserver.te e 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)
    

Implementa una classe client ereditando BnCarWatchdogClient

  1. In checkIfAlive, esegui il controllo di integrità. Ad esempio, pubblicare nel gestore del loop del thread. Se è integro, chiama ICarWatchdog::tellClientAlive. Consulta il codice seguente per WatchogClient.h e 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();
    }
    

Avvia il thread del binder e registra il client

  1. Crea un pool di thread per la comunicazione del binder. Se il fornitore HAL utilizza hwbinder per il proprio scopo, devi creare un altro pool di thread per le comunicazioni con il binder del watchdog dell'auto.
  2. Cerca il daemon con il nome e chiama ICarWatchdog::registerClient. Il nome dell'interfaccia del daemon del watchdog dell'auto è android.automotive.watchdog.ICarWatchdog/default.
  3. In base alla reattività del servizio, seleziona uno dei tre seguenti tipi di timeout supportati dal watchdog dell'auto e poi passa il timeout nella chiamata a ICarWatchdog::registerClient:
    • critico(3 s)
    • moderate(5s)
    • normale(10 s)
    Guarda il codice seguente per VehicleService.cpp e 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;
    }
    

Servizi dei fornitori (nativi)

Specifica il file make aidl del watchdog dell'auto

  1. Includi carwatchdog_aidl_interface-ndk_platform in shared_libs.

    Android.bp

    cc_binary {
        name: "sample_native_client",
        srcs: [
            "src/*.cpp"
        ],
        shared_libs: [
            "carwatchdog_aidl_interface-ndk_platform",
            "libbinder_ndk",
        ],
        vendor: true,
    }
    

Aggiungere un criterio SELinux

  1. Per aggiungere un criterio SELinux, consenti al dominio di servizio del fornitore di utilizzare il binder (macro binder_use) e aggiungi il dominio di servizio del fornitore al dominio client carwatchdog (macro carwatchdog_client_domain). Consulta il codice seguente per sample_client.te e 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)
    

    contesti_file

    /vendor/bin/sample_native_client  u:object_r:sample_client_exec:s0
    

Implementa una classe client ereditando BnCarWatchdogClient

  1. Esegui un controllo di integrità in checkIfAlive. Un'opzione è pubblicare un post nel gestore dell'istruzione for del thread. Se è integro, chiama ICarWatchdog::tellClientAlive. Consulta il codice seguente per SampleNativeClient.h e 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);
    }
    

Avvia un thread del binder e registra il client

Il nome dell'interfaccia del daemon watchdog dell'auto è android.automotive.watchdog.ICarWatchdog/default.

  1. Cerca il demone con il nome e chiama ICarWatchdog::registerClient. Consulta il codice seguente per main.cpp e 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);
    }
    

Servizi ai fornitori (Android)

Implementa un client ereditando CarWatchdogClientCallback

  1. Modifica il nuovo file come segue:
    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() {}
    };
    

Registra il client

  1. Chiama 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);
    }
    

Annulla la registrazione del client

  1. Chiama CarWatchdogManager.unregisterClient() al termine del servizio:
    private void finishClient() {
        CarWatchdogManager manager =
            (CarWatchdogManager) car.getCarManager(
            Car.CAR_WATCHDOG_SERVICE);
        manager.unregisterClient(mClientCallback);
    }
    

Rileva i processi terminati dal watchdog dell'auto

Il watchdog dell'auto esegue il dump/l'interruzione delle procedure (HAL del fornitore, servizi nativi del fornitore, servizi Android del fornitore) registrate al watchdog dell'auto quando sono bloccate e non rispondono. Questo tipo di dumping viene rilevato controllando i logcat. Il watchdog della vettura genera un log carwatchdog killed process_name (pid:process_id) quando un processo problematico viene dumpato o terminato. Pertanto:

$ adb logcat -s CarServiceHelper | fgrep "carwatchdog killed"

Vengono acquisiti i log pertinenti. Ad esempio, se l'app KitchenSink (un client di monitoraggio delle auto) si blocca, nel log viene scritta una riga come quella riportata di seguito:

05-01 09:50:19.683   578  5777 W CarServiceHelper: carwatchdog killed com.google.android.car.kitchensink (pid: 5574)

Per determinare il motivo o il punto in cui l'app KitchenSink si è bloccata, utilizza il dump del processo memorizzato in /data/anr come faresti con le segnalazioni ANR di attività.

$ adb root
$ adb shell grep -Hn "pid process_pid" /data/anr/*

Il seguente output di esempio è specifico per l'app 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 -----

Individua il file dump (ad esempio /data/anr/anr_2020-05-01-09-50-18-290 nell'esempio precedente) e avvia l'analisi.