411 lines
13 KiB
C++
411 lines
13 KiB
C++
/*
|
|
* 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 <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <binder/BpBinder.h>
|
|
#include <binder/IServiceManager.h>
|
|
#include <binder/Parcel.h>
|
|
#include <binder/RecordedTransaction.h>
|
|
#include <signal.h>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include "include/Analyzer.h"
|
|
|
|
using android::IBinder;
|
|
using android::NO_ERROR;
|
|
using android::sp;
|
|
using android::status_t;
|
|
using android::String16;
|
|
using android::aidl::Analyzer;
|
|
using android::binder::debug::RecordedTransaction;
|
|
using std::string;
|
|
|
|
namespace {
|
|
|
|
static volatile size_t gCtrlCCount = 0;
|
|
static constexpr size_t kCtrlCLimit = 3;
|
|
static const char kStandardRecordingPath[] = "/data/local/recordings/";
|
|
|
|
status_t startRecording(const sp<IBinder>& binder, const string& filePath) {
|
|
if (auto mkdir_return = mkdir(kStandardRecordingPath, 0666);
|
|
mkdir_return != 0 && errno != EEXIST) {
|
|
std::cout << "Failed to create recordings directory.\n";
|
|
return android::NO_ERROR;
|
|
}
|
|
|
|
int openFlags = O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC | O_BINARY;
|
|
android::base::unique_fd fd(open(filePath.c_str(), openFlags, 0666));
|
|
if (fd == -1) {
|
|
std::cout << "Failed to open file for recording with error: " << strerror(errno) << '\n';
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
// TODO (b/245804633): this still requires setenforce 0, but nothing above does
|
|
if (status_t err = binder->remoteBinder()->startRecordingBinder(fd); err != android::NO_ERROR) {
|
|
auto checkSE = std::ifstream("/sys/fs/selinux/enforce");
|
|
bool recommendSetenforce = false;
|
|
if ((checkSE.rdstate() & std::ifstream::failbit) != 0) {
|
|
std::cout << "Failed to determine selinux state.";
|
|
recommendSetenforce = true;
|
|
} else {
|
|
char seState = checkSE.get();
|
|
if (checkSE.good()) {
|
|
if (seState == '1') {
|
|
std::cout << "SELinux must be permissive.";
|
|
recommendSetenforce = true;
|
|
} else if (seState == '0') {
|
|
std::cout << "SELinux is permissive. Failing for some other reason.\n";
|
|
}
|
|
} else {
|
|
std::cout << "Failed to determine SELinux state.";
|
|
recommendSetenforce = true;
|
|
}
|
|
}
|
|
if (recommendSetenforce) {
|
|
std::cout << " Try running:\n\n setenforce 0\n\n";
|
|
}
|
|
std::cout << "Failed to start recording with error: " << android::statusToString(err) << '\n';
|
|
return err;
|
|
} else {
|
|
std::cout << "Recording started successfully.\n";
|
|
return android::NO_ERROR;
|
|
}
|
|
}
|
|
|
|
status_t stopRecording(const sp<IBinder>& binder) {
|
|
if (status_t err = binder->remoteBinder()->stopRecordingBinder(); err != NO_ERROR) {
|
|
std::cout << "Failed to stop recording with error: " << err << '\n';
|
|
return err;
|
|
} else {
|
|
std::cout << "Recording stopped successfully.\n";
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
void printTransaction(const RecordedTransaction& transaction) {
|
|
auto& analyzers = Analyzer::getAnalyzers();
|
|
|
|
auto analyzer = analyzers.find(transaction.getInterfaceName());
|
|
if (analyzer != analyzers.end()) {
|
|
(analyzer->second)
|
|
->getAnalyzeFunction()(transaction.getCode(), transaction.getDataParcel(),
|
|
transaction.getReplyParcel());
|
|
} else {
|
|
std::cout << "No analyzer:";
|
|
std::cout << " interface: " << transaction.getInterfaceName() << "\n";
|
|
std::cout << " code: " << transaction.getCode() << "\n";
|
|
std::cout << " data: " << transaction.getDataParcel().dataSize() << " bytes\n";
|
|
std::cout << " reply: " << transaction.getReplyParcel().dataSize() << " bytes\n";
|
|
}
|
|
std::cout << " status: " << transaction.getReturnedStatus() << "\n\n";
|
|
}
|
|
|
|
status_t inspectRecording(const string& path) {
|
|
auto& analyzers = Analyzer::getAnalyzers();
|
|
|
|
android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
|
|
if (fd.get() == -1) {
|
|
std::cout << "Failed to open recording file with error: " << strerror(errno) << '\n';
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
int i = 1;
|
|
while (auto transaction = RecordedTransaction::fromFile(fd)) {
|
|
std::cout << "Transaction " << i << ":\n";
|
|
printTransaction(transaction.value());
|
|
i++;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void incrementCtrlCCount(int signum) {
|
|
gCtrlCCount++;
|
|
if (gCtrlCCount > kCtrlCLimit) {
|
|
std::cout
|
|
<< "Ctrl+C multiple times, but could not quit application. If recording still running, you "
|
|
"might stop it manually.\n";
|
|
exit(signum);
|
|
}
|
|
}
|
|
|
|
status_t listenToFile(const string& filePath) {
|
|
android::base::unique_fd listenFd(open(filePath.c_str(), O_RDONLY));
|
|
if (listenFd == -1) {
|
|
std::cout << "Failed to open listening file with error: " << strerror(errno) << '\n';
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
auto& analyzers = Analyzer::getAnalyzers();
|
|
|
|
signal(SIGINT, incrementCtrlCCount);
|
|
std::cout << "Starting to listen:\n";
|
|
int i = 1;
|
|
while (gCtrlCCount == 0) {
|
|
auto transaction = RecordedTransaction::fromFile(listenFd);
|
|
if (!transaction) {
|
|
sleep(1);
|
|
continue;
|
|
}
|
|
std::cout << "Transaction " << i << ":\n";
|
|
printTransaction(transaction.value());
|
|
i++;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t replayFile(const sp<IBinder>& binder, const string& path) {
|
|
auto& analyzers = Analyzer::getAnalyzers();
|
|
|
|
android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
|
|
if (fd.get() == -1) {
|
|
std::cout << "Failed to open recording file with error: " << strerror(errno) << '\n';
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
int failureCount = 0;
|
|
int i = 1;
|
|
while (auto transaction = RecordedTransaction::fromFile(fd)) {
|
|
std::cout << "Replaying Transaction " << i << ":\n";
|
|
printTransaction(transaction.value());
|
|
|
|
android::Parcel send, reply;
|
|
send.setData(transaction->getDataParcel().data(), transaction->getDataParcel().dataSize());
|
|
android::status_t status = binder->remoteBinder()->transact(transaction->getCode(), send,
|
|
&reply, transaction->getFlags());
|
|
if (status != transaction->getReturnedStatus()) {
|
|
std::cout << "Failure: Expected status " << transaction->getReturnedStatus()
|
|
<< " but received status " << status << "\n\n";
|
|
failureCount++;
|
|
} else {
|
|
std::cout << "Transaction replayed correctly."
|
|
<< "\n\n";
|
|
}
|
|
i++;
|
|
}
|
|
std::cout << i << " transactions replayed.\n";
|
|
if (failureCount > 0) {
|
|
std::cout << failureCount << " transactions had unexpected status. See logs for details.\n";
|
|
return android::UNKNOWN_ERROR;
|
|
} else {
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
status_t listAvailableInterfaces(int, char**) {
|
|
auto& analyzers = Analyzer::getAnalyzers();
|
|
std::cout << "Available Interfaces (" << analyzers.size() << "):\n";
|
|
for (auto a = analyzers.begin(); a != analyzers.end(); a++) {
|
|
std::cout << " " << a->second->getInterfaceName() << '\n';
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
struct AnalyzerCommand {
|
|
std::function<status_t(int, char*[])> command;
|
|
std::string overview;
|
|
std::string compactArguments;
|
|
std::string helpDetail;
|
|
};
|
|
|
|
status_t helpCommandEntryPoint(int argc, char* argv[]);
|
|
|
|
const AnalyzerCommand helpCommand = {helpCommandEntryPoint, "Show help information.", "<command>",
|
|
""};
|
|
|
|
const AnalyzerCommand listCommand = {listAvailableInterfaces,
|
|
"Prints a list of available interfaces.", "", ""};
|
|
|
|
status_t startCommandEntryPoint(int argc, char* argv[]) {
|
|
if (argc != 3) {
|
|
helpCommandEntryPoint(argc, argv);
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
|
|
|
|
string filename = argv[2];
|
|
std::replace(filename.begin(), filename.end(), '/', '.');
|
|
auto filePath = kStandardRecordingPath + filename;
|
|
|
|
return startRecording(binder, filePath);
|
|
}
|
|
|
|
const AnalyzerCommand startCommand = {
|
|
startCommandEntryPoint, "Start recording Binder transactions from a given service.",
|
|
"<service>", " <service>\tService to record. See 'dumpsys -l'"};
|
|
|
|
status_t stopCommandEntryPoint(int argc, char* argv[]) {
|
|
if (argc != 3) {
|
|
helpCommandEntryPoint(argc, argv);
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
|
|
return stopRecording(binder);
|
|
}
|
|
|
|
const AnalyzerCommand stopCommand = {
|
|
stopCommandEntryPoint,
|
|
"Stops recording Binder transactions from a given process. (See 'start')", "<service>",
|
|
" <service>\tService to stop recording; <service> argument to previous 'start' command."};
|
|
|
|
status_t inspectCommandEntryPoint(int argc, char* argv[]) {
|
|
if (argc != 3) {
|
|
helpCommandEntryPoint(argc, argv);
|
|
return android::BAD_VALUE;
|
|
}
|
|
std::string path = kStandardRecordingPath + string(argv[2]);
|
|
|
|
return inspectRecording(path);
|
|
}
|
|
|
|
const AnalyzerCommand inspectCommand = {
|
|
inspectCommandEntryPoint,
|
|
"Writes the binder transactions in <file-name> to stdout in a human-friendly format.",
|
|
"<file-name>",
|
|
" <file-name>\tA recording in /data/local/recordings/, and the name of the service"};
|
|
|
|
status_t listenCommandEntryPoint(int argc, char* argv[]) {
|
|
if (argc != 3) {
|
|
helpCommandEntryPoint(argc, argv);
|
|
return android::BAD_VALUE;
|
|
}
|
|
|
|
sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
|
|
|
|
string filename = argv[2];
|
|
std::replace(filename.begin(), filename.end(), '/', '.');
|
|
auto filePath = kStandardRecordingPath + filename;
|
|
|
|
if (status_t startErr = startRecording(binder, filePath); startErr != NO_ERROR) {
|
|
return startErr;
|
|
}
|
|
|
|
status_t listenStatus = listenToFile(filePath);
|
|
|
|
if (status_t stopErr = stopRecording(binder); stopErr != NO_ERROR) {
|
|
return stopErr;
|
|
}
|
|
|
|
return listenStatus;
|
|
}
|
|
|
|
const AnalyzerCommand listenCommand = {
|
|
listenCommandEntryPoint,
|
|
"Starts recording binder transactions in <service> and writes transactions to "
|
|
"stdout.",
|
|
"<service>", " <service>\t?\n"};
|
|
|
|
int replayFunction(int argc, char* argv[]) {
|
|
if (argc != 4) {
|
|
return helpCommandEntryPoint(argc, argv);
|
|
}
|
|
|
|
sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
|
|
std::string path = kStandardRecordingPath + string(argv[3]);
|
|
|
|
return replayFile(binder, path);
|
|
}
|
|
|
|
const AnalyzerCommand replayCommand = {
|
|
replayFunction, "No overview", "<service> <file-name>",
|
|
" <service>\t?\n"
|
|
" <file-name>\tThe name of a file in /data/local/recordings/"};
|
|
|
|
auto& commands = *new std::map<std::string, AnalyzerCommand>{
|
|
{"start", startCommand}, {"stop", stopCommand}, {"inspect", inspectCommand},
|
|
{"listen", listenCommand}, {"replay", replayCommand}, {"help", helpCommand}};
|
|
|
|
void printGeneralHelp(std::string& toolName) {
|
|
std::cout << "USAGE: " << toolName << " <command> [<args>]\n\n";
|
|
std::cout << "COMMANDS:\n";
|
|
// Display overview this many characters from the start of a line.
|
|
// Subtract the length of the command name to calculate padding.
|
|
const size_t commandOverviewDisplayAlignment = 12;
|
|
for (const auto& command : commands) {
|
|
if (command.first == "help") {
|
|
continue;
|
|
}
|
|
std::cout << " " << command.first
|
|
<< std::string(commandOverviewDisplayAlignment - command.first.length(), ' ')
|
|
<< command.second.overview << "\n";
|
|
}
|
|
std::cout << "\n See '" << toolName << " help <command>' for detailed help.\n";
|
|
}
|
|
|
|
status_t helpCommandEntryPoint(int argc, char* argv[]) {
|
|
std::string toolName = argv[0];
|
|
|
|
if (argc < 2) {
|
|
printGeneralHelp(toolName);
|
|
return 0;
|
|
}
|
|
|
|
std::string commandName = argv[1];
|
|
|
|
if (commandName == "help") {
|
|
if (argc < 3) {
|
|
printGeneralHelp(toolName);
|
|
return 0;
|
|
}
|
|
commandName = argv[2];
|
|
} else {
|
|
commandName = argv[1];
|
|
}
|
|
|
|
auto command = commands.find(commandName);
|
|
if (command == commands.end()) {
|
|
std::cout << "Unrecognized command: " << commandName << "\n";
|
|
printGeneralHelp(toolName);
|
|
return -1;
|
|
}
|
|
|
|
std::cout << "OVERVIEW: " << command->second.overview << "\n\n";
|
|
std::cout << "USAGE: " << toolName << " " << commandName << " "
|
|
<< command->second.compactArguments << "\n\n";
|
|
std::cout << "ARGUMENTS:\n" << command->second.helpDetail << "\n";
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char* argv[]) {
|
|
std::string toolName = argv[0];
|
|
|
|
auto& analyzers = Analyzer::getAnalyzers();
|
|
if (analyzers.size() >= 1) {
|
|
commands["list"] = listCommand;
|
|
}
|
|
|
|
if (argc < 2 ||
|
|
(argc >= 2 && ((strcmp(argv[1], "--help") == 0) || (strcmp(argv[1], "-h") == 0)))) {
|
|
// General help
|
|
printGeneralHelp(toolName);
|
|
return 0;
|
|
}
|
|
|
|
auto command = commands.find(argv[1]);
|
|
if (command == commands.end()) {
|
|
std::cout << "Unrecognized command: " << argv[1] << "\n";
|
|
printGeneralHelp(toolName);
|
|
return -1;
|
|
}
|
|
|
|
return command->second.command(argc, argv);
|
|
}
|