TASKING. LSL(LINKER SCRIPT LANGUAGE)文档翻译

链接脚本语言 (LSL)
TASKING TriCore 工具集的技巧

简介

TASKING® 在TriCore™ VX工具集中提供的链接器/加载器功能丰富,能够帮助创建高效应用程序。
它支持优化选项、多核开发、增量链接,并可通过高级的链接脚本语言 (LSL) 进行控制。LSL 是一种
非常强大的语言,许多用户利用它来根据特定需求优化应用程序。

本文件提供了多个有用的技巧,帮助您充分利用LSL的功能。我们的支持团队与许多用户一起进行了
LSL调优,并收集了常用的提示。我们相信这些提示也可以帮助您成为LSL专家。如果您有任何问题或
建议,可以通过support@tasking.com联系我们。

如需了解有关链接器和LSL的详细信息,请参阅用户手册的以下章节:第7章“使用链接器”,10.5“链接
器选项”以及第16章“链接脚本语言 (LSL)”。

LSL代码核心关联和数据核心关联

TASKING TriCore工具提供了语言扩展 __share、__private0、__private1、__private2
__clone,用于将代码段或数据段分配到核心本地内存中。您也可以通过以下编译指令实现相同的效果:

#pragma code_core_association share | private{012} | clone
#pragma data_core_association share | private{012} | clone

这种方法会影响链接器用于正确分配段的名称,通过在段名称后添加核心关联名称。例如,一个必须
放置在核心1本地内存中的非初始化变量段将命名为 .bss.private1.module_name.variable_name

当C源代码不可用时(例如由于使用了第三方库或目标文件已认证,因此不可更改源代码),无法使用
上述C语言扩展或编译指令进行核心分配。在这种情况下,可以使用链接器 LSL 语言关键字 modify input
为段分配不同的核心。

 file_1.c
 int my_var_1;
 void init_func(void);
 void main(void)
 {
	init_func();
	while(1)
	{
	__nop();
	}
 }  
 void init_func(void)
 {
	my_var_1 = 0x1234;
 }

要将函数 init_func 从闪存移动到核心0的本地RAM中,以便仅由核心0执行,可以在LSL文件中使
用以下条目。启动代码将负责将函数代码的闪存镜像复制到核心0的本地RAM中:

// section_setup entry for the source location (here virtual linear memory, vtc)
section_setup mpe:vtc:linear
{
    // Link time code core association private to assign the section to
    // core0 memory
    modify input ( space = mpe:tc0:linear )
    {
        select “.text.file_1.init_func”;
    }
}
 
// section_layout entry for the placement of the section in TC0 local PSPR0 memory
// and the ‘copy’ keyword for the linker to create a ROM copy section
// for the initialized code
section_layout :tc0:linear
{
	group TC0_FUNCTIONS ( ordered, run_addr=mem:mpe:pspr0, copy )
	{
        select “.text.file_1.init_func”;
    }
}

链接器生成的映射文件显示了结果。

在闪存中的初始化段的ROM副本:

 + Space mpe:vtc:linear (MAU = 8bit) 
+---------------------------------------------------------------------------------------------+
 |Chip       |Group|Section                       |Size (MAU)|Space addr|Chip addr |Alignment |
 |============================================================================================|
 ...
 |mpe:pflash0|     |[.text.file_1.init_func] (367)|0x0000000e|0x800000ac|0x000000ac|0x00000002|
 ...

放置在核心本地PSPR0内存中:

 + Space mpe:tc0:linear (MAU = 8bit)
 +-----------------------------------------------------------------------------------------------+
 |Chip     |Group        |Section                     |Size (MAU)|Space addr|Chip addr|Alignment |
 |===============================================================================================|
 ...
 |mpe:pspr0|TC0_FUNCTIONS|.text.file_1.init_func (169)|0x0000000e|0x70100000|0x0      |0x00000002|
 ...

溢出关键字用于分布式输出段

输出段用于为特定段保留专用的内存范围。例如,假设您有不同的内存区域可用,如AURIX CPU的
DSPR0、DSPR1 RAM,而选定的段可能无法全部放入一个输出段。此时,您可以定义一个溢出段,
当“主”段大小不足以容纳所有分配的段时,才会创建该溢出段。

溢出输出段的大小可以是静态的,也可以通过 blocksize 条目实现自适应。如果需要容纳所有段,
则 blocksize 值会增加段的大小。

您可以将 size 关键字应用于输出节以及溢出输出节。但是,blocksize 关键字只能应用于非溢出输
出节。

/* Create some arrays with an overall size larger than the output section to provoke the need for 
the overflow section. */
unsigned int i_arr_1[0x800];
unsigned int i_arr_2[0x800];
unsigned int i_arr_3[0x800];
group ( ordered, run_addr = mem:mpe:dspr0 )
{
    /* Define output section BSS_DATA with a size of 16kB in memory DSPR0 and 
     * assign the arrays defined in file_1.c to this section.
     * Use overflow output section BSS_DATA_OVERFLOW for sections
     * that do not fit in BSS_DATA. */
    section “BSS_DATA” (size=16k, attributes=rw, overflow = 
	
“BSS_DATA_OVERFLOW”)
	{
		select “.bss.file_1.i_arr*”;
    }
}
group ( ordered, run_addr = mem:mpe:dspr1 )
{
    /* If the available space of 16kB is not enough for all sections starting 
     * with the name .bss.file_1.i_arr
     * this overflow section with a size
     * of 8 kB is used for the remaining sections. */
    section “BSS_DATA_OVERFLOW” (size=8k, attributes=rw)
    /* Instead of using a fixed absolute size for the output section you can
     * use the blocksize keyword to specify an adaptive size.
     * Then the size of the output section increases to a multiple of 
     * the blocksize value. */
    /* section “BSS_DATA_OVERFLOW” (blocksize=1k, attributes=rw) */
    {      
    }
}

+ Space mpe:vtc:linear (MAU = 8bit)
+-----------------------------------------------------------------------------------+
|Chip     |Group|Section                |Size (MAU)|Space addr|Chip addr |Alignment |
|===================================================================================|
|mpe:dspr1|     |BSS_DATA_OVERFLOW (371)|0x00002000|0x60000000|0x0       |0x00000002|
|mpe:dspr0|     |BSS_DATA (370)         |0x00004000|0x70005000|0x00005000|0x00000002|

用于从RAM内存运行代码的OVERLAY关键字

当您有一个需要在RAM内存中执行的函数时,需要一个RAM到ROM的复制循环,将函数代码从闪存
复制到运行时地址范围。启动代码可以自动执行此操作。为此,copy 关键字应用于LSL组定义:

section_layout :vtc:linear
{
//  generate a ROM copy section and a copy table entry for the startup code
//  in order to copy the code from ROM to RAM during startup
    group INIT_CODE ( ordered, run_addr=mem:mpe:lmuram, copy )
    {
	select “.text.file_1.func_1”;
    }
}

基于此关键字,链接器为初始化函数创建一个复制表条目,并由启动代码调用的初始化函数处理,
以将代码从ROM复制到RAM内存。

如果启动代码没有执行初始化,则应由应用代码稍后执行,将copy关键字更改为overlay关键字。初
始化过程本身是通过所谓的链接器标签实现的,以确定ROM复制组的起始和结束地址。这些标签在
应用代码执行的复制循环中使用。

overlay关键字也用于不同的目的。如果您需要多个节共享同一RAM内存区域,可以使用overlay来
实现。此用例在本节中未涉及。

section_layout :vtc:linear
{
	/* generate a ROM copy section for initialized code */
    group INIT_CODE (overlay, ordered, run_addr=mem:mpe:dspr0)
    {
        select “.text.file_1.func_1”;
    }
    /* assign the ROM copy of the initialized section to a group to determine start and end address */
    group ROM_COPY_INIT_CODE (ordered, load_addr)
    {
        select “.text.file_1.func_1”;
    }
}

要在C源代码中使用链接器标签,需要添加extern声明:

/* use a linker label to determine the start address of the group in flash */
extern __far unsigned short _lc_gb_ROM_COPY_INIT_CODE;
/* use a linker label to determine the end address of the group in flash */
extern __far unsigned short _lc_ge_ROM_COPY_INIT_CODE;
/* use a linker label to determine the start address of the overlay group in RAM memory */
extern __far unsigned short _lc_gb_INIT_CODE;

初始化RAM函数的复制循环可以如下所示:

/* load the start address of the ROM copy group into source pointer */
source = &_lc_gb_ROM_COPY_INIT_CODE;
/* load the start address of the RAM group into destination pointer */
dest = &_lc_gb_INIT_CODE;
/* initialize the RAM function using a copy loop */
for(loopcount=0; loopcount < 
(&_lc_ge_ROM_COPY_INIT_CODE-&_lc_gb_ROM_COPY_INIT_CODE); loopcount++)
{
    *dest++ = *source++;
}

在map文件中,覆盖组显示为一个节,因此组名不会列在组列中:

+ Space mpe:vtc:linear (MAU = 8bit)
+------------------------------------------------------------------------------------------------------+
|Chip       | Group            |Section                    |Size (MAU)|Space addr|Chip addr |Alignment |
|======================================================================================================|
|mpe:dspr0  |                  |INIT_CODE (364)            |0x0000000e|0x70000000|0x0       |0x00000002|
|mpe:pflash0|ROM_COPY_INIT_CODE|[.text.file_1.func_1] (365)|0x0000000e|0x80000024|0x00000024|0x00000002|

使用SECTION_SETUP关键字时的注意事项:

当您使用LSL组选择节时,例如用于在内存中进行专用放置,可能会发生节未按预期分配的情况。尽
管组名是可选的,但作为最佳实践,建议使用组名,因为当分配成功时,组名会显示在map文件中。
例如:

section_layout :vtc:linear
{
 // group entry to place a non initialized far addressed data section in LMURAM memory
    group MY_DATA ( ordered, run_addr=mem:mpe:lmuram )
    {
	    select “.bss.file_1.my_var_2”;
    }
}

+ Space mpe:vtc:linear (MAU = 8bit)
+-----------------------------------------------------------------------------------------+
| Chip      |Group  |Section                   |Size (MAU)|Space addr|Chip addr|Alignment |
|=========================================================================================|
|
...
| mpe:lmuram|MY_DATA|.bss.file_1.my_var_2 (170)|0x00000004|0x90000000|0x0      |0x00000002|

如果您不指定组名,则节可能会意外地放置在组的范围内,并且在您对项目的C源代码进行修改后,
这种情况很可能会发生变化。重要的是要记住,section_layout 条目决定了将选择哪些节。例如:

section_layout :vtc:linear
{
    group MY_DATA ( ordered, run_addr=mem:mpe:lmuram )
    {
        select “.zbss.file_1.my_var_1”;
    }
}

将无法工作,因为 .zbss 节是一个近地址可寻址节(abs18),而 section_layout 是针对线性(远
地址可寻址)范围的。TriCore用户指南第“7.9.5节:架构定义”中包含了一个关于地址空间关系的表格。

要解决此问题,您需要调整 section_layout 条目:

section_layout :vtc:abs18
{
//  group entry to place a non initialized near addressable data section 
//  in LMURAM memory
    group MY_NEAR_DATA ( ordered, run_addr=mem:mpe:lmuram )
    {
        select “.zbss.file_1.my_var_1”;
    }
}

+ Space mpe:vtc:abs18 (MAU = 8bit)
+----------------------------------------------------------------------------------------------+
|Chip      |Group       |Section                    |Size (MAU)|Space addr|Chip addr|Alignment |
|==============================================================================================|
|mpe:lmuram|MY_NEAR_DATA|.zbss.file_1.my_var_1 (169)|0x00000004|0x90000000|0x0      |0x00000002|

使用 LSL 符号:
如果应用程序不知道变量或函数的地址,因为该变量或函数是另一个单独链接的应用程序的一部分,
您可以使用LSL符号在链接时指定这些值。这样,目标文件就不需要为此进行修改。

/* var_1 defined in another application located at address 0x70001000 */
extern unsigned int var_1;
/* func_1 defined in another application located at address 0x80007000 */
void func_1(void);
void main(void)
{
    var_1 = 0x1234;
    func_1();
}

例如:链接器 LSL 文件条目将 var_1 和 func_1 分配到以下地址:

section_layout :vtc:linear

{
    /* variable var_1 is included in another project and located at address
       0x70001000 */
    “var_1”  = 0x70001000;
    
    /* function func_1 is included in another project and located at address
       0x80007000 */
    “func_1” = 0x80007000;
}

您可以使符号具有条件性。这样,它仅在目标文件中被引用时才会被创建。为此,请使用 := 。例如:

“func_1” := 0x80007000;

这个例子仅在目标文件中引用时定义 func_1。

在调试时,这些LSL文件符号没有调试信息。因此,您无法将变量添加到“变量监视”视图中。但是,您
可以使用指针解引用来访问地址0x70001000,以在调试器中显示该变量,如下所示:

*(unsigned int *)0x70001000

在TASKING C/C++调试器的“Expressions”视图中。

使用 RESERVED 关键字来防止节的放置

默认情况下,链接器根据为内存块输入的“优先级”值放置节。优先级值较高的内存将优先被填充。这
些内存将从低地址加载到高地址。如果某个内存范围不应被使用,您可以应用 reserved 关键字来防
止该内存范围内的节放置。例如:

section_layout :vtc:linear
{
    group ( ordered, run_addr = mem:mpe:pflash0[0x100] )
    {
        reserved “MY_RESERVE” ( size = 16k );
    }
}

上述列出的LSL条目在pflash0内存中从偏移量0x100开始保留了16 kB的内存。

+ Space mpe:vtc:linear (MAU = 8bit)
+-------------------------------------------------------------------------------+
| Chip       |Group|Section         |Size (MAU)|Space addr|Chip addr |Alignment |
|===============================================================================|
|
...
| mpe:pflash0|     |MY_RESERVE (354)|0x00004000|0x80000100|0x00000100|0x00000001|
...

如果您需要将一个或多个节放置在保留范围内,可以向保留组添加 alloc_allowed 条目。要允许绝对
放置的节,请使用 alloc_allowed=absolute。要允许在内存范围内放置的节,请使用
alloc_allowed=ranged

section_layout :vtc:linear
{
    group ( ordered, run_addr = mem:mpe:pflash0[0x100] )
    {
        reserved “MY_RESERVE” ( alloc_allowed=absolute, size = 16k );
    }
    
    group SPECIAL_FUNCTIONS ( ordered, contiguous, run_addr = mem:mpe:pflash0[0x110] )
    {
        select “.text.file_1.func_1”;
        select “.text.file_1.func_2”;
    }
}

这里 SPECIAL_FUNCTIONS 组被放置在pflash0内存中偏移量为0x110的绝对起始地址,该地址位于
保留范围内。

section_layout :vtc:linear
{
    group ( ordered, run_addr = mem:mpe:pflash0[0x100] )
    {
        reserved “MY_RESERVE” ( alloc_allowed=ranged, size = 16k );
    }
    
    group SPECIAL_FUNCTIONS ( ordered, contiguous, run_addr = 
mem:mpe:pflash0[0x110..0x200] )
    {
        select “.text.file_1.func_1”;
        select “.text.file_1.func_2”;
    }
}

这里 SPECIAL_FUNCTIONS 组被放置在pflash0内存中的一个地址范围内,起始于偏移量0x110,结
束于偏移量0x200,该范围位于保留范围内。

+ Space mpe:vtc:linear (MAU = 8bit)
+-------------------------------------------------------------------------------------------------------------+
|Chip       |Group            |Section                            |Size (MAU)|Space addr|Chip addr |Alignment |
|=============================================================================================================|
...
|mpe:pflash0|                 |MY_RESERVE (371)                   |0x00004000|0x80000100|0x00000100|0x00000001|
|mpe:pflash0|SPECIAL_FUNCTIONS|.text.file_1.func_1 (168)          |0x00000008|0x80000110|0x00000110|0x00000002|
|mpe:pflash0|SPECIAL_FUNCTIONS|.text.file_1.func_2 (169)          |0x00000008|0x80000118|0x00000118|0x00000002|
|mpe:pflash0|                 |.text._c_init_entry.libcs_fpu (222)|0x00000120|0x80004100|0x00004100|0x00000002|

为初始化数据专用放置ROM复制节

当一个变量被初始化为:

int var_1 = 10;

该变量的初始化值需要放置在闪存中。由C启动代码调用的初始化函数处理的复制表包含以下信息:

a)此ROM复制节的位置

b)需要复制到RAM内存中的地址

c)需要复制的字节数

有关C启动代码的更多详细信息包含在TriCore用户指南的“4.3节:C启动代码”中。

如果您需要将这些ROM复制节放置在专用内存范围内,LSL语言提供了不同的方法来解决这个问题。
一种解决方案利用了 run_addr 关键字,另一种则使用 load_addr

ROM复制节的名称与RAM节的名称相同,但ROM复制节的名称用方括号 [ ] 偏移。要选择ROM复制
节,您需要在LSL组的选择行中使用包括方括号在内的完整名称。为了让链接器接受它们,您需要使
用反斜杠 \ 进行转义。

对于上述列出的C源代码行,假设该行包含在名为 file_1.c 的文件中,并且默认的近地址分配值设
置为零,以防止任何不使用 __near 限定符的近地址可寻址节,默认节名称为 .data.file_1.var_1
该名称由以下部分组成:

<section name prefix>.<module name>.<variable name>

不同类型节使用的节名称前缀列在TriCore用户指南的“1.12节:编译器生成的节”中。

要在一个放置在ROM内存中的LSL组中选择此节(例如,从pflash0内存中的偏移量0x100开始),并
将RAM节放置(例如:从DSPR0 RAM中的偏移量0x200开始),您可以使用以下语法:

section_layout :vtc:linear
{
    group MY_ROM_COPY_SECTIONS ( ordered, run_addr = mem:mpe:pflash0[0x100] )
    {
        select “\[.data.file_1.var_1\]”;
    }
    group MY_INITIALIZED_SECTIONS ( ordered, run_addr = mem:mpe:dspr0[0x200] )
    {
        select “.data.file_1.var_1”;
    }
} 

+ Space mpe:vtc:linear (MAU = 8bit)
+-----------------------------------------------------------------------------------------------------------+
|Chip       | Group                  |Section                   |Size (MAU)|Space addr|Chip addr |Alignment |
|===========================================================================================================|
|mpe:dspr0  | MY_INITIALIZED_SECTIONS|.data.file_1.var_1 (170)  |0x00000004|0x70000200|0x00000200|0x00000002|
...
|mpe:pflash0| MY_ROM_COPY_SECTIONS   |[.data.file_1.var_1] (363)|0x00000004|0x80000100|0x00000100|0x00000002|

…

作为替代方法,您可以在组定义中使用 load_addr 关键字,而不是使用 run_addr。这告诉链接器选
择初始化节的ROM复制。在这样做时,您需要移除方括号以选择该节:

section_layout :vtc:linear
{
    group MY_ROM_COPY_SECTIONS ( ordered, load_addr = mem:mpe:pflash0[0x100] )
    {
        select “.data.file_1.var_1”;
    }
    group MY_INITIALIZED_SECTIONS ( ordered, run_addr = mem:mpe:dspr0[0x200] )
    {
        select “.data.file_1.var_1”;
    }
}

在链接阶段更改节属性 /
防止节初始化

C编译器为它创建的所有代码和数据节添加节属性。可用的节属性包括:

r----可读节

w----可写节

x----可执行节

i----初始化节

b----在程序启动时应清除的节

s----临时节(不清除且未初始化)

p----保护节

当模块的C源代码不可用或可能无法通过重新编译进行更改,但您需要更改节属性或禁用节的初始化
时,可以在链接阶段解决此问题。要防止节的初始化,您可以使用组条目关键字 nocopy

可能的链接阶段操作包括:

  • 通过添加 s(临时)属性来防止未初始化变量的清理。这样,链接器将不会为这些节添加复制表
    条目,启动代码将不会对它们进行处理。
  • 通过添加 nocopy 关键字来防止初始化变量的初始化。在这种情况下,链接器将不会为这些节
    添加复制表条目,启动代码也不会对它们进行处理。此外,链接器不会创建包括初始化值的ROM复
    制节。
  • 通过添加 p(保护)属性来防止未引用节被自动删除。当启用链接器优化“从输出文件中删除未
    引用的节”时,尽管该节未被引用,但它不应从链接器输出中删除。

为此,您可以在LSL文件的组定义中添加一个 attributes 条目。以下是两个示例,演示了如何使用
nocopy 关键字和 s(临时)属性。

section_layout :vtc:linear
{
#ifdef DO_NOT_INITIALIZE_CALIB_VALUE   
// nocopy is added here to remove the ROM copy section for this initialized data
// and the copy table entry
    group CALIBRATION_VALUE ( ordered, run_addr = mem:mpe:dspr0, nocopy )
    {
        select “.data.file_1.calib_value”;
    }
#else
    group CALIBRATION_VALUE ( ordered, run_addr = mem:mpe:dspr0 )
    {
        select “.data.file_1.calib_value”;
    }
#endif

#ifdef DO_NOT_CLEAR_VAR_1
// the scratch attribute ‘s’ is added here to prevent a copy table entry
// to clear this section
    group VAR_1 ( ordered, run_addr = mem:mpe:dspr0, attributes = rws )
    {
        select “.bss.file_1.var_1”;
    }
#else 
    group VAR_1 ( ordered, run_addr = mem:mpe:dspr0 )
    {
        select “.bss.file_1.var_1”;
    }
#endif
}

以下输出显示了地图文件的一部分,当未初始化节 .bss.file_1.var_1 被清除时的情况。复制表节
table的大小为0x90字节:

+Space mpe:vtc:linear (MAU = 8bit)
+-------------------------------------------------------------------------------------+
|Chip       |Group|Section                |Size (MAU)|Space addr|Chip addr |Alignment |
|=====================================================================================|
...
|mpe:dspr0  |VAR_1|.bss.file_1.var_1 (171)|0x00000004|0x70000004|0x00000004|0x00000002|
...
|mpe:pflash0|     |table (368)            |0x00000090|0x8000002c|0x0000002c|0x00000004|

…

以下输出显示了地图文件的一部分,当未初始化节 .bss.file_1.var_1 未被清除时的情况。这是通过
在LSL文件中定义 DO_NOT_CLEAR_VAR_1 宏来实现的。

复制表节table的大小为0x80字节,较小的原因是节 .bss.file_1.var_1 的清除条目不再包含。

+ Space mpe:vtc:linear (MAU = 8bit)
+--------------------------------------------------------------------------------------+
| Chip       |Group|Section                |Size (MAU)|Space addr|Chip addr |Alignment |
|======================================================================================|
...
| mpe:dspr0  |VAR_1|.bss.file_1.var_1 (171)|0x00000004|0x70000004|0x00000004|0x00000002|
...
| mpe:pflash0|     |table (368)            |0x00000080|0x8000002c|0x0000002c|0x00000004| 

…

还请查看map文件,了解当初始化被关闭时,初始化节 .data.file_1.calib_value 的ROM复制节会
发生什么。这时,ROM复制节 [.data.file_1.calib_value] 将会消失。

更多帮助?

希望您能从中学习到一些使用TASKING链接器/加载器的新技巧,并从LSL提供的机会中受益。再次提
醒,如果您有任何问题或其他建议/提示,请随时通过 support@tasking.com 与我们联系。

posted @ 2024-10-28 11:23  决云  阅读(164)  评论(0)    收藏  举报