frame_pointer_unwinder_unittest.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "base/profiler/frame_pointer_unwinder.h"
  5. #include <memory>
  6. #include "base/profiler/module_cache.h"
  7. #include "base/profiler/stack_sampling_profiler_test_util.h"
  8. #include "base/profiler/unwinder.h"
  9. #include "build/buildflag.h"
  10. #include "testing/gtest/include/gtest/gtest.h"
  11. #if BUILDFLAG(IS_APPLE)
  12. #include "base/mac/mac_util.h"
  13. #endif
  14. namespace base {
  15. namespace {
  16. constexpr uintptr_t kModuleStart = 0x1000;
  17. constexpr size_t kModuleSize = 0x1000;
  18. constexpr uintptr_t kNonNativeModuleStart = 0x4000;
  19. // Used to construct test stacks. If `relative` is true, the value should be the
  20. // address `offset` positions from the bottom of the stack (at 8-byte alignment)
  21. // Otherwise, `offset` is added to the stack as an absolute address/value.
  22. // For example, when creating a stack with bottom 0x2000, {false, 0xf00d} will
  23. // become 0xf00d, and {true, 0x3} will become 0x2018.
  24. struct StackEntrySpec {
  25. bool relative;
  26. uintptr_t offset;
  27. };
  28. // Enables constructing a stack buffer that has pointers to itself
  29. // and provides convenience methods for calling the unwinder.
  30. struct InputStack {
  31. explicit InputStack(const std::vector<StackEntrySpec>& offsets)
  32. : buffer(offsets.size()) {
  33. size_t size = offsets.size();
  34. for (size_t i = 0; i < size; ++i) {
  35. auto spec = offsets[i];
  36. if (spec.relative) {
  37. buffer[i] = bottom() + (spec.offset * sizeof(uintptr_t));
  38. } else {
  39. buffer[i] = spec.offset;
  40. }
  41. }
  42. }
  43. uintptr_t bottom() const {
  44. return reinterpret_cast<uintptr_t>(buffer.data());
  45. }
  46. uintptr_t top() const { return bottom() + buffer.size() * sizeof(uintptr_t); }
  47. private:
  48. std::vector<uintptr_t> buffer;
  49. };
  50. } // namespace
  51. class FramePointerUnwinderTest : public testing::Test {
  52. protected:
  53. FramePointerUnwinderTest() {
  54. #if BUILDFLAG(IS_APPLE)
  55. if (__builtin_available(iOS 12, *)) {
  56. #else
  57. {
  58. #endif
  59. unwinder_ = std::make_unique<FramePointerUnwinder>();
  60. auto test_module =
  61. std::make_unique<TestModule>(kModuleStart, kModuleSize);
  62. module_ = test_module.get();
  63. module_cache_.AddCustomNativeModule(std::move(test_module));
  64. auto non_native_module = std::make_unique<TestModule>(
  65. kNonNativeModuleStart, kModuleSize, false);
  66. non_native_module_ = non_native_module.get();
  67. std::vector<std::unique_ptr<const ModuleCache::Module>> wrapper;
  68. wrapper.push_back(std::move(non_native_module));
  69. module_cache()->UpdateNonNativeModules({}, std::move(wrapper));
  70. unwinder_->Initialize(&module_cache_);
  71. }
  72. }
  73. ModuleCache* module_cache() { return &module_cache_; }
  74. ModuleCache::Module* module() { return module_; }
  75. ModuleCache::Module* non_native_module() { return non_native_module_; }
  76. Unwinder* unwinder() { return unwinder_.get(); }
  77. private:
  78. std::unique_ptr<Unwinder> unwinder_;
  79. base::ModuleCache module_cache_;
  80. raw_ptr<ModuleCache::Module> module_;
  81. raw_ptr<ModuleCache::Module> non_native_module_;
  82. };
  83. TEST_F(FramePointerUnwinderTest, FPPointsOutsideOfStack) {
  84. InputStack input({
  85. {false, 0x1000},
  86. {false, 0x1000},
  87. {false, 0x1000},
  88. {false, 0x1000},
  89. {false, 0x1000},
  90. });
  91. RegisterContext context;
  92. RegisterContextStackPointer(&context) = input.bottom();
  93. RegisterContextInstructionPointer(&context) = kModuleStart;
  94. RegisterContextFramePointer(&context) = 0x1;
  95. std::vector<Frame> stack = {
  96. Frame(RegisterContextInstructionPointer(&context), module())};
  97. EXPECT_EQ(UnwindResult::kAborted,
  98. unwinder()->TryUnwind(&context, input.top(), &stack));
  99. EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
  100. RegisterContextFramePointer(&context) = input.bottom() - sizeof(uintptr_t);
  101. EXPECT_EQ(UnwindResult::kAborted,
  102. unwinder()->TryUnwind(&context, input.top(), &stack));
  103. EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
  104. RegisterContextFramePointer(&context) = input.top();
  105. EXPECT_EQ(UnwindResult::kAborted,
  106. unwinder()->TryUnwind(&context, input.top(), &stack));
  107. EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
  108. }
  109. TEST_F(FramePointerUnwinderTest, FPPointsToSelf) {
  110. InputStack input({
  111. {true, 0},
  112. {false, kModuleStart + 0x10},
  113. {true, 4},
  114. {false, kModuleStart + 0x20},
  115. {false, 0},
  116. {false, 0},
  117. });
  118. RegisterContext context;
  119. RegisterContextStackPointer(&context) = input.bottom();
  120. RegisterContextInstructionPointer(&context) = kModuleStart;
  121. RegisterContextFramePointer(&context) = input.bottom();
  122. std::vector<Frame> stack = {
  123. Frame(RegisterContextInstructionPointer(&context), module())};
  124. EXPECT_EQ(UnwindResult::kAborted,
  125. unwinder()->TryUnwind(&context, input.top(), &stack));
  126. EXPECT_EQ(std::vector<Frame>({
  127. {kModuleStart, module()},
  128. }),
  129. stack);
  130. }
  131. // Tests that two frame pointers that point to each other can't create an
  132. // infinite loop
  133. TEST_F(FramePointerUnwinderTest, FPCycle) {
  134. InputStack input({
  135. {true, 2},
  136. {false, kModuleStart + 0x10},
  137. {true, 0},
  138. {false, kModuleStart + 0x20},
  139. {true, 4},
  140. {false, kModuleStart + 0x30},
  141. {false, 0},
  142. {false, 0},
  143. });
  144. RegisterContext context;
  145. RegisterContextStackPointer(&context) = input.bottom();
  146. RegisterContextInstructionPointer(&context) = kModuleStart;
  147. RegisterContextFramePointer(&context) = input.bottom();
  148. std::vector<Frame> stack = {
  149. Frame(RegisterContextInstructionPointer(&context), module())};
  150. EXPECT_EQ(UnwindResult::kAborted,
  151. unwinder()->TryUnwind(&context, input.top(), &stack));
  152. EXPECT_EQ(std::vector<Frame>({
  153. {kModuleStart, module()},
  154. {kModuleStart + 0x10, module()},
  155. }),
  156. stack);
  157. }
  158. TEST_F(FramePointerUnwinderTest, NoModuleForIP) {
  159. uintptr_t not_in_module = kModuleStart - 0x10;
  160. InputStack input({
  161. {true, 2},
  162. {false, not_in_module},
  163. {true, 4},
  164. {true, kModuleStart + 0x10},
  165. {false, 0},
  166. {false, 0},
  167. });
  168. RegisterContext context;
  169. RegisterContextStackPointer(&context) = input.bottom();
  170. RegisterContextInstructionPointer(&context) = kModuleStart;
  171. RegisterContextFramePointer(&context) = input.bottom();
  172. std::vector<Frame> stack = {
  173. Frame(RegisterContextInstructionPointer(&context), module())};
  174. EXPECT_EQ(UnwindResult::kAborted,
  175. unwinder()->TryUnwind(&context, input.top(), &stack));
  176. EXPECT_EQ(
  177. std::vector<Frame>({{kModuleStart, module()}, {not_in_module, nullptr}}),
  178. stack);
  179. }
  180. // Tests that testing that checking if there's space to read two values from the
  181. // stack doesn't overflow.
  182. TEST_F(FramePointerUnwinderTest, FPAdditionOverflows) {
  183. uintptr_t will_overflow = std::numeric_limits<uintptr_t>::max() - 1;
  184. InputStack input({
  185. {true, 2},
  186. {false, kModuleStart + 0x10},
  187. {false, 0},
  188. {false, 0},
  189. });
  190. RegisterContext context;
  191. RegisterContextStackPointer(&context) = input.bottom();
  192. RegisterContextInstructionPointer(&context) = kModuleStart;
  193. RegisterContextFramePointer(&context) = will_overflow;
  194. std::vector<Frame> stack = {
  195. Frame(RegisterContextInstructionPointer(&context), module())};
  196. EXPECT_EQ(UnwindResult::kAborted,
  197. unwinder()->TryUnwind(&context, input.top(), &stack));
  198. EXPECT_EQ(std::vector<Frame>({
  199. {kModuleStart, module()},
  200. }),
  201. stack);
  202. }
  203. // Tests the happy path: a successful unwind with no non-native modules.
  204. TEST_F(FramePointerUnwinderTest, RegularUnwind) {
  205. InputStack input({
  206. {true, 4}, // fp of frame 1
  207. {false, kModuleStart + 0x20}, // ip of frame 1
  208. {false, 0xaaaa},
  209. {false, 0xaaaa},
  210. {true, 8}, // fp of frame 2
  211. {false, kModuleStart + 0x42}, // ip of frame 2
  212. {false, 0xaaaa},
  213. {false, 0xaaaa},
  214. {false, 0},
  215. {false, 1},
  216. });
  217. RegisterContext context;
  218. RegisterContextStackPointer(&context) = input.bottom();
  219. RegisterContextInstructionPointer(&context) = kModuleStart;
  220. RegisterContextFramePointer(&context) = input.bottom();
  221. std::vector<Frame> stack = {
  222. Frame(RegisterContextInstructionPointer(&context), module())};
  223. EXPECT_EQ(UnwindResult::kCompleted,
  224. unwinder()->TryUnwind(&context, input.top(), &stack));
  225. EXPECT_EQ(std::vector<Frame>({
  226. {kModuleStart, module()},
  227. {kModuleStart + 0x20, module()},
  228. {kModuleStart + 0x42, module()},
  229. }),
  230. stack);
  231. }
  232. // Tests that if a V8 frame is encountered, unwinding stops and
  233. // kUnrecognizedFrame is returned to facilitate continuing with the V8 unwinder.
  234. TEST_F(FramePointerUnwinderTest, NonNativeFrame) {
  235. InputStack input({
  236. {true, 4}, // fp of frame 1
  237. {false, kModuleStart + 0x20}, // ip of frame 1
  238. {false, 0xaaaa},
  239. {false, 0xaaaa},
  240. {true, 8}, // fp of frame 2
  241. {false, kNonNativeModuleStart + 0x42}, // ip of frame 2
  242. {false, 0xaaaa},
  243. {false, 0xaaaa},
  244. {true, 12}, // fp of frame 3
  245. {false, kModuleStart + 0x10}, // ip of frame 3
  246. {true, 0xaaaa},
  247. {true, 0xaaaa},
  248. {false, 0},
  249. {false, 1},
  250. });
  251. RegisterContext context;
  252. RegisterContextStackPointer(&context) = input.bottom();
  253. RegisterContextInstructionPointer(&context) = kModuleStart;
  254. RegisterContextFramePointer(&context) = input.bottom();
  255. std::vector<Frame> stack = {
  256. Frame(RegisterContextInstructionPointer(&context), module())};
  257. EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
  258. unwinder()->TryUnwind(&context, input.top(), &stack));
  259. EXPECT_EQ(std::vector<Frame>({
  260. {kModuleStart, module()},
  261. {kModuleStart + 0x20, module()},
  262. {kNonNativeModuleStart + 0x42, non_native_module()},
  263. }),
  264. stack);
  265. }
  266. // Tests that a V8 frame with an unaligned frame pointer correctly returns
  267. // kUnrecognizedFrame and not kAborted.
  268. TEST_F(FramePointerUnwinderTest, NonNativeUnaligned) {
  269. InputStack input({
  270. {true, 4}, // fp of frame 1
  271. {false, kModuleStart + 0x20}, // ip of frame 1
  272. {false, 0xaaaa},
  273. {false, 0xaaaa},
  274. {true, 7}, // fp of frame 2
  275. {false, kNonNativeModuleStart + 0x42}, // ip of frame 2
  276. {false, 0xaaaa},
  277. {true, 10}, // fp of frame 3
  278. {false, kModuleStart + 0x10}, // ip of frame 3
  279. {true, 0xaaaa},
  280. {false, 0},
  281. {false, 1},
  282. });
  283. RegisterContext context;
  284. RegisterContextStackPointer(&context) = input.bottom();
  285. RegisterContextInstructionPointer(&context) = kModuleStart;
  286. RegisterContextFramePointer(&context) = input.bottom();
  287. std::vector<Frame> stack = {
  288. Frame(RegisterContextInstructionPointer(&context), module())};
  289. EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
  290. unwinder()->TryUnwind(&context, input.top(), &stack));
  291. }
  292. } // namespace base