xref: /aosp_15_r20/external/cronet/base/profiler/chrome_unwinder_android.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2021 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 #include "base/profiler/chrome_unwinder_android.h"
6 
7 #include <algorithm>
8 
9 #include "base/check_op.h"
10 #include "base/memory/aligned_memory.h"
11 #include "base/notreached.h"
12 #include "base/numerics/checked_math.h"
13 #include "base/profiler/chrome_unwind_info_android.h"
14 
15 namespace base {
16 namespace {
17 
GetRegisterPointer(RegisterContext * context,uint8_t register_index)18 uintptr_t* GetRegisterPointer(RegisterContext* context,
19                               uint8_t register_index) {
20   DCHECK_LE(register_index, 15);
21   static unsigned long RegisterContext::*const registers[16] = {
22       &RegisterContext::arm_r0,  &RegisterContext::arm_r1,
23       &RegisterContext::arm_r2,  &RegisterContext::arm_r3,
24       &RegisterContext::arm_r4,  &RegisterContext::arm_r5,
25       &RegisterContext::arm_r6,  &RegisterContext::arm_r7,
26       &RegisterContext::arm_r8,  &RegisterContext::arm_r9,
27       &RegisterContext::arm_r10, &RegisterContext::arm_fp,
28       &RegisterContext::arm_ip,  &RegisterContext::arm_sp,
29       &RegisterContext::arm_lr,  &RegisterContext::arm_pc,
30   };
31   return reinterpret_cast<uintptr_t*>(&(context->*registers[register_index]));
32 }
33 
34 // Pops the value on the top of stack out and assign it to target register.
35 // This is equivalent to arm instruction `Pop r[n]` where n = `register_index`.
36 // Returns whether the pop is successful.
PopRegister(RegisterContext * context,uint8_t register_index)37 bool PopRegister(RegisterContext* context, uint8_t register_index) {
38   const uintptr_t sp = RegisterContextStackPointer(context);
39   const uintptr_t stacktop_value = *reinterpret_cast<uintptr_t*>(sp);
40   const auto new_sp = CheckedNumeric<uintptr_t>(sp) + sizeof(uintptr_t);
41   const bool success =
42       new_sp.AssignIfValid(&RegisterContextStackPointer(context));
43   if (success)
44     *GetRegisterPointer(context, register_index) = stacktop_value;
45   return success;
46 }
47 
48 // Decodes the given bytes as an ULEB128 format number and advances the bytes
49 // pointer by the size of ULEB128.
50 //
51 // This function assumes the given bytes are in valid ULEB128
52 // format and the decoded number should not overflow `uintptr_t` type.
DecodeULEB128(const uint8_t * & bytes)53 uintptr_t DecodeULEB128(const uint8_t*& bytes) {
54   uintptr_t value = 0;
55   unsigned shift = 0;
56   do {
57     DCHECK_LE(shift, sizeof(uintptr_t) * 8);  // ULEB128 must not overflow.
58     value += (*bytes & 0x7fu) << shift;
59     shift += 7;
60   } while (*bytes++ & 0x80);
61   return value;
62 }
63 
GetTopBits(uint8_t byte,unsigned bits)64 uint8_t GetTopBits(uint8_t byte, unsigned bits) {
65   DCHECK_LE(bits, 8u);
66   return byte >> (8 - bits);
67 }
68 
69 }  // namespace
70 
ChromeUnwinderAndroid(const ChromeUnwindInfoAndroid & unwind_info,uintptr_t chrome_module_base_address,uintptr_t text_section_start_address)71 ChromeUnwinderAndroid::ChromeUnwinderAndroid(
72     const ChromeUnwindInfoAndroid& unwind_info,
73     uintptr_t chrome_module_base_address,
74     uintptr_t text_section_start_address)
75     : unwind_info_(unwind_info),
76       chrome_module_base_address_(chrome_module_base_address),
77       text_section_start_address_(text_section_start_address) {
78   DCHECK_GT(text_section_start_address_, chrome_module_base_address_);
79 }
80 
CanUnwindFrom(const Frame & current_frame) const81 bool ChromeUnwinderAndroid::CanUnwindFrom(const Frame& current_frame) const {
82   return current_frame.module &&
83          current_frame.module->GetBaseAddress() == chrome_module_base_address_;
84 }
85 
TryUnwind(RegisterContext * thread_context,uintptr_t stack_top,std::vector<Frame> * stack)86 UnwindResult ChromeUnwinderAndroid::TryUnwind(RegisterContext* thread_context,
87                                               uintptr_t stack_top,
88                                               std::vector<Frame>* stack) {
89   DCHECK(CanUnwindFrom(stack->back()));
90   uintptr_t frame_initial_sp = RegisterContextStackPointer(thread_context);
91   const uintptr_t unwind_initial_pc =
92       RegisterContextInstructionPointer(thread_context);
93 
94   do {
95     const uintptr_t pc = RegisterContextInstructionPointer(thread_context);
96     const uintptr_t instruction_byte_offset_from_text_section_start =
97         pc - text_section_start_address_;
98 
99     const std::optional<FunctionOffsetTableIndex> function_offset_table_index =
100         GetFunctionTableIndexFromInstructionOffset(
101             unwind_info_.page_table, unwind_info_.function_table,
102             instruction_byte_offset_from_text_section_start);
103 
104     if (!function_offset_table_index) {
105       return UnwindResult::kAborted;
106     }
107 
108     const uint32_t current_unwind_instruction_index =
109         GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry(
110             &unwind_info_
111                  .function_offset_table[function_offset_table_index
112                                             ->function_offset_table_byte_index],
113             function_offset_table_index
114                 ->instruction_offset_from_function_start);
115 
116     const uint8_t* current_unwind_instruction =
117         &unwind_info_
118              .unwind_instruction_table[current_unwind_instruction_index];
119 
120     UnwindInstructionResult instruction_result;
121     bool pc_was_updated = false;
122 
123     do {
124       instruction_result = ExecuteUnwindInstruction(
125           current_unwind_instruction, pc_was_updated, thread_context);
126       const uintptr_t sp = RegisterContextStackPointer(thread_context);
127       if (sp > stack_top || sp < frame_initial_sp ||
128           !IsAligned(sp, sizeof(uintptr_t))) {
129         return UnwindResult::kAborted;
130       }
131     } while (instruction_result ==
132              UnwindInstructionResult::kInstructionPending);
133 
134     if (instruction_result == UnwindInstructionResult::kAborted) {
135       return UnwindResult::kAborted;
136     }
137 
138     DCHECK_EQ(instruction_result, UnwindInstructionResult::kCompleted);
139 
140     const uintptr_t new_sp = RegisterContextStackPointer(thread_context);
141     // Validate SP is properly aligned across frames.
142     // See
143     // https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/using-the-stack-in-aarch32-and-aarch64
144     // for SP alignment rules.
145     if (!IsAligned(new_sp, 2 * sizeof(uintptr_t))) {
146       return UnwindResult::kAborted;
147     }
148     // Validate that SP does not decrease across frames.
149     const bool is_leaf_frame = stack->size() == 1;
150     // Each frame unwind is expected to only pop from stack memory, which will
151     // cause sp to increase.
152     // Non-Leaf frames are expected to at least pop lr off stack, so sp is
153     // expected to strictly increase for non-leaf frames.
154     if (new_sp <= (is_leaf_frame ? frame_initial_sp - 1 : frame_initial_sp)) {
155       return UnwindResult::kAborted;
156     }
157 
158     // For leaf functions, if SP does not change, PC must change, otherwise,
159     // the overall execution state will be the same before/after the frame
160     // unwind.
161     if (is_leaf_frame && new_sp == frame_initial_sp &&
162         RegisterContextInstructionPointer(thread_context) ==
163             unwind_initial_pc) {
164       return UnwindResult::kAborted;
165     }
166 
167     frame_initial_sp = new_sp;
168 
169     stack->emplace_back(RegisterContextInstructionPointer(thread_context),
170                         module_cache()->GetModuleForAddress(
171                             RegisterContextInstructionPointer(thread_context)));
172   } while (CanUnwindFrom(stack->back()));
173   return UnwindResult::kUnrecognizedFrame;
174 }
175 
ExecuteUnwindInstruction(const uint8_t * & instruction,bool & pc_was_updated,RegisterContext * thread_context)176 UnwindInstructionResult ExecuteUnwindInstruction(
177     const uint8_t*& instruction,
178     bool& pc_was_updated,
179     RegisterContext* thread_context) {
180   if (GetTopBits(*instruction, 2) == 0b00) {
181     // 00xxxxxx
182     // vsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive.
183     const uintptr_t offset = ((*instruction++ & 0b00111111u) << 2) + 4;
184 
185     const auto new_sp =
186         CheckedNumeric<uintptr_t>(RegisterContextStackPointer(thread_context)) +
187         offset;
188     if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context))) {
189       return UnwindInstructionResult::kAborted;
190     }
191   } else if (GetTopBits(*instruction, 2) == 0b01) {
192     // 01xxxxxx
193     // vsp = vsp - (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive.
194     const uintptr_t offset = ((*instruction++ & 0b00111111u) << 2) + 4;
195     const auto new_sp =
196         CheckedNumeric<uintptr_t>(RegisterContextStackPointer(thread_context)) -
197         offset;
198     if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context))) {
199       return UnwindInstructionResult::kAborted;
200     }
201   } else if (GetTopBits(*instruction, 4) == 0b1001) {
202     // 1001nnnn (nnnn != 13,15)
203     // Set vsp = r[nnnn].
204     const uint8_t register_index = *instruction++ & 0b00001111;
205     DCHECK_NE(register_index, 13);  // Must not set sp to sp.
206     DCHECK_NE(register_index, 15);  // Must not set sp to pc.
207     // Note: We shouldn't have cases that are setting caller-saved registers
208     // using this instruction.
209     DCHECK_GE(register_index, 4);
210 
211     RegisterContextStackPointer(thread_context) =
212         *GetRegisterPointer(thread_context, register_index);
213   } else if (GetTopBits(*instruction, 5) == 0b10101) {
214     // 10101nnn
215     // Pop r4-r[4+nnn], r14
216     const uint8_t max_register_index = (*instruction++ & 0b00000111u) + 4;
217     for (uint8_t n = 4; n <= max_register_index; n++) {
218       if (!PopRegister(thread_context, n)) {
219         return UnwindInstructionResult::kAborted;
220       }
221     }
222     if (!PopRegister(thread_context, 14)) {
223       return UnwindInstructionResult::kAborted;
224     }
225   } else if (*instruction == 0b10000000 && *(instruction + 1) == 0) {
226     // 10000000 00000000
227     // Refuse to unwind.
228     instruction += 2;
229     return UnwindInstructionResult::kAborted;
230   } else if (GetTopBits(*instruction, 4) == 0b1000) {
231     const uint32_t register_bitmask =
232         ((*instruction & 0xfu) << 8) + *(instruction + 1);
233     instruction += 2;
234     // 1000iiii iiiiiiii
235     // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
236     for (uint8_t register_index = 4; register_index < 16; register_index++) {
237       if (register_bitmask & (1 << (register_index - 4))) {
238         if (!PopRegister(thread_context, register_index)) {
239           return UnwindInstructionResult::kAborted;
240         }
241       }
242     }
243     // If we set pc (r15) with value on stack, we should no longer copy lr to
244     // pc on COMPLETE.
245     pc_was_updated |= register_bitmask & (1 << (15 - 4));
246   } else if (*instruction == 0b10110000) {
247     // Finish
248     // Code 0xb0, Finish, copies VRS[r14] to VRS[r15] and also
249     // indicates that no further instructions are to be processed for this
250     // frame.
251 
252     instruction++;
253     // Only copy lr to pc when pc is not updated by other instructions before.
254     if (!pc_was_updated)
255       thread_context->arm_pc = thread_context->arm_lr;
256 
257     return UnwindInstructionResult::kCompleted;
258   } else if (*instruction == 0b10110010) {
259     // 10110010 uleb128
260     // vsp = vsp + 0x204 + (uleb128 << 2)
261     // (for vsp increments of 0x104-0x200, use 00xxxxxx twice)
262     instruction++;
263     const auto new_sp =
264         CheckedNumeric<uintptr_t>(RegisterContextStackPointer(thread_context)) +
265         (CheckedNumeric<uintptr_t>(DecodeULEB128(instruction)) << 2) + 0x204;
266 
267     if (!new_sp.AssignIfValid(&RegisterContextStackPointer(thread_context))) {
268       return UnwindInstructionResult::kAborted;
269     }
270   } else {
271     NOTREACHED();
272   }
273   return UnwindInstructionResult::kInstructionPending;
274 }
275 
GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry(const uint8_t * function_offset_table_entry,int instruction_offset_from_function_start)276 uintptr_t GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry(
277     const uint8_t* function_offset_table_entry,
278     int instruction_offset_from_function_start) {
279   DCHECK_GE(instruction_offset_from_function_start, 0);
280   const uint8_t* current_function_offset_table_position =
281       function_offset_table_entry;
282 
283   do {
284     const uintptr_t function_offset =
285         DecodeULEB128(current_function_offset_table_position);
286 
287     const uintptr_t unwind_table_index =
288         DecodeULEB128(current_function_offset_table_position);
289 
290     // Each function always ends at 0 offset. It is guaranteed to find an entry
291     // as long as the function offset table is well-structured.
292     if (function_offset <=
293         static_cast<uint32_t>(instruction_offset_from_function_start))
294       return unwind_table_index;
295 
296   } while (true);
297 
298   NOTREACHED();
299   return 0;
300 }
301 
302 const std::optional<FunctionOffsetTableIndex>
GetFunctionTableIndexFromInstructionOffset(span<const uint32_t> page_start_instructions,span<const FunctionTableEntry> function_offset_table_indices,uint32_t instruction_byte_offset_from_text_section_start)303 GetFunctionTableIndexFromInstructionOffset(
304     span<const uint32_t> page_start_instructions,
305     span<const FunctionTableEntry> function_offset_table_indices,
306     uint32_t instruction_byte_offset_from_text_section_start) {
307   DCHECK(!page_start_instructions.empty());
308   DCHECK(!function_offset_table_indices.empty());
309   // First function on first page should always start from 0 offset.
310   DCHECK_EQ(function_offset_table_indices.front()
311                 .function_start_address_page_instruction_offset,
312             0ul);
313 
314   const uint16_t page_number =
315       instruction_byte_offset_from_text_section_start >> 17;
316   const uint16_t page_instruction_offset =
317       (instruction_byte_offset_from_text_section_start >> 1) &
318       0xffff;  // 16 bits.
319 
320   // Invalid instruction_byte_offset_from_text_section_start:
321   // instruction_byte_offset_from_text_section_start falls after the last page.
322   if (page_number >= page_start_instructions.size()) {
323     return std::nullopt;
324   }
325 
326   const span<const FunctionTableEntry>::iterator function_table_entry_start =
327       function_offset_table_indices.begin() +
328       checked_cast<ptrdiff_t>(page_start_instructions[page_number]);
329   const span<const FunctionTableEntry>::iterator function_table_entry_end =
330       page_number == page_start_instructions.size() - 1
331           ? function_offset_table_indices.end()
332           : function_offset_table_indices.begin() +
333                 checked_cast<ptrdiff_t>(
334                     page_start_instructions[page_number + 1]);
335 
336   // `std::upper_bound` finds first element that > target in range
337   // [function_table_entry_start, function_table_entry_end).
338   const auto first_larger_entry_location = std::upper_bound(
339       function_table_entry_start, function_table_entry_end,
340       page_instruction_offset,
341       [](uint16_t page_instruction_offset, const FunctionTableEntry& entry) {
342         return page_instruction_offset <
343                entry.function_start_address_page_instruction_offset;
344       });
345 
346   // Offsets the element found by 1 to get the biggest element that <= target.
347   const auto entry_location = first_larger_entry_location - 1;
348 
349   // When all offsets in current range > page_instruction_offset (including when
350   // there is no entry in current range), the `FunctionTableEntry` we are
351   // looking for is not within the function_offset_table_indices range we are
352   // inspecting, because the function is too long that it spans multiple pages.
353   //
354   // We need to locate the previous entry on function_offset_table_indices and
355   // find its corresponding page_table index.
356   //
357   // Example:
358   // +--------------------+--------------------+
359   // | <-----2 byte-----> | <-----2 byte-----> |
360   // +--------------------+--------------------+
361   // | Page Offset        | Offset Table Index |
362   // +--------------------+--------------------+-----
363   // | 10                 | XXX                |  |
364   // +--------------------+--------------------+  |
365   // | ...                | ...                |Page 0x100
366   // +--------------------+--------------------+  |
367   // | 65500              | ZZZ                |  |
368   // +--------------------+--------------------+----- Page 0x101 is empty
369   // | 200                | AAA                |  |
370   // +--------------------+--------------------+  |
371   // | ...                | ...                |Page 0x102
372   // +--------------------+--------------------+  |
373   // | 65535              | BBB                |  |
374   // +--------------------+--------------------+-----
375   //
376   // Example:
377   // For
378   // - page_number = 0x100, page_instruction_offset >= 65535
379   // - page_number = 0x101, all page_instruction_offset
380   // - page_number = 0x102, page_instruction_offset < 200
381   // We should be able to map them all to entry [65500, ZZZ] in page 0x100.
382 
383   // Finds the page_number that corresponds to `entry_location`. The page
384   // might not be the page we are inspecting, when the function spans over
385   // multiple pages.
386   uint16_t function_start_page_number = page_number;
387   while (function_offset_table_indices.begin() +
388              checked_cast<ptrdiff_t>(
389                  page_start_instructions[function_start_page_number]) >
390          entry_location) {
391     // First page in page table must not be empty.
392     DCHECK_NE(function_start_page_number, 0);
393     function_start_page_number--;
394   };
395 
396   const uint32_t function_start_address_instruction_offset =
397       (uint32_t{function_start_page_number} << 16) +
398       entry_location->function_start_address_page_instruction_offset;
399 
400   const int instruction_offset_from_function_start =
401       static_cast<int>((instruction_byte_offset_from_text_section_start >> 1) -
402                        function_start_address_instruction_offset);
403 
404   DCHECK_GE(instruction_offset_from_function_start, 0);
405   return FunctionOffsetTableIndex{
406       instruction_offset_from_function_start,
407       entry_location->function_offset_table_byte_index,
408   };
409 }
410 
411 }  // namespace base
412