我的 C 代码中有一个函数被隐式调用,并被链接器转储。 如何防止这种现象?
我正在使用 gcc 和链接器标志 -gc-sections 进行编译,我不想从标志中排除整个文件。我尝试使用属性:"已使用"和"externally_visible",但都不起作用。
void __attribute__((section(".mySec"), nomicromips, used)) func(){
...
}
在地图文件上,我可以看到该函数已编译但未链接。 我用错了吗? 还有其他方法可以做到吗?
你误解了used
属性
使用
此属性附加到函数,意味着必须为函数发出代码,即使看起来没有引用该函数...
即编译器必须发出函数定义,即使函数出现 要取消引用。编译器永远不会得出函数未引用的结论 如果有外部联系。所以在这个程序中:
主1.c
static void foo(void){}
int main(void)
{
return 0;
}
编译方式:
$ gcc -c -O1 main1.c
根本不发出foo
的定义:
$ nm main1.o
0000000000000000 T main
因为foo
不在翻译单元中引用,不是外部的, 因此可以优化。
但是在这个程序中:
主2.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
但这不会阻止链接器丢弃某个部分 在其中定义foo
,在存在-gc-sections
的情况下,即使foo
是外部的,如果该部分未使用:
主3.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的评论,强制链接器保留 一个显然未使用的功能部分,你必须告诉它假设程序 引用未使用的函数,链接器选项{-u|--undefined} foo
:
主4.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
没有用。
除了这里已经提到的-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));
}
}