如何确保RISC-V自定义PAC/AUT指令编译后成对出现?
我正在修改RISC-V ISA,为返回地址添加认证(pac指令)与校验(aut指令)逻辑,要求函数调用和返回时两者成对出现。使用RISC-V GNU工具链编译程序时,发现aut指令数量仅为pac的一半,导致因未认证RA超出范围引发取指段错误。我已在riscv_expand_prologue和riscv_expand_epilogue函数中分别添加了pac和aut相关指令代码,但问题仍存在,请问如何让这两类指令在函数首尾正确成对生成?
附加已添加的编译器代码片段:
void riscv_expand_prologue (int style) { ... //veriable defination and initialization if (cfun->machine->naked_p) { gcc_assert (style == NORMAL_RETURN); emit_jump_insn (gen_return ()); return; } + //load key + insn = emit(gen_lui(gen_rtx_REG(Pmode,T3_REGNUM), + GEN_INT(32))); + insn = emit(gen_slli(gen_rtx_REG(Pmode,T3_REGNUM), + gen_rtx_REG(Pmode,T3_REGNUM), + GEN_INT(32))); + //inserting pac into ra + insn = emit(gen_pac(gen_rtx_REG(Pmode,RETURN_ADDR_REGNUM), + gen_rtx_REG(Pmode,RETURN_ADDR_REGNUM), + gen_rtx_REG(Pmode,T3_REGNUM))); + add_reg_note (insn, REG_CFA_TOGGLE_RA_MANGLE, const0_rtx); + RTX_FRAME_RELATED_P (insn) = 1; ... } void riscv_expand_epilogue (int style) { ... //veriable defination and initialization if (cfun->machine->naked_p) { gcc_assert (style == NORMAL_RETURN); emit_jump_insn (gen_return ()); return; } + /* pac */ + //load key + insn = emit(gen_lui(gen_rtx_REG(Pmode,T3_REGNUM), + GEN_INT(32))); + insn = emit(gen_slli(gen_rtx_REG(Pmode,T3_REGNUM), + gen_rtx_REG(Pmode,T3_REGNUM), + GEN_INT(32))); + //authenticate ra and strip pac from ra + insn = emit(gen_aut(gen_rtx_REG(Pmode,RETURN_ADDR_REGNUM), + gen_rtx_REG(Pmode,RETURN_ADDR_REGNUM), + gen_rtx_REG(Pmode,T3_REGNUM))); + add_reg_note (insn, REG_CFA_TOGGLE_RA_MANGLE, const0_rtx); + RTX_FRAME_RELATED_P (insn) = 1; ... }
以下是几个针对性的排查和修复方向:
处理非标准返回路径
仅在riscv_expand_epilogue中添加aut只能覆盖函数末尾的标准返回,但很多函数存在提前return、尾调用或异常跳转等路径,这些路径不会触发epilogue逻辑,导致遗漏aut。- 对于提前
return:在GCC的lower阶段或指令生成阶段,识别所有直接返回的指令,在跳转前插入aut校验逻辑; - 对于尾调用:尾调用会直接复用RA寄存器跳转,不需要保存和恢复RA,因此要在prologue阶段判断是否为尾调用函数,避免生成
pac,或者如果已生成pac,必须先执行aut再进行尾调用跳转。
- 对于提前
覆盖特殊函数属性场景
除了naked函数,部分函数可能带有__attribute__((no_prologue))或__attribute__((no_epilogue))属性,导致你的pac/aut代码被跳过。需要在代码中增加对这些属性的判断,确保符合条件的函数仍能生成配对指令。防止优化阶段误删指令
GCC的优化模块可能会将未标记为必要的aut指令误判为冗余代码删除。要确保aut指令的寄存器注释和框架标记正确:- 确认
REG_CFA_TOGGLE_RA_MANGLE注释被调试信息和优化器正确识别,标记RA寄存器的状态变化; - 可以给
aut指令添加REG_NO_DESTROY标记,避免被寄存器分配或优化阶段误处理。
- 确认
修正密钥加载逻辑
当前代码在prologue和epilogue中重复加载密钥,且32位RISC-V系统下slli32位会导致寄存器清零(密钥变成0),这会让pac/aut失去作用甚至引发错误。建议:- 统一密钥加载逻辑,比如将密钥存入全局内存或固定寄存器,避免重复操作;
- 根据目标架构(32/64位)调整密钥生成代码,确保密钥值符合预期。
通过RTL dump验证指令生成
使用-fdump-rtl-all参数编译目标程序,查看每个函数的RTL中间代码输出,对比哪些函数只生成了pac没有aut,分析这些函数的结构特征(比如多返回路径、尾调用、特殊属性),针对性修复遗漏场景。
内容的提问来源于stack exchange,提问作者Yuhe

