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