Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a1fd4ebc authored by Armelle Laine's avatar Armelle Laine Committed by Automerger Merge Worker
Browse files

Merge "trusty: Add trusty stats test" am: 8a42990c am: 3f7c2fae am: 6649500a

parents 20efe8c8 6649500a
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
// Copyright (C) 2021 The Android Open-Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

cc_test {
    name: "trusty_stats_test",
    vendor: true,
    srcs: [
        "stats_test.cpp",
    ],
    static_libs: [
        "libgmock",
    ],
    shared_libs: [
        "libbase",
        "liblog",
        "libtrusty",
        "libbinder",
        "libbinder_trusty",
        "libutils",

        // AIDL interface deps versions, please refer to below link
        // https://source.android.com/docs/core/architecture/aidl/stable-aidl#module-naming-rules
        "android.frameworks.stats-V1-cpp",
        "android.trusty.stats.nw.setter-cpp",
    ],
    cflags: [
        "-Wall",
        "-Werror",
    ],
    require_root: true,
    proprietary: true,
}
+97 −0
Original line number Diff line number Diff line
# Development Notes

*    First get [repo_pull.py and gerrit.py](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/) from aosp.

*    Although this repo is not currently in Trusty’s manifest, it’s sufficient to copy these two python scripts to the root of the Trusty project and run them from there. Make sure to follow the [repo_pull installation](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/#installation) steps if necessary.

## Build

Build Android:

```sh
source build/envsetup.sh
lunch qemu_trusty_arm64-userdebug
m
```

Build Trusty:

```sh
./trusty/vendor/google/aosp/scripts/build.py qemu-generic-arm64-test-debug --skip-tests 2>stderr.log
```

## Trusty PORT_TEST

On QEmu:

```sh
./build-root/build-qemu-generic-arm64-test-debug/run --headless --boot-test "com.android.trusty.stats.test" --verbose
```

On device: (Build for your device's debug target on both Adroid and Trusty)

```sh
/vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
```

On device, in a loop:

```sh
cat << 'EOF' > metrics.sh
#!/system/bin/sh
TIMES=${1:-0}
X=0
while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
do
  echo "######################## stats.test $X " $(( X++ ));
  /vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
done
EOF

adb wait-for-device
adb push metrics.sh /data/user/test/metrics.sh
adb shell sh /data/user/test/metrics.sh
```

## Android Native Test

On QEmu:

```sh
./build-root/build-qemu-generic-arm64-test-debug/run --headless --android $ANDROID_PROJECT_ROOT --shell-command "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test" --verbose
```

On device: (Build for your device's debug target on both Adroid and Trusty)

```sh
/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
```

On device, in a loop:

```sh
cat << 'EOF' > metrics-nw.sh
#!/system/bin/sh
TIMES=${1:-0}
X=0
while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
do
  echo "######################## stats.test $X " $(( X++ ));
  /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
done
EOF

adb wait-for-device
adb push metrics.sh /data/user/test/metrics-nw.sh
adb shell sh /data/user/test/metrics-nw.sh
```

## Trusty Backtrace analysis


```
$ export A2L=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-addr2line
$ export OD=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-objdump
$ $OD -d -C build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf > objdump.lst
$ $A2L -e build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf 0xe5104
```
+364 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <errno.h>
#include <getopt.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

#include <condition_variable>
#include <cstddef>
#include <mutex>
#include <queue>

#include <android-base/expected.h>
#include <android-base/logging.h>
#include <android/frameworks/stats/BnStats.h>
#include <android/frameworks/stats/IStats.h>
#include <android/trusty/stats/nw/setter/IStatsSetter.h>
#include <binder/RpcServer.h>
#include <binder/RpcSession.h>
#include <binder/RpcTransportRaw.h>
#include <binder/RpcTransportTipcAndroid.h>
#include <binder/RpcTrusty.h>
#include <trusty/tipc.h>

/** DOC:
 * ./build-root/build-qemu-generic-arm64-test-debug/run \
 *       --android $ANDROID_PROJECT_ROOT \
 *       --headless --shell-command \
 *       "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test"
 *
 * adb -s emulator-5554 shell \
 *       /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
 */
using ::android::base::unique_fd;
using ::android::binder::Status;
using ::android::frameworks::stats::BnStats;
using ::android::frameworks::stats::IStats;
using ::android::frameworks::stats::VendorAtom;
using ::android::frameworks::stats::VendorAtomValue;
using ::android::trusty::stats::nw::setter::IStatsSetter;

constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0";
constexpr const char kTrustyStatsSetterTest[] =
        "com.android.frameworks.stats.trusty.test.relayer.istats_setter";
constexpr const char kTrustyStatsSetterMetrics[] =
        "com.android.frameworks.stats.trusty.metrics.istats_setter";
constexpr const char kTrustyStatsPortTest[] = "com.android.trusty.stats.test";
constexpr const char kTrustyCrashPortTest[] = "com.android.trusty.crashtest";
constexpr const char kTrustyCrasherUuid[] = "7ee4dddc-177a-420a-96ea-5d413d88228e:crasher";

enum TrustyAtoms : int32_t {
    TrustyAppCrashed = 100072,
    TrustyError = 100145,
    TrustyStorageError = 100146
};

enum TestMsgHeader : int32_t {
    TEST_PASSED = 0,
    TEST_FAILED = 1,
    TEST_MESSAGE = 2,
};

namespace android {
namespace trusty {
namespace stats {

class Stats : public BnStats {
  public:
    Stats() : BnStats() {}

    Status reportVendorAtom(const VendorAtom& vendorAtom) override {
        const char* atomIdStr = vendorAtomStr(vendorAtom.atomId);
        ALOGD("Vendor atom reported of type: %s\n", atomIdStr);
        std::lock_guard lock(mLock);
        mQueueVendorAtom.push(vendorAtom);
        mCondVar.notify_one();
        return Status::ok();
    }

    status_t getVendorAtom(VendorAtom* pVendorAtom, int64_t waitForMs) {
        std::unique_lock lock(mLock);
        while (mQueueVendorAtom.empty()) {
            auto rc = mCondVar.wait_for(lock, std::chrono::milliseconds(waitForMs));
            if (rc == std::cv_status::timeout) {
                return TIMED_OUT;
            }
        }
        *pVendorAtom = mQueueVendorAtom.front();
        mQueueVendorAtom.pop();
        return NO_ERROR;
    }

  private:
    const char* vendorAtomStr(int32_t atomId) {
        switch (atomId) {
            case TrustyAtoms::TrustyAppCrashed:
                return "TrustyAtoms::TrustyAppCrashed";
            case TrustyAtoms::TrustyError:
                return "TrustyAtoms::TrustyError";
            case TrustyAtoms::TrustyStorageError:
                return "TrustyAtoms::TrustyStorageError";
            default:
                return "unknown TrustyAtoms type";
        }
    }
    std::mutex mLock;
    std::condition_variable mCondVar;
    std::queue<VendorAtom> mQueueVendorAtom;
};

class TrustyStatsTestBase : public ::testing::Test {
  protected:
    TrustyStatsTestBase(std::string&& portNameStatsSetter, std::string&& portNamePortTest)
        : mPortTestFd(-1),
          mPortNameStatsSetter(std::move(portNameStatsSetter)),
          mPortNamePortTest(std::move(portNamePortTest)) {}

    void SetUp() override {
        // Commenting out the server portion because we do not have any direct
        // incoming call Calls from TA are currently being handled on the mSession's
        // extra thread. android::sp<::android::RpcServer> server =
        // ::android::RpcServer::make(::android::RpcTransportCtxFactoryRaw::make());

        mStats = android::sp<Stats>::make();
        // Increasing number of incoming threads on mSession to be able to receive
        // callbacks
        auto session_initializer = [](sp<RpcSession>& session) {
            session->setMaxIncomingThreads(1);
        };

        ASSERT_FALSE(mSession);
        mSession = RpcTrustyConnectWithSessionInitializer(
                kTrustyDefaultDeviceName, mPortNameStatsSetter.c_str(), session_initializer);
        ASSERT_TRUE(mSession);

        auto root = mSession->getRootObject();
        ASSERT_TRUE(root);
        auto statsSetter = IStatsSetter::asInterface(root);
        ASSERT_TRUE(statsSetter);
        statsSetter->setInterface(mStats);
    }
    void TearDown() override {
        // close connection to unitest app
        if (mPortTestFd != -1) {
            tipc_close(mPortTestFd);
        }
        mPortTestFd = -1;

        if (mSession) {
            // shutdownAndWait here races with sending out the DecStrong
            // messages after reportVendorAtom returns, so we delay it a little
            // bit to give the messages time to go out over the transport
            usleep(50000);
            ASSERT_TRUE(mSession->shutdownAndWait(true));
        }
        mSession.clear();
        mStats.clear();
    }
    void StartPortTest() {
        // connect to unitest app
        mPortTestFd = tipc_connect(kTrustyDefaultDeviceName, mPortNamePortTest.c_str());
        if (mPortTestFd < 0) {
            ALOGE("Failed to connect to '%s' app: %s\n", kTrustyStatsPortTest,
                  strerror(-mPortTestFd));
        }
        ASSERT_GT(mPortTestFd, 0);
    }
    void WaitPortTestDone() {
        // wait for test to complete
        char rxBuf[1024];
        const char prolog[] = "Trusty PORT_TEST:";
        strncpy(rxBuf, prolog, sizeof(prolog) - 1);
        char* pRxBuf = rxBuf + sizeof(prolog) - 1;
        size_t remainingBufSize = sizeof(rxBuf) - sizeof(prolog) - 1;

        ASSERT_NE(mPortTestFd, -1);
        for (;;) {
            int rc = read(mPortTestFd, pRxBuf, remainingBufSize);
            ASSERT_GT(rc, 0);
            ASSERT_LT(rc, (int)remainingBufSize);
            if (pRxBuf[0] == TEST_PASSED) {
                break;
            } else if (pRxBuf[0] == TEST_FAILED) {
                break;
            } else if (pRxBuf[0] == TEST_MESSAGE) {
                pRxBuf[0] = ' ';
                write(STDOUT_FILENO, rxBuf, rc + sizeof(prolog) - 1);
            } else {
                ALOGE("Bad message header: %d\n", rxBuf[0]);
                break;
            }
        }
        ASSERT_EQ(pRxBuf[0], TEST_PASSED);
    }

    android::sp<Stats> mStats;

  private:
    android::sp<RpcSession> mSession;
    int mPortTestFd;
    std::string mPortNameStatsSetter;
    std::string mPortNamePortTest;
};

class TrustyStatsTest : public TrustyStatsTestBase {
  protected:
    TrustyStatsTest() : TrustyStatsTestBase(kTrustyStatsSetterTest, kTrustyStatsPortTest) {}
};

class TrustyMetricsCrashTest : public TrustyStatsTestBase {
  protected:
    TrustyMetricsCrashTest()
        : TrustyStatsTestBase(kTrustyStatsSetterMetrics, kTrustyCrashPortTest) {}
};

TEST_F(TrustyStatsTest, CheckAtoms) {
    int atomAppCrashedCnt = 0;
    int atomStorageErrorCnt = 0;
    int atomTrustyErrorCnt = 0;
    uint64_t blockForMs = 500;
    StartPortTest();
    WaitPortTestDone();
    for (;;) {
        VendorAtom vendorAtom;
        auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
        ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
        if (status == TIMED_OUT) {
            // No more atoms
            break;
        }

        ASSERT_THAT(vendorAtom.atomId,
                    ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
                                     ::testing::Eq(TrustyAtoms::TrustyError),
                                     ::testing::Eq(TrustyAtoms::TrustyStorageError)));
        ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
        switch (vendorAtom.atomId) {
            case TrustyAtoms::TrustyAppCrashed:
                ++atomAppCrashedCnt;
                ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
                break;
            case TrustyAtoms::TrustyStorageError:
                ++atomStorageErrorCnt;
                ASSERT_EQ(vendorAtom.values[0].get<VendorAtomValue::intValue>(), 5);
                ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()),
                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
                ASSERT_STREQ(String8(vendorAtom.values[2].get<VendorAtomValue::stringValue>()),
                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
                ASSERT_EQ(vendorAtom.values[3].get<VendorAtomValue::intValue>(), 1);
                ASSERT_EQ(vendorAtom.values[4].get<VendorAtomValue::intValue>(), 3);
                ASSERT_EQ(vendorAtom.values[5].get<VendorAtomValue::longValue>(),
                          0x4BCDEFABBAFEDCBALL);
                ASSERT_EQ(vendorAtom.values[6].get<VendorAtomValue::intValue>(), 4);
                ASSERT_EQ(vendorAtom.values[7].get<VendorAtomValue::longValue>(), 1023);
                break;
            case TrustyAtoms::TrustyError:
                ++atomTrustyErrorCnt;
                break;
            default:
                FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
                break;
        }
    };
    ASSERT_EQ(atomAppCrashedCnt, 1);
    ASSERT_EQ(atomStorageErrorCnt, 1);
    ASSERT_EQ(atomTrustyErrorCnt, 0);
}

TEST_F(TrustyMetricsCrashTest, CheckTrustyCrashAtoms) {
    const std::vector<uint32_t> kExpectedCrashReasonsArm64{
            0x00000001U,  // exit_failure (twice)
            0x00000001U,
            0x92000004U,  // read_null_ptr
            0xf200002aU,  // brk_instruction
            0x92000004U,  // read_bad_ptr
            0x92000044U,  // crash_write_bad_ptr
            0x9200004fU,  // crash_write_ro_ptr
            0x8200000fU,  // crash_exec_rodata
            0x8200000fU,  // crash_exec_data
    };
    const std::vector<uint32_t> kExpectedCrashReasonsArm32{
            0x00000001U,  // exit_failure (twice)
            0x00000001U,
            0x20000007U,  // read_null_ptr
            0x20000007U,  // read_bad_ptr
            0x20000807U,  // crash_write_bad_ptr
            0x2000080fU,  // crash_write_ro_ptr
            0x3000000fU,  // crash_exec_rodata
            0x3000000fU,  // crash_exec_data
    };

    int expectedAtomCnt = 7;
    int atomAppCrashedCnt = 0;
    int atomStorageErrorCnt = 0;
    int atomTrustyErrorCnt = 0;
    std::vector<uint32_t> atomCrashReasons;
    uint64_t blockForMs = 500;
    StartPortTest();
    WaitPortTestDone();
    for (;;) {
        VendorAtom vendorAtom;
        auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
        ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
        if (status == TIMED_OUT) {
            // No more atoms
            break;
        }

        ASSERT_THAT(vendorAtom.atomId,
                    ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
                                     ::testing::Eq(TrustyAtoms::TrustyError),
                                     ::testing::Eq(TrustyAtoms::TrustyStorageError)));
        ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");

        switch (vendorAtom.atomId) {
            case TrustyAtoms::TrustyAppCrashed:
                ++atomAppCrashedCnt;
                ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
                             kTrustyCrasherUuid);
                atomCrashReasons.push_back(vendorAtom.values[1].get<VendorAtomValue::intValue>());
                break;
            case TrustyAtoms::TrustyStorageError:
                ++atomStorageErrorCnt;
                break;
            case TrustyAtoms::TrustyError:
                ++atomTrustyErrorCnt;
                ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()), "");
                break;
            default:
                FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
        }
    }
    ASSERT_GE(atomAppCrashedCnt, expectedAtomCnt - 1);
    ASSERT_EQ(atomStorageErrorCnt, 0);
    // There is one dropped event left over from Trusty boot,
    // it may show up here
    ASSERT_LE(atomTrustyErrorCnt, 1);
    ASSERT_THAT(atomCrashReasons,
                ::testing::AnyOf(kExpectedCrashReasonsArm64, kExpectedCrashReasonsArm32));
};

}  // namespace stats
}  // namespace trusty
}  // namespace android