我的C代码中有一个函数被隐式调用,并被链接器转储。我该如何预防这种现象?
我正在使用gcc和链接器标志-gc-sections进行编译,并且我不想从标志中排除整个文件。我尝试使用属性:“ used”和“ externally_visible”,但都没有起作用。
void __attribute__((section(".mySec"), nomicromips, used)) func(){
...
}
在地图文件上,我可以看到该函数已编译但未链接。我使用错了吗?还有其他方法吗?
答案 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));
}
}