FPGA上RV32I CPU的链接脚本问题:栈位置不符合预期
RV32I CPU裸机C程序栈基址不符问题
我在FPGA上实现了一款RV32I CPU,此前仅用汇编代码测试。本周尝试运行一个简单C测试程序,用于观察寄存器a的移位1模式,但反汇编后发现栈基址被设为4GB上限以下的最高地址,和我在链接脚本中指定的内存区域不符。
C测试代码
int main(){ register unsigned int a = 1; register unsigned int b = 1; while(1){ a = a << b; if(a == 0x80000000){ a = 1; } } return 0; }
反汇编结果
test.elf: file format elf32-littleriscv Disassembly of section .text: 00000000 <main>: 0: ff010113 add sp,sp,-16 4: 00112623 sw ra,12(sp) 8: 00812423 sw s0,8(sp) c: 00912223 sw s1,4(sp) 10: 01010413 add s0,sp,16 14: 00100093 li ra,1 18: 00100493 li s1,1 1c: 009090b3 sll ra,ra,s1 20: 800007b7 lui a5,0x80000 24: fef09ce3 bne ra,a5,1c <main+0x1c> 28: 00100093 li ra,1 2c: ff1ff06f j 1c <main+0x1c>
链接脚本
ENTRY(main) BRAM_SIZE = 1024; MEMORY{ INSTR(RX) : ORIGIN =0x00000000 , LENGTH = BRAM_SIZE DATA(RWX) : ORIGIN =0x01000000 , LENGTH = BRAM_SIZE } STACK_SIZE = 0x100; /* Section Definitions */ SECTIONS { .text : { KEEP(*(.vectors .vectors.*)) *(.text*) *(.rodata*) } > INSTR /* .bss section which is used for uninitialized data */ .bss (NOLOAD) : { *(.bss*) *(COMMON) } > DATA .data : { *(.data*); } > DATA AT >INSTR /* stack section */ .stack (NOLOAD): { . = ALIGN(8); . = . + STACK_SIZE; . = ALIGN(8); } > DATA _end = . ; }
编译命令
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -ffreestanding -T linkerscript_reduced.ld -o test.elf test.c
问题
明明在链接脚本里指定了内存区域,为什么编译器把栈基址设为4GB上限以下的最高地址?哪里出错了?
原因与解决方法
你的链接脚本仅定义了.stack段的大小和位置,但没有向链接器提供栈指针的初始值。在无操作系统的裸机环境下,链接器默认会将栈指针(sp)初始化为RV32I虚拟地址空间的最高端(0xFFFFFFFF附近),这就是栈基址不符合预期的原因。
解决需要两步:
- 在链接脚本中定义栈顶符号
修改.stack段的定义,明确标记栈顶地址(RISC-V栈向下生长,栈顶是栈区域的起始地址):
/* stack section */ .stack (NOLOAD): { . = ALIGN(8); __stack_top = .; /* 定义栈顶符号 */ . = . + STACK_SIZE; __stack_bottom = .; /* 可选,标记栈底 */ . = ALIGN(8); } > DATA
同时把链接脚本开头的ENTRY(main)改为ENTRY(_start),因为后续要添加自定义启动代码。
- 添加启动代码初始化栈指针
由于使用了-nostdlib,没有标准启动代码,需要自己编写汇编启动代码(比如创建start.S),在进入main前初始化sp:
.section .vectors, "ax" .globl _start _start: la sp, __stack_top /* 加载栈顶地址到sp寄存器 */ j main /* 跳转到main函数 */
编译时将启动代码加入编译命令:
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -ffreestanding -T linkerscript_reduced.ld -o test.elf start.S test.c
补充说明
- 你代码中的
register关键字只是提示编译器优先分配寄存器,最终寄存器分配由编译器决定,当前反汇编中a分配到ra、b分配到s1是正常行为。 - 确认
.stack段未超出DATA区域:DATA起始地址0x01000000,大小1024字节,STACK_SIZE为256字节,栈顶地址会落在0x01000000到0x01000400范围内,不会越界。
内容的提问来源于stack exchange,提问作者lazerbeam
相关产品推荐
相关产品推荐

