1 /*
2 * Copyright (C) 2024 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 <filesystem>
20 #include <iostream>
21 #include <string>
22
23 #include <android-base/file.h>
24 #include <android-base/logging.h>
25 #include <android-base/parseint.h>
26 #include <android-base/properties.h>
27 #include <android-base/strings.h>
28 #include <config.pb.h>
29 #include <json/json.h>
30
31 #include "Art.h"
32 #include "ConfigResolver.h"
33 #include "DebugLog.h"
34 #include "DynamicInstrumentationManager.h"
35 #include "FlagSelector.h"
36 #include "Process.h"
37
38 namespace android {
39 namespace uprobestats {
40 namespace config_resolver {
41
operator <<(std::ostream & os,const ResolvedTask & c)42 std::ostream &operator<<(std::ostream &os, const ResolvedTask &c) {
43 os << "pid: " << c.pid << " taskConfig: " << c.taskConfig.DebugString();
44 return os;
45 }
46
operator <<(std::ostream & os,const ResolvedProbe & c)47 std::ostream &operator<<(std::ostream &os, const ResolvedProbe &c) {
48 os << "filename: " << c.filename << " offset: " << c.offset
49 << " probeConfig: " << c.probeConfig.DebugString();
50 return os;
51 }
52
53 // Reads probing configuration from a file, which should be the serialized
54 // bytes of a UprobestatsConfig proto.
55 std::optional<::uprobestats::protos::UprobestatsConfig>
readConfig(std::string configFilePath)56 readConfig(std::string configFilePath) {
57 std::string config_str;
58 if (!android::base::ReadFileToString(configFilePath, &config_str)) {
59 LOG(ERROR) << "Failed to open config file " << configFilePath;
60 return {};
61 }
62
63 ::uprobestats::protos::UprobestatsConfig config;
64 bool success = config.ParseFromString(config_str);
65 if (!success) {
66 LOG(ERROR) << "Failed to parse file " << configFilePath
67 << " to UprobestatsConfig";
68 return {};
69 }
70
71 return config;
72 }
73
74 std::optional<ResolvedTask>
resolveSingleTask(::uprobestats::protos::UprobestatsConfig config)75 resolveSingleTask(::uprobestats::protos::UprobestatsConfig config) {
76 auto task_count = config.tasks().size();
77 if (task_count == 0) {
78 LOG(ERROR) << "config has no tasks";
79 return {};
80 }
81 if (task_count > 1) {
82 LOG(ERROR) << "config has " << task_count
83 << " tasks. Only 1 is supported. The first task is read and the "
84 "rest are ignored.";
85 }
86 auto taskConfig = config.tasks().Get(0);
87 if (!taskConfig.has_duration_seconds()) {
88 LOG(ERROR) << "config task has no duration";
89 return {};
90 }
91 if (taskConfig.duration_seconds() <= 0) {
92 LOG(ERROR) << "config task cannot have zero or negative duration";
93 }
94 if (!taskConfig.has_target_process_name()) {
95 LOG(ERROR) << "task.target_process_name is required.";
96 return {};
97 }
98 auto process_name = taskConfig.target_process_name();
99 int pid = process::getPid(process_name);
100 if (pid < 0) {
101 LOG(ERROR) << "Unable to find pid of " << process_name;
102 return {};
103 }
104 ResolvedTask task;
105 task.taskConfig = taskConfig;
106 task.pid = pid;
107 return task;
108 }
109
110 std::optional<std::vector<ResolvedProbe>>
resolveProbes(::uprobestats::protos::UprobestatsConfig::Task & taskConfig)111 resolveProbes(::uprobestats::protos::UprobestatsConfig::Task &taskConfig) {
112 if (taskConfig.probe_configs().size() == 0) {
113 LOG(ERROR) << "task has no probe configs";
114 return {};
115 }
116 std::vector<ResolvedProbe> result;
117 for (auto &probeConfig : taskConfig.probe_configs()) {
118 if (android::uprobestats::flag_selector::executable_method_file_offsets() &&
119 probeConfig.has_fully_qualified_class_name()) {
120 LOG_IF_DEBUG("using getExecutableMethodFileOffsets to retrieve offsets");
121 std::vector<std::string> fqParameters(
122 probeConfig.fully_qualified_parameters().begin(),
123 probeConfig.fully_qualified_parameters().end());
124 std::string processName(taskConfig.target_process_name());
125 std::string fqcn(probeConfig.fully_qualified_class_name());
126 std::string methodName(probeConfig.method_name());
127 std::optional<
128 dynamic_instrumentation_manager::ExecutableMethodFileOffsets>
129 offsets =
130 dynamic_instrumentation_manager::getExecutableMethodFileOffsets(
131 processName, fqcn, methodName, fqParameters);
132 if (!offsets.has_value()) {
133 LOG(ERROR) << "Unable to find method offset for "
134 << probeConfig.fully_qualified_class_name() << "#"
135 << probeConfig.method_name();
136 return {};
137 }
138
139 ResolvedProbe probe;
140 probe.filename = offsets->containerPath;
141 probe.offset = offsets->methodOffset;
142 probe.probeConfig = probeConfig;
143 result.push_back(probe);
144 continue;
145 }
146
147 LOG_IF_DEBUG("using oatdump to retrieve offsets");
148 int offset = 0;
149 std::string matched_file_path;
150 for (auto &file_path : probeConfig.file_paths()) {
151 offset = art::getMethodOffsetFromOatdump(file_path,
152 probeConfig.method_signature());
153 if (offset > 0) {
154 matched_file_path = file_path;
155 break;
156 } else {
157 LOG(WARNING) << "File " << file_path << " has no offset for "
158 << probeConfig.method_signature();
159 }
160 }
161 if (offset == 0) {
162 LOG(ERROR) << "Unable to find method offset for "
163 << probeConfig.method_signature();
164 return {};
165 }
166
167 ResolvedProbe probe;
168 probe.filename = matched_file_path;
169 probe.offset = offset;
170 probe.probeConfig = probeConfig;
171 result.push_back(probe);
172 }
173
174 return result;
175 }
176
177 } // namespace config_resolver
178 } // namespace uprobestats
179 } // namespace android
180