Peter Maydell <[email protected]> writes:
> On Sat, 14 Jun 2025 at 13:50, <[email protected]> wrote:
>>
>> Hi!
>>
>> Is `qemu-aarch64 -cpu neoverse-n1` supposed to emulate the `retaa`
>> instruction?
>>
>> I have a binary called `main_pac` compiled from
>> https://learn.arm.com/learning-paths/servers-and-cloud-computing/pac/example/
>> .
>>
>> The compiling command is `aarch64-linux-gnu-gcc -march=armv8.5-a
>> -fPIC -pedantic -Wall -Wextra -ggdb3 -O0
>> -mbranch-protection=standard -fno-stack-protector -fPIE -static
>> main.c -o main_pac`. The binary includes the `paciasp` and `retaa`
>> instructions associated with ARM PAC.
>>
>> ```
>> (gdb) disas main
>> Dump of assembler code for function main:
>> 0x0000000000400858 <+0>: paciasp
This instruction is in the HINT opcode space so can be NOP.
>> 0x000000000040085c <+4>: stp x29, x30, [sp, #-32]!
>> […]
>> 0x0000000000400898 <+64>: ldp x29, x30, [sp], #32
>> 0x000000000040089c <+68>: retaa
>> End of assembler dump.
>> (gdb) quit
>> ```
>>
>> When emulated using `qemu-aarch64 -cpu neoverse-n1` , the program completes
>> without issues.
>> ```
>> user@dell-op7020:~/learning/arm_learning_path_pac$ qemu-aarch64 -cpu
>> neoverse-n1 main_pac test
>> Hello World!
The code just does:
static bool trans_RETA(DisasContext *s, arg_reta *a)
{
TCGv_i64 dst;
dst = auth_branch_target(s, cpu_reg(s, 30), cpu_X[31], !a->m);
gen_a64_set_pc(s, dst);
s->base.is_jmp = DISAS_JUMP;
return true;
}
Where auth_branch_target does:
static TCGv_i64 auth_branch_target(DisasContext *s, TCGv_i64 dst,
TCGv_i64 modifier, bool use_key_a)
{
TCGv_i64 truedst;
/*
* Return the branch target for a BRAA/RETA/etc, which is either
* just the destination dst, or that value with the pauth check
* done and the code removed from the high bits.
*/
if (!s->pauth_active) {
return dst;
}
truedst = tcg_temp_new_i64();
if (use_key_a) {
gen_helper_autia_combined(truedst, tcg_env, dst, modifier);
} else {
gen_helper_autib_combined(truedst, tcg_env, dst, modifier);
}
return truedst;
}
Essentially if no pauth is active then just returns dest. I think this
matches the pseudocode:
GCSInstruction inst_type;
bits(64) target = X[30, 64];
constant bits(64) modifier = SP[64];
bits(64) modifier2;
boolean use_modifier2 = FALSE;
if IsFeatureImplemented(FEAT_PAuth_LR) && PSTATE.PACM == '1' then
modifier2 = X[16, 64];
use_modifier2 = TRUE;
if use_key_a then
if use_modifier2 && IsFeatureImplemented(FEAT_PAuth_LR) then
target = AuthIA2(target, modifier, modifier2, auth_then_branch);
else
target = AuthIA(target, modifier, auth_then_branch);
else
if use_modifier2 && IsFeatureImplemented(FEAT_PAuth_LR) then
target = AuthIB2(target, modifier, modifier2, auth_then_branch);
else
target = AuthIB(target, modifier, auth_then_branch);
if IsFeatureImplemented(FEAT_GCS) && GCSPCREnabled(PSTATE.EL) then
inst_type = if use_key_a then GCSInstType_PRETAA else GCSInstType_PRETAB;
target = LoadCheckGCSRecord(target, inst_type);
SetCurrentGCSPointer(GetCurrentGCSPointer() + 8);
// Value in BTypeNext will be used to set PSTATE.BTYPE
BTypeNext = '00';
constant boolean branch_conditional = FALSE;
BranchTo(target, BranchType_RET, branch_conditional);
which has no explicit UNDEF for the instruction. I think the
fall-through is to AuthIB:
bits(64) AuthIB(bits(64) x, bits(64) y, boolean is_combined)
constant bits(128) APIBKey_EL1 = APIBKeyHi_EL1<63:0> :
APIBKeyLo_EL1<63:0>;
if !IsAPIBKeyEnabled() then
return x;
else
return Auth(x, y, APIBKey_EL1, FALSE, '1', is_combined);
So I think the correct response if no key is enabled is to return the
address as is unmolested. Which makes sense as you want PAuth code to be
run-able unaltered on systems without it.
>
> This might be a bug, but why are you compiling for armv8.5 and
> then trying to run the code on a CPU type that isn't armv8.5
> in the first place?
>
> thanks
> -- PMM
--
Alex Bennée
Virtualisation Tech Lead @ Linaro