• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2022-2025 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 
16 #include "debug_info_cache.h"
17 
18 #include "debug_info_extractor.h"
19 #include "include/tooling/pt_location.h"
20 #include "libpandabase/utils/bit_utils.h"
21 #include "method_data_accessor.h"
22 #include "os/mutex.h"
23 
24 namespace ark::tooling::inspector {
AddPandaFile(const panda_file::File & file,bool isUserPandafile)25 void DebugInfoCache::AddPandaFile(const panda_file::File &file, bool isUserPandafile)
26 {
27     os::memory::LockHolder lock(debugInfosMutex_);
28     auto &debugInfo =
29         debugInfos_
30             .emplace(std::piecewise_construct, std::forward_as_tuple(&file),
31                      std::forward_as_tuple(file,
32                                            [this, &file](auto methodId, auto sourceName) {
33                                                os::memory::LockHolder l(disassembliesMutex_);
34                                                disassemblies_.emplace(std::piecewise_construct,
35                                                                       std::forward_as_tuple(sourceName),
36                                                                       std::forward_as_tuple(file, methodId));
37                                            }))
38             .first->second;
39     debugInfo.SetUserFile(isUserPandafile);
40 
41     // For all methods add non-empty source code read from debug-info
42     for (auto methodId : debugInfo.GetMethodIdList()) {
43         std::string_view sourceRelativePath = debugInfo.GetSourceFile(methodId);
44         std::string_view sourceCode = debugInfo.GetSourceCode(methodId);
45         if (!sourceCode.empty()) {
46             auto inserted = fileToSourceCode_.try_emplace(sourceRelativePath, sourceCode).second;
47             if (!inserted) {
48                 LOG(WARNING, DEBUGGER) << "Duplicate source code in debug info for file \"" << sourceRelativePath
49                                        << '"';
50             }
51         }
52     }
53 }
54 
GetSourceLocation(const PtFrame & frame,std::string_view & sourceFile,std::string_view & methodName,size_t & lineNumber)55 void DebugInfoCache::GetSourceLocation(const PtFrame &frame, std::string_view &sourceFile, std::string_view &methodName,
56                                        size_t &lineNumber)
57 {
58     auto method = frame.GetMethod();
59     auto pandaFile = method->GetPandaFile();
60     auto debugInfo = GetDebugInfo(pandaFile);
61     if (debugInfo == nullptr || !debugInfo->IsUserFile()) {
62         lineNumber = 1;
63         return;
64     }
65     sourceFile = debugInfo->GetSourceFile(method->GetFileId());
66 
67     methodName = utf::Mutf8AsCString(method->GetName().data);
68 
69     // Line number entry corresponding to the bytecode location is
70     // the last such entry for which the bytecode offset is not greater than
71     // the given offset. Use `std::upper_bound` to find the first entry
72     // for which the condition is not true, and then step back.
73     auto &table = debugInfo->GetLineNumberTable(method->GetFileId());
74     auto lineNumberIter = std::upper_bound(table.begin(), table.end(), frame.GetBytecodeOffset(),
75                                            [](auto offset, auto &entry) { return offset < entry.offset; });
76     ASSERT(lineNumberIter != table.begin());
77     lineNumber = std::prev(lineNumberIter)->line;
78 }
79 
GetCurrentLineLocations(const PtFrame & frame)80 std::unordered_set<PtLocation, HashLocation> DebugInfoCache::GetCurrentLineLocations(const PtFrame &frame)
81 {
82     std::unordered_set<PtLocation, HashLocation> locations;
83 
84     auto method = frame.GetMethod();
85     auto pandaFile = method->GetPandaFile();
86     auto methodId = method->GetFileId();
87     auto &table = GetDebugInfo(pandaFile)->GetLineNumberTable(methodId);
88     auto it = std::upper_bound(table.begin(), table.end(), frame.GetBytecodeOffset(),
89                                [](auto offset, auto entry) { return offset < entry.offset; });
90     if (it == table.begin()) {
91         return locations;
92     }
93     auto lineNumber = (--it)->line;
94 
95     for (it = table.begin(); it != table.end(); ++it) {
96         if (it->line != lineNumber) {
97             continue;
98         }
99 
100         auto next = it + 1;
101         auto nextOffset = next != table.end() ? next->offset : method->GetCodeSize();
102         for (auto o = it->offset; o < nextOffset; o++) {
103             locations.emplace(pandaFile->GetFilename().c_str(), methodId, o);
104         }
105     }
106 
107     return locations;
108 }
109 
GetContinueToLocations(std::string_view sourceFile,size_t lineNumber)110 std::unordered_set<PtLocation, HashLocation> DebugInfoCache::GetContinueToLocations(std::string_view sourceFile,
111                                                                                     size_t lineNumber)
112 {
113     std::unordered_set<PtLocation, HashLocation> locations;
114     EnumerateLineEntries(
115         [](auto, auto &) { return true; },
116         [sourceFile](auto, auto &debugInfo, auto methodId) { return debugInfo.GetSourceFile(methodId) == sourceFile; },
117         [lineNumber, &locations](auto pandaFile, auto &, auto methodId, auto &entry, auto next) {
118             if (entry.line != lineNumber) {
119                 // continue enumeration
120                 return true;
121             }
122 
123             uint32_t nextOffset;
124             if (next == nullptr) {
125                 panda_file::MethodDataAccessor mda(*pandaFile, methodId);
126                 if (auto codeId = mda.GetCodeId()) {
127                     nextOffset = panda_file::CodeDataAccessor(*pandaFile, *codeId).GetCodeSize();
128                 } else {
129                     nextOffset = 0;
130                 }
131             } else {
132                 nextOffset = next->offset;
133             }
134 
135             for (auto o = entry.offset; o < nextOffset; o++) {
136                 locations.emplace(pandaFile->GetFilename().data(), methodId, o);
137             }
138             return true;
139         });
140     return locations;
141 }
142 
GetBreakpointLocations(const std::function<bool (std::string_view)> & sourceFileFilter,size_t lineNumber,std::set<std::string_view> & sourceFiles) const143 std::unordered_set<PtLocation, HashLocation> DebugInfoCache::GetBreakpointLocations(
144     const std::function<bool(std::string_view)> &sourceFileFilter, size_t lineNumber,
145     std::set<std::string_view> &sourceFiles) const
146 {
147     std::unordered_set<PtLocation, HashLocation> locations;
148     sourceFiles.clear();
149     // clang-format off
150     EnumerateLineEntries(
151         [](auto, auto &) { return true; },
152         [&sourceFileFilter](auto, auto &debugInfo, auto methodId) {
153             return sourceFileFilter(debugInfo.GetSourceFile(methodId));
154         },
155         [lineNumber, &sourceFiles, &locations](auto pandaFile, auto &debugInfo, auto methodId,
156                                                auto &entry, auto /* next */) {
157             if (entry.line == lineNumber) {
158                 sourceFiles.insert(debugInfo.GetSourceFile(methodId));
159                 locations.emplace(pandaFile->GetFilename().data(), methodId, entry.offset);
160                 // Must choose the first found bytecode location in each method
161                 return false;
162             }
163             // Continue search
164             return true;
165         });
166     // clang-format on
167     return locations;
168 }
169 
GetValidLineNumbers(std::string_view sourceFile,size_t startLine,size_t endLine,bool restrictToFunction)170 std::set<size_t> DebugInfoCache::GetValidLineNumbers(std::string_view sourceFile, size_t startLine, size_t endLine,
171                                                      bool restrictToFunction)
172 {
173     std::set<size_t> lineNumbers;
174     auto lineHandler = [startLine, endLine, &lineNumbers](auto, auto &, auto, auto &entry, auto /* next */) {
175         if (entry.line >= startLine && entry.line < endLine) {
176             lineNumbers.insert(entry.line);
177         }
178 
179         return true;
180     };
181     if (!restrictToFunction) {
182         EnumerateLineEntries([](auto, auto &) { return true; },
183                              [sourceFile](auto, auto &debugInfo, auto methodId) {
184                                  return (debugInfo.GetSourceFile(methodId) == sourceFile);
185                              },
186                              lineHandler);
187         return lineNumbers;
188     }
189 
190     auto methodFilter = [sourceFile, startLine](auto, auto &debugInfo, auto methodId) {
191         if (debugInfo.GetSourceFile(methodId) != sourceFile) {
192             return false;
193         }
194 
195         bool hasLess = false;
196         bool hasGreater = false;
197         for (auto &entry : debugInfo.GetLineNumberTable(methodId)) {
198             if (entry.line <= startLine) {
199                 hasLess = true;
200             }
201 
202             if (entry.line >= startLine) {
203                 hasGreater = true;
204             }
205 
206             if (hasLess && hasGreater) {
207                 break;
208             }
209         }
210 
211         return hasLess && hasGreater;
212     };
213     EnumerateLineEntries([](auto, auto &) { return true; }, methodFilter, lineHandler);
214     return lineNumbers;
215 }
216 
217 // NOLINTBEGIN(readability-magic-numbers)
CreateTypedValueFromReg(uint64_t reg,panda_file::Type::TypeId type)218 static TypedValue CreateTypedValueFromReg(uint64_t reg, panda_file::Type::TypeId type)
219 {
220     switch (type) {
221         case panda_file::Type::TypeId::INVALID:
222             return TypedValue::Invalid();
223         case panda_file::Type::TypeId::VOID:
224             return TypedValue::Void();
225         case panda_file::Type::TypeId::U1:
226             return TypedValue::U1(static_cast<bool>(ExtractBits(reg, 0U, 1U)));
227         case panda_file::Type::TypeId::I8:
228             return TypedValue::I8(static_cast<int8_t>(ExtractBits(reg, 0U, 8U)));
229         case panda_file::Type::TypeId::U8:
230             return TypedValue::U8(static_cast<uint8_t>(ExtractBits(reg, 0U, 8U)));
231         case panda_file::Type::TypeId::I16:
232             return TypedValue::I16(static_cast<int16_t>(ExtractBits(reg, 0U, 16U)));
233         case panda_file::Type::TypeId::U16:
234             return TypedValue::U16(static_cast<uint16_t>(ExtractBits(reg, 0U, 16U)));
235         case panda_file::Type::TypeId::I32:
236             return TypedValue::I32(static_cast<int32_t>(ExtractBits(reg, 0U, 32U)));
237         case panda_file::Type::TypeId::U32:
238             return TypedValue::U32(static_cast<uint32_t>(ExtractBits(reg, 0U, 32U)));
239         case panda_file::Type::TypeId::F32:
240             return TypedValue::F32(bit_cast<float>(static_cast<int32_t>(ExtractBits(reg, 0U, 32U))));
241         case panda_file::Type::TypeId::F64:
242             return TypedValue::F64(bit_cast<double>(reg));
243         case panda_file::Type::TypeId::I64:
244             return TypedValue::I64(reg);
245         case panda_file::Type::TypeId::U64:
246             return TypedValue::U64(reg);
247         case panda_file::Type::TypeId::REFERENCE:
248             return TypedValue::Reference(reinterpret_cast<ObjectHeader *>(reg));
249         case panda_file::Type::TypeId::TAGGED:
250             return TypedValue::Tagged(coretypes::TaggedValue(static_cast<coretypes::TaggedType>(reg)));
251         default:
252             UNREACHABLE();
253             return TypedValue::Invalid();
254     }
255 }
256 // NOLINTEND(readability-magic-numbers)
257 
GetTypeIdBySignature(char signature)258 static panda_file::Type::TypeId GetTypeIdBySignature(char signature)
259 {
260     switch (signature) {
261         case 'V':
262             return panda_file::Type::TypeId::VOID;
263         case 'Z':
264             return panda_file::Type::TypeId::U1;
265         case 'B':
266             return panda_file::Type::TypeId::I8;
267         case 'H':
268             return panda_file::Type::TypeId::U8;
269         case 'S':
270             return panda_file::Type::TypeId::I16;
271         case 'C':
272             return panda_file::Type::TypeId::U16;
273         case 'I':
274             return panda_file::Type::TypeId::I32;
275         case 'U':
276             return panda_file::Type::TypeId::U32;
277         case 'F':
278             return panda_file::Type::TypeId::F32;
279         case 'D':
280             return panda_file::Type::TypeId::F64;
281         case 'J':
282             return panda_file::Type::TypeId::I64;
283         case 'Q':
284             return panda_file::Type::TypeId::U64;
285         case 'A':
286             return panda_file::Type::TypeId::TAGGED;
287         case 'L':
288         case '[':
289             return panda_file::Type::TypeId::REFERENCE;
290         default:
291             return panda_file::Type::TypeId::INVALID;
292     }
293 }
294 
GetLocals(const PtFrame & frame)295 std::map<std::string, TypedValue> DebugInfoCache::GetLocals(const PtFrame &frame)
296 {
297     std::map<std::string, TypedValue> result;
298 
299     auto localHandler = [&result](const std::string &name, const std::string &signature, uint64_t reg,
300                                   PtFrame::RegisterKind kind) {
301         auto type = signature.empty() ? panda_file::Type::TypeId::INVALID : GetTypeIdBySignature(signature[0]);
302         if (type == panda_file::Type::TypeId::INVALID) {
303             switch (kind) {
304                 case PtFrame::RegisterKind::PRIMITIVE:
305                     type = panda_file::Type::TypeId::U64;
306                     break;
307                 case PtFrame::RegisterKind::REFERENCE:
308                     type = panda_file::Type::TypeId::REFERENCE;
309                     break;
310                 case PtFrame::RegisterKind::TAGGED:
311                     type = panda_file::Type::TypeId::TAGGED;
312                     break;
313                 default:
314                     UNREACHABLE();
315                     break;
316             }
317         }
318 
319         result.emplace(name, CreateTypedValueFromReg(reg, type));
320     };
321 
322     auto method = frame.GetMethod();
323     auto methodId = method->GetFileId();
324     auto debugInfo = GetDebugInfo(method->GetPandaFile());
325     if (debugInfo == nullptr) {
326         return result;
327     }
328     auto &parameters = debugInfo->GetParameterInfo(methodId);
329     for (auto i = 0U; i < parameters.size(); i++) {
330         auto &parameter = parameters[i];
331         localHandler(parameter.name, parameter.signature, frame.GetArgument(i), frame.GetArgumentKind(i));
332     }
333 
334     auto &variables = debugInfo->GetLocalVariableTable(methodId);
335     auto frameOffset = frame.GetBytecodeOffset();
336     for (auto &variable : variables) {
337         if (variable.IsAccessibleAt(frameOffset)) {
338             localHandler(variable.name, variable.typeSignature,
339                          // We introduced a hack in DisasmBackedDebugInfoExtractor, assigning -1 to Accumulator
340                          variable.regNumber == -1 ? frame.GetAccumulator() : frame.GetVReg(variable.regNumber),
341                          variable.regNumber == -1 ? frame.GetAccumulatorKind() : frame.GetVRegKind(variable.regNumber));
342         }
343     }
344 
345     return result;
346 }
347 
GetSourceCode(std::string_view sourceFile)348 std::string DebugInfoCache::GetSourceCode(std::string_view sourceFile)
349 {
350     {
351         os::memory::LockHolder lock(disassembliesMutex_);
352 
353         auto it = disassemblies_.find(sourceFile);
354         if (it != disassemblies_.end()) {
355             return GetDebugInfo(&it->second.first)->GetSourceCode(it->second.second);
356         }
357     }
358 
359     // Try to get source code read from debug info
360     {
361         os::memory::LockHolder lock(debugInfosMutex_);
362 
363         auto iter = fileToSourceCode_.find(sourceFile);
364         if (iter != fileToSourceCode_.end()) {
365             return std::string(iter->second);
366         }
367     }
368 
369     if (!os::file::File::IsRegularFile(sourceFile.data())) {
370         return {};
371     }
372 
373     std::string result;
374 
375     std::stringstream buffer;
376     buffer << std::ifstream(sourceFile.data()).rdbuf();
377 
378     result = buffer.str();
379     if (!result.empty() && result.back() != '\n') {
380         result += "\n";
381     }
382 
383     return result;
384 }
385 
GetPandaFiles(const std::function<bool (std::string_view)> & sourceFileFilter)386 std::vector<std::string> DebugInfoCache::GetPandaFiles(const std::function<bool(std::string_view)> &sourceFileFilter)
387 {
388     std::vector<std::string> pandaFiles;
389     // clang-format off
390     EnumerateLineEntries(
391         [](auto, auto &) { return true; },
392         [&sourceFileFilter](auto, auto &debugInfo, auto methodId) {
393             return sourceFileFilter(debugInfo.GetSourceFile(methodId));
394         },
395         [&pandaFiles](const auto *pf, auto &, auto, auto &, auto) {
396             pandaFiles.emplace_back(pf->GetFilename());
397             return false;
398         });
399     // clang-format on
400     return pandaFiles;
401 }
402 
GetDebugInfo(const panda_file::File * file) const403 const panda_file::DebugInfoExtractor *DebugInfoCache::GetDebugInfo(const panda_file::File *file) const
404 {
405     os::memory::LockHolder lock(debugInfosMutex_);
406     auto it = debugInfos_.find(file);
407     if (it == debugInfos_.end()) {
408         return nullptr;
409     }
410     return &it->second;
411 }
412 
GetSourceFile(Method * method)413 const char *DebugInfoCache::GetSourceFile(Method *method)
414 {
415     auto pandaFile = method->GetPandaFile();
416     auto debugInfo = GetDebugInfo(pandaFile);
417     if (debugInfo == nullptr) {
418         return nullptr;
419     }
420     return debugInfo->GetSourceFile(method->GetFileId());
421 }
422 
GetUserSourceFile(Method * method)423 const char *DebugInfoCache::GetUserSourceFile(Method *method)
424 {
425     auto pandaFile = method->GetPandaFile();
426     auto debugInfo = GetDebugInfo(pandaFile);
427     if ((debugInfo == nullptr) || !debugInfo->IsUserFile()) {
428         return nullptr;
429     }
430     return debugInfo->GetSourceFile(method->GetFileId());
431 }
432 
433 }  // namespace ark::tooling::inspector
434