xref: /aosp_15_r20/external/cronet/base/trace_event/cfi_backtrace_android.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2018 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_TRACE_EVENT_CFI_BACKTRACE_ANDROID_H_
6 #define BASE_TRACE_EVENT_CFI_BACKTRACE_ANDROID_H_
7 
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <memory>
12 
13 #include "base/base_export.h"
14 #include "base/files/memory_mapped_file.h"
15 #include "base/gtest_prod_util.h"
16 #include "base/memory/raw_ptr.h"
17 
18 namespace base {
19 namespace trace_event {
20 
21 // This class is used to unwind stack frames in the current thread. The unwind
22 // information (dwarf debug info) is stripped from the chrome binary and we do
23 // not build with exception tables (ARM EHABI) in release builds. So, we use a
24 // custom unwind table which is generated and added to specific android builds,
25 // when add_unwind_tables_in_apk build option is specified. This unwind table
26 // contains information for unwinding stack frames when the functions calls are
27 // from lib[mono]chrome.so. The file is added as an asset to the apk and the
28 // table is used to unwind stack frames for profiling. This class implements
29 // methods to read and parse the unwind table and unwind stack frames using this
30 // data.
31 class BASE_EXPORT CFIBacktraceAndroid {
32  public:
33   // The CFI information that correspond to an instruction.
34   struct CFIRow {
35     bool operator==(const CFIBacktraceAndroid::CFIRow& o) const {
36       return cfa_offset == o.cfa_offset && ra_offset == o.ra_offset;
37     }
38 
39     // The offset of the call frame address of previous function from the
40     // current stack pointer. Rule for unwinding SP: SP_prev = SP_cur +
41     // cfa_offset.
42     uint16_t cfa_offset = 0;
43     // The offset of location of return address from the previous call frame
44     // address. Rule for unwinding PC: PC_prev = * (SP_prev - ra_offset).
45     uint16_t ra_offset = 0;
46   };
47 
48   // A simple cache that stores entries in table using prime modulo hashing.
49   // This cache with 500 entries already gives us 95% hit rate, and fits in a
50   // single system page (usually 4KiB). Using a thread local cache for each
51   // thread gives us 30% improvements on performance of heap profiling.
52   class CFICache {
53    public:
54     // Add new item to the cache. It replaces an existing item with same hash.
55     // Constant time operation.
56     void Add(uintptr_t address, CFIRow cfi);
57 
58     // Finds the given address and fills |cfi| with the info for the address.
59     // returns true if found, otherwise false. Assumes |address| is never 0.
60     bool Find(uintptr_t address, CFIRow* cfi);
61 
62    private:
63     FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestCFICache);
64 
65     // Size is the highest prime which fits the cache in a single system page,
66     // usually 4KiB. A prime is chosen to make sure addresses are hashed evenly.
67     static const int kLimit = 509;
68 
69     struct AddrAndCFI {
70       uintptr_t address;
71       CFIRow cfi;
72     };
73     AddrAndCFI cache_[kLimit] = {};
74   };
75 
76   static_assert(sizeof(CFIBacktraceAndroid::CFICache) < 4096,
77                 "The cache does not fit in a single page.");
78 
79   // Creates and initializes by memory mapping the unwind tables from apk assets
80   // on first call.
81   static CFIBacktraceAndroid* GetInitializedInstance();
82 
83   // Returns true if the given program counter |pc| is mapped in chrome library.
84   static bool is_chrome_address(uintptr_t pc);
85 
86   // Returns the start and end address of the current library.
87   static uintptr_t executable_start_addr();
88   static uintptr_t executable_end_addr();
89 
90   // Returns true if stack unwinding is possible using CFI unwind tables in apk.
91   // There is no need to check this before each unwind call. Will always return
92   // the same value based on CFI tables being present in the binary.
can_unwind_stack_frames()93   bool can_unwind_stack_frames() const { return can_unwind_stack_frames_; }
94 
95   // Returns the program counters by unwinding stack in the current thread in
96   // order of latest call frame first. Unwinding works only if
97   // can_unwind_stack_frames() returns true. For each stack frame, this method
98   // searches through the unwind table mapped in memory to find the unwind
99   // information for function and walks the stack to find all the return
100   // address. This only works until the last function call from the chrome.so.
101   // We do not have unwind information to unwind beyond any frame outside of
102   // chrome.so. Calls to Unwind() are thread safe and lock free.
103   size_t Unwind(const void** out_trace, size_t max_depth);
104 
105   // Same as above function, but starts from a given program counter |pc|,
106   // stack pointer |sp| and link register |lr|. This can be from current thread
107   // or any other thread. But the caller must make sure that the thread's stack
108   // segment is not racy to read.
109   size_t Unwind(uintptr_t pc,
110                 uintptr_t sp,
111                 uintptr_t lr,
112                 const void** out_trace,
113                 size_t max_depth);
114 
115   // Finds the CFI row for the given |func_addr| in terms of offset from
116   // the start of the current binary. Concurrent calls are thread safe.
117   bool FindCFIRowForPC(uintptr_t func_addr, CFIRow* out);
118 
119  private:
120   FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestCFICache);
121   FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestFindCFIRow);
122   FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestUnwinding);
123 
124   // Initializes unwind tables using the CFI asset file in the apk if present.
125   // Also stores the limits of mapped region of the lib[mono]chrome.so binary,
126   // since the unwind is only feasible for addresses within the .so file. Once
127   // initialized, the memory map of the unwind table is never cleared since we
128   // cannot guarantee that all the threads are done using the memory map when
129   // heap profiling is turned off. But since we keep the memory map is clean,
130   // the system can choose to evict the unused pages when needed. This would
131   // still reduce the total amount of address space available in process.
132   CFIBacktraceAndroid();
133 
134   ~CFIBacktraceAndroid();
135 
136   // Finds the UNW_INDEX and UNW_DATA tables in from the CFI file memory map.
137   void ParseCFITables();
138 
139   // The start address of the memory mapped unwind table asset file. Unique ptr
140   // because it is replaced in tests.
141   std::unique_ptr<MemoryMappedFile> cfi_mmap_;
142 
143   // The UNW_INDEX table: Start address of the function address column. The
144   // memory segment corresponding to this column is treated as an array of
145   // uintptr_t.
146   raw_ptr<const uintptr_t, AllowPtrArithmetic> unw_index_function_col_ =
147       nullptr;
148   // The UNW_INDEX table: Start address of the index column. The memory segment
149   // corresponding to this column is treated as an array of uint16_t.
150   raw_ptr<const uint16_t, AllowPtrArithmetic> unw_index_indices_col_ = nullptr;
151   // The number of rows in UNW_INDEX table.
152   size_t unw_index_row_count_ = 0;
153 
154   // The start address of UNW_DATA table.
155   raw_ptr<const uint16_t, AllowPtrArithmetic> unw_data_start_addr_ = nullptr;
156 
157   bool can_unwind_stack_frames_ = false;
158 };
159 
160 }  // namespace trace_event
161 }  // namespace base
162 
163 #endif  // BASE_TRACE_EVENT_CFI_BACKTRACE_ANDROID_H_
164