1 /**
2 * Copyright (c) 2021-2022 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_extractor.h"
17 #include "annotation_data_accessor.h"
18 #include "class_data_accessor-inl.h"
19 #include "code_data_accessor-inl.h"
20 #include "debug_data_accessor-inl.h"
21 #include "field_data_accessor-inl.h"
22 #include "file.h"
23 #include "file_item_container.h"
24 #include "file_writer.h"
25 #include "helpers.h"
26 #include "method_data_accessor-inl.h"
27 #include "modifiers.h"
28 #include "proto_data_accessor-inl.h"
29
30 #include <cstdio>
31
32 #include <memory>
33 #include <vector>
34
35 #include <gtest/gtest.h>
36
37 namespace panda::panda_file::test {
38
39 static const char SOURCE_FILE[] = "asm.pa";
40
PreparePandaFile(ItemContainer * container)41 void PreparePandaFile(ItemContainer *container)
42 {
43 ClassItem *class_item = container->GetOrCreateClassItem("A");
44 class_item->SetAccessFlags(ACC_PUBLIC);
45
46 StringItem *method_name = container->GetOrCreateStringItem("foo");
47
48 PrimitiveTypeItem *ret_type = container->GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
49 std::vector<MethodParamItem> params;
50 params.emplace_back(container->GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
51 ProtoItem *proto_item = container->GetOrCreateProtoItem(ret_type, params);
52 MethodItem *method_item = class_item->AddMethod(method_name, proto_item, ACC_PUBLIC | ACC_STATIC, params);
53
54 std::vector<uint8_t> instructions {1, 2, 3, 4};
55 CodeItem *code_item = container->CreateItem<CodeItem>(4, 1, instructions);
56
57 method_item->SetCode(code_item);
58
59 StringItem *source_file_item = container->GetOrCreateStringItem(SOURCE_FILE);
60 StringItem *param_string_item = container->GetOrCreateStringItem("arg0");
61 StringItem *local_variable_name_0 = container->GetOrCreateStringItem("local_0");
62 StringItem *local_variable_name_1 = container->GetOrCreateStringItem("local_1");
63 StringItem *local_variable_name_2 = container->GetOrCreateStringItem("local_2");
64 StringItem *local_variable_type_i32 = container->GetOrCreateStringItem("I");
65 StringItem *local_variable_sig_type_i32 = container->GetOrCreateStringItem("type_i32");
66
67 LineNumberProgramItem *line_number_program_item = container->CreateLineNumberProgramItem();
68 DebugInfoItem *debug_info_item = container->CreateItem<DebugInfoItem>(line_number_program_item);
69 method_item->SetDebugInfo(debug_info_item);
70
71 // Add static method with ref arg
72
73 StringItem *method_name_bar = container->GetOrCreateStringItem("bar");
74
75 PrimitiveTypeItem *ret_type_bar = container->GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
76 std::vector<MethodParamItem> params_bar;
77 params_bar.emplace_back(container->GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
78 params_bar.emplace_back(container->GetOrCreateClassItem("RefArg"));
79 ProtoItem *proto_item_bar = container->GetOrCreateProtoItem(ret_type_bar, params_bar);
80 MethodItem *method_item_bar =
81 class_item->AddMethod(method_name_bar, proto_item_bar, ACC_PUBLIC | ACC_STATIC, params_bar);
82
83 CodeItem *code_item_bar = container->CreateItem<CodeItem>(0, 2, instructions);
84
85 method_item_bar->SetCode(code_item_bar);
86
87 StringItem *param_string_item_bar1 = container->GetOrCreateStringItem("arg0");
88 StringItem *param_string_item_bar2 = container->GetOrCreateStringItem("arg1");
89
90 LineNumberProgramItem *line_number_program_item_bar = container->CreateLineNumberProgramItem();
91 DebugInfoItem *debug_info_item_bar = container->CreateItem<DebugInfoItem>(line_number_program_item_bar);
92 method_item_bar->SetDebugInfo(debug_info_item_bar);
93
94 // Add non static method with ref arg
95
96 StringItem *method_name_baz = container->GetOrCreateStringItem("baz");
97
98 PrimitiveTypeItem *ret_type_baz = container->GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
99 std::vector<MethodParamItem> params_baz;
100 params_baz.emplace_back(container->GetOrCreateClassItem("RefArg"));
101 params_baz.emplace_back(container->GetOrCreatePrimitiveTypeItem(Type::TypeId::U1));
102 ProtoItem *proto_item_baz = container->GetOrCreateProtoItem(ret_type_baz, params_baz);
103 MethodItem *method_item_baz = class_item->AddMethod(method_name_baz, proto_item_baz, ACC_PUBLIC, params_baz);
104
105 CodeItem *code_item_baz = container->CreateItem<CodeItem>(0, 2, instructions);
106
107 method_item_baz->SetCode(code_item_baz);
108
109 StringItem *param_string_item_baz1 = container->GetOrCreateStringItem("arg0");
110 StringItem *param_string_item_baz2 = container->GetOrCreateStringItem("arg1");
111
112 LineNumberProgramItem *line_number_program_item_baz = container->CreateLineNumberProgramItem();
113 DebugInfoItem *debug_info_item_baz = container->CreateItem<DebugInfoItem>(line_number_program_item_baz);
114 method_item_baz->SetDebugInfo(debug_info_item_baz);
115
116 // Add debug info for the following source file;
117
118 // 1 # file: asm.pa
119 // 2 .function foo(i32 arg0) {
120 // 3 ldai arg0
121 // 4 stai v1 // START_LOCAL: reg=1, name="local_0", type="i32"
122 // 5 ldai 2
123 // 6 stai v2 // START_LOCAL_EXTENDED: reg=2, name="local_1",
124 // type="i32", type_signature="type_i32" 7 // END_LOCAL: reg=1
125 // 8 stai v3 // START_LOCAL: reg=3, name="local_2", type="i32"
126 // 9
127 // 10 return.void
128 // 11 }
129 // 12 .function bar(i32 arg0, B arg1) { // static
130 // 13 ldai arg0
131 // 13 return.void
132 // 14 }
133 // 15 .function baz(B arg0, u1 arg1) { // non static
134 // 17 ldai arg0
135 // 16 return.void
136 // 17 }
137
138 container->ComputeLayout();
139
140 // foo line number program
141 auto *constant_pool = debug_info_item->GetConstantPool();
142 // Line 3
143 debug_info_item->SetLineNumber(3);
144 line_number_program_item->EmitSetFile(constant_pool, source_file_item);
145 line_number_program_item->EmitAdvancePc(constant_pool, 1);
146 line_number_program_item->EmitAdvanceLine(constant_pool, 1);
147 line_number_program_item->EmitSpecialOpcode(0, 0);
148 line_number_program_item->EmitColumn(constant_pool, 0, 7);
149 // Line 4
150 line_number_program_item->EmitStartLocal(constant_pool, 1, local_variable_name_0, local_variable_type_i32);
151 line_number_program_item->EmitSpecialOpcode(1, 1);
152 line_number_program_item->EmitColumn(constant_pool, 0, 8);
153 // Line 5
154 line_number_program_item->EmitSpecialOpcode(1, 1);
155 line_number_program_item->EmitColumn(constant_pool, 0, 9);
156 // Line 6
157 line_number_program_item->EmitStartLocalExtended(constant_pool, 2, local_variable_name_1, local_variable_type_i32,
158 local_variable_sig_type_i32);
159 line_number_program_item->EmitEndLocal(1);
160 line_number_program_item->EmitSpecialOpcode(1, 2);
161 line_number_program_item->EmitColumn(constant_pool, 0, 10);
162 // Line 8
163 line_number_program_item->EmitStartLocal(constant_pool, 3, local_variable_name_2, local_variable_type_i32);
164 line_number_program_item->EmitAdvanceLine(constant_pool, 2);
165 line_number_program_item->EmitSpecialOpcode(0, 0);
166 line_number_program_item->EmitColumn(constant_pool, 0, 11);
167 // Line 10
168 line_number_program_item->EmitEnd();
169
170 debug_info_item->AddParameter(param_string_item);
171
172 method_item->SetDebugInfo(debug_info_item);
173
174 // bar line number program
175 auto *constant_pool_bar = debug_info_item_bar->GetConstantPool();
176 debug_info_item_bar->SetLineNumber(13);
177 line_number_program_item_bar->EmitSetFile(constant_pool_bar, source_file_item);
178 line_number_program_item_bar->EmitAdvancePc(constant_pool_bar, 1);
179 line_number_program_item_bar->EmitAdvanceLine(constant_pool_bar, 1);
180 line_number_program_item_bar->EmitSpecialOpcode(0, 0);
181 line_number_program_item_bar->EmitColumn(constant_pool_bar, 0, 5);
182 line_number_program_item_bar->EmitEnd();
183
184 debug_info_item_bar->AddParameter(param_string_item_bar1);
185 debug_info_item_bar->AddParameter(param_string_item_bar2);
186
187 method_item_bar->SetDebugInfo(debug_info_item_bar);
188
189 // baz line number program
190 auto *constant_pool_baz = debug_info_item_baz->GetConstantPool();
191 debug_info_item_baz->SetLineNumber(15);
192 line_number_program_item_baz->EmitSetFile(constant_pool_baz, source_file_item);
193 line_number_program_item_baz->EmitAdvancePc(constant_pool_baz, 1);
194 line_number_program_item_baz->EmitAdvanceLine(constant_pool_baz, 1);
195 line_number_program_item_baz->EmitSpecialOpcode(0, 0);
196 line_number_program_item_baz->EmitColumn(constant_pool_baz, 0, 6);
197 line_number_program_item_baz->EmitEnd();
198
199 debug_info_item_baz->AddParameter(param_string_item_baz1);
200 debug_info_item_baz->AddParameter(param_string_item_baz2);
201
202 method_item_baz->SetDebugInfo(debug_info_item_baz);
203 }
204
205 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
206 struct SourcePairLocation {
207 std::string path; // NOLINT(misc-non-private-member-variables-in-classes)
208 size_t line; // NOLINT(misc-non-private-member-variables-in-classes)
209
operator ==panda::panda_file::test::SourcePairLocation210 bool operator==(const SourcePairLocation &other) const
211 {
212 return path == other.path && line == other.line;
213 }
214
IsValidpanda::panda_file::test::SourcePairLocation215 bool IsValid() const
216 {
217 return !path.empty();
218 }
219 };
220
GetLineNumberByTableOffsetWrapper(const panda_file::LineNumberTable & table,uint32_t offset)221 static std::optional<size_t> GetLineNumberByTableOffsetWrapper(const panda_file::LineNumberTable &table,
222 uint32_t offset)
223 {
224 for (const auto &value : table) {
225 if (value.offset == offset) {
226 return value.line;
227 }
228 }
229 return std::nullopt;
230 }
231
GetOffsetByTableLineNumberWrapper(const panda_file::LineNumberTable & table,size_t line)232 static std::optional<uint32_t> GetOffsetByTableLineNumberWrapper(const panda_file::LineNumberTable &table, size_t line)
233 {
234 for (const auto &value : table) {
235 if (value.line == line) {
236 return value.offset;
237 }
238 }
239 return std::nullopt;
240 }
241
GetBreakpointAddressWrapper(DebugInfoExtractor extractor,const SourcePairLocation & source_location)242 static std::pair<File::EntityId, uint32_t> GetBreakpointAddressWrapper(DebugInfoExtractor extractor,
243 const SourcePairLocation &source_location)
244 {
245 auto pos = source_location.path.find_last_of("/\\");
246 auto name = source_location.path;
247
248 if (pos != std::string::npos) {
249 name = name.substr(pos + 1);
250 }
251
252 std::vector<panda_file::File::EntityId> methods = extractor.GetMethodIdList();
253 for (const auto &method : methods) {
254 if (extractor.GetSourceFile(method) == source_location.path || extractor.GetSourceFile(method) == name) {
255 panda_file::LineNumberTable line_table = extractor.GetLineNumberTable(method);
256 if (line_table.empty()) {
257 continue;
258 }
259
260 std::optional<size_t> offset = GetOffsetByTableLineNumberWrapper(line_table, source_location.line);
261 if (offset == std::nullopt) {
262 continue;
263 }
264 return {method, offset.value()};
265 }
266 }
267 return {File::EntityId(), 0};
268 }
269
GetLocalVariableInfoWrapper(DebugInfoExtractor extractor,File::EntityId method_id,size_t offset)270 static std::vector<panda_file::LocalVariableInfo> GetLocalVariableInfoWrapper(DebugInfoExtractor extractor,
271 File::EntityId method_id, size_t offset)
272 {
273 std::vector<panda_file::LocalVariableInfo> variables = extractor.GetLocalVariableTable(method_id);
274 std::vector<panda_file::LocalVariableInfo> result;
275
276 for (const auto &variable : variables) {
277 if (variable.start_offset <= offset && offset <= variable.end_offset) {
278 result.push_back(variable);
279 }
280 }
281 return result;
282 }
283
GetSourcePairLocationWrapper(DebugInfoExtractor extractor,File::EntityId method_id,uint32_t bytecode_offset)284 static SourcePairLocation GetSourcePairLocationWrapper(DebugInfoExtractor extractor, File::EntityId method_id,
285 uint32_t bytecode_offset)
286 {
287 panda_file::LineNumberTable line_table = extractor.GetLineNumberTable(method_id);
288 if (line_table.empty()) {
289 return SourcePairLocation();
290 }
291
292 std::optional<size_t> line = GetLineNumberByTableOffsetWrapper(line_table, bytecode_offset);
293 if (line == std::nullopt) {
294 return SourcePairLocation();
295 }
296
297 return SourcePairLocation {extractor.GetSourceFile(method_id), line.value()};
298 }
299
GetPandaFile(std::vector<uint8_t> & data)300 static std::unique_ptr<const File> GetPandaFile(std::vector<uint8_t> &data)
301 {
302 os::mem::ConstBytePtr ptr(reinterpret_cast<std::byte *>(data.data()), data.size(),
303 [](std::byte *, size_t) noexcept {});
304 return File::OpenFromMemory(std::move(ptr));
305 }
306
307 class ExtractorTest : public testing::Test {
308 public:
SetUpTestSuite()309 static void SetUpTestSuite()
310 {
311 ItemContainer container;
312 PreparePandaFile(&container);
313 MemoryWriter writer;
314 ASSERT_TRUE(container.Write(&writer));
315
316 file_data = writer.GetData();
317 u_file = GetPandaFile(file_data);
318 }
319
320 static std::unique_ptr<const panda_file::File> u_file;
321 static std::vector<uint8_t> file_data;
322 };
323
324 std::unique_ptr<const panda_file::File> ExtractorTest::u_file {nullptr};
325 std::vector<uint8_t> ExtractorTest::file_data;
326
327 HWTEST_F(ExtractorTest, DebugInfoTest, testing::ext::TestSize.Level0)
328 {
329 const panda_file::File *pf = u_file.get();
330 ASSERT_TRUE(pf != nullptr);
331 DebugInfoExtractor extractor(pf);
332
333 auto breakpoint1_address = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 1});
334 ASSERT_FALSE(breakpoint1_address.first.IsValid());
335 auto [method_id, bytecode_offset] = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 6});
336 ASSERT_TRUE(method_id.IsValid());
337 ASSERT_EQ(bytecode_offset, 3U);
338
339 auto source_location = GetSourcePairLocationWrapper(extractor, method_id, 2);
340 ASSERT_EQ(source_location.path, SOURCE_FILE);
341 ASSERT_EQ(source_location.line, 5U);
342
343 auto vars = GetLocalVariableInfoWrapper(extractor, method_id, 4);
344 EXPECT_EQ(vars.size(), 2U);
345 ASSERT_EQ(vars[0].name, "local_1");
346 ASSERT_EQ(vars[0].type, "I");
347 ASSERT_EQ(vars[1].name, "local_2");
348 ASSERT_EQ(vars[1].type, "I");
349 }
350
351 HWTEST_F(ExtractorTest, DebugInfoTestStaticWithRefArg, testing::ext::TestSize.Level0)
352 {
353 const panda_file::File *pf = u_file.get();
354 ASSERT_TRUE(pf != nullptr);
355 DebugInfoExtractor extractor(pf);
356
357 auto breakpoint1_address = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 1});
358 ASSERT_FALSE(breakpoint1_address.first.IsValid());
359 auto method_id = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 14}).first;
360 ASSERT_TRUE(method_id.IsValid());
361
362 auto vars = GetLocalVariableInfoWrapper(extractor, method_id, 14);
363 EXPECT_EQ(vars.size(), 0U);
364 }
365
366 HWTEST_F(ExtractorTest, DebugInfoTestNonStaticWithRefArg, testing::ext::TestSize.Level0)
367 {
368 const panda_file::File *pf = u_file.get();
369 ASSERT_TRUE(pf != nullptr);
370 DebugInfoExtractor extractor(pf);
371
372 auto breakpoint1_address = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 1});
373 ASSERT_FALSE(breakpoint1_address.first.IsValid());
374 auto method_id = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 16}).first;
375 ASSERT_TRUE(method_id.IsValid());
376
377 auto vars = GetLocalVariableInfoWrapper(extractor, method_id, 16);
378 EXPECT_EQ(vars.size(), 0U);
379 }
380
381 HWTEST_F(ExtractorTest, DebugInfoTestColumnNumber, testing::ext::TestSize.Level0)
382 {
383 const panda_file::File *pf = u_file.get();
384 ASSERT_TRUE(pf != nullptr);
385 DebugInfoExtractor extractor(pf);
386
387 auto methods = extractor.GetMethodIdList();
388 constexpr uint32_t column_start = 5;
389 uint32_t i = column_start;
390 for (auto const &method_id : methods) {
391 auto &cnt = extractor.GetColumnNumberTable(method_id);
392 for (auto const &col : cnt) {
393 EXPECT_EQ(col.column, i++);
394 }
395 }
396 }
397 } // namespace panda::panda_file::test
398