This is an archive of the discontinued LLVM Phabricator instance.

[ARM] Implement PAC return address signing mechanism for PACBTI-M

Authored by stuij on Oct 25 2021, 3:33 AM.



This patch implements PAC return address signing for armv8-m. This patch roughly
accomplishes the following things:

  • PAC and AUT instructions are generated.
  • They're part of the stack frame setup, so that shrink-wrapping can move them

inwards to cover only part of a function

  • The auth code generated by PAC is saved across subroutine calls so that AUT

can find it again to check

  • PAC is emitted before stacking registers (so that the SP it signs is the one

on function entry).

  • The new pseudo-register ra_auth_code is mentioned in the DWARF frame data
  • With CMSE also in use: PAC is emitted before stacking FPCXTNS, and AUT

validates the corresponding value of SP

  • Emit correct unwind information when PAC is replaced by PACBTI
  • Handle tail calls correctly

Some notes:

We make the assembler accept the .save {ra_auth_code} directive that is
emitted by the compiler when it saves a register that contains a
return address authentication code.

For EHABI we need to have the FrameSetup flag on the instruction and
handle the t2PACBTI opcode (identically to t2PAC), so we can emit
.save {ra_auth_code}, instead of .save {r12}.

For PACBTI-M, the instruction which computes return address PAC should use SP
value before adjustment for the argument registers save are (used for variadic
functions and when a parameter is is split between stack and register), but at
the same it should be after the instruction that saves FPCXT when compiling a
CMSE entry function.

This patch moves the varargs SP adjustment after the FPCXT save (they are never
enabled at the same time), so in a following patch handling of the PAC
instruction can be placed between them.

Epilogue emission code adjusted in a similar manner.

PACBTI-M code generation should not emit any instructions for architectures
v6-m, v8-m.base, and for A- and R-class cores. Diagnostic message for such cases
is handled separately by a future ticket.

note on tail calls:

If the called function has four arguments that occupy registers r0-r3, the
only option for holding the function pointer itself is r12, but this register
is used to keep the PAC during function/prologue epilogue and clobbers the
function pointer.

When we do the tail call we need the five registers (r0-r3 and r12) to
keep six values - the four function arguments, the function pointer and the PAC,
which is obviously impossible.

One option would be to authenticate the return address before all callee-saved
registers are restored, so we have a scratch register to temporarily keep the
value of r12. The issue with this approach is that it violates a fundamental
invariant that PAC is computed using CFA as a modifier. It would also mean using
separate instructions to pop lr and the rest of the callee-saved registers,
which would offset the advantages of doing a tail call.

Instead, this patch disables indirect tail calls when the called function take
four or more arguments and the return address sign and authentication is enabled
for the caller function, conservatively assuming the caller function would spill

This patch is part of a series that adds support for the PACBTI-M extension of
the Armv8.1-M architecture, as detailed here:

The PACBTI-M specification can be found in the Armv8-M Architecture Reference

The following people contributed to this patch:

  • Momchil Velikov
  • Ties Stuij

Diff Detail

Event Timeline

stuij created this revision.Oct 25 2021, 3:33 AM
stuij requested review of this revision.Oct 25 2021, 3:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptOct 25 2021, 3:33 AM
stuij updated this revision to Diff 382031.Oct 25 2021, 9:42 AM

trimmed commit message

stuij edited the summary of this revision. (Show Details)Oct 25 2021, 9:43 AM
stuij added a reviewer: pcc.
stuij added a comment.Nov 15 2021, 7:01 AM

Unfortunately we're seeing an issue where the shrinkwrap pass isn't aware of PACBTI, and moves the prologue further down the function. Now r12 can already have been used before PAC does.

Of course we'll fix that, however it would be great if someone could review this patch to see if there are other things that need to change.

stuij updated this revision to Diff 389148.Nov 23 2021, 3:34 AM

Disable shrinkwrapping for now when using PAC. A separate patch will tackle PAC and shrinkwrapping playing nice.

danielkiss edited the summary of this revision. (Show Details)Nov 26 2021, 8:58 AM
danielkiss accepted this revision.Nov 26 2021, 9:01 AM

LGTM besides NITs.
Could you make clang-format happy?



This revision is now accepted and ready to land.Nov 26 2021, 9:01 AM
stuij updated this revision to Diff 390329.Nov 29 2021, 6:16 AM
stuij marked 2 inline comments as done.

address review comments

stuij updated this revision to Diff 392313.Dec 7 2021, 2:12 AM


This revision was landed with ongoing or failed builds.Dec 7 2021, 2:16 AM
This revision was automatically updated to reflect the committed changes.
RKSimon added inline comments.

@stuij Static analysis is warning about dead code here - we've already handled the Reg == ARM::RA_AUTH_CODE case at line 4519, should this just be:

if (!RC->contains(Reg))
  return Error(AfterMinusLoc, "invalid register in register list");
stuij added inline comments.Mar 28 2022, 5:30 AM

Thanks! Made for this.

Herald added a project: Restricted Project. · View Herald TranscriptMar 28 2022, 5:30 AM