1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "base/profiler/stack_copier.h"
11
12 #include <cstring>
13 #include <iterator>
14 #include <memory>
15 #include <numeric>
16
17 #include "base/profiler/register_context.h"
18 #include "base/profiler/stack_buffer.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 namespace base {
23
24 namespace {
25
26 class CopyFunctions : public StackCopier {
27 public:
28 using StackCopier::CopyStackContentsAndRewritePointers;
29 using StackCopier::RewritePointerIfInOriginalStack;
30
GetRegistersToRewrite(RegisterContext * thread_context)31 std::vector<uintptr_t*> GetRegistersToRewrite(
32 RegisterContext* thread_context) override {
33 return {&RegisterContextStackPointer(thread_context)};
34 }
35
CopyStack(StackBuffer * stack_buffer,uintptr_t * stack_top,TimeTicks * timestamp,RegisterContext * thread_context,Delegate * delegate)36 bool CopyStack(StackBuffer* stack_buffer,
37 uintptr_t* stack_top,
38 TimeTicks* timestamp,
39 RegisterContext* thread_context,
40 Delegate* delegate) override {
41 return false;
42 }
43 };
44
45 static constexpr size_t kTestStackBufferSize = sizeof(uintptr_t) * 4;
46
47 union alignas(StackBuffer::kPlatformStackAlignment) TestStackBuffer {
48 uintptr_t as_uintptr[kTestStackBufferSize / sizeof(uintptr_t)];
49 uint16_t as_uint16[kTestStackBufferSize / sizeof(uint16_t)];
50 uint8_t as_uint8[kTestStackBufferSize / sizeof(uint8_t)];
51 };
52
53 } // namespace
54
TEST(StackCopierTest,RewritePointerIfInOriginalStack_InStack)55 TEST(StackCopierTest, RewritePointerIfInOriginalStack_InStack) {
56 uintptr_t original_stack[4];
57 uintptr_t stack_copy[4];
58 EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy[2]),
59 CopyFunctions::RewritePointerIfInOriginalStack(
60 reinterpret_cast<uint8_t*>(&original_stack[0]),
61 &original_stack[0] + std::size(original_stack),
62 reinterpret_cast<uint8_t*>(&stack_copy[0]),
63 reinterpret_cast<uintptr_t>(&original_stack[2])));
64 }
65
TEST(StackCopierTest,RewritePointerIfInOriginalStack_NotInStack)66 TEST(StackCopierTest, RewritePointerIfInOriginalStack_NotInStack) {
67 // We use this variable only for its address, which is outside of
68 // original_stack.
69 uintptr_t non_stack_location;
70 uintptr_t original_stack[4];
71 uintptr_t stack_copy[4];
72
73 EXPECT_EQ(reinterpret_cast<uintptr_t>(&non_stack_location),
74 CopyFunctions::RewritePointerIfInOriginalStack(
75 reinterpret_cast<uint8_t*>(&original_stack[0]),
76 &original_stack[0] + std::size(original_stack),
77 reinterpret_cast<uint8_t*>(&stack_copy[0]),
78 reinterpret_cast<uintptr_t>(&non_stack_location)));
79 }
80
TEST(StackCopierTest,StackCopy)81 TEST(StackCopierTest, StackCopy) {
82 TestStackBuffer original_stack;
83 // Fill the stack buffer with increasing uintptr_t values.
84 std::iota(
85 &original_stack.as_uintptr[0],
86 &original_stack.as_uintptr[0] + std::size(original_stack.as_uintptr),
87 100);
88 // Replace the third value with an address within the buffer.
89 original_stack.as_uintptr[2] =
90 reinterpret_cast<uintptr_t>(&original_stack.as_uintptr[1]);
91 TestStackBuffer stack_copy;
92
93 CopyFunctions::CopyStackContentsAndRewritePointers(
94 &original_stack.as_uint8[0],
95 &original_stack.as_uintptr[0] + std::size(original_stack.as_uintptr),
96 StackBuffer::kPlatformStackAlignment, &stack_copy.as_uintptr[0]);
97
98 EXPECT_EQ(original_stack.as_uintptr[0], stack_copy.as_uintptr[0]);
99 EXPECT_EQ(original_stack.as_uintptr[1], stack_copy.as_uintptr[1]);
100 EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy.as_uintptr[1]),
101 stack_copy.as_uintptr[2]);
102 EXPECT_EQ(original_stack.as_uintptr[3], stack_copy.as_uintptr[3]);
103 }
104
TEST(StackCopierTest,StackCopy_NonAlignedStackPointerCopy)105 TEST(StackCopierTest, StackCopy_NonAlignedStackPointerCopy) {
106 TestStackBuffer stack_buffer;
107
108 // Fill the stack buffer with increasing uint16_t values.
109 std::iota(&stack_buffer.as_uint16[0],
110 &stack_buffer.as_uint16[0] + std::size(stack_buffer.as_uint16),
111 100);
112
113 // Set the stack bottom to the unaligned location one uint16_t into the
114 // buffer.
115 uint8_t* unaligned_stack_bottom =
116 reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
117
118 // Leave extra space within the stack buffer beyond the end of the stack, but
119 // preserve the platform alignment.
120 const size_t extra_space = StackBuffer::kPlatformStackAlignment;
121 uintptr_t* stack_top =
122 &stack_buffer.as_uintptr[std::size(stack_buffer.as_uintptr) -
123 extra_space / sizeof(uintptr_t)];
124
125 // Initialize the copy to all zeros.
126 TestStackBuffer stack_copy_buffer = {{0}};
127
128 const uint8_t* stack_copy_bottom =
129 CopyFunctions::CopyStackContentsAndRewritePointers(
130 unaligned_stack_bottom, stack_top,
131 StackBuffer::kPlatformStackAlignment,
132 &stack_copy_buffer.as_uintptr[0]);
133
134 // The stack copy bottom address is expected to be at the same offset into the
135 // stack copy buffer as the unaligned stack bottom is from the stack buffer.
136 // Since the buffers have the same platform stack alignment this also ensures
137 // the alignment of the bottom addresses is the same.
138 EXPECT_EQ(unaligned_stack_bottom - &stack_buffer.as_uint8[0],
139 stack_copy_bottom - &stack_copy_buffer.as_uint8[0]);
140
141 // The first value in the copy should not be overwritten since the stack
142 // starts at the second uint16_t.
143 EXPECT_EQ(0u, stack_copy_buffer.as_uint16[0]);
144
145 // The next values up to the extra space should have been copied.
146 const size_t max_index =
147 std::size(stack_copy_buffer.as_uint16) - extra_space / sizeof(uint16_t);
148 for (size_t i = 1; i < max_index; ++i)
149 EXPECT_EQ(i + 100, stack_copy_buffer.as_uint16[i]);
150
151 // None of the values in the empty space should have been copied.
152 for (size_t i = max_index; i < std::size(stack_copy_buffer.as_uint16); ++i)
153 EXPECT_EQ(0u, stack_copy_buffer.as_uint16[i]);
154 }
155
156 // Checks that an unaligned within-stack pointer value at the start of the stack
157 // is not rewritten.
TEST(StackCopierTest,StackCopy_NonAlignedStackPointerUnalignedRewriteAtStart)158 TEST(StackCopierTest, StackCopy_NonAlignedStackPointerUnalignedRewriteAtStart) {
159 // Initially fill the buffer with 0s.
160 TestStackBuffer stack_buffer = {{0}};
161
162 // Set the stack bottom to the unaligned location one uint16_t into the
163 // buffer.
164 uint8_t* unaligned_stack_bottom =
165 reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
166
167 // Set the first unaligned pointer-sized value to an address within the stack.
168 uintptr_t within_stack_pointer =
169 reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
170 std::memcpy(unaligned_stack_bottom, &within_stack_pointer,
171 sizeof(within_stack_pointer));
172
173 TestStackBuffer stack_copy_buffer = {{0}};
174
175 const uint8_t* stack_copy_bottom =
176 CopyFunctions::CopyStackContentsAndRewritePointers(
177 unaligned_stack_bottom,
178 &stack_buffer.as_uintptr[0] + std::size(stack_buffer.as_uintptr),
179 StackBuffer::kPlatformStackAlignment,
180 &stack_copy_buffer.as_uintptr[0]);
181
182 uintptr_t copied_within_stack_pointer;
183 std::memcpy(&copied_within_stack_pointer, stack_copy_bottom,
184 sizeof(copied_within_stack_pointer));
185
186 // The rewriting should only operate on pointer-aligned values so the
187 // unaligned value should be copied verbatim.
188 EXPECT_EQ(within_stack_pointer, copied_within_stack_pointer);
189 }
190
191 // Checks that an unaligned within-stack pointer after the start of the stack is
192 // not rewritten.
TEST(StackCopierTest,StackCopy_NonAlignedStackPointerUnalignedRewriteAfterStart)193 TEST(StackCopierTest,
194 StackCopy_NonAlignedStackPointerUnalignedRewriteAfterStart) {
195 // Initially fill the buffer with 0s.
196 TestStackBuffer stack_buffer = {{0}};
197
198 // Set the stack bottom to the unaligned location one uint16_t into the
199 // buffer.
200 uint8_t* unaligned_stack_bottom =
201 reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
202
203 // Set the second unaligned pointer-sized value to an address within the
204 // stack.
205 uintptr_t within_stack_pointer =
206 reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
207 std::memcpy(unaligned_stack_bottom + sizeof(uintptr_t), &within_stack_pointer,
208 sizeof(within_stack_pointer));
209
210 TestStackBuffer stack_copy_buffer = {{0}};
211
212 const uint8_t* stack_copy_bottom =
213 CopyFunctions::CopyStackContentsAndRewritePointers(
214 unaligned_stack_bottom,
215 &stack_buffer.as_uintptr[0] + std::size(stack_buffer.as_uintptr),
216 StackBuffer::kPlatformStackAlignment,
217 &stack_copy_buffer.as_uintptr[0]);
218
219 uintptr_t copied_within_stack_pointer;
220 std::memcpy(&copied_within_stack_pointer,
221 stack_copy_bottom + sizeof(uintptr_t),
222 sizeof(copied_within_stack_pointer));
223
224 // The rewriting should only operate on pointer-aligned values so the
225 // unaligned value should be copied verbatim.
226 EXPECT_EQ(within_stack_pointer, copied_within_stack_pointer);
227 }
228
TEST(StackCopierTest,StackCopy_NonAlignedStackPointerAlignedRewrite)229 TEST(StackCopierTest, StackCopy_NonAlignedStackPointerAlignedRewrite) {
230 // Initially fill the buffer with 0s.
231 TestStackBuffer stack_buffer = {{0}};
232
233 // Set the stack bottom to the unaligned location one uint16_t into the
234 // buffer.
235 uint8_t* unaligned_stack_bottom =
236 reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
237
238 // Set the second aligned pointer-sized value to an address within the stack.
239 stack_buffer.as_uintptr[1] =
240 reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
241
242 TestStackBuffer stack_copy_buffer = {{0}};
243
244 CopyFunctions::CopyStackContentsAndRewritePointers(
245 unaligned_stack_bottom,
246 &stack_buffer.as_uintptr[0] + std::size(stack_buffer.as_uintptr),
247 StackBuffer::kPlatformStackAlignment, &stack_copy_buffer.as_uintptr[0]);
248
249 // The aligned pointer should have been rewritten to point within the stack
250 // copy.
251 EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy_buffer.as_uintptr[2]),
252 stack_copy_buffer.as_uintptr[1]);
253 }
254
TEST(StackCopierTest,CloneStack)255 TEST(StackCopierTest, CloneStack) {
256 StackBuffer original_stack(kTestStackBufferSize);
257 // Fill the stack buffer with increasing uintptr_t values.
258 std::iota(
259 &original_stack.buffer()[0],
260 &original_stack.buffer()[0] + (original_stack.size() / sizeof(uintptr_t)),
261 100);
262 // Replace the third value with an address within the buffer.
263 original_stack.buffer()[2] =
264 reinterpret_cast<uintptr_t>(&original_stack.buffer()[1]);
265
266 uintptr_t stack_top = reinterpret_cast<uintptr_t>(original_stack.buffer()) +
267 original_stack.size();
268 CopyFunctions copy_functions;
269 RegisterContext thread_context;
270 RegisterContextStackPointer(&thread_context) =
271 reinterpret_cast<uintptr_t>(original_stack.buffer());
272 std::unique_ptr<StackBuffer> cloned_stack =
273 copy_functions.CloneStack(original_stack, &stack_top, &thread_context);
274
275 EXPECT_EQ(original_stack.buffer()[0], cloned_stack->buffer()[0]);
276 EXPECT_EQ(original_stack.buffer()[1], cloned_stack->buffer()[1]);
277 EXPECT_EQ(reinterpret_cast<uintptr_t>(&cloned_stack->buffer()[1]),
278 cloned_stack->buffer()[2]);
279 EXPECT_EQ(original_stack.buffer()[3], cloned_stack->buffer()[3]);
280 uintptr_t expected_stack_top =
281 reinterpret_cast<uintptr_t>(cloned_stack->buffer()) +
282 original_stack.size();
283 EXPECT_EQ(RegisterContextStackPointer(&thread_context),
284 reinterpret_cast<uintptr_t>(cloned_stack->buffer()));
285 EXPECT_EQ(stack_top, expected_stack_top);
286 }
287
288 } // namespace base
289