如何获取RISCV单片机中C语言内联汇编块首指令的PC值?
问题描述
我正在为RISC-V单片机编写代码,需要在非内联汇编所在的文件中,获取某段扩展内联汇编块首指令的程序计数器(PC)值。注意:这段汇编块不是函数中的初始代码。
原因是当执行到达该特定PC时,硬件会触发一些副作用。
以下是我设想的伪C方案(无法运行):
File_A.c
// some_code char routine_name[10] = "routine"; uint32_t routine_start_PC = get_start_PC(routine_name); // some_other_code
File_B.c
void foo_rou(){ // some_code_BEFORE (IMPORTANT!) routine: asm volatile( ... ); }
可行解决方案
针对RISC-V平台,这里提供几种可靠的实现方式:
方法1:导出汇编标签为全局符号
直接将目标汇编块的标签声明为全局可见符号,C代码中可直接引用其地址:
- 修改File_B.c的内联汇编,给目标标签添加全局属性:
void foo_rou(){ // some_code_BEFORE asm volatile ( ".global routine_label\n" "routine_label:\n" // 你的内联汇编指令 "addi x0, x0, 0\n" // 示例指令 ... ); }
如果是GCC环境,也可以在函数内单独用asm(".global routine_label");声明全局符号,再在汇编块前加标签。
- 在File_A.c中通过外部声明获取标签地址:
extern uint32_t routine_label; uint32_t routine_start_PC = (uint32_t)&routine_label;
原理:汇编标签的地址就是对应指令的起始PC值,RISC-V指令地址天然对齐(RV32为4字节,RV64为8字节),直接取符号地址即可得到正确PC。
方法2:通过链接脚本导出标签地址
若需要更灵活的地址控制,可借助链接脚本导出目标标签的绝对地址:
- 保留File_B.c中的
routine:标签,确保汇编器能识别。 - 修改项目的链接脚本,添加如下内容(需替换偏移量为实际值):
PROVIDE(routine_start = ADDR(foo_rou) + 0x10);
其中0x10是routine标签相对于foo_rou函数起始地址的偏移量,可通过objdump -d 目标文件查看反汇编结果得到。
- 在File_A.c中引用该导出符号:
extern uint32_t routine_start; uint32_t routine_start_PC = (uint32_t)&routine_start;
方法3:内联汇编中主动记录PC值
若允许修改目标汇编块,可在汇编开头直接读取当前PC并存储到全局变量:
- 在File_B.c中定义全局变量:
uint32_t routine_pc = 0;
- 修改内联汇编块,加入读取PC的指令(RISC-V用
auipc指令获取当前PC):
void foo_rou(){ // some_code_BEFORE asm volatile ( "auipc t0, 0\n" // 将当前PC加载到t0寄存器 "sw t0, %0\n" // 把PC值存入routine_pc变量 // 你的原有内联汇编指令 ... : "=m"(routine_pc) // 指定输出为内存变量routine_pc : : "t0" // 声明被修改的寄存器,避免编译器冲突 ); }
- 在File_A.c中引用该变量:
extern uint32_t routine_pc; // 注意:需确保foo_rou至少执行过一次,routine_pc才会有有效PC值 uint32_t routine_start_PC = routine_pc;
这种方法适合需要动态获取PC的场景,但依赖汇编块的执行。
注意事项
- RISC-V指令地址必须严格对齐,确保获取的PC是指令起始地址而非中间地址。
- 若开启编译优化(如
-O2),需注意编译器可能重排代码,可给foo_rou添加__attribute__((optimize("O0")))属性,或给内联汇编添加合适的内存/寄存器约束,防止标签位置被优化移动。
内容的提问来源于stack exchange,提问作者PresentEco
相关产品推荐
相关产品推荐

