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