将 __attribute__((used)) 设置为 C variable/constant 无效

Setting __attribute__((used)) to C variable/constant has no effect

在 ARM GCC(纯 C 代码)上,当我声明一个常量时

__attribute__((used,section(".rodata.$AppID")))
const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

我没有在代码中引用它,它优化掉,并列在丢弃在地图文件中输入 sections。 只有当我在源代码的其他地方引用它时,它才会包含在二进制输出中。

单靠“used”标签不就足够了吗?在 GCC 手册 (6.34.1 Common Variable Attributes) 中,我读到:

used

This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.

的意思是放在固定的内存地址,在指定的section中,让单独的应用程序去检查

我是 运行 NXP MCUXpresso 11.1 提供的 ARM GCC,报告详细版本为

GNU C17 (GNU Tools for Arm Embedded Processors 8-2019-q3-update) version 8.3.1 20190703 (release) [gcc-8-branch revision 273027] (arm-none-eabi)
compiled by GNU C version 5.3.1 20160211, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl version isl-0.18-GMP

Shouldn't the "used" tag alone be enough?

不够,也没有必要。不相关。

根据您引用的 GCC 文档,属性 used 是 适用于 static 变量的定义。作为现在的答案 作者指出删除,你的ApplicationID不是静态的,所以属性used 没有效果。

这里:

/* app_id_extern.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

我们默认将 ApplicationID 定义为 extern 变量。默认存储 class 一个文件范围变量,如 ApplicationID,是 extern。编译器将 相应地生成一个目标文件,其中定义了 ApplicationID 暴露了 linkage,正如我们所见:

$ gcc -c app_id_extern.c
$ readelf -s app_id_extern.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     9: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    4 ApplicationID

在目标文件中,ApplicationID 是第 4 节中的 16 字节 GLOBAL 符号 (在这种情况下恰好是 .rodata)。 GLOBAL绑定意味着静态linker可以看到这个符号。

这里:

/* app_id_static.c */

#include <stdint.h>

static const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

我们将 ApplicationID 明确定义为 static 变量。编译器 将相应地生成一个目标文件,其中 ApplicationID 的定义 暴露link年龄,我们还可以看到:

$ gcc -c app_id_static.c
$ readelf -s app_id_static.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     6: 0000000000000000    16 OBJECT  LOCAL  DEFAULT    4 ApplicationID
     ...

在此目标文件中,ApplicationID.rodata 部分中的 16 字节 LOCAL 符号 LOCAL绑定意味着静态linker 不能看到这个符号。

编译器将总是在目标文件中发出一个extern变量的定义, 就像 app_id_extern.c 中的 ApplicationID,即使那个定义不是 在目标文件中引用,因为外部定义将可用于 linker,因此可能会在 link 时从其他目标文件中引用,因为 编译器可能知道。

但是如果一个变量是static,那么编译器就知道它的定义是不可用的 link年龄。因此,如果它可以确定该定义未在 目标文件本身,它可能会断定该定义是多余的并且不会发出它 在目标文件中。像这样:

$ gcc -O1 -c app_id_static.c

这一次,我们要求编译器执行最少的优化。然后

$ readelf -s app_id_static.o

Symbol table '.symtab' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS app_id_static.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    4

目标文件中不再存在 ApplicationID 的未引用定义 完全。它被优化了。

现在对于某些不寻常的应用程序,我们可能希望编译器发出定义 对象文件中未引用它的符号, 将其隐藏在静态 linker 之外。那是 属性 used 发挥作用的地方:

/* app_id_static_used .c */

#include <stdint.h>

static const uint8_t   ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

我们再次使用 -O1 优化进行编译:

$ gcc -O1 -c app_id_static_used.c
$ readelf -s app_id_static_used.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     6: 0000000000000000    16 OBJECT  LOCAL  DEFAULT    4 ApplicationID
     ...

但这一次,由于属性 usedApplicationIDLOCAL 定义 再次出现在第 4 部分(在此目标文件中为 .rodata.$AppID

这就是属性 used 的工作原理。它影响编译器的行为:它没有 对 linker.

的影响

我们还没有完成任何 link年龄。让我们现在做一些。

/* hello_world.c */

#include <stdio.h>

int main(void)
{
    puts("Hello world!")
    return 0;
}

此程序没有引用 ApplicationID,但我们将输入 app_id_static_used.o 到 link 年龄:

$ gcc -O1 -c hello_world.c
$ gcc -o hello hello_world.o app_id_static_used.o -Wl,-gc-sections,-Map=mapfile.txt

在 link 时代,我要求删除未使用的输入部分,并要求一个映射文件 待输出 (-Wl,-gc-sections,-Map=mapfile.txt)

在映射文件中我们发现:

Mapfile.txt

...
Discarded input sections
  ...
  .rodata.$AppID
                0x0000000000000000       0x10 app_id_static_used.o
  ...

linker 丢弃了来自 app_id_static_used.o.rodata.$AppID 部分 因为程序中没有引用该部分中定义的任何符号。和 属性 used,我们强制编译器发出 static 符号的定义 在 app_id_static_used.o。这不会迫使 linker 需要它,或保持 它在可执行文件中。

我们从app_id_static_used.c切换到:

/* app_id_extern_used.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

然后我们正在做您所做的,将属性 used 应用于 extern 定义。属性 used 在这种情况下没有效果,因为编译器必然会发出 extern 任何情况下的定义。 linker 将 still 丢弃 .rodata.$AppID 输入部分 如果程序没有引用其中的任何内容,则可执行。

到目前为止,您的 app-id 源文件可能是:

/* app_id_extern_section.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] __attribute__((section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

然后您需要做的是通知linker您想要定义 符号 ApplicationID 保留,即使它没有被您的程序引用,并且 如果即使未使用的部分被删除。

要实现这一点,请使用 linker 选项 --undefined=ApplicationID。这会 指导 linker 从一开始就假设你的程序 linkage 遇到了对 ApplicationID 的未定义引用并迫使 linker 查找并 link 它的定义,如果有任何输入文件提供的话。因此:

$ gcc -O1 -c app_id_extern_section.c
$ gcc -o hello hello_world.o app_id_extern_section.o -Wl,-gc-sections,--undefined=ApplicationID

现在程序包含 ApplicationID 的定义,尽管没有引用它:

$ readelf -s hello | grep ApplicationID
    58: 0000000000002010    16 OBJECT  GLOBAL DEFAULT   18 ApplicationID

#18 部分是程序的 .rodata 部分:

$ readelf --sections hello | grep '.rodata'
  [18] .rodata           PROGBITS         0000000000002000  00002000

最后,请注意 app_id_extern_section.o 中的输入部分 .rodata.$AppID 已经合并到输出部分.rodata,因为linker的默认 linker 脚本指定:

.rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }

即所有匹配 .rodata.rodata.*.gnu.linkonce.r.* 的输入部分 将输出到.rodata。这意味着甚至:

__attribute__((section(".rodata.$AppID")))

是多余的。所以 app-id 源文件也可能就是那个 我从 app_id_extern.c 和 linkage 选项开始 --undefined=ApplicationID 是在程序中保留未引用符号所必需的。除非 你的 linker 在这方面是不同的,你会发现相同的。