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

Pedro Alves
2019-07-17T11:15:52Z

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. 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.

>
> 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.

> --- 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?

> {
> 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

?

> /* 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.

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

Thanks,
Pedro Alves