MIPS32添加noncacheable段后二进制文件异常增大的问题求助
我基于MIPS32架构开发嵌入式系统,为让USB软件正常工作,在链接脚本中添加了noncacheable段,将其VMA设置在MIPS32的非缓存区域KSEG1中,相关脚本片段如下:
MEMORY_START = 0x80000000; MEMORY_SIZE = 32*1024*1024; .... _bss_end = .; . = ALIGN(8); .stack : { _system_stack_start = .; _user_stack_start = .; . = . + 0x2000; _system_stack = .; _user_stack_end = .; } /** NONCacheable in KSEG1 **/ . = ALIGN(16); __nocache_phys_start = .; .noncacheable (__nocache_phys_start + 0x20000000) : AT(__nocache_phys_start) { . = ALIGN(4); __noncacheable_start = .; KEEP(*(.usbh_nocache)) *(.noncacheable) *(.noncacheable.*) . = ALIGN(4); __noncacheable_end = .; } __noncacheable_size = __noncacheable_end - __noncacheable_start; __nocache_phys_end = __nocache_phys_start + __noncacheable_size; . = __nocache_phys_end; /** End of NONCacheable **/ __heap_start_phys = .; __heap_limit_phys = SYSTEM_START + SYSTEM_SIZE; __heap_size_phys = __heap_limit_phys - __heap_start_phys; .heap __heap_start_phys : AT(__heap_start_phys) { /* Reserve heap space: advance '.' by __heap_size_phys bytes */ . += __heap_size_phys; /* ← FIXED: use += instead of = addr + size */ } ....
未启用USB栈时,编译得到的文件大小:
-rwxrwxr-x 1 t t 5501260 Jan 30 10:13 'test.bin' -rwxrwxr-x 1 t t 20231224 Jan 30 10:13 'test.elf'
启用USB栈后,文件大小出现异常增长:
-rwxrwxr-x 1 t t 9099008 Jan 30 10:15 'test_new.bin' -rwxrwxr-x 1 t t 20340492 Jan 30 10:15 'test_new.elf'
test_new.bin的大小比之前增加了约5MB,远超过USB栈实际的代码与数据增量。
用mips-linux-gnu-readelf -l分析ELF文件,结果如下:
test.elf的程序头:
$ mips-linux-gnu-readelf -l test.elf Elf file type is EXEC (Executable file) Entry point 0x80100000 There are 3 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align ABIFLAGS 0x33e6a0 0x8042e6a0 0x8042e6a0 0x00018 0x00018 R 0x8 LOAD 0x010000 0x80100000 0x80100000 0x53f14c 0x7f00000 RWE 0x10000 GNU_EH_FRAME 0x33e610 0x8042e610 0x8042e610 0x0002c 0x0002c R 0x4
test_new.elf的程序头:
$ mips-linux-gnu-readelf -l test_new.elf Elf file type is EXEC (Executable file) Entry point 0x80100000 There are 5 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align ABIFLAGS 0x350fd0 0x80440fd0 0x80440fd0 0x00018 0x00018 R 0x8 LOAD 0x010000 0x80100000 0x80100000 0x55242c 0x8abfa0 RWE 0x10000 LOAD 0x56bfa0 0xa09abfa0 0x809abfa0 0x01760 0x01760 RW 0x10000 LOAD 0x56d700 0x809ad700 0x809ad700 0x00000 0x7652900 RW 0x10000 GNU_EH_FRAME 0x350f3c 0x80440f3c 0x80440f3c 0x0002c 0x0002c R 0x4
test_new.elf新增了两个LOAD段,其中第二个是noncacheable段,大小仅为0x1760,但最终二进制文件却增大了约4.5MB。尝试将noncacheable段移至SDATA末尾时,二进制文件增量恢复正常,但链接时出现以下警告或错误:
ld: warning: dot moved backwards before .sbss'
ld: warning: dot moved backwards before .sbss'
或
ld:257 cannot move location counter backwards (from 00000000a09ad720 to 0000000088000000)
collect2: error: ld returned 1 exit status
1. 二进制文件增大的原因
从readelf输出可见,test_new.elf中新增的第三个LOAD段是heap段:它的FileSiz为0(无需写入文件),但MemSiz为0x7652900(约18MB)。问题根源在ELF转BIN的默认行为:
- 第一个LOAD段结束于物理地址0x808abfa0,第二个LOAD段的物理地址从0x809abfa0开始,两者之间存在约4.5MB的空白区域;
- 由于第二个LOAD段的VMA在KSEG1(0xA开头),和第一个LOAD段的VMA(0x8开头)不连续,链接器将其拆分为独立LOAD段;
- 用
objcopy生成BIN时,默认会覆盖所有LOAD段的物理地址范围,导致中间的空白区域被填充为0,最终使BIN文件异常增大。
2. 调整方案
方案一:修改链接脚本,保持物理地址连续
核心思路是让noncacheable段的**物理地址(PhysAddr)**与其他段保持连续,仅通过VMA映射到KSEG1,这样链接器只会生成一个包含所有连续物理地址的LOAD段,不会出现地址空隙。
修改后的链接脚本片段:
MEMORY_START = 0x80000000; MEMORY_SIZE = 32*1024*1024; .... _bss_end = .; . = ALIGN(8); .stack : { _system_stack_start = .; _user_stack_start = .; . = . + 0x2000; _system_stack = .; _user_stack_end = .; } __heap_start_phys = .; __heap_limit_phys = SYSTEM_START + SYSTEM_SIZE; __heap_size_phys = __heap_limit_phys - __heap_start_phys; .heap __heap_start_phys : AT(__heap_start_phys) { . += __heap_size_phys; } /** NONCacheable in KSEG1 - 调整到heap之后,物理地址连续 **/ . = ALIGN(16); __nocache_phys_start = .; .noncacheable (__nocache_phys_start + 0x20000000) : AT(__nocache_phys_start) { . = ALIGN(4); __noncacheable_start = .; KEEP(*(.usbh_nocache)) *(.noncacheable) *(.noncacheable.*) . = ALIGN(4); __noncacheable_end = .; } __noncacheable_size = __noncacheable_end - __noncacheable_start; __nocache_phys_end = __nocache_phys_start + __noncacheable_size; . = __nocache_phys_end; /** End of NONCacheable **/ ....
所有段的物理地址连续后,链接器只会生成一个LOAD段,转BIN时不会填充空白区域。
方案二:优化二进制转换命令,只提取有效段
如果无法调整段顺序,可修改objcopy命令,只提取需要写入FLASH的有效段:
mips-linux-gnu-objcopy -O binary -j .text -j .data -j .rodata -j .noncacheable test_new.elf test_new.bin
通过-j选项指定要包含的段,跳过无需写入的空白区域。
方案三:解决移动段到SDATA末尾的报错
移动段时的报错,是因为位置计数器(.)从KSEG0的0x8开头地址跳到KSEG1的0xA开头地址后,又要回到0x8开头的heap地址,导致计数器向后移动(地址变小),而链接器不允许此操作。解决方法是:
将noncacheable段的物理地址放在所有KSEG0段的最后,保持位置计数器单向递增。
内容的提问来源于stack exchange,提问作者wangt13

