You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MIPS32添加noncacheable段后二进制文件异常增大的问题求助

问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 15:34:53