在C中访问链接器脚本变量未定义行为的“值”?

问题描述 投票:0回答:1

GNU ld(链接器脚本)手册章节3.5.5 Source Code Reference有一些关于如何在C源代码中访问链接器脚本“变量”(实际上只是整数地址)的重要信息。我用过这个信息。广泛使用链接器脚本变量,我在这里写了这个答案:How to get value of variable defined in ld linker script from C

但是,很容易做错,并且错误地尝试访问链接器脚本变量的值(错误地)而不是其地址,因为这有点深奥。手册(上面的链接)说:

这意味着您无法访问链接器脚本定义的符号值 - 它没有值 - 您所能做的就是访问链接器脚本定义的符号的地址。

因此,当您在源代码中使用链接器脚本定义符号时,应始终使用符号的地址,而不要尝试使用其值。

问题:那么,如果您尝试访问链接器脚本变量的值,这是“未定义的行为”吗?

快速复习:

想象一下在链接器脚本中(例如:STM32F103RBTx_FLASH.ld)你有:

/* Specify the memory areas */
MEMORY
{
    FLASH (rx)      : ORIGIN = 0x8000000,  LENGTH = 128K
    RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 20K
}

/* Some custom variables (addresses) I intend to access from my C source code */
__flash_start__ = ORIGIN(FLASH);
__flash_end__ = ORIGIN(FLASH) + LENGTH(FLASH);
__ram_start__ = ORIGIN(RAM);
__ram_end__ = ORIGIIN(RAM) + LENGTH(RAM);

在您的C源代码中,您可以:

// 1. correct way A:
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)&__flash_start__);

// OR 2. correct way B (my preferred approach):
extern uint32_t __flash_start__[]; // not a true array; [] is required to access linker script variables (addresses) as though they were normal variables
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)__flash_start__);

// OR 3. COMPLETELY WRONG WAY TO DO IT!
// - IS THIS UNDEFINED BEHAVIOR?
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", __flash_start__);

样本打印输出

(这是实际输出:它实际上由STM32 mcu编译,运行和打印):

  1. __flash_start__ addr = 0x8000000
  2. __flash_start__ addr = 0x8000000
  3. __flash_start__ addr = 0x20080000 <==注意我上面说过:这个是完全错误的(即使它编译并运行)!

更新:

回复@Eric Postpischil的第一条评论:

C标准没有对链接器脚本符号进行任何定义。任何行为规范都取决于GNU工具。也就是说,如果链接描述符号在内存中标识了存储某个有效对象的位置,我希望访问该对象的值才能工作,如果它是以适当的类型访问的话。假设flash_start通常是可访问的内存,除了系统对flash_start的任何要求之外,理论上你可以将uint32_t(使用适当的输入连接到链接器)然后通过flash_start访问它。

是的,但这不是我的问题。我不确定你是否接受了我的问题的微妙之处。看一下我提供的例子。确实你可以很好地访问这个位置,但要确保你理解你是如何做到的,然后我的问题就会变得明显。特别注意上面的例子3,这是错误的,即使对于C程序员它看起来是正确的。要阅读uint32_t,例如,在__flash_start__,你会这样做:

extern uint32_t __flash_start__;
uint32_t u32 = *((uint32_t *)&__flash_start__); // correct, even though it *looks like* you're taking the address (&) of an address (__flash_start__)

或这个:

extern uint32_t __flash_start__[];
uint32_t u32 = *((uint32_t *)__flash_start__); // also correct, and my preferred way of doing it because it looks more correct to the trained "C-programmer" eye

但绝大多数不是这样的:

extern uint32_t __flash_start__;
uint32_t u32 = __flash_start__; // incorrect; <==UPDATE: THIS IS ALSO CORRECT! (and more straight-forward too, actually; see comment discussion under this question)

而不是这个:

extern uint32_t __flash_start__;
uint32_t u32 = *((uint32_t *)__flash_start__); // incorrect, but *looks* right

Related:

c linker ld binutils linker-scripts
1个回答
1
投票

我在问题中说:

// 1. correct way A:
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)&__flash_start__);

// OR 2. correct way B (my preferred approach):
extern uint32_t __flash_start__[]; // not a true array; [] is required to access linker script variables (addresses) as though they were normal variables
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)__flash_start__);

// OR 3. COMPLETELY WRONG WAY TO DO IT!
// - IS THIS UNDEFINED BEHAVIOR?
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", __flash_start__);

(请参阅问题讨论我是如何做到的)。

具体看上面的#3:

好吧,实际上,如果你的目标是读取__flash_start__的地址,在这种情况下是0x8000000,那么是的,这是完全错误的。但是,它不是未定义的行为!实际上它正在做的是将该地址(0x8000000)的内容(值)读作uint32_t类型。换句话说,它只是读取FLASH部分的前4个字节,并将它们解释为uint32_t。在这种情况下,内容(此地址的uint32_t值)恰好是0x20080000

为了进一步证明这一点,以下内容完全相同:

// Read the actual *contents* of the __flash_start__ address as a 4-byte value!
// The 2 techniques should be the same.
extern uint32_t __flash_start__;
uint32_t u32_1 = __flash_start__;
uint32_t u32_2 = *((uint32_t *)&__flash_start__);
printf("u32_1 = 0x%lX\n", u32_1);
printf("u32_2 = 0x%lX\n", u32_2);

输出是:

u32_1 = 0x20080000
u32_2 = 0x20080000

注意它们会产生相同的结果。它们每个都产生一个有效的uint32_t类型值,存储在地址0x8000000

然而,事实证明,上面显示的u32_1技术是一种更直接,更直接的方式来读取价值,而且,这并非未定义的行为。相反,它正确地读取该地址的值(内容)。

我好像在说话。无论如何,心灵都被吹了,但我现在明白了。在我应该使用上面显示的u32_2技术之前我确信,但事实证明它们都很好,再次,u32_1技术显然更直接(我再次在圈子里说话)。 :)

干杯。


Digging deeper: Where did the 0x20080000 value stored right at the start of my FLASH memory come from?

还有一点点花絮。我实际上在STM32F777 mcu上运行了这个测试代码,它有512KiB的RAM。由于RAM从地址0x20000000开始,这意味着0x20000000 + 512K = 0x20080000。这恰好也是地址零处RAM的内容,因为Programming Manual PM0253 Rev 4,pg。 42,“图10.向量表”表示向量表的前4个字节包含“初始SP [堆栈指针]值”。看这里:

enter image description here

我知道向量表就位于程序存储器的开头,它位于Flash中,因此这意味着0x20080000是我的初始堆栈指针值。这是有道理的,因为Reset_Handler是程序的开始(顺便说一下它的向量恰好是向量表开头的第二个4字节值),并且它做的第一件事,如图所示在我的“startup_stm32f777xx.s”启动程序集文件中,将堆栈指针(sp)设置为_estack

Reset_Handler:  
  ldr   sp, =_estack      /* set stack pointer */

此外,_estack在我的链接器脚本中定义如下:

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM);    /* end of RAM */

所以你有它!我的向量表中的第一个4字节值,就在Flash的开头,被设置为初始堆栈指针值,在我的链接器脚本文件中定义为_estack,而_estack是我的末尾的地址RAM,即0x20000000 + 512K = 0x20080000。所以,这一切都有道理!我刚刚证明我读了正确的价值!

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.