• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-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 <compiler/compiler_options.h>
17 #include <gtest/gtest.h>
18 
19 #include <bitset>
20 #include <vector>
21 
22 #include "assembly-emitter.h"
23 #include "assembly-parser.h"
24 #include "code_info/code_info_builder.h"
25 #include "libpandabase/utils/cframe_layout.h"
26 #include "libpandabase/utils/utils.h"
27 #include "runtime/bridge/bridge.h"
28 #include "runtime/include/runtime.h"
29 #include "runtime/include/stack_walker-inl.h"
30 #include "runtime/include/thread_scopes.h"
31 #include "runtime/mem/refstorage/global_object_storage.h"
32 #include "runtime/tests/test_utils.h"
33 #include "compiler/tests/panda_runner.h"
34 
35 namespace ark::test {
36 
37 // NOLINTNEXTLINE(google-build-using-namespace)
38 using namespace compiler;
39 
40 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
41 #define HOOK_ASSERT(cond, action)                                                     \
42     do {                                                                              \
43         if (!(cond)) {                                                                \
44             std::cerr << "ASSERT FAILED(" << __LINE__ << "): " << #cond << std::endl; \
45             action;                                                                   \
46         }                                                                             \
47     } while (0)
48 
49 class StackWalkerTest : public testing::Test {
50 public:
51     using Callback = int (*)(uintptr_t, uintptr_t);
52 
StackWalkerTest()53     StackWalkerTest()
54         : defaultCompilerNonOptimizing_(g_options.IsCompilerNonOptimizing()),
55           defaultCompilerRegex_(g_options.GetCompilerRegex())
56     {
57     }
58 
~StackWalkerTest()59     ~StackWalkerTest() override
60     {
61         g_options.SetCompilerNonOptimizing(defaultCompilerNonOptimizing_);
62         g_options.SetCompilerRegex(defaultCompilerRegex_);
63     }
64 
65     NO_COPY_SEMANTIC(StackWalkerTest);
66     NO_MOVE_SEMANTIC(StackWalkerTest);
67 
SetUp()68     void SetUp() override
69     {
70 #ifndef PANDA_COMPILER_ENABLE
71         GTEST_SKIP();
72 #endif
73     }
74 
75     void TestModifyManyVregs(bool isCompiled);
76 
GetMethod(std::string_view methodName)77     static Method *GetMethod(std::string_view methodName)
78     {
79         PandaString descriptor;
80         auto *thread = MTManagedThread::GetCurrent();
81         thread->ManagedCodeBegin();
82         auto *extension = Runtime::GetCurrent()->GetClassLinker()->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
83         auto cls = extension->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
84         thread->ManagedCodeEnd();
85         ASSERT(cls);
86         return cls->GetDirectMethod(utf::CStringAsMutf8(methodName.data()));
87     }
88 
89     static mem::Reference *globalObj_;
90 
91 private:
92     bool defaultCompilerNonOptimizing_;
93     std::string defaultCompilerRegex_;
94 };
95 
96 mem::Reference *StackWalkerTest::globalObj_;
97 
98 template <typename T>
ConvertToU64(T val)99 uint64_t ConvertToU64(T val)
100 {
101     if constexpr (sizeof(T) == sizeof(uint32_t)) {
102         return bit_cast<uint32_t>(val);
103     } else if constexpr (sizeof(T) == sizeof(uint64_t)) {
104         return bit_cast<uint64_t>(val);
105     } else {
106         return static_cast<uint64_t>(val);
107     }
108 }
109 
110 template <Arch ARCH>
ToCalleeRegister(size_t reg)111 int32_t ToCalleeRegister(size_t reg)
112 {
113     return reg + GetFirstCalleeReg(ARCH, false);
114 }
115 
116 template <Arch ARCH>
ToCalleeFpRegister(size_t reg)117 int32_t ToCalleeFpRegister(size_t reg)
118 {
119     return reg + GetFirstCalleeReg(ARCH, true);
120 }
121 
122 static auto g_modifyVregSource = R"(
123     .function i32 main() {
124         movi.64 v0, 27
125     try_begin:
126         call.short testa, v0
127         jmp try_end
128     try_end:
129         return
130     .catchall try_begin, try_end, try_end
131     }
132     .function i32 testa(i64 a0) {
133         call.short testb, a0
134         return
135     }
136     .function i32 testb(i64 a0) {
137         call.short testc, a0
138         return
139     }
140     .function i32 testc(i64 a0) {
141         lda a0
142     try_begin:
143         call.short hook  # change vregs in all frames
144         jnez exit
145         call.short hook  # verify vregs in all frames
146         jmp exit
147     exit:
148         return
149     .catchall try_begin, exit, exit
150     }
151     .function i32 hook() {
152         ldai 1
153         return
154     }
155 )";
156 
TEST_F(StackWalkerTest,ModifyVreg)157 TEST_F(StackWalkerTest, ModifyVreg)
158 {
159     auto source = g_modifyVregSource;
160 
161     PandaRunner runner;
162     runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
163     runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
164     runner.GetCompilerOptions().SetCompilerRematConst(false);
165     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::testb|_GLOBAL::hook).*");
166     [[maybe_unused]] static constexpr std::array<uint64_t, 3> FRAME_VALUES = {0x123456789abcdef, 0xaaaabbbbccccdddd,
167                                                                               0xabcdef20};
168     static int runCount = 0;
169     runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
170         StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
171         bool success = false;
172         walker.NextFrame();
173         if (runCount == 0) {
174             bool wasSet = false;
175             HOOK_ASSERT(!walker.IsCFrame(), return 1);
176             success = walker.IterateVRegsWithInfo([&wasSet, &walker](const auto &regInfo, const auto &reg) {
177                 if (!regInfo.IsAccumulator()) {
178                     HOOK_ASSERT(reg.GetLong() == 27L, return false);
179                     walker.SetVRegValue(regInfo, FRAME_VALUES[0]);
180                     wasSet = true;
181                 }
182                 return true;
183             });
184             HOOK_ASSERT(success, return 1);
185             HOOK_ASSERT(wasSet, return 1);
186 
187             walker.NextFrame();
188             HOOK_ASSERT(walker.IsCFrame(), return 1);
189             success = walker.IterateVRegsWithInfo([&walker](const auto &regInfo, const auto &reg) {
190                 if (!regInfo.IsAccumulator()) {
191                     HOOK_ASSERT(reg.GetLong() == 27L, return false);
192                     walker.SetVRegValue(regInfo, FRAME_VALUES[1]);
193                 }
194                 return true;
195             });
196             HOOK_ASSERT(success, return 1);
197 
198             walker.NextFrame();
199             HOOK_ASSERT(walker.IsCFrame(), return 1);
200             success = walker.IterateVRegsWithInfo([&walker](const auto &regInfo, const auto &reg) {
201                 if (!regInfo.IsAccumulator()) {
202                     HOOK_ASSERT(reg.GetLong() == 27L, return true);
203                     walker.SetVRegValue(regInfo, FRAME_VALUES[2U]);
204                 }
205                 return true;
206             });
207             HOOK_ASSERT(success, return 1);
208         } else if (runCount == 1) {
209             HOOK_ASSERT(!walker.IsCFrame(), return 1);
210             success = walker.IterateVRegsWithInfo([](const auto &regInfo, const auto &reg) {
211                 if (!regInfo.IsAccumulator()) {
212                     HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[0]), return true);
213                 }
214                 return true;
215             });
216             HOOK_ASSERT(success, return 1);
217 
218             walker.NextFrame();
219             HOOK_ASSERT(walker.IsCFrame(), return 1);
220             success = walker.IterateVRegsWithInfo([](const auto &regInfo, const auto &reg) {
221                 if (!regInfo.IsAccumulator()) {
222                     HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[1]), return true);
223                 }
224                 return true;
225             });
226             HOOK_ASSERT(success, return 1);
227 
228             walker.NextFrame();
229             HOOK_ASSERT(walker.IsCFrame(), return 1);
230             success = walker.IterateVRegsWithInfo([](const auto &regInfo, const auto &reg) {
231                 if (!regInfo.IsAccumulator()) {
232                     HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[2U]), return true);
233                 }
234                 return true;
235             });
236             HOOK_ASSERT(success, return 1);
237         } else {
238             return 1;
239         }
240         runCount++;
241         return 0;
242     });
243     ASSERT_EQ(runCount, 2_I);
244 }
245 
246 static auto g_testModifyManyVregsSource = R"(
247     .function i32 main() {
248         movi.64 v0, 5
249     try_begin:
250         call.short test
251         jmp try_end
252     try_end:
253         return
254 
255     .catchall try_begin, try_end, try_end
256     }
257 
258     .function i32 test() {
259         movi.64 v1, 1
260         movi.64 v2, 2
261         movi.64 v3, 3
262         movi.64 v4, 4
263         movi.64 v5, 5
264         movi.64 v6, 6
265         movi.64 v7, 7
266         movi.64 v8, 8
267         movi.64 v9, 9
268         movi.64 v10, 10
269         movi.64 v11, 11
270         movi.64 v12, 12
271         movi.64 v13, 13
272         movi.64 v14, 14
273         movi.64 v15, 15
274         movi.64 v16, 16
275         movi.64 v17, 17
276         movi.64 v18, 18
277         movi.64 v19, 19
278         movi.64 v20, 20
279         movi.64 v21, 21
280         movi.64 v22, 22
281         movi.64 v23, 23
282         movi.64 v24, 24
283         movi.64 v25, 25
284         movi.64 v26, 26
285         movi.64 v27, 27
286         movi.64 v28, 28
287         movi.64 v29, 29
288         movi.64 v30, 30
289         movi.64 v31, 31
290     try_begin:
291         mov v0, v31
292         newarr v0, v0, i32[]
293         call.short stub
294         jmp try_end
295     try_end:
296         return
297 
298     .catchall try_begin, try_end, try_end
299     }
300 
301     .function i32 stub() {
302     try_begin:
303         call.short hook  # change vregs in all frames
304         jnez exit
305         call.short hook  # verify vregs in all frames
306         jmp exit
307     exit:
308         return
309 
310     .catchall try_begin, exit, exit
311     }
312 
313     .function i32 hook() {
314         ldai 1
315         return
316     }
317 )";
318 
319 template <typename VRegRef>
FirstRunModifyVregs(int * regIndex,StackWalker * walker,ObjectHeader * obj,const VRegInfo * regInfo,const VRegRef & reg)320 static bool FirstRunModifyVregs(int *regIndex, StackWalker *walker, ObjectHeader *obj, const VRegInfo *regInfo,
321                                 const VRegRef &reg)
322 {
323     if (!regInfo->IsAccumulator()) {
324         if (reg.HasObject()) {
325             HOOK_ASSERT(reg.GetReference() != nullptr, return false);
326             walker->SetVRegValue(*regInfo, obj);
327         } else if (regInfo->GetLocation() != VRegInfo::Location::CONSTANT) {
328             // frame_size is more than nregs_ now for inst `V4_V4_ID16` and `V4_V4_V4_V4_ID16`
329             auto regIdx = regInfo->GetIndex();
330             if (regIdx < walker->GetMethod()->GetNumVregs()) {
331                 HOOK_ASSERT(regIdx == reg.GetLong(), return false);
332             }
333             // NOLINTNEXTLINE(readability-magic-numbers)
334             walker->SetVRegValue(*regInfo, regIdx + 100000000000L);
335         }
336         (*regIndex)++;
337     }
338     return true;
339 }
340 
341 template <typename VRegRef>
CheckVregs(int * regIndex,ObjectHeader * obj,const VRegInfo & regInfo,const VRegRef & reg)342 static bool CheckVregs(int *regIndex, ObjectHeader *obj, const VRegInfo &regInfo, const VRegRef &reg)
343 {
344     if (!regInfo.IsAccumulator()) {
345         if (reg.HasObject()) {
346             HOOK_ASSERT((reg.GetReference() == reinterpret_cast<ObjectHeader *>(Low32Bits(obj))), return false);
347         } else {
348             if (regInfo.GetLocation() != VRegInfo::Location::CONSTANT) {
349                 HOOK_ASSERT(reg.GetLong() == (regInfo.GetIndex() + 100000000000L), return false);
350             }
351             (*regIndex)++;
352         }
353     }
354     return true;
355 }
356 
TestModifyManyVregs(bool isCompiled)357 void StackWalkerTest::TestModifyManyVregs(bool isCompiled)
358 {
359     auto source = g_testModifyManyVregsSource;
360     static bool firstRun;
361     static bool compiled;
362 
363     PandaRunner runner;
364     runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
365     runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
366 
367     if (!isCompiled) {
368         runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main)(?!_GLOBAL::test)(?!_GLOBAL::hook).*");
369     }
370 
371     firstRun = true;
372     compiled = isCompiled;
373     runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
374         StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
375 
376         HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::stub", return 1);
377         walker.NextFrame();
378         HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::test", return 1);
379         HOOK_ASSERT(walker.IsCFrame() == compiled, return 1);
380 
381         int regIndex = 1;
382         bool success = false;
383         if (firstRun) {
384             auto storage = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage();
385             StackWalkerTest::globalObj_ =
386                 storage->Add(ark::mem::AllocateNullifiedPayloadString(1), mem::Reference::ObjectType::GLOBAL);
387         }
388         auto obj = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage()->Get(StackWalkerTest::globalObj_);
389         if (firstRun) {
390             success = walker.IterateVRegsWithInfo([&regIndex, &walker, &obj](const auto &regInfo, const auto &reg) {
391                 return FirstRunModifyVregs(&regIndex, &walker, obj, &regInfo, reg);
392             });
393             HOOK_ASSERT(success, return 1);
394             HOOK_ASSERT(regIndex >= 32_I, return 1);
395             firstRun = false;
396         } else {
397             success = walker.IterateVRegsWithInfo([&regIndex, &obj](const auto &regInfo, const auto &reg) {
398                 return CheckVregs(&regIndex, obj, regInfo, reg);
399             });
400             HOOK_ASSERT(success, return 1);
401             HOOK_ASSERT(regIndex >= 32_I, return 1);
402         }
403 
404         HOOK_ASSERT(success, return 1);
405         return 0;
406     });
407 }
408 
TEST_F(StackWalkerTest,ModifyMultipleVregs)409 TEST_F(StackWalkerTest, ModifyMultipleVregs)
410 {
411     if constexpr (ArchTraits<RUNTIME_ARCH>::SUPPORT_DEOPTIMIZATION) {
412         TestModifyManyVregs(true);
413         TestModifyManyVregs(false);
414     }
415 }
416 
417 static auto g_throwExceptionThroughMultipleFramesSource = R"(
418         .record E {}
419 
420         .function u1 f4() {
421             newobj v0, E
422             throw v0
423             return
424         }
425 
426         .function u1 f3() {
427             call f4
428             return
429         }
430 
431         .function u1 f2() {
432             call f3
433             return
434         }
435 
436         .function u1 f1() {
437             call f2
438             return
439         }
440 
441         .function u1 main() {
442         try_begin:
443             movi v0, 123
444             call f1
445             ldai 1
446             return
447         try_end:
448 
449         catch_block1_begin:
450             movi v1, 123
451             lda v0
452             jne v1, exit_1
453             ldai 0
454             return
455             exit_1:
456             ldai 1
457             return
458 
459         .catch E, try_begin, try_end, catch_block1_begin
460         }
461     )";
462 
TEST_F(StackWalkerTest,ThrowExceptionThroughMultipleFrames)463 TEST_F(StackWalkerTest, ThrowExceptionThroughMultipleFrames)
464 {
465     auto source = g_throwExceptionThroughMultipleFramesSource;
466 
467     PandaRunner runner;
468     runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
469     runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
470 
471     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
472     runner.Run(source, nullptr);
473 
474     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
475     runner.Run(source, nullptr);
476 
477     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f2).*");
478     runner.Run(source, nullptr);
479 
480     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4).*");
481     runner.Run(source, nullptr);
482 
483     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
484     runner.Run(source, nullptr);
485 
486     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
487     runner.Run(source, nullptr);
488 
489     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
490     runner.Run(source, nullptr);
491 
492     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
493     runner.Run(source, nullptr);
494 
495     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f2).*");
496     runner.Run(source, nullptr);
497 
498     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4).*");
499     runner.Run(source, nullptr);
500 
501     runner.GetCompilerOptions().SetCompilerRegex(".*");
502     runner.Run(source, nullptr);
503 
504     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
505     runner.Run(source, nullptr);
506 
507     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2).*");
508     runner.Run(source, nullptr);
509 
510     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f2).*");
511     runner.Run(source, nullptr);
512 
513     runner.GetCompilerOptions().SetCompilerRegex(".*");
514     runner.Run(source, nullptr);
515 }
516 
TEST_F(StackWalkerTest,CatchInCompiledCode)517 TEST_F(StackWalkerTest, CatchInCompiledCode)
518 {
519     auto source = R"(
520         .record panda.String <external>
521         .record panda.ArrayIndexOutOfBoundsException <external>
522 
523         .function i32 f2() {
524             movi v0, 4
525             newarr v1, v0, i64[]
526             ldai 42
527             ldarr v1 # BoundsException
528             return
529         }
530 
531         .function i32 f1() {
532         try_begin:
533             call f2
534             ldai 1
535             return
536         try_end:
537 
538         catch_block1_begin:
539             ldai 123
540             return
541 
542         .catch panda.ArrayIndexOutOfBoundsException, try_begin, try_end, catch_block1_begin
543         }
544 
545         .function u1 main() {
546             call f1
547             movi v0, 123
548             jne v0, exit_1
549             ldai 0
550             return
551             exit_1:
552             ldai 1
553             return
554         }
555     )";
556 
557     PandaRunner runner;
558     runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
559     runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
560 
561     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f2).*");
562     runner.Run(source, nullptr);
563 
564     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1).*");
565     runner.Run(source, nullptr);
566 
567     runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
568     runner.Run(source, nullptr);
569 
570     runner.GetCompilerOptions().SetCompilerRegex(".*");
571     runner.Run(source, nullptr);
572 }
573 
574 }  // namespace ark::test
575