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 ¶meters = debugInfo->GetParameterInfo(methodId);
329 for (auto i = 0U; i < parameters.size(); i++) {
330 auto ¶meter = 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