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 <vector>
13
14 #include "base/bits.h"
15 #include "base/compiler_specific.h"
16 #include "base/profiler/stack_buffer.h"
17
18 #if PA_BUILDFLAG(USE_PARTITION_ALLOC)
19 #include "partition_alloc/tagging.h" // nogncheck
20 #endif
21
22 namespace base {
23
24 StackCopier::~StackCopier() = default;
25
CloneStack(const StackBuffer & stack_buffer,uintptr_t * stack_top,RegisterContext * thread_context)26 std::unique_ptr<StackBuffer> StackCopier::CloneStack(
27 const StackBuffer& stack_buffer,
28 uintptr_t* stack_top,
29 RegisterContext* thread_context) {
30 const uintptr_t original_top = *stack_top;
31 const uintptr_t original_bottom =
32 reinterpret_cast<uintptr_t>(stack_buffer.buffer());
33 size_t stack_size = original_top - original_bottom;
34 auto cloned_stack_buffer = std::make_unique<StackBuffer>(stack_size);
35 const uint8_t* stack_copy_bottom = CopyStackContentsAndRewritePointers(
36 reinterpret_cast<const uint8_t*>(stack_buffer.buffer()),
37 reinterpret_cast<const uintptr_t*>(original_top),
38 StackBuffer::kPlatformStackAlignment, cloned_stack_buffer->buffer());
39
40 // `stack_buffer` is double pointer aligned by default so we should always
41 // get the same result.
42 CHECK(stack_copy_bottom ==
43 reinterpret_cast<uint8_t*>(cloned_stack_buffer->buffer()));
44 *stack_top =
45 reinterpret_cast<const uintptr_t>(stack_copy_bottom) + stack_size;
46
47 for (uintptr_t* reg : GetRegistersToRewrite(thread_context)) {
48 *reg = RewritePointerIfInOriginalStack(
49 reinterpret_cast<const uint8_t*>(original_bottom),
50 reinterpret_cast<const uintptr_t*>(original_top), stack_copy_bottom,
51 *reg);
52 }
53 return cloned_stack_buffer;
54 }
55
56 // static
RewritePointerIfInOriginalStack(const uint8_t * original_stack_bottom,const uintptr_t * original_stack_top,const uint8_t * stack_copy_bottom,uintptr_t pointer)57 uintptr_t StackCopier::RewritePointerIfInOriginalStack(
58 const uint8_t* original_stack_bottom,
59 const uintptr_t* original_stack_top,
60 const uint8_t* stack_copy_bottom,
61 uintptr_t pointer) {
62 auto original_stack_bottom_uint =
63 reinterpret_cast<uintptr_t>(original_stack_bottom);
64 auto original_stack_top_uint =
65 reinterpret_cast<uintptr_t>(original_stack_top);
66 auto stack_copy_bottom_uint = reinterpret_cast<uintptr_t>(stack_copy_bottom);
67
68 if (pointer < original_stack_bottom_uint ||
69 pointer >= original_stack_top_uint)
70 return pointer;
71
72 return stack_copy_bottom_uint + (pointer - original_stack_bottom_uint);
73 }
74
75 // static
76 NO_SANITIZE("address")
CopyStackContentsAndRewritePointers(const uint8_t * original_stack_bottom,const uintptr_t * original_stack_top,size_t platform_stack_alignment,uintptr_t * stack_buffer_bottom)77 const uint8_t* StackCopier::CopyStackContentsAndRewritePointers(
78 const uint8_t* original_stack_bottom,
79 const uintptr_t* original_stack_top,
80 size_t platform_stack_alignment,
81 uintptr_t* stack_buffer_bottom) {
82 #if PA_BUILDFLAG(USE_PARTITION_ALLOC)
83 // Disable MTE during this function because this function indiscriminately
84 // reads stack frames, some of which belong to system libraries, not Chrome
85 // itself. With stack tagging, some bytes on the stack have MTE tags different
86 // from the stack pointer tag.
87 partition_alloc::SuspendTagCheckingScope suspend_tag_checking_scope;
88 #endif
89
90 const uint8_t* byte_src = original_stack_bottom;
91 // The first address in the stack with pointer alignment. Pointer-aligned
92 // values from this point to the end of the stack are possibly rewritten using
93 // RewritePointerIfInOriginalStack(). Bytes before this cannot be a pointer
94 // because they occupy less space than a pointer would.
95 const uint8_t* first_aligned_address =
96 bits::AlignUp(byte_src, sizeof(uintptr_t));
97
98 // The stack copy bottom, which is offset from |stack_buffer_bottom| by the
99 // same alignment as in the original stack. This guarantees identical
100 // alignment between values in the original stack and the copy. This uses the
101 // platform stack alignment rather than pointer alignment so that the stack
102 // copy is aligned to platform expectations.
103 uint8_t* stack_copy_bottom =
104 reinterpret_cast<uint8_t*>(stack_buffer_bottom) +
105 (byte_src - bits::AlignDown(byte_src, platform_stack_alignment));
106 uint8_t* byte_dst = stack_copy_bottom;
107
108 // Copy bytes verbatim up to the first aligned address.
109 for (; byte_src < first_aligned_address; ++byte_src, ++byte_dst)
110 *byte_dst = *byte_src;
111
112 // Copy the remaining stack by pointer-sized values, rewriting anything that
113 // looks like a pointer into the stack.
114 const uintptr_t* src = reinterpret_cast<const uintptr_t*>(byte_src);
115 uintptr_t* dst = reinterpret_cast<uintptr_t*>(byte_dst);
116 for (; src < original_stack_top; ++src, ++dst) {
117 *dst = RewritePointerIfInOriginalStack(
118 original_stack_bottom, original_stack_top, stack_copy_bottom, *src);
119 }
120
121 return stack_copy_bottom;
122 }
123
124 } // namespace base
125