Re: [PATCH] AArch64 pauth: Indicate unmasked addresses in backtrace

Alan Hayward
2019-07-17T13:36:14Z

> On 17 Jul 2019, at 12:15, Pedro Alves <palves@redhat.com> wrote:
>
> On 7/17/19 9:14 AM, Alan Hayward wrote:
>> Armv8.3-a Pointer Authentication causes the function return address to be
>> obfuscated on entry to some functions. GDB must unmask the link register in
>> order to produce a backtrace.
>>
>> The following patch adds markers of <unmasked> to the bracktrace, to indicate
>> which addresses needed unmasking.
>>
>> For example, consider the following backtrace:
>>
>> (gdb) bt
>> 0 0x0000000000400490 in puts@plt ()
>> 1 0x00000000004005dc in foo ("hello") at cbreak-lib.c:6
>> 2 0x0000000000400604<unmasked> in bar () at cbreak-lib.c:12
>> 3 0x0000000000400620<unmasked> in barbar () at cbreak.c:17
>> 4 0x00000000004005b4 in main () at cbreak-3.c:10
>>
>> The functions in the cbreak-lib use pointer auth, obfuscating the return address
>> to the previous function. The caused the addresses of bar and barbar to require
>> unmasking in order to unwind the backtrace.
>>
>> Alternatively, I considered replacing <unmasked> with a single chracter, such
>> as * for brevity reasons, but felt this would be non obvious for the user.
>
> I don't have a particular suggestion, though my first reaction was that
> it seemed a bit verbose.
>
> IMHO, the marker doesn't have to stand out and be expressive, since users can
> always look at the manual.

Reading the manual is an assumption I’m not sure is anywhere near the common case.
Saying that, I agree we shouldn’t be designing the output for the non-readers.

This comment has reminded me I need to add something to the manual as part of this
patch.

> Once they learn something, often being concise
> helps -- or in other words, once you learn what "<unmasked>" or "U" or whatever
> is, and you're used to it, what would you rather see? What's the main
> information you're looking for when staring at the backtrace? Thoughts
> like that should guide the output too, IMO.

PAC is the official abbreviation for the feature, so maybe :PAC works best.

(gdb) bt
0 0x0000000000400490 in puts@plt ()
1 0x00000000004005dc in foo ("hello") at cbreak-lib.c:6
2 0x0000000000400604:PAC in bar () at cbreak-lib.c:12
3 0x0000000000400620:PAC in barbar () at cbreak.c:17
4 0x00000000004005b4 in main () at cbreak-3.c:10

Some of my attempts at different representations:
2 0x0000000000400604* in bar () at cbreak-lib.c:12
2 0x0000000000400604! in bar () at cbreak-lib.c:12
2 0x0000000000400604U in bar () at cbreak-lib.c:122
2 0x0000000000400604:U in bar () at cbreak-lib.c:122
2 0x0000000000400604 in bar () at cbreak-lib.c:12
2 0x0000000000400604[U] in bar () at cbreak-lib.c:12
2 0x0000000000400604<M> in bar () at cbreak-lib.c:12
2 0x0000000000400604 in bar () at cbreak-lib.c:12
2 0x0000000000400604<PAC> in bar () at cbreak-lib.c:12
2 0x0000000000400604PAC in bar () at cbreak-lib.c:12
2 0x0000000000400604:PAC in bar () at cbreak-lib.c:12
2 0x0000000000400604,PAC in bar () at cbreak-lib.c:12

I found a single character was too hidden. A single character or symbol was also
a little confusing - my brain read U as unsigned, * as pointer, [] as an array.

I also like ,PAC as it might be easier to add future extensions.

>
>>
>> An extra bool is added alongside the prev_pc in the frame struture. At the
>> point at which the link register is unmasked, the AArch64 port calls into frame
>> to sets the bool. This seemed the most efficient way of doing it.
>>
>> aarch64_frame_unmask_address is designed to work for any address, however at
>> the momoment it is only used for the link address. An extra default parameter
>> is added to ensure it only caches for the link register.
>
> typo: “momoment"

>
>>
>> I expect this will potentially cause issues with some tests in the testsuite
>> when Armv8.3 pointer authentication is used. This should be fixed up in the
>> the future once real hardware is available for full testsuite testing.
>>
>> 2019-07-17 Alan Hayward <alan.hayward@arm.com>
>>
>> * NEWS: Expand the Pointer Authentication entry.
>> * aarch64-tdep.c (aarch64_frame_unmask_address): Mark frame if
>> unmasking link register.
>> * frame.c (struct frame_info): Add masked variable.
>
> * frame.c (struct frame_info): Add "masked" variable.
>
>> (frame_set_previous_pc_masked) (frame_get_pc_masked): New function.
>
> (frame_set_previous_pc_masked, frame_get_pc_masked): New functions.
>
>> * frame.h (frame_set_previous_pc_masked) (frame_get_pc_masked): New
>> declaration.
>
> * frame.h (frame_set_previous_pc_masked, frame_get_pc_masked): New
> declarations.

Ok to all these.

>
>> --- a/gdb/aarch64-tdep.c
>> +++ b/gdb/aarch64-tdep.c
>> @@ -251,12 +251,13 @@ class instruction_reader : public abstract_instruction_reader
>> } // namespace
>>
>> /* If address signing is enabled, mask off the signature bits from ADDR, using
>> - the register values in THIS_FRAME. */
>> + the register values in THIS_FRAME. IS_LR indicates if ADDR is from the link
>> + register for the current frame. */
>>
>> static CORE_ADDR
>> aarch64_frame_unmask_address (struct gdbarch_tdep *tdep,
>> - struct frame_info *this_frame,
>> - CORE_ADDR addr)
>> + struct frame_info *this_frame, CORE_ADDR addr,
>> + bool is_lr = true)
>
> I didn't see anywhere in the patch that passes is_lr == true?
>
> Looks like the patch is a nop?

Yes, the is_lr check will always be true.
The function is designed to be for any address, but right now it’s only used for
the link register. But, I didn’t want someone in the future using it as a generic
address unmask function and it causing the link register to be marked.

An other option was to add a warning to the comment block above the function. Or
do the lr marking in the caller (but then that adds extra logic).

Maybe the better option would be to rename the function to
aarch64_frame_unmask_lr_address.

>
>
>> {
>> if (tdep->has_pauth ()
>> && frame_unwind_register_unsigned (this_frame,
>> @@ -265,6 +266,11 @@ aarch64_frame_unmask_address (struct gdbarch_tdep *tdep,
>> int cmask_num = AARCH64_PAUTH_CMASK_REGNUM (tdep->pauth_reg_base);
>> CORE_ADDR cmask = frame_unwind_register_unsigned (this_frame, cmask_num);
>> addr = addr & ~cmask;
>> +
>> + /* If addr was taken from the Link Register, then record this in the
>> + frame. */
>> + if (is_lr)
>> + frame_set_previous_pc_masked (this_frame);
>> }
>>
>> return addr;
>> diff --git a/gdb/frame.c b/gdb/frame.c
>> index 84e0397db9..b7bb44986e 100644
>> --- a/gdb/frame.c
>> +++ b/gdb/frame.c
>> @@ -124,6 +124,8 @@ struct frame_info
>> struct {
>> enum cached_copy_status status;
>> CORE_ADDR value;
>> + /* Did VALUE require unmasking when being read. */
>> + bool masked;
>> } prev_pc;
>>
>> /* Cached copy of the previous frame's function address. */
>> @@ -161,6 +163,21 @@ struct frame_info
>> const char *stop_string;
>> };
>>
>> +/* See frame.h. */
>> +
>> +void
>> +frame_set_previous_pc_masked (struct frame_info *frame)
>> +{
>> + frame->prev_pc.masked = true;
>> +}
>> +
>> +/* See frame.h. */
>> +
>> +bool frame_get_pc_masked (struct frame_info *frame)
>
> Formatting -- line break after bool.

>
>> +{
>> + return frame->next->prev_pc.masked;
>
> I think this should assert that there's a frame->next,
> and that its status is CC_VALUE.

>
>> +}
>> +
>
> How about making the naming of the setter/getter consistent
> with other setters/getters in the file, get_frame_type,
> get_frame_pc, get_frame_program_space, etc, etc.
>
> set_frame_previous_pc_masked
> get_frame_pc_masked
>
> ?

Ok with all these.

>
>> /* A frame stash used to speed up frame lookups. Create a hash table
>> to stash frames previously accessed from the frame cache for
>> quicker subsequent retrieval. The hash table is emptied whenever
>> diff --git a/gdb/frame.h b/gdb/frame.h
>> index a79eeeeab1..61c12ec0b2 100644
>> --- a/gdb/frame.h
>> +++ b/gdb/frame.h
>> @@ -930,4 +930,13 @@ extern const gdb::option::option_def set_backtrace_option_defs[2];
>> /* The values behind the global "set backtrace ..." settings. */
>> extern set_backtrace_options user_set_backtrace_options;
>>
>> +/* Mark that the PC value is masked for the previous frame. */
>> +
>> +extern void frame_set_previous_pc_masked (struct frame_info *frame);
>> +
>
>> +/* Get whether the PC value is masked for the given frame. */
>> +
>> +extern bool frame_get_pc_masked (struct frame_info *frame);
>> +
>> +
>> #endif /* !defined (FRAME_H) */
>> diff --git a/gdb/stack.c b/gdb/stack.c
>> index 9b1d1a6856..cef1c29a82 100644
>> --- a/gdb/stack.c
>> +++ b/gdb/stack.c
>> @@ -1254,7 +1254,11 @@ print_frame (const frame_print_options &fp_opts,
>> {
>> annotate_frame_address ();
>> if (pc_p)
>> - uiout->field_core_addr ("addr", gdbarch, pc);
>> + {
>> + uiout->field_core_addr ("addr", gdbarch, pc);
>> + if (frame_get_pc_masked (frame))
>> + uiout->text ("<unmasked>");
>
> Did you consider whether a MI frontend would want to show this
> in its address column?
>
> Also, sounds like you'd want to tweak fprint_frame too,
> for debugging.

I’ll add both of those into the patch too.

Thanks for the review!

Alan.

>
>> + }
>> else
>> uiout->field_string ("addr", "<unavailable>",
>> ui_out_style_kind::ADDRESS);
>>
>
> Thanks,
> Pedro Alves