ARM学习(31)编译器对overlay方式的支持

时间:2024-10-13 20:34:11

ARM学习(31)编译器对overlay方式的支持

1、overlay介绍

overlay:重叠得意思,就是可以重复利用得空间,一般在内存上使用这种空间。比如以Windows操作系统为例,其存储空间(ROM/FLASH)一般相对较大,但是内存相对较少,内存要加载Flash上面得较多数据,就得空间上面重复使用。

比如一个游戏里面油很多动态链接库dll,内存有限,只能加载一部分dll库,当用到一些库时,就会将一些库覆盖掉,然后调用这些库,当然这里有很多替换得算法,比如LRU,least recently used,最近最少使用得会被替换,由于被替换得库不确定,所以加载得地址不确定,这就要求dll可以动态加载,根据加载得地址进行偏移寻址,这就是PIC,位置无关,dll里面得代码均使用相对寻址,所以加载到任何地址均可以使用(理论上)。

嵌入式系统为了运行效率高,往往会在ram上面执行代码(相对Flash),所以ram既要放代码,也要存放数据,相对比较紧张,就会将内存空间重复使用。

嵌入式系统当中往往使用绝对地址寻址,不采用相对地址,笔者这里以不带Linux操作系统得应用场景为例。所以如果需要这种重复利用内存空间,就需要确定一块重复利用得地址,然后加载到这块得代码都需要采用这块得地址来进行编译,即在链接脚本里面指定绝对地址。
在这里插入图片描述
以上图为例,有4个功能代码1-4,都需要运行到动态内存地址,则笔者需要将这4个地址都编译到同一个动态内存的地址,然后需要哪个函数的时候就将哪个函数搬到对应的地址,然后再跳转过去执行。

2、编译器armcc/armclang对overlay的支持

armcc/armclang编译器支持overlay,主要是链接脚本这块的支持,通常情况下,

  • 如果两个.o文件放到同一块地址,
  • 或者两个函数放到同一块地址,都会报错误
    如下:两个overlay区域有重叠,因为链接器的作业就是分配运行地址,当然不能重叠,否则该怎么放置代码和执行code呢?
LR_OVERLAY0 0x30000000  0x1000
{
  ER_OVERLAY0 0x2001E000    0x1000 
  {
    overlay0.o(BANK_SEC, +FIRST)
    overlay0.o(+RO)
    overlay0.o(.text)
  }
}

LR_OVERLAY1 0x30001000  0x1000
{
  ER_OVERLAY1 0x2001E000   0x1000 
  {
    overlay1.o(BANK_SEC, +FIRST)
    overlay1.o(+RO)
    overlay1.o(.text)
  }
}

LR_OVERLAY2 0x30002000  0x1000
{
  ER_OVERLAY2 0x2001E000   0x1000 
  {
    overlay2.o(BANK_SEC, +FIRST)
    overlay2.o(+RO)
    overlay2.o(.text)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
"", line 32 (column 17): Warning: L6329W: Pattern overlay0.o(RO) only matches removed unused sections.
"", line 33 (column 16): Warning: L6314W: No section matches pattern overlay0.o(.text).
"", line 42 (column 17): Warning: L6329W: Pattern overlay1.o(RO) only matches removed unused sections.
"", line 43 (column 16): Warning: L6314W: No section matches pattern overlay1.o(.text).
"", line 52 (column 17): Warning: L6329W: Pattern overlay2.o(RO) only matches removed unused sections.
"", line 53 (column 16): Warning: L6314W: No section matches pattern overlay2.o(.text).
Error: L6221E: Execution region ER_OVERLAY0 with Execution range [0x2001e000,0x2001e080) overlaps with Execution region ER_OVERLAY1 with Execution range [0x2001e000,0x2001e074).
Error: L6221E: Execution region ER_OVERLAY0 with Execution range [0x2001e000,0x2001e080) overlaps with Execution region ER_OVERLAY2 with Execution range [0x2001e000,0x2001e074).
Error: L6221E: Execution region ER_OVERLAY1 with Execution range [0x2001e000,0x2001e074) overlaps with Execution region ER_OVERLAY2 with Execution range [0x2001e000,0x2001e074).
Finished: 0 information, 6 warning and 3 error messages.
make: *** [out/AdvancedClock.axf] Error 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

为了让链接器识别这种情况,把相同的地址放置多个函数,就必须加一个关键字,笔者找到手册上面的关键字overlay。

如下面例子所述,只要在多个想要执行地址的域空间描述上面加上overlay的关键字,该错误就不会报,
在这里插入图片描述
笔者做了尝试果然是这样,

LR_OVERLAY0 0x30000000  0x1000
{
  ER_OVERLAY0 0x2001E000 OVERLAY   0x1000 
  { 
    (OVERLAY_SEC, +FIRST)
    (+RO)
    (.text)
  }
}

LR_OVERLAY1 0x30001000  0x1000
{
  ER_OVERLAY1 0x2001E000 OVERLAY  0x1000 
  {
    (OVERLAY_SEC, +FIRST)
    (+RO)
    (.text)
  }
}

LR_OVERLAY2 0x30002000  0x1000
{
  ER_OVERLAY2 0x2001E000 OVERLAY  0x1000 
  {
    (OVERLAY_SEC, +FIRST)
    (+RO)
    (.text)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在这里插入图片描述
需要注意两点:
1、如果是独立设置加载域,则需要将入口函数声明为root属性,不然跳转的地址异常,可能跑飞等
2、保证函数声明为used,不然链接器会将overlay里面的函数stripped掉(删除掉),因为没有用到。
3、注意声明的OVERLAY 属性要放在执行域 长度属性的前面,不然会报错
4、因为笔者用的cm4架构,跳转的时候需要注意使用奇地址,不然可能会跑飞。

LR_OVERLAY0 0x30000000  0x1000
{
  ER_OVERLAY0 0x2001E000 OVERLAY   0x1000 
  { 
    (+RO)
    (.text)
  }
}

LR_OVERLAY1 0x30001000  0x1000
{
  ER_OVERLAY1 0x2001E000 OVERLAY  0x1000 
  {
    (+RO)
    (.text)
  }
}

LR_OVERLAY2 0x30002000  0x1000
{
  ER_OVERLAY2 0x2001E000 OVERLAY  0x1000 
  {
    (+RO)
    (.text)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
overlay1函数实例,没有root属性,只有used属性。
__attribute__((used))  static  void overlay_handler(u8 overlay_id, u8 func_id)
{
    switch(func_id)
    {
        case 1:
        {
            rt_kprintf("this is overlay func,overlay id=%d func id=%d\r\n", overlay_id, func_id);
        }break;
        default:
        rt_kprintf("this is overlay func,func id=%d,err\r\n", func_id);
        break;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

笔者尝试了如果不加root属性,则overlay1和overlay则会入口函数是编译器生成的code,会异常。
在这里插入图片描述
编译器生成的code如下:不是压栈所操作,入口地址变成了2001E00C,所以可能导致跑飞
在这里插入图片描述
对于生成的veneer code,笔者经过查询资料发现:“veneer code” 指的是一种特殊的代码,用于实现函数调用和分支跳转。
以下场景比较常见:

  1. 跳转表:当目标函数或代码片段地址较大时,veneer代码可以用来实现从一个地方跳转到另一个地方。这是因为ARM处理器有不同的地址模式,例如32位和64位。在一些模式下,直接的分支指令可能无法覆盖整个地址空间,veneer代码帮助在不同地址间进行跳转。
  2. 异常处理:在异常处理和中断服务例程中,veneer代码用于简化和优化跳转逻辑,从而快速且高效地进行跳转和恢复,从而保证系统的稳定性和响应速度。
  3. ABI(应用二进制接口):veneer代码可以帮助在不同的ABI之间进行兼容,使得不同编译器生成的代码可以相互调用。

这些代码通常是由编译器或链接器生成的,用于优化和管理ARM架构下的代码跳转和调用。

正常的code应该如下所示:
在这里插入图片描述

如果不加used以及根区属性,则符号都没有被链接进来,因为overlay的函数本身需要运行态来决定运行哪个函数的,所以静态编译的时候编译器并不知道链接哪个,不过不指定used属性,则就会全部strpped掉。

 static  void overlay_handler(u8 overlay_id, u8 func_id)
{
    switch(func_id)
    {
        case 2:
        {
            rt_kprintf("this is overlay func,overlay id=%d func id=%d\r\n", overlay_id, func_id);
        }break;
        default:
        rt_kprintf("this is overlay func,func id=%d,err\r\n", func_id);
        break;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
如果overlay属性位置放错,就会报如下错误。

"", line 29 (column 36): Error: L6228E: Expected '{', found 'O...'.
"", line 29 (column 36): Error: L6228E: Expected '}', found 'EOF'.
Not enough information to list the image map.
Finished: 1 information, 0 warning and 2 error messages.
make: *** [out/AdvancedClock.axf] Error 1
  • 1
  • 2
  • 3
  • 4
  • 5

关于链接脚本的其他关于overlay的写法如下图所示:

  • region1 不是overlay属性,则region2的地址是region1地址的末地址
  • region1 是overlay属性,且offset是0,则region2和region1的地址一样
  • region1 是overlay属性,且offset不是0,则region2是region1末地址+offset
    在这里插入图片描述
    跳转的时候使用奇地址,不然会报错,因为cm4使用thumb指令,
overlay_handler_fun overlay_handler_func = (overlay_handler_fun)(overlay_EXEC_ADDR+1);
  • 1

在这里插入图片描述

笔者写了一个参考例子如下:
overlay manager:
set_overlay_id,会请求切换当前的bank,
overlay_process,会处理当前的请求,并执行函数。

#include ""


#define overlay_EXEC_ADDR 0x2001E000


#define overlay0_SAVE_ADDR  0x08020000
#define overlay1_SAVE_ADDR  0x08020400
#define overlay2_SAVE_ADDR  0x08020800
#define overlay_FLASH_BASE  overlay0_SAVE_ADDR


typedef void (*overlay_handler_fun)(u8 overlay_id,u8 func_id);

u8 current_overlay_id_g = 0;
u8 set_overlay_id_g = 0;
void overlay_init()
{
    u32 current_overlay_flash_addr = overlay_FLASH_BASE + current_overlay_id_g*0x400;
    STMFLASH_Read(current_overlay_flash_addr, (u32*)overlay_EXEC_ADDR, 0x400);
    overlay_handler_fun overlay_handler_func = (overlay_handler_fun)(overlay_EXEC_ADDR+1);
    (*overlay_handler_func)(current_overlay_id_g, current_overlay_id_g);
}

void set_overlay_id(u8 req_overlay_id)
{
    set_overlay_id_g = req_overlay_id;
}

void overlay_process()
{
    if(set_overlay_id_g != current_overlay_id_g)
    {
        current_overlay_id_g = set_overlay_id_g;
        u32 current_overlay_flash_addr = overlay_FLASH_BASE + current_overlay_id_g*0x400;
        STMFLASH_Read(current_overlay_flash_addr, (u32*)overlay_EXEC_ADDR, 0x400);
        overlay_handler_fun overlay_handler_func = (overlay_handler_fun)(overlay_EXEC_ADDR+1);
        (*overlay_handler_func)(current_overlay_id_g, current_overlay_id_g);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
#include ""


overlay0.c
__attribute__((section("overlay_SEC"),used))  static void overlay_handler(u8 overlay_id, u8 func_id)
{
    switch(func_id)
    {
        case 0:
        {
            rt_kprintf("this is overlay func,overlay id=%d func id=%d\r\n", overlay_id, func_id);
        }break;
        default:
         rt_kprintf("this is overlay func,func id=%d,err\r\n", func_id);
        break;
    }
}
overlay1.c
__attribute__((section("overlay_SEC"),used))  static  void overlay_handler(u8 overlay_id, u8 func_id)
{
    switch(func_id)
    {
        case 1:
        {
            rt_kprintf("this is overlay func,overlay id=%d func id=%d\r\n", overlay_id, func_id);
        }break;
        default:
        rt_kprintf("this is overlay func,func id=%d,err\r\n", func_id);
        break;
    }
}
overlay2.c
 __attribute__((section("overlay_SEC"),used)) static  void overlay_handler(u8 overlay_id, u8 func_id)
{
    switch(func_id)
    {
        case 2:
        {
            rt_kprintf("this is overlay func,overlay id=%d func id=%d\r\n", overlay_id, func_id);
        }break;
        default:
        rt_kprintf("this is overlay func,func id=%d,err\r\n", func_id);
        break;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

实际效果如下:
在这里插入图片描述
在这里插入图片描述
如果有Trace32调试器,可以通过Trace32对overlay的支持来进行调试。
trace32 设置指令:

  1. ON
  2. 自动识别ID
  3. 查看当前处于哪个overlay
    由下图可以可以看到笔者的overlay 处于overlay1,根据右边的打印,然后trace32调试器也显示的overlay1.
    在这里插入图片描述
    笔者切到overlay2,则对应的调试器显示overlay2。
    在这里插入图片描述

3、参考

armcc官方手册
DUI0472M_armcc_user_guide
DUI0474M_armlink_user_guide