1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include <thread>
16 #include <sys/file.h>
17 #include "common.h"
18 #include "command_poller.h"
19 #include "hook_manager.h"
20 #include "logging.h"
21 #include "plugin_service_types.pb.h"
22 #include "writer_adapter.h"
23 #include "hook_standalone.h"
24 #include "hook_common.h"
25 #include "native_memory_profiler_sa_service.h"
26
27 using namespace OHOS::Developtools::NativeDaemon;
28
29 namespace {
30 const int SLEEP_ONE_SECOND = 1000;
31 const int VC_ARG_TWAIN = 2;
32 const int VC_ARG_STEP_SIZE = 2;
33 const int SMBSIZE_BASE = 4096;
34
ProcessExist(const std::string pid)35 bool ProcessExist(const std::string pid)
36 {
37 std::string pid_path = "";
38 struct stat stat_buf;
39 if (pid.size() == 0) {
40 return false;
41 }
42 pid_path = "/proc/" + pid + "/status";
43 if (stat(pid_path.c_str(), &stat_buf) != 0) {
44 return false;
45 }
46 return true;
47 }
48
ParseCommand(const std::vector<std::string>& args, HookData& hookData)49 bool ParseCommand(const std::vector<std::string>& args, HookData& hookData)
50 {
51 size_t idx = 0;
52 while (idx < args.size()) {
53 if (args[idx] == "-o") {
54 hookData.fileName = args[idx + 1].c_str();
55 } else if (args[idx] == "-p") {
56 std::vector<std::string> pids = StringSplit(args[idx + 1], ",");
57 hookData.pids.insert(pids.begin(), pids.end());
58 for (auto iter = hookData.pids.begin(); iter != hookData.pids.end();) {
59 if (!ProcessExist(*iter)) {
60 iter = hookData.pids.erase(iter);
61 printf("process does not exist %s\n", iter->c_str());
62 } else {
63 ++iter;
64 }
65 }
66 if (hookData.pids.empty()) {
67 printf("all process does not exist\n");
68 return false;
69 }
70 } else if (args[idx] == "-n") {
71 hookData.processName = args[idx + 1];
72 } else if (args[idx] == "-s") {
73 hookData.smbSize = static_cast<uint32_t>(IsDigits(args[idx + 1]) ? std::stoi(args[idx + 1]) : 0);
74 if (std::to_string(hookData.smbSize) != args[idx + 1]) {
75 return false;
76 }
77 } else if (args[idx] == "-f") {
78 hookData.filterSize = static_cast<uint32_t>(IsDigits(args[idx + 1]) ? std::stoi(args[idx + 1]) : 0);
79 if (std::to_string(hookData.filterSize) != args[idx + 1]) {
80 return false;
81 }
82 if (hookData.filterSize > MAX_UNWIND_DEPTH) {
83 printf("set max depth = %d\n", MAX_UNWIND_DEPTH);
84 }
85 } else if (args[idx] == "-d") {
86 hookData.maxStackDepth = static_cast<uint32_t>(IsDigits(args[idx + 1]) ? std::stoi(args[idx + 1]) : 0);
87 if (std::to_string(hookData.maxStackDepth) != args[idx + 1]) {
88 return false;
89 }
90 } else if (args[idx] == "-L") {
91 if (idx + 1 < args.size()) {
92 hookData.duration = std::stoull(args[idx + 1]);
93 }
94 } else if (args[idx] == "-F") {
95 if (idx + 1 < args.size()) {
96 hookData.performanceFilename = args[idx + 1];
97 }
98 } else if (args[idx] == "-u") {
99 std::string unwind = args[idx + 1];
100 if (unwind == "dwarf") {
101 hookData.fpUnwind = false;
102 } else if (unwind == "fp") {
103 hookData.fpUnwind = true;
104 } else {
105 return false;
106 }
107 printf("set unwind mode:%s\n", unwind.c_str());
108 } else if (args[idx] == "-S") {
109 hookData.statisticsInterval = static_cast<uint32_t>(IsDigits(args[idx + 1]) ?
110 std::stoi(args[idx + 1]) : 0);
111 if (std::to_string(hookData.statisticsInterval) != args[idx + 1]) {
112 return false;
113 }
114 } else if (args[idx] == "-i") {
115 hookData.sampleInterval = static_cast<uint32_t>(IsDigits(args[idx + 1]) ? std::stoi(args[idx + 1]) : 0);
116 if (std::to_string(hookData.sampleInterval) != args[idx + 1]) {
117 return false;
118 }
119 } else if (args[idx] == "-O") {
120 std::string offline = args[idx + 1];
121 if (offline == "false") {
122 hookData.offlineSymbolization = false;
123 } else if (offline == "true") {
124 hookData.offlineSymbolization = true;
125 } else {
126 return false;
127 }
128 printf("set offlineSymbolization mode:%s\n", offline.c_str());
129 } else if (args[idx] == "-C") {
130 std::string callframeCompress = args[idx + 1];
131 if (callframeCompress == "false") {
132 hookData.callframeCompress = false;
133 } else if (callframeCompress == "true") {
134 hookData.callframeCompress = true;
135 } else {
136 return false;
137 }
138 printf("set callframeCompress mode:%s\n", callframeCompress.c_str());
139 } else if (args[idx] == "-c") {
140 std::string stringCompressed = args[idx + 1];
141 if (stringCompressed == "false") {
142 hookData.stringCompressed = false;
143 } else if (stringCompressed == "true") {
144 hookData.stringCompressed = true;
145 } else {
146 return false;
147 }
148 printf("set stringCompressed mode:%s\n", stringCompressed.c_str());
149 } else if (args[idx] == "-r") {
150 std::string rawString = args[idx + 1];
151 if (rawString == "false") {
152 hookData.rawString = false;
153 } else if (rawString == "true") {
154 hookData.rawString = true;
155 } else {
156 return false;
157 }
158 printf("set rawString mode:%s\n", rawString.c_str());
159 } else if (args[idx] == "-so") {
160 std::string rawString = args[idx + 1];
161 if (rawString == "false") {
162 hookData.responseLibraryMode = false;
163 } else if (rawString == "true") {
164 hookData.responseLibraryMode = true;
165 } else {
166 return false;
167 }
168 printf("set responseLibraryMode mode:%s\n", rawString.c_str());
169 } else if (args[idx] == "-js") {
170 hookData.jsStackReport = IsDigits(args[idx + 1]) ? std::stoi(args[idx + 1]) : 0;
171 if (std::to_string(hookData.jsStackReport) != args[idx + 1]) {
172 return false;
173 }
174 } else if (args[idx] == "-jsd") {
175 hookData.maxJsStackdepth = static_cast<uint32_t>(IsDigits(args[idx + 1]) ? std::stoi(args[idx + 1]) : 0);
176 if (std::to_string(hookData.maxJsStackdepth) != args[idx + 1]) {
177 return false;
178 }
179 } else if (args[idx] == "-jn") {
180 hookData.filterNapiName = args[idx + 1];
181 } else if (args[idx] == "-mfm") {
182 hookData.mallocFreeMatchingInterval = static_cast<uint32_t>(IsDigits(args[idx + 1]) ?
183 std::stoi(args[idx + 1]) : 0);
184 if (std::to_string(hookData.mallocFreeMatchingInterval) != args[idx + 1]) {
185 return false;
186 }
187 } else {
188 printf("args[%zu] = %s\n", idx, args[idx].c_str());
189 return false;
190 }
191 idx += VC_ARG_STEP_SIZE;
192 }
193 return true;
194 }
195
VerifyCommand(const std::vector<std::string>& args, HookData& hookData)196 bool VerifyCommand(const std::vector<std::string>& args, HookData& hookData)
197 {
198 if ((args.size() % VC_ARG_TWAIN) != 0) {
199 return false;
200 }
201 if (!ParseCommand(args, hookData)) {
202 return false;
203 }
204 if ((hookData.smbSize % SMBSIZE_BASE) != 0) {
205 printf("Please configure a multiple of 4096 for the shared memory size\n");
206 return false;
207 }
208 if (!hookData.fileName.empty() && (!hookData.processName.empty() || hookData.pids.size() > 0)) {
209 return true;
210 }
211 return false;
212 }
213
214 volatile sig_atomic_t g_isRunning = true;
SignalSigintHandler(int sig)215 void SignalSigintHandler(int sig)
216 {
217 g_isRunning = false;
218 }
219
GetHookedProceInfo(HookData& hookData)220 void GetHookedProceInfo(HookData& hookData)
221 {
222 printf("Record file = %s, apply sharememory size = %u\n", hookData.fileName.c_str(), hookData.smbSize);
223 if (hookData.pids.size() > 0) {
224 for (const auto& pid : hookData.pids) {
225 printf("hook target process %s start\n", pid.c_str());
226 }
227 } else if (!hookData.processName.empty()) {
228 int pidValue = -1;
229 const std::string processName = hookData.processName;
230 bool isExist = COMMON::IsProcessExist(processName, pidValue);
231 if (!isExist) {
232 hookData.startupMode = true;
233 printf("startup mode ,Please start process %s\n", hookData.processName.c_str());
234 } else {
235 hookData.pids.emplace(std::to_string(pidValue));
236 }
237 }
238
239 if (hookData.maxStackDepth > 0) {
240 printf("depth greater than %u will not display\n", hookData.maxStackDepth);
241 }
242 if (hookData.filterSize > 0) {
243 printf("malloc size smaller than %u will not record\n", hookData.filterSize);
244 }
245
246 if (!OHOS::Developtools::Profiler::Hook::StartHook(hookData)) {
247 return;
248 }
249 while (g_isRunning) {
250 std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_ONE_SECOND));
251 }
252 OHOS::Developtools::Profiler::Hook::EndHook();
253 }
254 } // namespace
255
main(int argc, char* argv[])256 int main(int argc, char* argv[])
257 {
258 int lockFileFd = -1;
259 if (COMMON::IsProcessRunning(lockFileFd)) { // process is running
260 return 0;
261 }
262
263 if (argc > 1) {
264 if (argc == 2 && strcmp(argv[1], "sa") == 0) { // 2: argc size
265 if (!OHOS::Developtools::NativeDaemon::NativeMemoryProfilerSaService::StartServiceAbility()) {
266 if (lockFileFd > 0) {
267 flock(lockFileFd, LOCK_UN);
268 close(lockFileFd);
269 }
270 return 0;
271 }
272 while (true) {
273 std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_ONE_SECOND));
274 }
275 } else {
276 if (!COMMON::IsBetaVersion()) {
277 printf("memory profiler only support in beta version\n");
278 if (lockFileFd > 0) {
279 flock(lockFileFd, LOCK_UN);
280 close(lockFileFd);
281 }
282 return 0;
283 }
284 std::vector<std::string> args;
285 for (int i = 1; i < argc; i++) {
286 args.push_back(argv[i]);
287 }
288 HookData hookData;
289 if (VerifyCommand(args, hookData)) {
290 signal(SIGINT, SignalSigintHandler);
291 GetHookedProceInfo(hookData);
292 } else {
293 std::string help = R"(Usage: native_daemon
294 [-o file]
295 [-s smb_size]
296 <-n process_name>
297 <-p pids>
298 <-f filter_size>
299 <-d max_stack_depth>
300 <-i sample_interval>
301 <-u fp|dwarf>
302 <-S statistics_interval>
303 <-O offline_symbolization true|false>
304 <-C callframe_compress true|false>
305 <-c string_compressed true|false>
306 <-r raw_string true|false>
307 <-so responseLibraryMode true|false>
308 <-js jsStackReport>
309 <-jsd maxJsStackDepth>
310 <-jn filterNapiName>
311 <-mfm mallocFreeMatchingInterval_>
312 )";
313 printf("%s\n", help.c_str());
314 if (lockFileFd > 0) {
315 flock(lockFileFd, LOCK_UN);
316 close(lockFileFd);
317 }
318 return 0;
319 }
320 }
321 } else {
322 auto hookManager = std::make_shared<HookManager>();
323 if (hookManager == nullptr) {
324 if (lockFileFd > 0) {
325 flock(lockFileFd, LOCK_UN);
326 close(lockFileFd);
327 PROFILER_LOG_INFO(LOG_CORE, "create PluginManager FAILED!");
328 return 1;
329 }
330 return 0;
331 }
332 auto commandPoller = std::make_shared<CommandPoller>(hookManager);
333 if (commandPoller == nullptr) {
334 if (lockFileFd > 0) {
335 flock(lockFileFd, LOCK_UN);
336 close(lockFileFd);
337 PROFILER_LOG_INFO(LOG_CORE, "create CommandPoller FAILED!");
338 return 1;
339 }
340 return 0;
341 }
342 if (!commandPoller->OnConnect()) {
343 if (lockFileFd > 0) {
344 flock(lockFileFd, LOCK_UN);
345 close(lockFileFd);
346 PROFILER_LOG_INFO(LOG_CORE, "connect FAILED");
347 return 1;
348 }
349 return 0;
350 }
351 hookManager->SetCommandPoller(commandPoller);
352 hookManager->RegisterAgentPlugin("nativehook");
353
354 while (true) {
355 std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_ONE_SECOND));
356 }
357 }
358 if (lockFileFd > 0) {
359 flock(lockFileFd, LOCK_UN);
360 close(lockFileFd);
361 }
362 return 0;
363 }