将 C 变量放入数据存储器输出段需要哪些输入段属性?

What input section attributes are required to place a C variable in a data memory output section?

我的应用程序有许多模块,每个模块都需要一些变量存储在 off-chip non-volatile 内存中。为了使这些读取和写入更容易,我试图将它们收集到一个连续的 RAM 区域中,以便 NVM driver 在与 NVM 设备通信时可以寻址单个内存块。

为此,我创建了一个包含以下部分定义的自定义链接描述文件。

.nvm_fram :
{
    /* Include the "nvm_header" input section first. */    
    *(.nvm_header)
    /* Include all other input sections prefixed with "nvm_" from all modules. */    
    *(.nvm_*)

    /* Allocate a 16 bit variable at the end of the section to hold the CRC. */
    . = ALIGN(2);
    _gld_NvmFramCrc = .;
    LONG(0);
} > data
_GLD_NVM_FRAM_SIZE = SIZEOF(.nvm_fram);

data 区域使用 Microchip 为目标设备提供的标准定义在 MEMORY 部分中定义。

data (a!xr) : ORIGIN = 0x1000, LENGTH = 0xD000

C 源文件的一个示例试图将其变量放在该部分中,即 NVM driver 本身。 driver 在 NVM 部分的开头保存了一个简短的 header 结构,以便它可以在将 NVM 设备的内容加载到 RAM 之前验证它的内容。没有报告此变量的链接器错误。

// Locate the NVM configuration in the non-volatile RAM section.
nvm_header_t _nvmHeader  __attribute__((section(".nvm_header")));

另一个有变量要存储在 .nvm_fram 部分的模块是通信 (CANopen) 堆栈。这会在 NVM 中保存模块 ID 和比特率。

// Locate LSS Slave configuration in the non-volatile RAM section.
LSS_slave_config_t _slaveConfig __attribute__((section(".nvm_canopen"))) =
    { .BitRate = DEFAULT_BITRATE, .ModuleId = DEFAULT_MODULEID };

一切编译很好,但是当链接器运行时,以下错误停止构建。

elf-ld.exe: Link Error: attributes for input section '.nvm_canopen' conflict with
output section '.nvm_fram'

重要的是变量可以由 crt 启动用值初始化,如上面的 _slaveConfig 声明所示,以防 NVM driver 无法从 NVM 设备加载它们(它是空白,或软件版本已更改等)。这是导致属性不匹配的原因吗?

这里和 Microchip 论坛上有几个问题,涉及从 C 访问链接描述文件中定义的符号。其中大部分涉及程序闪存中的值以及如何从 C 访问它们;我知道该怎么做。有 a similar question,但这似乎没有解决属性问题,并且由于特定于不同目标处理器的链接器而有点混乱。

我已经在线阅读了 Microchip 链接器手册和各种 GCC 链接器文档,但找不到相关部分,因为我不太了解错误的含义以及它与我的代码的关系。 'input and output section attributes' 是什么,它们在我的代码中指定在哪里,如何让它们相互匹配?

问题是由于 _nvmHeader 变量在 C 源代码中没有分配初始值,但 _slaveConfig 变量有。

这导致链接器根据 .nvm_header 输入节属性推断出 .nvm_fram 输出节未初始化 (nbss)。因此,当它在 _slaveConfig 变量的 .nvm_canopen 输入部分中遇到初始化数据时,输入部分属性不匹配:.nvm_fram 用于未初始化数据,但 .nvm_canopen 包含初始化数据。

解决方案是确保要放置在.nvm_fram输出部分的所有变量在C源代码中被赋予初始值。

// Type used to hold metadata for the content of the NVM.
typedef struct
{
    void*       NvmBase;    // The original RAM address.
    uint16_t    NvmSize;    // The original NVM section size.
}   nvm_header_t;

// The linker supplies the gld_NVM_FRAM_SIZE symbol as a 'number'.
// This is represented as the address of an array of unspecified 
// length, so that it cannot be implicitly dereferenced, and cast
// to the correct type when used.
extern char GLD_NVM_FRAM_SIZE[];

// The following defines are used to convert linker symbols into values that
// can be used to initialise the _nvmHeader structure below.
#define NVM_FRAM_BASE ((void*)&_nvmHeader)
#define NVM_FRAM_SIZE ((uint16_t)GLD_NVM_FRAM_SIZE)

// Locate the NVM configuration in the non-volatile RAM section.
nvm_header_t _nvmHeader  __attribute__((section(".nvm_header"))) =
{
    .NvmBase = NVM_FRAM_BASE, .NvmSize = NVM_FRAM_SIZE
};

因此,答案是输出部分属性可能部分由该部分所在的内存区域以及分配给它的第一个输入部分的属性决定。已初始化和未初始化的 C 变量具有不同的输入节属性,因此不能位于同一输出节中。