自定义RISC-V CPU模拟器中malloc返回错误内存区域的问题
RISC-V自定义CPU模拟器malloc地址异常问题分析
我开发了一款自定义RISC-V CPU模拟器,其余功能均正常,但malloc()及同类内存分配函数存在异常——返回的指针指向FLASH内存区域,未落在RAM的起始地址范围内。
内存映射配置
MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 16384K /* KiB */ RAM (rw) : ORIGIN = 0x20000000, LENGTH = 512K /* KiB */ }
测试代码与现象
基础测试代码
#include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> extern uint32_t _sidata; /* Start of .data in FLASH */ extern uint32_t _sdata; /* Start of .data in RAM */ extern uint32_t _edata; /* End of .data in RAM */ extern uint32_t _sbss; /* Start of .bss in RAM */ extern uint32_t _ebss; /* End of .bss in RAM */ void initialize_memory(void) { // Copy initialized variables from FLASH to RAM (.data section) uint32_t *src = &_sidata; uint32_t *dest = &_sdata; while (dest < &_edata) { *dest++ = *src++; } // Zero-initialize the .bss section in RAM dest = &_sbss; while (dest < &_ebss) { *dest++ = 0; } } int main(void){ initialize_memory(); int *ptr1 = (int*)malloc(4*sizeof(int)); __asm__("nop"); // 用于定位汇编位置 __asm__("lui t0, 0x80000"); // 输出地址 __asm__("mv t1, a5"); __asm__("sw t1, 0(t0)"); // 存储malloc返回值 __asm__("nop"); }
测试结果
通过内嵌汇编观测到,0x80000000地址存储的值为8,即malloc返回的地址是0x8,而非预期的0x20000008这类RAM区域地址。二次分配测试中,第二次malloc返回值为0x20,依然落在FLASH地址空间。
对应RISC-V汇编片段
000000bc <main>: bc: fe010113 addi sp,sp,-32 c0: 00112e23 sw ra,28(sp) c4: 00812c23 sw s0,24(sp) c8: 02010413 addi s0,sp,32 cc: f4dff0ef jal 18 <initialize_memory> d0: 01000513 li a0,16 d4: 038000ef jal 10c <malloc> d8: 00050793 mv a5,a0 dc: fef42623 sw a5,-20(s0) e0: 00000013 nop e4: 800002b7 lui t0,0x80000 e8: 00078313 mv t1,a5 ec: 0062a023 sw t1,0(t0) # 80000000 <_estack+0x5ff80004> f0: 00000013 nop f4: 00000793 li a5,0 f8: 00078513 mv a0,a5 fc: 01c12083 lw ra,28(sp) 100: 01812403 lw s0,24(sp) 104: 02010113 addi sp,sp,32 108: 00008067 ret
二次分配测试代码
... int *ptr1 = (int*)malloc(4*sizeof(int)); __asm__("nop"); __asm__("lui t0, 0x80000"); __asm__("mv t1, a5"); __asm__("sw t1, 0(t0)"); __asm__("nop"); int *ptr2 = (int*)malloc(4*sizeof(int)); __asm__("nop"); __asm__("lui t0, 0x80000"); __asm__("mv t1, a5"); __asm__("sw t1, 0(t0)"); __asm__("nop"); }
当前链接脚本片段
.data : { . = ALIGN(4); _sdata = .; *(.data) *(.data*) *(.*data*) . = ALIGN(4); _edata = .; } >RAM AT> FLASH /* } AT> FLASH */ /* } >FLASH AT>RAM */ _sibss = LOADADDR(.bss); . = ALIGN(4); .bss : { _sbss = .; __bss_start__ = _sbss; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); _ebss = .; __bss_end__ = _ebss; /* } >RAM */ /* } >RAM */ } >RAM AT> FLASH ._user_heap_stack : { . = ALIGN(8); PROVIDE ( __heap_start__ = . ); . = . + 0x1000; PROVIDE ( __heap_end__ = . ); } >RAM
问题原因分析
malloc的底层实现依赖链接脚本中定义的__heap_start__和__heap_end__符号确定堆的地址范围。从链接脚本可以看出:
.data和.bss段虽然指定了>RAM AT> FLASH(加载到RAM,存储在FLASH),但链接器默认使用FLASH的起始地址0x00000000作为链接地址基准。._user_heap_stack段中的.(当前地址)是基于链接地址计算的,即.bss段结束后的偏移地址,而非RAM的物理起始地址0x20000000加上偏移。- 最终
__heap_start__被设置在FLASH的地址区间,导致malloc从该地址开始分配,返回的地址自然落在FLASH范围内。
地址正确性判断与冲突风险
- 地址正确性判断:正确的malloc返回地址必须落在RAM的地址区间
0x20000000到0x20080000(512KiB)内。当前返回的0x8、0x20均属于FLASH地址空间,明显错误。 - 冲突风险:这些地址会直接与FLASH中预加载的程序代码、.data段镜像数据冲突。若模拟器允许写FLASH,写入操作会覆盖原有内容导致程序崩溃;若FLASH为只读,malloc分配的地址无法写入,直接触发异常。
修复方案
修改链接脚本,将.data、.bss和堆的链接地址基准切换到RAM的物理起始地址:
SECTIONS { . = ORIGIN(FLASH); /* 代码段等FLASH区域的定义 */ .text : { *(.text*) } >FLASH /* 切换链接地址基准到RAM起始地址 */ . = ORIGIN(RAM); .data : { . = ALIGN(4); _sdata = .; *(.data) *(.data*) *(.*data*) . = ALIGN(4); _edata = .; } >RAM AT> FLASH .bss : { . = ALIGN(4); _sbss = .; __bss_start__ = _sbss; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); _ebss = .; __bss_end__ = _ebss; } >RAM AT> FLASH ._user_heap_stack : { . = ALIGN(8); PROVIDE ( __heap_start__ = . ); . = . + 0x1000; PROVIDE ( __heap_end__ = . ); } >RAM }
此修改后,链接器会将.data、.bss和堆的链接地址都设置在RAM的物理地址范围内,malloc即可正确返回RAM中的地址。
内容的提问来源于stack exchange,提问作者Filip
相关产品推荐
相关产品推荐

