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 与我们联系。