1 // Copyright 2023 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 #ifndef BASE_DEBUG_ALLOCATION_TRACE_H_
6 #define BASE_DEBUG_ALLOCATION_TRACE_H_
7
8 #include <algorithm>
9 #include <array>
10 #include <atomic>
11 #include <cstdint>
12
13 #include "base/allocator/dispatcher/subsystem.h"
14 #include "base/base_export.h"
15 #include "base/bits.h"
16 #include "base/compiler_specific.h"
17 #include "base/debug/debugging_buildflags.h"
18 #include "base/debug/stack_trace.h"
19 #include "base/memory/raw_ptr_exclusion.h"
20 #include "build/build_config.h"
21
22 namespace base::debug::tracer {
23
24 // Number of traces that can be stored. This number must be a power of two to
25 // allow for fast computation of modulo.
26 constexpr size_t kMaximumNumberOfMemoryOperationTraces = (1 << 15);
27 // Number of frames stored for each operation. Probably the lower frames
28 // represent the memory allocation system. Hence, we store more frames to
29 // increase chances of having a meaningful trace of the path that caused the
30 // allocation or free.
31 constexpr size_t kStackTraceSize = 16;
32
33 // The type of an operation stored in the recorder.
34 enum class OperationType {
35 // The state of an operation record before calling any of the initialization
36 // functions.
37 kNone = 0,
38 // The record represents an allocation operation.
39 kAllocation,
40 // The record represents a free operation.
41 kFree,
42 };
43
44 using StackTraceContainer = std::array<const void*, kStackTraceSize>;
45
46 // The record for a single operation. A record can represent any type of
47 // operation, allocation or free, but not at the same time.
48 //
49 // A record protects itself from concurrent initializations. If a thread B calls
50 // any of the Initialize*-functions while another thread A is currently
51 // initializing, B's invocations shall immediately return |false| without
52 // interfering with thread A.
53 class BASE_EXPORT OperationRecord {
54 public:
55 constexpr OperationRecord() = default;
56
57 OperationRecord(const OperationRecord&) = delete;
58 OperationRecord& operator=(const OperationRecord&) = delete;
59
60 // Is the record currently being taken?
61 bool IsRecording() const;
62
63 OperationType GetOperationType() const;
64 // The address allocated or freed.
65 const void* GetAddress() const;
66 // Number of allocated bytes. Returns 0 for free operations.
67 size_t GetSize() const;
68 // The stacktrace as taken by the Initialize*-functions.
69 const StackTraceContainer& GetStackTrace() const;
70
71 // Initialize the record with data for another operation. Data from any
72 // previous operation will be silently overwritten. These functions are
73 // declared ALWAYS_INLINE to minimize pollution of the recorded stack trace.
74 //
75 // Both functions return false in case no record was taken, i.e. if another
76 // thread is capturing.
InitializeFree(const void * freed_address)77 ALWAYS_INLINE bool InitializeFree(const void* freed_address) {
78 return InitializeOperationRecord(freed_address, 0, OperationType::kFree);
79 }
80
InitializeAllocation(const void * allocated_address,size_t allocated_size)81 ALWAYS_INLINE bool InitializeAllocation(const void* allocated_address,
82 size_t allocated_size) {
83 return InitializeOperationRecord(allocated_address, allocated_size,
84 OperationType::kAllocation);
85 }
86
87 private:
88 // Initialize a record with the given data. Return true if the record was
89 // initialized successfully, false if no record was taken, i.e. if another
90 // thread is capturing.
91 ALWAYS_INLINE bool InitializeOperationRecord(const void* address,
92 size_t size,
93 OperationType operation_type);
94 ALWAYS_INLINE void StoreStackTrace();
95
96 // The stack trace taken in one of the Initialize* functions.
97 StackTraceContainer stack_trace_ = {};
98 // The number of allocated bytes.
99 size_t size_ = 0;
100 // The address that was allocated or freed.
101 // We use a raw C++ pointer instead of base::raw_ptr for performance
102 // reasons.
103 // - In the recorder we only store pointers, we never allocate or free on
104 // our own.
105 // - Storing is the hot path. base::raw_ptr::operator== may perform sanity
106 // checks which do not make sense in our case (otherwise the allocated
107 // address would have been quirky)
108 RAW_PTR_EXCLUSION const void* address_ = nullptr;
109 // The type of the operation that was performed. In the course of making a
110 // record, this value is reset to |OperationType::kNone| and later set to
111 // the operation type specific value, so if the process crashes whilst writing
112 // the record, it's marked as empty. To prevent the compiler from optimizing
113 // away the initial reset, this value is marked as volatile.
114 volatile OperationType operation_type_ = OperationType::kNone;
115 // Is the record currently being taken from another thread? Used to prevent
116 // concurrent writes to the same record.
117 //
118 // The value is mutable since pre C++20 there is no const getter in
119 // atomic_flag. All ways to get the value involve setting it.
120 // TODO(https://crbug.com/1284275): Remove mutable and make IsRecording() use
121 // atomic_flag::test();
122 mutable std::atomic_flag is_recording_ = ATOMIC_FLAG_INIT;
123 };
124
InitializeOperationRecord(const void * address,size_t size,OperationType operation_type)125 ALWAYS_INLINE bool OperationRecord::InitializeOperationRecord(
126 const void* address,
127 size_t size,
128 OperationType operation_type) {
129 if (is_recording_.test_and_set(std::memory_order_acquire)) {
130 return false;
131 }
132
133 operation_type_ = operation_type;
134 StoreStackTrace();
135 address_ = address;
136 size_ = size;
137
138 is_recording_.clear(std::memory_order_release);
139
140 return true;
141 }
142
StoreStackTrace()143 ALWAYS_INLINE void OperationRecord::StoreStackTrace() {
144 stack_trace_.fill(nullptr);
145
146 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
147 // Currently we limit ourselves to use TraceStackFramePointers. We know that
148 // TraceStackFramePointers has an acceptable performance impact on Android.
149 base::debug::TraceStackFramePointers(&stack_trace_[0], stack_trace_.size(),
150 0);
151 #elif BUILDFLAG(IS_LINUX)
152 // Use base::debug::CollectStackTrace as an alternative for tests on Linux. We
153 // still have a check in /base/debug/debug.gni to prevent that
154 // AllocationStackTraceRecorder is enabled accidentally on Linux.
155 base::debug::CollectStackTrace(&stack_trace_[0], stack_trace_.size());
156 #else
157 #error "No supported stack tracer found."
158 #endif
159 }
160
161 struct BASE_EXPORT AllocationTraceRecorderStatistics {
162 #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING)
163 AllocationTraceRecorderStatistics(size_t total_number_of_allocations,
164 size_t total_number_of_collisions);
165 #else
166 AllocationTraceRecorderStatistics(size_t total_number_of_allocations);
167 #endif
168
169 // The total number of allocations that have been recorded.
170 size_t total_number_of_allocations;
171 #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING)
172 // The total number of collisions that have been encountered. A collision
173 // happens when two threads concurrently try to record using the same slot.
174 size_t total_number_of_collisions;
175 #endif
176 };
177
178 // The recorder which holds entries for past memory operations.
179 //
180 // The memory image of the recorder will be copied into the crash-handler.
181 // Therefore, it must not hold any references to external data which are vital
182 // for proper functioning.
183 //
184 // It is important that the recorder itself does not allocate to prevent
185 // recursive calls and save as much runtime overhead as possible.
186 //
187 // Therefore, records are stored in a preallocated buffer with a compile time
188 // constant maximum size, see |kMaximumNumberOfMemoryOperationTraces|. Once all
189 // records have been used, old records will be overwritten (fifo-style).
190 //
191 // The recorder works in an multithreaded environment without external locking.
192 // Concurrent writes are prevented by two means:
193 // 1 - We atomically increment and calculate the effective index of the record
194 // to be written.
195 // 2 - If this entry is still being used (the recording thread didn't finish
196 // yet), we go back to step 1
197 // Currently we do not enforce separate cache lines for each entry, which means
198 // false sharing can occur. On the other hand, with 64 byte cachelines a clean
199 // separation would introduce some 3*64 - sizeof(OperationRecord) = 40 bytes of
200 // padding per entry.
201 //
202 // Note: As a process might be terminated for whatever reason while stack
203 // traces are being written, the recorded data may contain some garbage.
204 //
205 // TODO(https://crbug.com/1419908): Evaluate the impact of the shared cache
206 // lines between entries.
207 class BASE_EXPORT AllocationTraceRecorder {
208 public:
209 constexpr AllocationTraceRecorder() = default;
210
211 AllocationTraceRecorder(const AllocationTraceRecorder&) = delete;
212 AllocationTraceRecorder& operator=(const AllocationTraceRecorder&) = delete;
213
214 // The allocation event observer interface. See the dispatcher for further
215 // details. The functions are marked NO_INLINE. All other functions called but
216 // the one taking the call stack are marked ALWAYS_INLINE. This way we ensure
217 // the number of frames recorded from these functions is fixed.
218
219 // Handle all allocation events.
220 NOINLINE void OnAllocation(
221 const void* allocated_address,
222 size_t allocated_size,
223 base::allocator::dispatcher::AllocationSubsystem subsystem,
224 const char* type);
225
226 // Handle all free events.
227 NOINLINE void OnFree(const void* freed_address);
228
229 // Access functions to retrieve the current content of the recorder.
230 // Note: Since the recorder is usually updated upon each allocation or free,
231 // it is important to prevent updates if you want to read the entries at any
232 // point.
233
234 // Get the current number of entries stored in the recorder. When the
235 // recorder has reached its maximum capacity, it always returns
236 // |GetMaximumNumberOfTraces()|.
237 size_t size() const;
238
239 // Access the record of an operation by index. Oldest operation is always
240 // accessible at index 0, latest operation at |size()-1|.
241 // Note: Since a process might have crashed while a trace is being written,
242 // especially the last records might be corrupted.
243 const OperationRecord& operator[](size_t idx) const;
244
GetMaximumNumberOfTraces()245 constexpr size_t GetMaximumNumberOfTraces() const {
246 return kMaximumNumberOfMemoryOperationTraces;
247 }
248
249 AllocationTraceRecorderStatistics GetRecorderStatistics() const;
250
251 private:
252 ALWAYS_INLINE size_t GetNextIndex();
253
254 ALWAYS_INLINE static constexpr size_t WrapIdxIfNeeded(size_t idx);
255
256 // The actual container.
257 std::array<OperationRecord, kMaximumNumberOfMemoryOperationTraces>
258 alloc_trace_buffer_ = {};
259 // The total number of records that have been taken so far. Note that this
260 // might be greater than |kMaximumNumberOfMemoryOperationTraces| since we
261 // overwrite oldest items.
262 std::atomic<size_t> total_number_of_records_ = 0;
263 #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING)
264 std::atomic<size_t> total_number_of_collisions_ = 0;
265 #endif
266 };
267
WrapIdxIfNeeded(size_t idx)268 ALWAYS_INLINE constexpr size_t AllocationTraceRecorder::WrapIdxIfNeeded(
269 size_t idx) {
270 // Wrapping around counter, e.g. for BUFFER_SIZE = 256, the counter will
271 // wrap around when reaching 256. To enable the compiler to emit more
272 // optimized code we assert |kMaximumNumberOfMemoryOperationTraces| is a power
273 // of two .
274 static_assert(
275 base::bits::IsPowerOfTwo(kMaximumNumberOfMemoryOperationTraces),
276 "kMaximumNumberOfMemoryOperationTraces should be a power of 2 to "
277 "allow for fast modulo operation.");
278
279 return idx % kMaximumNumberOfMemoryOperationTraces;
280 }
281
GetNextIndex()282 ALWAYS_INLINE size_t AllocationTraceRecorder::GetNextIndex() {
283 const auto raw_idx =
284 total_number_of_records_.fetch_add(1, std::memory_order_relaxed);
285 return WrapIdxIfNeeded(raw_idx);
286 }
287
288 } // namespace base::debug::tracer
289
290 #endif // BASE_DEBUG_ALLOCATION_TRACE_H_
291