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