abi-aa icon indicating copy to clipboard operation
abi-aa copied to clipboard

[AADWARF32] Allocate a register number for CPSR

Open statham-arm opened this issue 7 months ago • 13 comments

The document currently says of CPSR, along with the VFP (and FPA) control registers, "It is considered unlikely that these will be needed for producing a stack back-trace in a debugger."

However, CPSR can be required for producing a correct stack backtrace. This occurs due to conditional return sequences, for example

function:
  PUSH   {r4, r5, r6, lr}
  SUB    sp, sp, #64
  // ... do stuff ...
  CMP    this, that       // we will return early if they are equal
  ADDEQ  sp, sp, #64
  POPEQ  {r4, r5, r6, pc}
  // ... now, if we didn't return, continue using our stack frame

In between ADDEQ and POPEQ, the state of the stack depends on the flags in CPSR. If the Z flag is set, then the ADDEQ has happened, and the POPEQ is about to; if Z is clear, neither one has happened. In this example the function has no frame pointer, so the CFA is defined as an offset from sp, and what offset depends on whether we just added 64 to sp.

This style of conditional return has always been possible, since it depends only on the earliest features of the Arm instruction set. The accepted idiom in my experience has always been to write stack frame information that is valid for one case but not the other. Introducing a DWARF register number for CPSR makes it possible to write stack frame information that is valid in both cases. For example, you could write an expression along these lines, which uses the 4 bits at the top of CPSR to decide which bit of the constant bitmask to test, and then each Arm condition code is representable as a different bitmask:

  DW_OP_breg_13                    // fetch sp
  DW_OP_constu #bitmask            // bit mask for the particular condition
  DW_OP_bregx #CPSR                // fetch CPSR
  DW_OP_const1u #28
  DW_OP_shr                        // make (CPSR >> 28), just the NZCV bits
  DW_OP_shr                        // shift bit mask right by the NZCV value
  DW_OP_const1u #1
  DW_OP_and                        // AND with 1 to isolate low bit
  DW_OP_bra #label-offset-else     // if nonzero, branch over the next constant
  DW_OP_constu #offset2            // load one possible value to add to sp
  DW_OP_skip #label-offset-end     // and skip over the other constant
label-offset-else:
  DW_OP_constu #offset1            // load the other value to add to sp
label-offset-end:
  DW_OP_minus                      // subtract either offset2 or offset1 from sp

Of course, debuggers will take time to catch up. But a more interesting use case for being able to precisely describe stack situations like this is automatic static checkers for handwritten assembly language with handwritten call frame directives, which verify the semantics of the instructions against the call frame updates next to them. A debugger might almost never stop at the difficult location above, but a static checker will traverse it every time, and needs a way to avoid getting confused.

statham-arm avatar Jul 03 '25 16:07 statham-arm