| 1 | //===-- sanitizer_stacktrace_test.cpp -------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file is a part of ThreadSanitizer/AddressSanitizer runtime. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "sanitizer_common/sanitizer_stacktrace.h" |
| 14 | |
| 15 | #include <string.h> |
| 16 | |
| 17 | #include <algorithm> |
| 18 | #include <string> |
| 19 | |
| 20 | #include "gmock/gmock.h" |
| 21 | #include "gtest/gtest.h" |
| 22 | #include "sanitizer_common/sanitizer_common.h" |
| 23 | #include "sanitizer_internal_defs.h" |
| 24 | |
| 25 | using testing::ContainsRegex; |
| 26 | using testing::MatchesRegex; |
| 27 | |
| 28 | namespace __sanitizer { |
| 29 | |
| 30 | class FastUnwindTest : public ::testing::Test { |
| 31 | protected: |
| 32 | virtual void SetUp(); |
| 33 | virtual void TearDown(); |
| 34 | |
| 35 | void UnwindFast(); |
| 36 | |
| 37 | void *mapping; |
| 38 | uhwptr *fake_stack; |
| 39 | const uptr fake_stack_size = 10; |
| 40 | uhwptr start_pc; |
| 41 | |
| 42 | uhwptr fake_bp; |
| 43 | uhwptr fake_top; |
| 44 | uhwptr fake_bottom; |
| 45 | BufferedStackTrace trace; |
| 46 | |
| 47 | #if defined(__loongarch__) || defined(__riscv) |
| 48 | const uptr kFpOffset = 4; |
| 49 | const uptr kBpOffset = 2; |
| 50 | #else |
| 51 | const uptr kFpOffset = 2; |
| 52 | const uptr kBpOffset = 0; |
| 53 | #endif |
| 54 | |
| 55 | private: |
| 56 | CommonFlags tmp_flags_; |
| 57 | }; |
| 58 | |
| 59 | static uptr PC(uptr idx) { |
| 60 | return (1<<20) + idx; |
| 61 | } |
| 62 | |
| 63 | void FastUnwindTest::SetUp() { |
| 64 | size_t ps = GetPageSize(); |
| 65 | mapping = MmapOrDie(size: 2 * ps, mem_type: "FastUnwindTest" ); |
| 66 | MprotectNoAccess(addr: (uptr)mapping, size: ps); |
| 67 | |
| 68 | // Unwinder may peek 1 word down from the starting FP. |
| 69 | fake_stack = (uhwptr *)((uptr)mapping + ps + sizeof(uhwptr)); |
| 70 | |
| 71 | // Fill an array of pointers with fake fp+retaddr pairs. Frame pointers have |
| 72 | // even indices. |
| 73 | for (uptr i = 0; i + 1 < fake_stack_size; i += 2) { |
| 74 | fake_stack[i] = (uptr)&fake_stack[i + kFpOffset]; // fp |
| 75 | fake_stack[i+1] = PC(idx: i + 1); // retaddr |
| 76 | } |
| 77 | // Mark the last fp point back up to terminate the stack trace. |
| 78 | fake_stack[RoundDownTo(x: fake_stack_size - 1, boundary: 2)] = (uhwptr)&fake_stack[0]; |
| 79 | |
| 80 | // Top is two slots past the end because UnwindFast subtracts two. |
| 81 | fake_top = (uhwptr)&fake_stack[fake_stack_size + kFpOffset]; |
| 82 | // Bottom is one slot before the start because UnwindFast uses >. |
| 83 | fake_bottom = (uhwptr)mapping; |
| 84 | fake_bp = (uptr)&fake_stack[kBpOffset]; |
| 85 | start_pc = PC(idx: 0); |
| 86 | |
| 87 | tmp_flags_.CopyFrom(other: *common_flags()); |
| 88 | } |
| 89 | |
| 90 | void FastUnwindTest::TearDown() { |
| 91 | size_t ps = GetPageSize(); |
| 92 | UnmapOrDie(addr: mapping, size: 2 * ps); |
| 93 | |
| 94 | // Restore default flags. |
| 95 | OverrideCommonFlags(cf: tmp_flags_); |
| 96 | } |
| 97 | |
| 98 | #if SANITIZER_CAN_FAST_UNWIND |
| 99 | |
| 100 | #ifdef __sparc__ |
| 101 | // Fake stacks don't meet SPARC UnwindFast requirements. |
| 102 | #define SKIP_ON_SPARC(x) DISABLED_##x |
| 103 | #else |
| 104 | #define SKIP_ON_SPARC(x) x |
| 105 | #endif |
| 106 | |
| 107 | void FastUnwindTest::UnwindFast() { |
| 108 | trace.UnwindFast(pc: start_pc, bp: fake_bp, stack_top: fake_top, stack_bottom: fake_bottom, max_depth: kStackTraceMax); |
| 109 | } |
| 110 | |
| 111 | TEST_F(FastUnwindTest, SKIP_ON_SPARC(Basic)) { |
| 112 | UnwindFast(); |
| 113 | // Should get all on-stack retaddrs and start_pc. |
| 114 | EXPECT_EQ(6U, trace.size); |
| 115 | EXPECT_EQ(start_pc, trace.trace[0]); |
| 116 | for (uptr i = 1; i <= 5; i++) { |
| 117 | EXPECT_EQ(PC(idx: i*2 - 1), trace.trace[i]); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | // From: https://github.com/google/sanitizers/issues/162 |
| 122 | TEST_F(FastUnwindTest, SKIP_ON_SPARC(FramePointerLoop)) { |
| 123 | // Make one fp point to itself. |
| 124 | fake_stack[4] = (uhwptr)&fake_stack[4]; |
| 125 | UnwindFast(); |
| 126 | // Should get all on-stack retaddrs up to the 4th slot and start_pc. |
| 127 | EXPECT_EQ(4U, trace.size); |
| 128 | EXPECT_EQ(start_pc, trace.trace[0]); |
| 129 | for (uptr i = 1; i <= 3; i++) { |
| 130 | EXPECT_EQ(PC(idx: i*2 - 1), trace.trace[i]); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | TEST_F(FastUnwindTest, SKIP_ON_SPARC(MisalignedFramePointer)) { |
| 135 | // Make one fp misaligned. |
| 136 | fake_stack[4] += 3; |
| 137 | UnwindFast(); |
| 138 | // Should get all on-stack retaddrs up to the 4th slot and start_pc. |
| 139 | EXPECT_EQ(4U, trace.size); |
| 140 | EXPECT_EQ(start_pc, trace.trace[0]); |
| 141 | for (uptr i = 1; i < 4U; i++) { |
| 142 | EXPECT_EQ(PC(idx: i*2 - 1), trace.trace[i]); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | TEST_F(FastUnwindTest, OneFrameStackTrace) { |
| 147 | trace.Unwind(start_pc, fake_bp, nullptr, true, 1); |
| 148 | EXPECT_EQ(1U, trace.size); |
| 149 | EXPECT_EQ(start_pc, trace.trace[0]); |
| 150 | EXPECT_EQ((uhwptr)&fake_stack[kBpOffset], trace.top_frame_bp); |
| 151 | } |
| 152 | |
| 153 | TEST_F(FastUnwindTest, ZeroFramesStackTrace) { |
| 154 | trace.Unwind(start_pc, fake_bp, nullptr, true, 0); |
| 155 | EXPECT_EQ(0U, trace.size); |
| 156 | EXPECT_EQ(0U, trace.top_frame_bp); |
| 157 | } |
| 158 | |
| 159 | TEST_F(FastUnwindTest, SKIP_ON_SPARC(FPBelowPrevFP)) { |
| 160 | // The next FP points to unreadable memory inside the stack limits, but below |
| 161 | // current FP. |
| 162 | fake_stack[0] = (uhwptr)&fake_stack[-50]; |
| 163 | fake_stack[1] = PC(1); |
| 164 | UnwindFast(); |
| 165 | EXPECT_EQ(2U, trace.size); |
| 166 | EXPECT_EQ(PC(idx: 0), trace.trace[0]); |
| 167 | EXPECT_EQ(PC(idx: 1), trace.trace[1]); |
| 168 | } |
| 169 | |
| 170 | TEST_F(FastUnwindTest, SKIP_ON_SPARC(CloseToZeroFrame)) { |
| 171 | // Make one pc a NULL pointer. |
| 172 | fake_stack[5] = 0x0; |
| 173 | UnwindFast(); |
| 174 | // The stack should be truncated at the NULL pointer (and not include it). |
| 175 | EXPECT_EQ(3U, trace.size); |
| 176 | EXPECT_EQ(start_pc, trace.trace[0]); |
| 177 | for (uptr i = 1; i < 3U; i++) { |
| 178 | EXPECT_EQ(PC(i*2 - 1), trace.trace[i]); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | using StackPrintTest = FastUnwindTest; |
| 183 | |
| 184 | TEST_F(StackPrintTest, SKIP_ON_SPARC(ContainsFullTrace)) { |
| 185 | // Override stack trace format to make testing code independent of default |
| 186 | // flag values. |
| 187 | CommonFlags flags; |
| 188 | flags.CopyFrom(other: *common_flags()); |
| 189 | flags.stack_trace_format = "#%n %p" ; |
| 190 | OverrideCommonFlags(cf: flags); |
| 191 | |
| 192 | UnwindFast(); |
| 193 | |
| 194 | char buf[3000]; |
| 195 | trace.PrintTo(buf, sizeof(buf)); |
| 196 | EXPECT_THAT(std::string(buf), |
| 197 | MatchesRegex("(#[0-9]+ 0x[0-9a-f]+\n){" + |
| 198 | std::to_string(trace.size) + "}\n" )); |
| 199 | } |
| 200 | |
| 201 | TEST_F(StackPrintTest, SKIP_ON_SPARC(TruncatesContents)) { |
| 202 | UnwindFast(); |
| 203 | |
| 204 | char buf[3000]; |
| 205 | uptr actual_len = trace.PrintTo(buf, sizeof(buf)); |
| 206 | ASSERT_LT(actual_len, sizeof(buf)); |
| 207 | |
| 208 | char tinybuf[10]; |
| 209 | trace.PrintTo(tinybuf, sizeof(tinybuf)); |
| 210 | |
| 211 | // This the truncation case. |
| 212 | ASSERT_GT(actual_len, sizeof(tinybuf)); |
| 213 | |
| 214 | // The truncated contents should be a prefix of the full contents. |
| 215 | size_t lastpos = sizeof(tinybuf) - 1; |
| 216 | EXPECT_EQ(strncmp(s1: buf, s2: tinybuf, n: lastpos), 0); |
| 217 | EXPECT_EQ(tinybuf[lastpos], '\0'); |
| 218 | |
| 219 | // Full bufffer has more contents... |
| 220 | EXPECT_NE(buf[lastpos], '\0'); |
| 221 | } |
| 222 | |
| 223 | TEST_F(StackPrintTest, SKIP_ON_SPARC(WorksWithEmptyStack)) { |
| 224 | char buf[3000]; |
| 225 | trace.PrintTo(buf, sizeof(buf)); |
| 226 | EXPECT_NE(strstr(haystack: buf, needle: "<empty stack>" ), nullptr); |
| 227 | } |
| 228 | |
| 229 | TEST_F(StackPrintTest, SKIP_ON_SPARC(ReturnsCorrectLength)) { |
| 230 | UnwindFast(); |
| 231 | |
| 232 | char buf[3000]; |
| 233 | uptr len = trace.PrintTo(buf, sizeof(buf)); |
| 234 | size_t actual_len = strlen(s: buf); |
| 235 | ASSERT_LT(len, sizeof(buf)); |
| 236 | EXPECT_EQ(len, actual_len); |
| 237 | |
| 238 | char tinybuf[5]; |
| 239 | len = trace.PrintTo(tinybuf, sizeof(tinybuf)); |
| 240 | size_t truncated_len = strlen(s: tinybuf); |
| 241 | ASSERT_GE(len, sizeof(tinybuf)); |
| 242 | EXPECT_EQ(len, actual_len); |
| 243 | EXPECT_EQ(truncated_len, sizeof(tinybuf) - 1); |
| 244 | } |
| 245 | |
| 246 | TEST_F(StackPrintTest, SKIP_ON_SPARC(AcceptsZeroSize)) { |
| 247 | UnwindFast(); |
| 248 | char buf[1]; |
| 249 | EXPECT_GT(trace.PrintTo(buf, 0), 0u); |
| 250 | } |
| 251 | |
| 252 | using StackPrintDeathTest = StackPrintTest; |
| 253 | |
| 254 | TEST_F(StackPrintDeathTest, SKIP_ON_SPARC(RequiresNonNullBuffer)) { |
| 255 | UnwindFast(); |
| 256 | EXPECT_DEATH(trace.PrintTo(NULL, 100), "" ); |
| 257 | } |
| 258 | |
| 259 | #endif // SANITIZER_CAN_FAST_UNWIND |
| 260 | |
| 261 | TEST(SlowUnwindTest, ShortStackTrace) { |
| 262 | UNINITIALIZED BufferedStackTrace stack; |
| 263 | uptr pc = StackTrace::GetCurrentPc(); |
| 264 | uptr bp = GET_CURRENT_FRAME(); |
| 265 | stack.Unwind(pc, bp, context: nullptr, request_fast: false, /*max_depth=*/0); |
| 266 | EXPECT_EQ(0U, stack.size); |
| 267 | EXPECT_EQ(0U, stack.top_frame_bp); |
| 268 | stack.Unwind(pc, bp, context: nullptr, request_fast: false, /*max_depth=*/1); |
| 269 | EXPECT_EQ(1U, stack.size); |
| 270 | EXPECT_EQ(pc, stack.trace[0]); |
| 271 | EXPECT_EQ(bp, stack.top_frame_bp); |
| 272 | } |
| 273 | |
| 274 | TEST(GetCurrentPc, Basic) { |
| 275 | // Test that PCs obtained via GET_CURRENT_PC() |
| 276 | // and StackTrace::GetCurrentPc() are all different |
| 277 | // and are close to the function start. |
| 278 | struct Local { |
| 279 | static NOINLINE void Test() { |
| 280 | const uptr pcs[] = { |
| 281 | (uptr)&Local::Test, |
| 282 | GET_CURRENT_PC(), |
| 283 | StackTrace::GetCurrentPc(), |
| 284 | StackTrace::GetCurrentPc(), |
| 285 | }; |
| 286 | for (uptr i = 0; i < ARRAY_SIZE(pcs); i++) |
| 287 | Printf(format: "pc%zu: %p\n" , i, (void *)(pcs[i])); |
| 288 | for (uptr i = 1; i < ARRAY_SIZE(pcs); i++) { |
| 289 | EXPECT_GT(pcs[i], pcs[0]); |
| 290 | EXPECT_LT(pcs[i], pcs[0] + 1000); |
| 291 | for (uptr j = 0; j < i; j++) EXPECT_NE(pcs[i], pcs[j]); |
| 292 | } |
| 293 | } |
| 294 | }; |
| 295 | Local::Test(); |
| 296 | } |
| 297 | |
| 298 | // Dummy implementation. This should never be called, but is required to link |
| 299 | // non-optimized builds of this test. |
| 300 | void BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context, |
| 301 | bool request_fast, u32 max_depth) { |
| 302 | UNIMPLEMENTED(); |
| 303 | } |
| 304 | |
| 305 | } // namespace __sanitizer |
| 306 | |