trusty: Add trusty stats test

Test an Android Daemon consuming IStats.aidl vendor atoms
from Trusty.

Test: /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
Bug: 259517277
Change-Id: I7486db5494a8fd4a995ec8a1a865e6e5fa515dfc
This commit is contained in:
Armelle Laine 2022-11-25 01:10:55 +00:00 committed by Andrei Homescu
parent 3feefa8b17
commit 8185610aa4
3 changed files with 508 additions and 0 deletions

View file

@ -0,0 +1,47 @@
// 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,
}

View file

@ -0,0 +1,97 @@
# 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 Trustys manifest, its 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
```

View file

@ -0,0 +1,364 @@
/*
* 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