1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <android-base/file.h>
18 #include <android-base/logging.h>
19 #include <binder/BpBinder.h>
20 #include <binder/IServiceManager.h>
21 #include <binder/Parcel.h>
22 #include <binder/RecordedTransaction.h>
23 #include <signal.h>
24 #include <fstream>
25 #include <sstream>
26 #include "include/Analyzer.h"
27
28 using android::IBinder;
29 using android::NO_ERROR;
30 using android::sp;
31 using android::status_t;
32 using android::String16;
33 using android::aidl::Analyzer;
34 using android::binder::debug::RecordedTransaction;
35 using std::string;
36
37 namespace {
38
39 static volatile size_t gCtrlCCount = 0;
40 static constexpr size_t kCtrlCLimit = 3;
41 static const char kStandardRecordingPath[] = "/data/local/recordings/";
42
startRecording(const sp<IBinder> & binder,const string & filePath)43 status_t startRecording(const sp<IBinder>& binder, const string& filePath) {
44 if (auto mkdir_return = mkdir(kStandardRecordingPath, 0666);
45 mkdir_return != 0 && errno != EEXIST) {
46 std::cout << "Failed to create recordings directory.\n";
47 return android::NO_ERROR;
48 }
49
50 int openFlags = O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC | O_BINARY;
51 android::base::unique_fd fd(open(filePath.c_str(), openFlags, 0666));
52 if (fd == -1) {
53 std::cout << "Failed to open file for recording with error: " << strerror(errno) << '\n';
54 return android::BAD_VALUE;
55 }
56
57 // TODO (b/245804633): this still requires setenforce 0, but nothing above does
58 if (status_t err = binder->remoteBinder()->startRecordingBinder(fd); err != android::NO_ERROR) {
59 auto checkSE = std::ifstream("/sys/fs/selinux/enforce");
60 bool recommendSetenforce = false;
61 if ((checkSE.rdstate() & std::ifstream::failbit) != 0) {
62 std::cout << "Failed to determine selinux state.";
63 recommendSetenforce = true;
64 } else {
65 char seState = checkSE.get();
66 if (checkSE.good()) {
67 if (seState == '1') {
68 std::cout << "SELinux must be permissive.";
69 recommendSetenforce = true;
70 } else if (seState == '0') {
71 std::cout << "SELinux is permissive. Failing for some other reason.\n";
72 }
73 } else {
74 std::cout << "Failed to determine SELinux state.";
75 recommendSetenforce = true;
76 }
77 }
78 if (recommendSetenforce) {
79 std::cout << " Try running:\n\n setenforce 0\n\n";
80 }
81 std::cout << "Failed to start recording with error: " << android::statusToString(err) << '\n';
82 return err;
83 } else {
84 std::cout << "Recording started successfully.\n";
85 return android::NO_ERROR;
86 }
87 }
88
stopRecording(const sp<IBinder> & binder)89 status_t stopRecording(const sp<IBinder>& binder) {
90 if (status_t err = binder->remoteBinder()->stopRecordingBinder(); err != NO_ERROR) {
91 std::cout << "Failed to stop recording with error: " << err << '\n';
92 return err;
93 } else {
94 std::cout << "Recording stopped successfully.\n";
95 return NO_ERROR;
96 }
97 }
98
printTransaction(const RecordedTransaction & transaction)99 void printTransaction(const RecordedTransaction& transaction) {
100 auto& analyzers = Analyzer::getAnalyzers();
101
102 auto analyzer = analyzers.find(transaction.getInterfaceName());
103 if (analyzer != analyzers.end()) {
104 (analyzer->second)
105 ->getAnalyzeFunction()(transaction.getCode(), transaction.getDataParcel(),
106 transaction.getReplyParcel());
107 } else {
108 std::cout << "No analyzer:";
109 std::cout << " interface: " << transaction.getInterfaceName() << "\n";
110 std::cout << " code: " << transaction.getCode() << "\n";
111 std::cout << " data: " << transaction.getDataParcel().dataSize() << " bytes\n";
112 std::cout << " reply: " << transaction.getReplyParcel().dataSize() << " bytes\n";
113 }
114 std::cout << " status: " << transaction.getReturnedStatus() << "\n\n";
115 }
116
inspectRecording(const string & path)117 status_t inspectRecording(const string& path) {
118 auto& analyzers = Analyzer::getAnalyzers();
119
120 android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
121 if (fd.get() == -1) {
122 std::cout << "Failed to open recording file with error: " << strerror(errno) << '\n';
123 return android::BAD_VALUE;
124 }
125
126 int i = 1;
127 while (auto transaction = RecordedTransaction::fromFile(fd)) {
128 std::cout << "Transaction " << i << ":\n";
129 printTransaction(transaction.value());
130 i++;
131 }
132 return NO_ERROR;
133 }
134
incrementCtrlCCount(int signum)135 void incrementCtrlCCount(int signum) {
136 gCtrlCCount++;
137 if (gCtrlCCount > kCtrlCLimit) {
138 std::cout
139 << "Ctrl+C multiple times, but could not quit application. If recording still running, you "
140 "might stop it manually.\n";
141 exit(signum);
142 }
143 }
144
listenToFile(const string & filePath)145 status_t listenToFile(const string& filePath) {
146 android::base::unique_fd listenFd(open(filePath.c_str(), O_RDONLY));
147 if (listenFd == -1) {
148 std::cout << "Failed to open listening file with error: " << strerror(errno) << '\n';
149 return android::BAD_VALUE;
150 }
151
152 auto& analyzers = Analyzer::getAnalyzers();
153
154 signal(SIGINT, incrementCtrlCCount);
155 std::cout << "Starting to listen:\n";
156 int i = 1;
157 while (gCtrlCCount == 0) {
158 auto transaction = RecordedTransaction::fromFile(listenFd);
159 if (!transaction) {
160 sleep(1);
161 continue;
162 }
163 std::cout << "Transaction " << i << ":\n";
164 printTransaction(transaction.value());
165 i++;
166 }
167 return NO_ERROR;
168 }
169
replayFile(const sp<IBinder> & binder,const string & path)170 status_t replayFile(const sp<IBinder>& binder, const string& path) {
171 auto& analyzers = Analyzer::getAnalyzers();
172
173 android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
174 if (fd.get() == -1) {
175 std::cout << "Failed to open recording file with error: " << strerror(errno) << '\n';
176 return android::BAD_VALUE;
177 }
178
179 int failureCount = 0;
180 int i = 1;
181 while (auto transaction = RecordedTransaction::fromFile(fd)) {
182 std::cout << "Replaying Transaction " << i << ":\n";
183 printTransaction(transaction.value());
184
185 android::Parcel send, reply;
186 send.setData(transaction->getDataParcel().data(), transaction->getDataParcel().dataSize());
187 android::status_t status = binder->remoteBinder()->transact(transaction->getCode(), send,
188 &reply, transaction->getFlags());
189 if (status != transaction->getReturnedStatus()) {
190 std::cout << "Failure: Expected status " << transaction->getReturnedStatus()
191 << " but received status " << status << "\n\n";
192 failureCount++;
193 } else {
194 std::cout << "Transaction replayed correctly."
195 << "\n\n";
196 }
197 i++;
198 }
199 std::cout << i << " transactions replayed.\n";
200 if (failureCount > 0) {
201 std::cout << failureCount << " transactions had unexpected status. See logs for details.\n";
202 return android::UNKNOWN_ERROR;
203 } else {
204 return NO_ERROR;
205 }
206 }
207
listAvailableInterfaces(int,char **)208 status_t listAvailableInterfaces(int, char**) {
209 auto& analyzers = Analyzer::getAnalyzers();
210 std::cout << "Available Interfaces (" << analyzers.size() << "):\n";
211 for (auto a = analyzers.begin(); a != analyzers.end(); a++) {
212 std::cout << " " << a->second->getInterfaceName() << '\n';
213 }
214 return NO_ERROR;
215 }
216
217 struct AnalyzerCommand {
218 std::function<status_t(int, char*[])> command;
219 std::string overview;
220 std::string compactArguments;
221 std::string helpDetail;
222 };
223
224 status_t helpCommandEntryPoint(int argc, char* argv[]);
225
226 const AnalyzerCommand helpCommand = {helpCommandEntryPoint, "Show help information.", "<command>",
227 ""};
228
229 const AnalyzerCommand listCommand = {listAvailableInterfaces,
230 "Prints a list of available interfaces.", "", ""};
231
startCommandEntryPoint(int argc,char * argv[])232 status_t startCommandEntryPoint(int argc, char* argv[]) {
233 if (argc != 3) {
234 helpCommandEntryPoint(argc, argv);
235 return android::BAD_VALUE;
236 }
237
238 sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
239
240 string filename = argv[2];
241 std::replace(filename.begin(), filename.end(), '/', '.');
242 auto filePath = kStandardRecordingPath + filename;
243
244 return startRecording(binder, filePath);
245 }
246
247 const AnalyzerCommand startCommand = {
248 startCommandEntryPoint, "Start recording Binder transactions from a given service.",
249 "<service>", " <service>\tService to record. See 'dumpsys -l'"};
250
stopCommandEntryPoint(int argc,char * argv[])251 status_t stopCommandEntryPoint(int argc, char* argv[]) {
252 if (argc != 3) {
253 helpCommandEntryPoint(argc, argv);
254 return android::BAD_VALUE;
255 }
256
257 sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
258 return stopRecording(binder);
259 }
260
261 const AnalyzerCommand stopCommand = {
262 stopCommandEntryPoint,
263 "Stops recording Binder transactions from a given process. (See 'start')", "<service>",
264 " <service>\tService to stop recording; <service> argument to previous 'start' command."};
265
inspectCommandEntryPoint(int argc,char * argv[])266 status_t inspectCommandEntryPoint(int argc, char* argv[]) {
267 if (argc != 3) {
268 helpCommandEntryPoint(argc, argv);
269 return android::BAD_VALUE;
270 }
271 std::string path = kStandardRecordingPath + string(argv[2]);
272
273 return inspectRecording(path);
274 }
275
276 const AnalyzerCommand inspectCommand = {
277 inspectCommandEntryPoint,
278 "Writes the binder transactions in <file-name> to stdout in a human-friendly format.",
279 "<file-name>",
280 " <file-name>\tA recording in /data/local/recordings/, and the name of the service"};
281
listenCommandEntryPoint(int argc,char * argv[])282 status_t listenCommandEntryPoint(int argc, char* argv[]) {
283 if (argc != 3) {
284 helpCommandEntryPoint(argc, argv);
285 return android::BAD_VALUE;
286 }
287
288 sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
289
290 string filename = argv[2];
291 std::replace(filename.begin(), filename.end(), '/', '.');
292 auto filePath = kStandardRecordingPath + filename;
293
294 if (status_t startErr = startRecording(binder, filePath); startErr != NO_ERROR) {
295 return startErr;
296 }
297
298 status_t listenStatus = listenToFile(filePath);
299
300 if (status_t stopErr = stopRecording(binder); stopErr != NO_ERROR) {
301 return stopErr;
302 }
303
304 return listenStatus;
305 }
306
307 const AnalyzerCommand listenCommand = {
308 listenCommandEntryPoint,
309 "Starts recording binder transactions in <service> and writes transactions to "
310 "stdout.",
311 "<service>", " <service>\t?\n"};
312
replayFunction(int argc,char * argv[])313 int replayFunction(int argc, char* argv[]) {
314 if (argc != 4) {
315 return helpCommandEntryPoint(argc, argv);
316 }
317
318 sp<IBinder> binder = android::defaultServiceManager()->checkService(String16(argv[2]));
319 std::string path = kStandardRecordingPath + string(argv[3]);
320
321 return replayFile(binder, path);
322 }
323
324 const AnalyzerCommand replayCommand = {
325 replayFunction, "No overview", "<service> <file-name>",
326 " <service>\t?\n"
327 " <file-name>\tThe name of a file in /data/local/recordings/"};
328
329 auto& commands = *new std::map<std::string, AnalyzerCommand>{
330 {"start", startCommand}, {"stop", stopCommand}, {"inspect", inspectCommand},
331 {"listen", listenCommand}, {"replay", replayCommand}, {"help", helpCommand}};
332
printGeneralHelp(std::string & toolName)333 void printGeneralHelp(std::string& toolName) {
334 std::cout << "USAGE: " << toolName << " <command> [<args>]\n\n";
335 std::cout << "COMMANDS:\n";
336 // Display overview this many characters from the start of a line.
337 // Subtract the length of the command name to calculate padding.
338 const size_t commandOverviewDisplayAlignment = 12;
339 for (const auto& command : commands) {
340 if (command.first == "help") {
341 continue;
342 }
343 std::cout << " " << command.first
344 << std::string(commandOverviewDisplayAlignment - command.first.length(), ' ')
345 << command.second.overview << "\n";
346 }
347 std::cout << "\n See '" << toolName << " help <command>' for detailed help.\n";
348 }
349
helpCommandEntryPoint(int argc,char * argv[])350 status_t helpCommandEntryPoint(int argc, char* argv[]) {
351 std::string toolName = argv[0];
352
353 if (argc < 2) {
354 printGeneralHelp(toolName);
355 return 0;
356 }
357
358 std::string commandName = argv[1];
359
360 if (commandName == "help") {
361 if (argc < 3) {
362 printGeneralHelp(toolName);
363 return 0;
364 }
365 commandName = argv[2];
366 } else {
367 commandName = argv[1];
368 }
369
370 auto command = commands.find(commandName);
371 if (command == commands.end()) {
372 std::cout << "Unrecognized command: " << commandName << "\n";
373 printGeneralHelp(toolName);
374 return -1;
375 }
376
377 std::cout << "OVERVIEW: " << command->second.overview << "\n\n";
378 std::cout << "USAGE: " << toolName << " " << commandName << " "
379 << command->second.compactArguments << "\n\n";
380 std::cout << "ARGUMENTS:\n" << command->second.helpDetail << "\n";
381
382 return 0;
383 }
384
385 } // namespace
386
main(int argc,char * argv[])387 int main(int argc, char* argv[]) {
388 std::string toolName = argv[0];
389
390 auto& analyzers = Analyzer::getAnalyzers();
391 if (analyzers.size() >= 1) {
392 commands["list"] = listCommand;
393 }
394
395 if (argc < 2 ||
396 (argc >= 2 && ((strcmp(argv[1], "--help") == 0) || (strcmp(argv[1], "-h") == 0)))) {
397 // General help
398 printGeneralHelp(toolName);
399 return 0;
400 }
401
402 auto command = commands.find(argv[1]);
403 if (command == commands.end()) {
404 std::cout << "Unrecognized command: " << argv[1] << "\n";
405 printGeneralHelp(toolName);
406 return -1;
407 }
408
409 return command->second.command(argc, argv);
410 }
411