1 /*
2  * Copyright (C) 2023 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 #define LOG_TAG "uprobestats"
18 
19 #include <android-base/file.h>
20 #include <android-base/logging.h>
21 #include <android-base/parseint.h>
22 #include <android-base/properties.h>
23 #include <android-base/scopeguard.h>
24 #include <android-base/strings.h>
25 #include <android/binder_process.h>
26 #include <config.pb.h>
27 #include <iostream>
28 #include <stdio.h>
29 #include <string>
30 #include <thread>
31 
32 #include "Bpf.h"
33 #include "ConfigResolver.h"
34 #include "DebugLog.h"
35 #include "FlagSelector.h"
36 #include "Guardrail.h"
37 #include <stats_event.h>
38 
39 using namespace android::uprobestats;
40 
41 const std::string kGenericBpfMapDetail =
42     std::string("GenericInstrumentation_call_detail");
43 const std::string kGenericBpfMapTimestamp =
44     std::string("GenericInstrumentation_call_timestamp");
45 const std::string kUpdateDeviceIdleTempAllowlistMap =
46     std::string("ProcessManagement_update_device_idle_temp_allowlist_records");
47 const std::string kProcessManagementMap =
48     std::string("ProcessManagement_output_buf");
49 const int kJavaArgumentRegisterOffset = 2;
50 
isUprobestatsEnabled()51 bool isUprobestatsEnabled() {
52   return android::uprobestats::flag_selector::enable_uprobestats();
53 }
54 
55 const std::string kBpfPath = std::string("/sys/fs/bpf/uprobestats/");
prefixBpf(std::string value)56 std::string prefixBpf(std::string value) { return kBpfPath + value.c_str(); }
57 
58 struct PollArgs {
59   std::string mapPath;
60   ::uprobestats::protos::UprobestatsConfig::Task taskConfig;
61 };
62 
doPoll(PollArgs args)63 void doPoll(PollArgs args) {
64   auto mapPath = args.mapPath;
65   auto durationSeconds = args.taskConfig.duration_seconds();
66   auto duration = std::chrono::seconds(durationSeconds);
67   auto startTime = std::chrono::steady_clock::now();
68   auto now = startTime;
69   while (now - startTime < duration) {
70     auto remaining = duration - (std::chrono::steady_clock::now() - startTime);
71     auto timeoutMs = static_cast<int>(
72         std::chrono::duration_cast<std::chrono::milliseconds>(remaining)
73             .count());
74     if (mapPath.find(kGenericBpfMapDetail) != std::string::npos) {
75       LOG_IF_DEBUG("polling for GenericDetail result");
76       auto result =
77           bpf::pollRingBuf<bpf::CallResult>(mapPath.c_str(), timeoutMs);
78       for (auto value : result) {
79         LOG_IF_DEBUG("GenericDetail result...");
80         LOG_IF_DEBUG("register: pc = " << value.pc);
81         for (int i = 0; i < 10; i++) {
82           auto reg = value.regs[i];
83           LOG_IF_DEBUG("register: " << i << " = " << reg);
84         }
85         if (!args.taskConfig.has_statsd_logging_config()) {
86           LOG_IF_DEBUG("no statsd logging config");
87           continue;
88         }
89 
90         auto statsd_logging_config = args.taskConfig.statsd_logging_config();
91         int atom_id = statsd_logging_config.atom_id();
92         LOG_IF_DEBUG("attempting to write atom id: " << atom_id);
93         AStatsEvent *event = AStatsEvent_obtain();
94         AStatsEvent_setAtomId(event, atom_id);
95         for (int primitiveArgumentPosition :
96              statsd_logging_config.primitive_argument_positions()) {
97           int primitiveArgument = value.regs[primitiveArgumentPosition +
98                                              kJavaArgumentRegisterOffset];
99           LOG_IF_DEBUG("writing argument value: " << primitiveArgument
100                                                   << " from position: "
101                                                   << primitiveArgumentPosition);
102           AStatsEvent_writeInt32(event, primitiveArgument);
103         }
104         AStatsEvent_write(event);
105         AStatsEvent_release(event);
106         LOG_IF_DEBUG("successfully wrote atom id: " << atom_id);
107       }
108     } else if (mapPath.find(kGenericBpfMapTimestamp) != std::string::npos) {
109       LOG_IF_DEBUG("polling for GenericTimestamp result");
110       auto result =
111           bpf::pollRingBuf<bpf::CallTimestamp>(mapPath.c_str(), timeoutMs);
112       for (auto value : result) {
113         LOG_IF_DEBUG("GenericTimestamp result: event "
114                      << value.event << " timestampNs: " << value.timestampNs);
115         if (!args.taskConfig.has_statsd_logging_config()) {
116           LOG_IF_DEBUG("no statsd logging config");
117           continue;
118         }
119         // TODO: for now, we assume an atom structure of event, then timestamp.
120         // We will build a cleaner abstraction for handling "just give me
121         // timestamps when X API is called", but we're just trying ot get things
122         // working for now.
123         auto statsd_logging_config = args.taskConfig.statsd_logging_config();
124         int atom_id = statsd_logging_config.atom_id();
125         LOG_IF_DEBUG("attempting to write atom id: " << atom_id);
126         AStatsEvent *event = AStatsEvent_obtain();
127         AStatsEvent_setAtomId(event, atom_id);
128         AStatsEvent_writeInt32(event, value.event);
129         AStatsEvent_writeInt64(event, value.timestampNs);
130         AStatsEvent_write(event);
131         AStatsEvent_release(event);
132         LOG_IF_DEBUG("successfully wrote atom id: " << atom_id);
133       }
134     } else if (mapPath.find(kUpdateDeviceIdleTempAllowlistMap) !=
135                std::string::npos) {
136       LOG_IF_DEBUG("Polling for UpdateDeviceIdleTempAllowlistRecord result");
137       auto result = bpf::pollRingBuf<bpf::UpdateDeviceIdleTempAllowlistRecord>(
138           mapPath.c_str(), timeoutMs);
139       for (auto value : result) {
140         LOG_IF_DEBUG("UpdateDeviceIdleTempAllowlistRecord result... "
141                      << " changing_uid: " << value.changing_uid
142                      << " reason_code: " << value.reason_code << " reason: "
143                      << value.reason << " calling_uid: " << value.calling_uid
144                      << " mapPath: " << mapPath);
145         if (!args.taskConfig.has_statsd_logging_config()) {
146           LOG_IF_DEBUG("no statsd logging config");
147           continue;
148         }
149         auto statsd_logging_config = args.taskConfig.statsd_logging_config();
150         int atom_id = statsd_logging_config.atom_id();
151         AStatsEvent *event = AStatsEvent_obtain();
152         AStatsEvent_setAtomId(event, atom_id);
153         AStatsEvent_writeInt32(event, value.changing_uid);
154         AStatsEvent_writeBool(event, value.adding);
155         AStatsEvent_writeInt64(event, value.duration_ms);
156         AStatsEvent_writeInt32(event, value.type);
157         AStatsEvent_writeInt32(event, value.reason_code);
158         AStatsEvent_writeString(event, value.reason);
159         AStatsEvent_writeInt32(event, value.calling_uid);
160         AStatsEvent_write(event);
161         AStatsEvent_release(event);
162       }
163     } else if (mapPath.find(kProcessManagementMap) != std::string::npos) {
164       LOG_IF_DEBUG("Polling for SetUidTempAllowlistStateRecord result");
165       auto result = bpf::pollRingBuf<bpf::SetUidTempAllowlistStateRecord>(
166           mapPath.c_str(), timeoutMs);
167       for (auto value : result) {
168         LOG_IF_DEBUG("SetUidTempAllowlistStateRecord result... uid: "
169                      << value.uid << " onAllowlist: " << value.onAllowlist
170                      << " mapPath: " << mapPath);
171         if (!args.taskConfig.has_statsd_logging_config()) {
172           LOG_IF_DEBUG("no statsd logging config");
173           continue;
174         }
175         auto statsd_logging_config = args.taskConfig.statsd_logging_config();
176         int atom_id = statsd_logging_config.atom_id();
177         AStatsEvent *event = AStatsEvent_obtain();
178         AStatsEvent_setAtomId(event, atom_id);
179         AStatsEvent_writeInt32(event, value.uid);
180         AStatsEvent_writeBool(event, value.onAllowlist);
181         AStatsEvent_write(event);
182         AStatsEvent_release(event);
183       }
184     } else {
185       LOG_IF_DEBUG("Polling for i64 result");
186       auto result = bpf::pollRingBuf<uint64_t>(mapPath.c_str(), timeoutMs);
187       for (auto value : result) {
188         LOG_IF_DEBUG("Other result... value: " << value
189                                                << " mapPath: " << mapPath);
190       }
191     }
192     now = std::chrono::steady_clock::now();
193   }
194   LOG_IF_DEBUG("finished polling for mapPath: " << mapPath);
195 }
196 
main()197 int main() {
198   if (android::uprobestats::flag_selector::executable_method_file_offsets()) {
199     ABinderProcess_startThreadPool();
200   }
201   const auto guard = ::android::base::make_scope_guard([] {
202     if (android::uprobestats::flag_selector::executable_method_file_offsets()) {
203       ABinderProcess_joinThreadPool();
204     }
205   });
206   if (!isUprobestatsEnabled()) {
207     LOG(ERROR) << "uprobestats disabled by flag. Exiting.";
208     return 1;
209   }
210   auto config =
211       config_resolver::readConfig("/data/misc/uprobestats-configs/config");
212   if (!config.has_value()) {
213     LOG(ERROR) << "Failed to parse uprobestats config.";
214     return 1;
215   }
216   if (!guardrail::isAllowed(
217           config.value(),
218           android::base::GetProperty("ro.build.type", "unknown"),
219           android::uprobestats::flag_selector::
220               executable_method_file_offsets())) {
221     LOG(ERROR) << "uprobestats probing config disallowed on this device.";
222     return 1;
223   }
224   auto resolvedTask = config_resolver::resolveSingleTask(config.value());
225   if (!resolvedTask.has_value()) {
226     LOG(ERROR) << "Failed to parse task";
227     return 1;
228   }
229 
230   LOG_IF_DEBUG("Found task config: " << resolvedTask.value());
231   auto resolvedProbeConfigs =
232       config_resolver::resolveProbes(resolvedTask.value().taskConfig);
233   if (!resolvedProbeConfigs.has_value()) {
234     LOG(ERROR) << "Failed to resolve a probe config from task";
235     return 1;
236   }
237   for (auto &resolvedProbe : resolvedProbeConfigs.value()) {
238     LOG_IF_DEBUG("Opening bpf perf event from probe: " << resolvedProbe);
239     if (resolvedProbe.filename ==
240             "prog_ProcessManagement_uprobe_update_device_idle_temp_allowlist" &&
241         !android::uprobestats::flag_selector::
242             uprobestats_support_update_device_idle_temp_allowlist()) {
243       LOG(ERROR) << "update_device_idle_temp_allowlist disabled by flag";
244     }
245     auto openResult = bpf::bpfPerfEventOpen(
246         resolvedProbe.filename.c_str(), resolvedProbe.offset,
247         resolvedTask.value().pid,
248         prefixBpf(resolvedProbe.probeConfig.bpf_name()).c_str());
249     if (openResult != 0) {
250       LOG(ERROR) << "Failed to open bpf "
251                  << resolvedProbe.probeConfig.bpf_name();
252       return 1;
253     }
254   }
255 
256   std::vector<std::thread> threads;
257   for (auto mapPath : resolvedTask.value().taskConfig.bpf_maps()) {
258     if (mapPath ==
259             "map_ProcessManagement_update_device_idle_temp_allowlist_record" &&
260         !android::uprobestats::flag_selector::
261             uprobestats_support_update_device_idle_temp_allowlist()) {
262       LOG(ERROR) << "update_device_idle_temp_allowlist disabled by flag";
263     }
264     auto pollArgs =
265         PollArgs{prefixBpf(mapPath), resolvedTask.value().taskConfig};
266     LOG_IF_DEBUG(
267         "Starting thread to collect results from mapPath: " << mapPath);
268     threads.emplace_back(doPoll, pollArgs);
269   }
270   for (auto &thread : threads) {
271     thread.join();
272   }
273 
274   LOG_IF_DEBUG("done.");
275 
276   return 0;
277 }
278