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