如何防止链接器丢弃功能?

时间:2018-12-31 08:38:05

标签: c gcc linker attributes

我的C代码中有一个函数被隐式调用,并被链接器转储。我该如何预防这种现象?

我正在使用gcc和链接器标志-gc-sections进行编译,并且我不想从标志中排除整个文件。我尝试使用属性:“ used”和“ externally_visible”,但都没有起作用。

void __attribute__((section(".mySec"), nomicromips, used)) func(){
...
}

在地图文件上,我可以看到该函数已编译但未链接。我使用错了吗?还有其他方法吗?

2 个答案:

答案 0 :(得分:0)

您误解了used attribute

  

使用

     

此属性附加到函数上,意味着即使看起来未引用该函数,也必须为该函数发出代码...

编译器必须发出函数定义,即使函数出现 未被引用。编译器永远不会得出未引用函数的结论 如果具有外部链接。所以在这个程序中:

main1.c

static void foo(void){}

int main(void)
{
    return 0;
}

编译为:

$ gcc -c -O1 main1.c

根本没有发出foo的定义:

$ nm main1.o
0000000000000000 T main

因为foo没有在翻译单元中引用,不是外部的, 因此可以进行优化。

但是在此程序中:

main2.c

static void __attribute__((used)) foo(void){}

int main(void)
{
    return 0;
}

__attribute__((used))迫使编译器发出本地定义:

$ gcc -c -O1 main2.c
$ nm main2.o
0000000000000000 t foo
0000000000000001 T main

但这并不能阻止 linker 丢弃某节 定义foo的情况下,在-gc-sections存在的情况下,即使foo 是外部的(如果该节未使用):

main3.c

void foo(void){}

int main(void)
{
    return 0;
}

使用功能部分进行编译:

$ gcc -c -ffunction-sections -O1 main3.c

foo的全局定义在目标文件中:

$ nm main3.o
0000000000000000 T foo
0000000000000000 T main

但是在链接之后:

$ gcc -Wl,-gc-sections,-Map=mapfile main3.o

foo未在程序中定义:

$ nm a.out | grep foo; echo Done
Done

定义foo的功能部分被丢弃:

地图文件

...
...
Discarded input sections
 ...
 ...
 .text.foo      0x0000000000000000        0x1 main3.o
 ...
 ...

根据Eric Postpischil的评论,强制保留 linker 一个显然未使用的功能部分,您必须告诉它假设 使用链接器选项{-u|--undefined} foo引用未使用的函数:

main4.c

void __attribute__((section(".mySec"))) foo(void){}

int main(void)
{
    return 0;
}

如果您不这样说:

$ gcc -c main4.c
$ gcc -Wl,-gc-sections main4.o
$ nm a.out | grep foo; echo Done
Done
程序中未定义

foo。如果您告诉它:

$ gcc -c main4.c
$ gcc -Wl,-gc-sections,--undefined=foo main4.o
$ nm a.out | grep foo; echo Done
0000000000001191 T foo
Done

已定义。属性used没有用。

答案 1 :(得分:0)

除了此处已经提到的 -u 之外,还有两种使用 GCC 保留符号的方法。

在不调用的情况下创建对它的引用

这种方法不需要弄乱链接器脚本,这意味着它适用于使用操作系统默认链接器脚本的托管程序和库。

但是它随编译器优化设置而变化,并且可能不是很便携。

例如,在带有 LD 2.31.1 的 GCC 7.3.1 中,您可以保留一个函数而无需实际调用它,通过在其地址上调用另一个函数,或在指向其地址的指针上进行分支。

bool function_exists(void *address) {
    return (address != NULL);
}

// Somewhere reachable from main
assert(function_exists(foo));
assert(foo != NULL);  // Won't work, GCC optimises out the constant expression
assert(&foo != NULL);  // works on GCC 7.3.1 but not GCC 10.2.1

另一种方法是创建一个包含函数指针的 struct,然后您可以将它们组合在一起,只需检查 struct 的地址。我经常将它用于中断处理程序。

修改链接脚本以保留部分

如果您正在开发托管程序或库,则更改链接描述文件非常棘手。

即使这样做,它也不是很便携,例如,OSX 上的 gcc 实际上并不使用 GNU 链接器,因为 OSX 使用 Mach-O 格式而不是 ELF。

不过,您的代码已经显示了一个自定义部分,因此您可能正在使用嵌入式系统并且可以轻松修改链接器脚本。

SECTIONS {
    // ...
    .mySec {
        KEEP(*(.mySec));
    }
}