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