1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
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 #define HILOG_TAG "Runtime"
16
17 #include "virtual_runtime.h"
18
19 #include <cinttypes>
20 #include <iostream>
21 #include <sstream>
22 #include <unistd.h>
23 #if !is_mingw
24 #include <sys/mman.h>
25 #endif
26
27 #include "register.h"
28 #include "symbols_file.h"
29 #include "utilities.h"
30
31 using namespace std::chrono;
32 namespace OHOS {
33 namespace Developtools {
34 namespace NativeDaemon {
35 // we unable to access 'swapper' from /proc/0/
ClearMaps()36 void VirtualRuntime::ClearMaps()
37 {
38 processMemMaps_.clear();
39 }
40
VirtualRuntime(bool onDevice)41 VirtualRuntime::VirtualRuntime(bool onDevice)
42 {
43 threadMapsLock_ = PTHREAD_MUTEX_INITIALIZER;
44 threadMemMapsLock_ = PTHREAD_MUTEX_INITIALIZER;
45 }
~VirtualRuntime()46 VirtualRuntime::~VirtualRuntime()
47 {
48 ClearMaps();
49 }
ReadThreadName(pid_t tid)50 std::string VirtualRuntime::ReadThreadName(pid_t tid)
51 {
52 std::string comm = ReadFileToString(StringPrintf("/proc/%d/comm", tid)).c_str();
53 comm.erase(std::remove(comm.begin(), comm.end(), '\r'), comm.end());
54 comm.erase(std::remove(comm.begin(), comm.end(), '\n'), comm.end());
55 return comm;
56 }
57
UpdateThread(pid_t pid,pid_t tid,const std::string name)58 VirtualThread &VirtualRuntime::UpdateThread(pid_t pid, pid_t tid, const std::string name)
59 {
60 #ifdef HIPERF_DEBUG_TIME
61 const auto startTime = steady_clock::now();
62 #endif
63 VirtualThread &thread = GetThread(pid, tid);
64 if (!name.empty()) {
65 thread.name_ = name;
66 }
67 #ifdef HIPERF_DEBUG_TIME
68 updateThreadTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
69 #endif
70 return thread;
71 }
72
CreateThread(pid_t pid,pid_t tid)73 VirtualThread &VirtualRuntime::CreateThread(pid_t pid, pid_t tid)
74 {
75 // make a new one
76 userSpaceThreadMap_.emplace(std::piecewise_construct, std::forward_as_tuple(tid),
77 std::forward_as_tuple(pid, tid, symbolsFiles_, this));
78 VirtualThread& thr = userSpaceThreadMap_.at(tid);
79 return thr;
80 }
81
GetThread(pid_t pid,pid_t tid)82 VirtualThread &VirtualRuntime::GetThread(pid_t pid, pid_t tid)
83 {
84 HLOGV("find thread %u:%u", pid, tid);
85 pthread_mutex_lock(&threadMapsLock_);
86 auto it = userSpaceThreadMap_.find(tid);
87 if (it == userSpaceThreadMap_.end()) {
88 // we also need thread
89 VirtualThread& thr = CreateThread(pid, tid);
90 pthread_mutex_unlock(&threadMapsLock_);
91 return thr;
92 } else {
93 VirtualThread& thr = it->second;
94 pthread_mutex_unlock(&threadMapsLock_);
95 return thr;
96 }
97 }
98
MakeCallFrame(Symbol & symbol,CallFrame & callFrame)99 void VirtualRuntime::MakeCallFrame(Symbol &symbol, CallFrame &callFrame)
100 {
101 callFrame.vaddrInFile_ = symbol.funcVaddr_;
102 callFrame.symbolName_ = symbol.symbolName_;
103 callFrame.symbolIndex_ = symbol.index_;
104 callFrame.filePath_ = symbol.module_.empty() ? symbol.comm_ : symbol.module_;
105 callFrame.symbolOffset_ = symbol.offset_;
106 if (symbol.funcVaddr_ != 0) {
107 callFrame.offset_ = symbol.funcVaddr_;
108 } else {
109 callFrame.offset_ = callFrame.ip_;
110 }
111 }
112
GetSymbolName(pid_t pid,pid_t tid,std::vector<CallFrame> & callsFrames,int offset,bool first)113 bool VirtualRuntime::GetSymbolName(pid_t pid, pid_t tid, std::vector<CallFrame>& callsFrames, int offset, bool first)
114 {
115 #ifdef HIPERF_DEBUG_TIME
116 const auto startTime = steady_clock::now();
117 #endif
118 // Symbolic the Call Stack
119 HLOGV("total %zu frames", callsFrames.size());
120
121 perf_callchain_context perfCallchainContext = PERF_CONTEXT_MAX;
122 for (auto callFrameIt = callsFrames.begin() + offset; callFrameIt != callsFrames.end(); ++callFrameIt) {
123 auto &callFrame = callFrameIt.operator*();
124 if (callFrame.ip_ >= PERF_CONTEXT_MAX) {
125 // dont care, this is not issue.
126 HLOGV("%s", UpdatePerfContext(callFrame.ip_, perfCallchainContext).c_str());
127 continue;
128 }
129 auto symbol = GetSymbol(callFrame.ip_, pid, tid,
130 perfCallchainContext);
131 if (symbol.isValid()) {
132 MakeCallFrame(symbol, callFrame);
133 } else {
134 #ifdef TRY_UNWIND_TWICE
135 if (first) {
136 if (failedIPs_.find(callFrame.ip_) == failedIPs_.end()) {
137 return false;
138 } else {
139 callsFrames.erase(callFrameIt, callsFrames.end());
140 return true;
141 }
142 } else {
143 failedIPs_.insert(callFrame.ip_);
144 callsFrames.erase(callFrameIt, callsFrames.end());
145 return true;
146 }
147 #else
148 callsFrames.erase(callFrameIt, callsFrames.end());
149 return true;
150 #endif
151 }
152 int index = callFrameIt - callsFrames.begin();
153 HLOGV(" (%u)unwind symbol: %*s%s", index, index, "", callFrame.ToSymbolString().c_str());
154 }
155 #ifdef HIPERF_DEBUG_TIME
156 auto usedTime = duration_cast<microseconds>(steady_clock::now() - startTime);
157 if (usedTime.count() != 0) {
158 HLOGV("cost %0.3f ms to symbolic ", usedTime.count() / MS_DUARTION);
159 }
160 symbolicRecordTimes_ += usedTime;
161 #endif
162 return true;
163 }
164
UpdateMaps(pid_t pid,pid_t tid)165 void VirtualRuntime::UpdateMaps(pid_t pid, pid_t tid)
166 {
167 auto &thread = UpdateThread(pid, tid);
168 if (thread.ParseMap(processMemMaps_, true)) {
169 HILOG_INFO(LOG_CORE, "voluntarily update maps succeed");
170 } else {
171 HILOG_INFO(LOG_CORE, "voluntarily update maps ignore");
172 }
173 }
174
UnwindStack(std::vector<u64> regs,const u8 * stack_addr,int stack_size,pid_t pid,pid_t tid,std::vector<CallFrame> & callsFrames,size_t maxStackLevel)175 bool VirtualRuntime::UnwindStack(std::vector<u64> regs,
176 const u8* stack_addr,
177 int stack_size,
178 pid_t pid,
179 pid_t tid,
180 std::vector<CallFrame>& callsFrames,
181 size_t maxStackLevel)
182 {
183 #ifdef HIPERF_DEBUG_TIME
184 const auto startTime = steady_clock::now();
185 #endif
186 // if we have userstack ?
187 int offset = 0;
188 auto &thread = UpdateThread(pid, tid);
189 if (stack_size > 0) {
190 callstack_.UnwindCallStack(thread, ®s[0], regs.size(), stack_addr, stack_size, callsFrames, maxStackLevel);
191 if (callsFrames.size() <= FILTER_STACK_DEPTH) {
192 callsFrames.clear();
193 return false;
194 }
195 // Do not symbolize the first two frame, cause the two frame implement by tool itself
196 offset = FILTER_STACK_DEPTH;
197 #ifdef HIPERF_DEBUG_TIME
198 unwindCallStackTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
199 #endif
200 }
201 #ifdef HIPERF_DEBUG_TIME
202 unwindFromRecordTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
203 #endif
204 if (!GetSymbolName(pid, tid, callsFrames, offset, true)) {
205 #ifdef TRY_UNWIND_TWICE
206 HLOGD("clear and unwind one more time");
207 if (!thread.ParseMap(processMemMaps_, true)) {
208 GetSymbolName(pid, tid, callsFrames, offset, false);
209 return false;
210 }
211 if (stack_size > 0) {
212 callsFrames.clear();
213 callstack_.UnwindCallStack(thread, ®s[0], regs.size(), stack_addr,
214 stack_size, callsFrames, maxStackLevel);
215 }
216 if (callsFrames.size() <= FILTER_STACK_DEPTH) {
217 callsFrames.clear();
218 return false;
219 }
220 if (!GetSymbolName(pid, tid, callsFrames, offset, false)) {
221 return false;
222 }
223 #endif
224 }
225 return true;
226 }
227
IsSymbolExist(std::string fileName)228 bool VirtualRuntime::IsSymbolExist(std::string fileName)
229 {
230 for (auto &symbolsFile : symbolsFiles_) {
231 if (symbolsFile->filePath_ == fileName) {
232 HLOGV("already have '%s'", fileName.c_str());
233 return true;
234 }
235 }
236 return false;
237 }
238
239
UpdateSymbols(std::string fileName)240 void VirtualRuntime::UpdateSymbols(std::string fileName)
241 {
242 HLOGD("try to find symbols for file: %s", fileName.c_str());
243 #ifdef HIPERF_DEBUG_TIME
244 const auto startTime = steady_clock::now();
245 #endif
246 for (auto &symbolsFile : symbolsFiles_) {
247 if (symbolsFile->filePath_ == fileName) {
248 HLOGV("already have '%s'", fileName.c_str());
249 return;
250 }
251 }
252 // found it by name
253 auto symbolsFile = SymbolsFile::CreateSymbolsFile(fileName);
254
255 // set sybol path If it exists
256 if (symbolsPaths_.size() > 0) {
257 symbolsFile->setSymbolsFilePath(symbolsPaths_); // also load from search path
258 }
259 if (loadSymboleWhenNeeded_) {
260 // load it when we need it
261 symbolsFiles_.insert(std::move(symbolsFile));
262 } else if (symbolsFile->LoadSymbols()) {
263 symbolsFiles_.insert(std::move(symbolsFile));
264 } else {
265 HLOGW("symbols file for '%s' not found.", fileName.c_str());
266 }
267 #ifdef HIPERF_DEBUG_TIME
268 auto usedTime = duration_cast<microseconds>(steady_clock::now() - startTime);
269 if (usedTime.count() != 0) {
270 HLOGV("cost %0.3f ms to load '%s'", usedTime.count() / MS_DUARTION, fileName.c_str());
271 }
272 updateSymbolsTimes_ += usedTime;
273 #endif
274 }
275
GetKernelSymbol(uint64_t ip,const std::vector<MemMapItem> & memMaps,const VirtualThread & thread)276 const Symbol VirtualRuntime::GetKernelSymbol(uint64_t ip, const std::vector<MemMapItem> &memMaps,
277 const VirtualThread &thread)
278 {
279 Symbol vaddrSymbol(ip, thread.name_);
280 for (auto &map : memMaps) {
281 if (ip > map.begin_ && ip < map.end_) {
282 HLOGM("found addr 0x%" PRIx64 " in kernel map 0x%" PRIx64 " - 0x%" PRIx64 " from %s",
283 ip, map.begin_, map.end_, map.name_.c_str());
284 vaddrSymbol.module_ = map.name_;
285 // found symbols by file name
286 for (auto &symbolsFile : symbolsFiles_) {
287 if (symbolsFile->filePath_ == map.name_) {
288 vaddrSymbol.fileVaddr_ =
289 symbolsFile->GetVaddrInSymbols(ip, map.begin_, map.pageoffset_);
290 HLOGV("found symbol vaddr 0x%" PRIx64 " for runtime vaddr 0x%" PRIx64
291 " at '%s'",
292 vaddrSymbol.fileVaddr_, ip, map.name_.c_str());
293 if (!symbolsFile->SymbolsLoaded()) {
294 symbolsFile->LoadSymbols();
295 }
296 Symbol foundSymbols = symbolsFile->GetSymbolWithVaddr(vaddrSymbol.fileVaddr_);
297 foundSymbols.taskVaddr_ = ip;
298 if (!foundSymbols.isValid()) {
299 HLOGW("addr 0x%" PRIx64 " vaddr 0x%" PRIx64 " NOT found in symbol file %s",
300 ip, vaddrSymbol.fileVaddr_, map.name_.c_str());
301 return vaddrSymbol;
302 } else {
303 return foundSymbols;
304 }
305 }
306 }
307 HLOGW("addr 0x%" PRIx64 " in map but NOT found the symbol file %s", ip,
308 map.name_.c_str());
309 } else {
310 HLOGM("addr 0x%" PRIx64 " not in map 0x%" PRIx64 " - 0x%" PRIx64 " from %s", ip,
311 map.begin_, map.end_, map.name_.c_str());
312 }
313 }
314 return vaddrSymbol;
315 }
316
GetUserSymbol(uint64_t ip,const VirtualThread & thread)317 const Symbol VirtualRuntime::GetUserSymbol(uint64_t ip, const VirtualThread &thread)
318 {
319 Symbol vaddrSymbol(ip, thread.name_);
320 const MemMapItem *mmap = thread.FindMapByAddr(ip);
321 if (mmap != nullptr) {
322 SymbolsFile *symbolsFile = thread.FindSymbolsFileByMap(*mmap);
323 if (symbolsFile != nullptr) {
324 vaddrSymbol.fileVaddr_ =
325 symbolsFile->GetVaddrInSymbols(ip, mmap->begin_, mmap->pageoffset_);
326 vaddrSymbol.module_ = mmap->nameHold_;
327 vaddrSymbol.symbolName_ = vaddrSymbol.Name();
328 HLOGV("found symbol vaddr 0x%" PRIx64 " for runtime vaddr 0x%" PRIx64 " at '%s'",
329 vaddrSymbol.fileVaddr_, ip, mmap->name_.c_str());
330 if (!symbolsFile->SymbolsLoaded()) {
331 symbolsFile->LoadSymbols();
332 }
333 Symbol foundSymbols = symbolsFile->GetSymbolWithVaddr(vaddrSymbol.fileVaddr_);
334 foundSymbols.taskVaddr_ = ip;
335 foundSymbols.symbolName_ = foundSymbols.Name();
336 if (!foundSymbols.isValid()) {
337 HLOGW("addr 0x%" PRIx64 " vaddr 0x%" PRIx64 " NOT found in symbol file %s", ip,
338 vaddrSymbol.fileVaddr_, mmap->name_.c_str());
339 return vaddrSymbol;
340 } else {
341 return foundSymbols;
342 }
343 } else {
344 HLOGW("addr 0x%" PRIx64 " in map but NOT found the symbol file %s", ip,
345 mmap->name_.c_str());
346 }
347 } else {
348 HLOGW("ReportVaddrMapMiss");
349 #ifdef HIPERF_DEBUG
350 thread.ReportVaddrMapMiss(ip);
351 #endif
352 }
353 return vaddrSymbol;
354 }
355
GetSymbolCache(uint64_t ip,pid_t pid,pid_t tid,Symbol & symbol,const perf_callchain_context & context)356 bool VirtualRuntime::GetSymbolCache(uint64_t ip, pid_t pid, pid_t tid, Symbol &symbol,
357 const perf_callchain_context &context)
358 {
359 if (context != PERF_CONTEXT_USER and kernelSymbolCache_.count(ip)) {
360 if (kernelSymbolCache_.find(ip) == kernelSymbolCache_.end()) {
361 return false;
362 }
363 Symbol &foundSymbol = kernelSymbolCache_[ip];
364 foundSymbol.hit_++;
365 HLOGM("hit kernel cache 0x%" PRIx64 " %d", ip, foundSymbol.hit_);
366 symbol = foundSymbol;
367 return true;
368 } else if (threadSymbolCache_[tid].count(ip)) {
369 Symbol &foundSymbol = threadSymbolCache_[tid][ip];
370 foundSymbol.hit_++;
371 HLOGM("hit user cache 0x%" PRIx64 " %d", ip, foundSymbol.hit_);
372 symbol = foundSymbol;
373 return true;
374 } else {
375 HLOGM("cache miss k %zu u %zu", kernelSymbolCache_.size(), threadSymbolCache_[tid].size());
376 }
377 return false;
378 }
379
UpdateSymbolCache(uint64_t ip,Symbol & symbol,HashList<uint64_t,Symbol> & cache)380 void VirtualRuntime::UpdateSymbolCache(uint64_t ip, Symbol &symbol,
381 HashList<uint64_t, Symbol> &cache)
382 {
383 // review change to LRU for memmory
384 HLOG_ASSERT_MESSAGE(cache.count(ip) == 0, "already have cached ip 0x%" PRIx64 "", ip);
385 cache[ip] = symbol;
386 }
387
GetSymbol(uint64_t ip,pid_t pid,pid_t tid,const perf_callchain_context & context)388 const Symbol VirtualRuntime::GetSymbol(uint64_t ip, pid_t pid, pid_t tid,
389 const perf_callchain_context &context)
390 {
391 HLOGM("try find tid %u ip 0x%" PRIx64 " in %zu symbolsFiles ", tid, ip, symbolsFiles_.size());
392 Symbol symbol;
393 if (!threadSymbolCache_.count(tid)) {
394 threadSymbolCache_[tid].reserve(THREAD_SYMBOL_CACHE_LIMIT);
395 }
396 if (GetSymbolCache(ip, pid, tid, symbol, context)) {
397 return symbol;
398 }
399 if (context == PERF_CONTEXT_USER or (context == PERF_CONTEXT_MAX and !symbol.isValid())) {
400 // check userspace memmap
401 symbol = GetUserSymbol(ip, GetThread(pid, tid));
402 if (symbol.isValid()) {
403 HLOGM("GetUserSymbol valid tid = %d ip = 0x%" PRIx64 "", tid, ip);
404 threadSymbolCache_[tid][ip] = symbol;
405 } else {
406 HLOGM("GetUserSymbol invalid!");
407 }
408 }
409
410 return symbol;
411 }
412
SetSymbolsPaths(const std::vector<std::string> & symbolsPaths)413 bool VirtualRuntime::SetSymbolsPaths(const std::vector<std::string> &symbolsPaths)
414 {
415 std::unique_ptr<SymbolsFile> symbolsFile = SymbolsFile::CreateSymbolsFile(SYMBOL_UNKNOW_FILE);
416 // we need check if the path is accessable
417 bool accessable = symbolsFile->setSymbolsFilePath(symbolsPaths);
418 if (accessable) {
419 symbolsPaths_ = symbolsPaths;
420 } else {
421 if (!symbolsPaths.empty()) {
422 printf("some symbols path unable access\n");
423 }
424 }
425 return accessable;
426 }
427 } // namespace NativeDaemon
428 } // namespace Developtools
429 } // namespace OHOS