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 }