symbolic_common/heuristics.rs
1//! Heuristics for correcting instruction pointers based on the CPU architecture.
2
3use crate::types::{Arch, CpuFamily};
4
5const SIGILL: u32 = 4;
6const SIGBUS: u32 = 10;
7const SIGSEGV: u32 = 11;
8
9/// Helper to work with instruction addresses.
10///
11/// Directly symbolicated stack traces may show the wrong calling symbols, as the stack frame's
12/// return addresses point a few bytes past the original call site, which may place the address
13/// within a different symbol entirely.
14///
15/// The most useful function is [`caller_address`], which applies some heuristics to determine the
16/// call site of a function call based on the return address.
17///
18/// # Examples
19///
20/// ```
21/// use symbolic_common::{Arch, InstructionInfo};
22///
23/// const SIGSEGV: u32 = 11;
24///
25/// let caller_address = InstructionInfo::new(Arch::Arm64, 0x1337)
26/// .is_crashing_frame(false)
27/// .signal(Some(SIGSEGV))
28/// .ip_register_value(Some(0x4242))
29/// .caller_address();
30///
31/// assert_eq!(caller_address, 0x1330);
32/// ```
33///
34/// # Background
35///
36/// When *calling* a function, it is necessary for the *called* function to know where it should
37/// return to upon completion. To support this, a *return address* is supplied as part of the
38/// standard function call semantics. This return address specifies the instruction that the called
39/// function should jump to upon completion of its execution.
40///
41/// When a crash reporter generates a backtrace, it first collects the thread state of all active
42/// threads, including the **actual** current execution address. The reporter then iterates over
43/// those threads, walking backwards to find calling frames – what it's actually finding during this
44/// process are the **return addresses**. The actual address of the call instruction is not recorded
45/// anywhere. The only address available is the address at which execution should resume after
46/// function return.
47///
48/// To make things more complicated, there is no guarantee that a return address be set to exactly
49/// one instruction after the call. It's entirely proper for a function to remove itself from the
50/// call stack by setting a different return address entirely. This is why you never see
51/// `objc_msgSend` in your backtrace unless you actually crash inside of `objc_msgSend`. When
52/// `objc_msgSend` jumps to a method's implementation, it leaves its caller's return address in
53/// place, and `objc_msgSend` itself disappears from the stack trace. In the case of `objc_msgSend`,
54/// the loss of that information is of no great importance, but it's hardly the only function that
55/// elides its own code from the return address.
56///
57/// # Heuristics
58///
59/// To resolve this particular issue, it is necessary for the symbolication implementor to apply a
60/// per-architecture heuristics to the return addresses, and thus derive the **likely** address of
61/// the actual calling instruction. There is a high probability of correctness, but absolutely no
62/// guarantee.
63///
64/// This derived address **should** be used as the symbolication address, but **should not** replace
65/// the return address in the crash report. This derived address is a best guess, and if you replace
66/// the return address in the report, the end-user will have lost access to the original canonical
67/// data from which they could have made their own assessment.
68///
69/// These heuristics must not be applied to frame #0 on any thread. The first frame of all threads
70/// contains the actual register state of that thread at the time that it crashed (if it's the
71/// crashing thread), or at the time it was suspended (if it is a non-crashing thread). These
72/// heuristics should only be applied to frames *after* frame #0 – that is, starting with frame #1.
73///
74/// Additionally, these heuristics assume that your symbolication implementation correctly handles
75/// addresses that occur within an instruction, rather than directly at the start of a valid
76/// instruction. This should be the case for any reasonable implementation, but is something to be
77/// aware of when deploying these changes.
78///
79/// ## x86 and x86-64
80///
81/// x86 uses variable-width instruction encodings; subtract one byte from the return address to
82/// derive an address that should be within the calling instruction. This will provide an address
83/// within a calling instruction found directly prior to the return address.
84///
85/// ## ARMv6 and ARMv7
86///
87/// - **Step 1:** Strip the low order thumb bit from the return address. ARM uses the low bit to
88/// inform the processor that it should enter thumb mode when jumping to the return address. Since
89/// all instructions are at least 2 byte aligned, an actual instruction address will never have
90/// the low bit set.
91///
92/// - **Step 2:** Subtract 2 Bytes. 32-bit ARM instructions are either 2 or 4 bytes long, depending
93/// on the use of thumb. This will place the symbolication address within the likely calling
94/// instruction. All ARM64 instructions are 4 bytes long; subtract 4 bytes from the return address
95/// to derive the likely address of the calling instruction.
96///
97/// # More Information
98///
99/// The above information was taken and slightly updated from the now-gone *PLCrashReporter Wiki*.
100/// An old copy can still be found in the [internet archive].
101///
102/// [internet archive]: https://web.archive.org/web/20161012225323/https://opensource.plausible.coop/wiki/display/PLCR/Automated+Crash+Report+Analysis
103/// [`caller_address`]: struct.InstructionInfo.html#method.caller_address
104#[derive(Clone, Debug)]
105pub struct InstructionInfo {
106 addr: u64,
107 arch: Arch,
108 crashing_frame: bool,
109 signal: Option<u32>,
110 ip_reg: Option<u64>,
111}
112
113impl InstructionInfo {
114 /// Creates a new instruction info instance.
115 ///
116 /// By default, the frame is not marked as *crashing frame*. The signal and instruction pointer
117 /// register value are empty.
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// use symbolic_common::{Arch, InstructionInfo};
123 ///
124 /// let caller_address = InstructionInfo::new(Arch::X86, 0x1337)
125 /// .caller_address();
126 /// ```
127 pub fn new(arch: Arch, instruction_address: u64) -> Self {
128 Self {
129 arch,
130 addr: instruction_address,
131 crashing_frame: false,
132 signal: None,
133 ip_reg: None,
134 }
135 }
136
137 /// Marks this as the crashing frame.
138 ///
139 /// The crashing frame is the first frame yielded by the stack walker. In such a frame, the
140 /// instruction address is the location of the direct crash. This is used by
141 /// [`should_adjust_caller`] to determine which frames need caller address adjustment.
142 ///
143 /// Defaults to `false`.
144 ///
145 /// [`should_adjust_caller`]: struct.InstructionInfo.html#method.should_adjust_caller
146 pub fn is_crashing_frame(&mut self, flag: bool) -> &mut Self {
147 self.crashing_frame = flag;
148 self
149 }
150
151 /// Sets a POSIX signal number.
152 ///
153 /// The signal number is used by [`should_adjust_caller`] to determine which frames need caller
154 /// address adjustment.
155 ///
156 /// [`should_adjust_caller`]: struct.InstructionInfo.html#method.should_adjust_caller
157 pub fn signal(&mut self, signal: Option<u32>) -> &mut Self {
158 self.signal = signal;
159 self
160 }
161
162 /// Sets the value of the instruction pointer register.
163 ///
164 /// This should be the original register value at the time of the crash, and not a restored
165 /// register value. This is used by [`should_adjust_caller`] to determine which frames need
166 /// caller address adjustment.
167 ///
168 /// [`should_adjust_caller`]: struct.InstructionInfo.html#method.should_adjust_caller
169 pub fn ip_register_value(&mut self, value: Option<u64>) -> &mut Self {
170 self.ip_reg = value;
171 self
172 }
173
174 /// Tries to resolve the start address of the current instruction.
175 ///
176 /// For architectures without fixed alignment (such as Intel with variable instruction lengths),
177 /// this will return the same address. Otherwise, the address is aligned to the architecture's
178 /// instruction alignment.
179 ///
180 /// # Examples
181 ///
182 /// For example, on 64-bit ARM, addresses are aligned at 4 byte boundaries. This applies to all
183 /// 64-bit ARM variants, even unknown ones:
184 ///
185 /// ```
186 /// use symbolic_common::{Arch, InstructionInfo};
187 ///
188 /// let info = InstructionInfo::new(Arch::Arm64, 0x1337);
189 /// assert_eq!(info.aligned_address(), 0x1334);
190 /// ```
191 pub fn aligned_address(&self) -> u64 {
192 if let Some(alignment) = self.arch.cpu_family().instruction_alignment() {
193 self.addr - (self.addr % alignment)
194 } else {
195 self.addr
196 }
197 }
198
199 /// Returns the instruction preceding the current one.
200 ///
201 /// For known architectures, this will return the start address of the instruction immediately
202 /// before the current one in the machine code. This is likely the instruction that was just
203 /// executed or that called a function returning at the current address.
204 ///
205 /// For unknown architectures or those using variable instruction size, the exact start address
206 /// cannot be determined. Instead, an address *within* the preceding instruction will be
207 /// returned. For this reason, the return value of this function should be considered an upper
208 /// bound.
209 ///
210 /// # Examples
211 ///
212 /// On 64-bit ARM, instructions have 4 bytes in size. The previous address is therefore 4 bytes
213 /// before the start of the current instruction (returned by [`aligned_address`]):
214 ///
215 /// ```
216 /// use symbolic_common::{Arch, InstructionInfo};
217 ///
218 /// let info = InstructionInfo::new(Arch::Arm64, 0x1337);
219 /// assert_eq!(info.previous_address(), 0x1330);
220 /// ```
221 ///
222 /// On the contrary, Intel uses variable-length instruction encoding. In such a case, the best
223 /// effort is to subtract 1 byte and hope that it points into the previous instruction:
224 ///
225 /// ```
226 /// use symbolic_common::{Arch, InstructionInfo};
227 ///
228 /// let info = InstructionInfo::new(Arch::X86, 0x1337);
229 /// assert_eq!(info.previous_address(), 0x1336);
230 /// ```
231 ///
232 /// [`aligned_address`]: struct.InstructionInfo.html#method.aligned_address
233 pub fn previous_address(&self) -> u64 {
234 let instruction_size = self.arch.cpu_family().instruction_alignment().unwrap_or(1);
235
236 // In MIPS, the return address apparently often points two instructions after the the
237 // previous program counter. On other architectures, just subtract one instruction.
238 let pc_offset = match self.arch.cpu_family() {
239 CpuFamily::Mips32 | CpuFamily::Mips64 => 2 * instruction_size,
240 _ => instruction_size,
241 };
242
243 self.aligned_address() - pc_offset
244 }
245
246 /// Returns whether the application attempted to jump to an invalid, privileged or misaligned
247 /// address.
248 ///
249 /// This indicates that certain adjustments should be made on the caller instruction address.
250 ///
251 /// # Example
252 ///
253 /// ```
254 /// use symbolic_common::{Arch, InstructionInfo};
255 ///
256 /// const SIGSEGV: u32 = 11;
257 ///
258 /// let is_crash = InstructionInfo::new(Arch::X86, 0x1337)
259 /// .signal(Some(SIGSEGV))
260 /// .is_crash_signal();
261 ///
262 /// assert!(is_crash);
263 /// ```
264 pub fn is_crash_signal(&self) -> bool {
265 matches!(self.signal, Some(SIGILL) | Some(SIGBUS) | Some(SIGSEGV))
266 }
267
268 /// Determines whether the given address should be adjusted to resolve the call site of a stack
269 /// frame.
270 ///
271 /// This generally applies to all frames except the crashing / suspended frame. However, if the
272 /// process crashed with an illegal instruction, even the top-most frame needs to be adjusted to
273 /// account for the signal handler.
274 ///
275 /// # Examples
276 ///
277 /// By default, all frames need to be adjusted. There are only few exceptions to this rule: The
278 /// crashing frame is the first frame yielded in the stack trace and specifies the actual
279 /// instruction pointer address. Therefore, it does not need to be adjusted:
280 ///
281 /// ```
282 /// use symbolic_common::{Arch, InstructionInfo};
283 ///
284 /// let should_adjust = InstructionInfo::new(Arch::X86, 0x1337)
285 /// .is_crashing_frame(true)
286 /// .should_adjust_caller();
287 ///
288 /// assert!(!should_adjust);
289 /// ```
290 pub fn should_adjust_caller(&self) -> bool {
291 // All frames other than the crashing frame (or suspended frame for
292 // other threads) report the return address. This address (generally)
293 // points to the instruction after the function call. Therefore, we
294 // need to adjust the caller address for these frames.
295 if !self.crashing_frame {
296 return true;
297 }
298
299 // KSCrash applies a heuristic to remove the signal handler frame from
300 // the top of the stack trace, if the crash was caused by certain
301 // signals. However, that means that the top-most frame contains a
302 // return address just like any other and needs to be adjusted.
303 if let Some(ip) = self.ip_reg {
304 if ip != self.addr && self.is_crash_signal() {
305 return true;
306 }
307 }
308
309 // The crashing frame usually contains the actual register contents,
310 // which points to the exact instruction that crashed and must not be
311 // adjusted.
312 false
313 }
314
315 /// Determines the address of the call site based on a return address.
316 ///
317 /// In the top most frame (often referred to as context frame), this is the value of the
318 /// instruction pointer register. In all other frames, the return address is generally one
319 /// instruction after the jump / call.
320 ///
321 /// This function actually resolves an address _within_ the call instruction rather than its
322 /// beginning. Also, in some cases the top most frame has been modified by certain signal
323 /// handlers or return optimizations. A set of heuristics tries to recover this for well-known
324 /// cases.
325 ///
326 /// # Examples
327 ///
328 /// Returns the aligned address for crashing frames:
329 ///
330 /// ```
331 /// use symbolic_common::{Arch, InstructionInfo};
332 ///
333 /// let caller_address = InstructionInfo::new(Arch::Arm64, 0x1337)
334 /// .is_crashing_frame(true)
335 /// .caller_address();
336 ///
337 /// assert_eq!(caller_address, 0x1334);
338 /// ```
339 ///
340 /// For all other frames, it returns the previous address:
341 ///
342 /// ```
343 /// use symbolic_common::{Arch, InstructionInfo};
344 ///
345 /// let caller_address = InstructionInfo::new(Arch::Arm64, 0x1337)
346 /// .is_crashing_frame(false)
347 /// .caller_address();
348 ///
349 /// assert_eq!(caller_address, 0x1330);
350 /// ```
351 pub fn caller_address(&self) -> u64 {
352 if self.should_adjust_caller() {
353 self.previous_address()
354 } else {
355 self.aligned_address()
356 }
357
358 // NOTE: Currently, we only provide stack traces from KSCrash and
359 // Breakpad. Both already apply a set of heuristics while stackwalking
360 // in order to fix return addresses. It seems that no further heuristics
361 // are necessary at the moment.
362 }
363}