如何防止 GCC 在 link 时间优化期间插入 memset?
How to prevent GCC from inserting memset during link-time optimization?
在用 C 语言为 RV32IM 目标 (RISC-V) 开发裸机固件时,启用 LTO 时遇到链接错误:
/home/duranda/riscv/lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/firmware.elf.5cZNyC.ltrans0.ltrans.o: in function `.L0 ':
/home/duranda/whatever/firmware.c:493: undefined reference to `memset'
但是我的固件中没有调用 memset
。 memset
由 GCC 在优化 as described here 期间插入。该构建使用 GCC -Os
和 -flto -fuse-linker-plugin
标志针对大小进行了优化。此外,-fno-builtin-memset -nostdinc -fno-tree-loop-distribute-patterns -nostdlib -ffreestanding
标志用于防止在优化期间使用 memset
并且不包括标准库。
如何在 LTO 期间防止 memset
插入?请注意,固件不应链接到 libc。我还尝试提供 memset
的自定义实现,但链接器不想将其用于优化期间插入的 memset
(仍然抛出 未定义引用)。
我不确定 -fno-builtin-*
是否如您所想。如果您使用这些标志,那么 GCC 将尝试调用外部函数。如果您不使用这些标志,GCC 将只插入内联代码而不是依赖库。
所以在我看来你不应该使用任何 -fno-builtin
标志。
几年前我遇到过类似的问题服务器并试图修复它,但事实证明我误解了 -fno-builtin
[1] 的含义,-fno-builtin
不能保证 GCC 不会调用 memcpy
、memmove
或 memset
隐含。
我想最简单的解决方案是,不要用 -flto
编译你的 libc.c
,或者换句话说,用 -fno-lto
编译 libc.c
。
这是我对发生的事情的猜测,我不知道如何重现你所看到的,所以它可能不正确,
- 在 LTO 的第一阶段,LTO 将收集您在程序中使用的任何符号
- 然后要求链接器提供这些文件,并丢弃任何未使用的符号。
- 然后将这些文件读入GCC并再次优化,此时gcc使用一些built-in函数来优化或代码生成,但它不是pull-in之前。
- 符号引用是在 LTO 阶段创建的,在当前 GCC LTO 流程中拉入任何符号都为时已晚,在这种情况下,memset 在早期阶段被丢弃...
所以您可能有疑问,为什么用 -fno-lto
编译 libc.c
会起作用?因为如果它不涉及到LTO流程,这意味着它不会在LTO流程中被丢弃。
即使您使用 -fno-builtin 编译,一些显示 gcc 的示例程序也会调用 memset,aarch64 gcc 和 riscv gcc 将生成对 memset 的函数调用。
// $ riscv64-unknown-elf-gcc x.c -o - -O3 -S -fno-builtin
struct bar {
int a[100];
};
struct bar y;
void foo(){
struct bar x = {{0}};
y = x;
}
这里是对应的 gcc 源代码[2]。
[1] https://gcc.gnu.org/pipermail/gcc-patches/2014-August/397382.html
[2] https://github.com/riscv/riscv-gcc/blob/riscv-gcc-10.2.0/gcc/expr.c#L3143
在用 C 语言为 RV32IM 目标 (RISC-V) 开发裸机固件时,启用 LTO 时遇到链接错误:
/home/duranda/riscv/lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /tmp/firmware.elf.5cZNyC.ltrans0.ltrans.o: in function `.L0 ':
/home/duranda/whatever/firmware.c:493: undefined reference to `memset'
但是我的固件中没有调用 memset
。 memset
由 GCC 在优化 as described here 期间插入。该构建使用 GCC -Os
和 -flto -fuse-linker-plugin
标志针对大小进行了优化。此外,-fno-builtin-memset -nostdinc -fno-tree-loop-distribute-patterns -nostdlib -ffreestanding
标志用于防止在优化期间使用 memset
并且不包括标准库。
如何在 LTO 期间防止 memset
插入?请注意,固件不应链接到 libc。我还尝试提供 memset
的自定义实现,但链接器不想将其用于优化期间插入的 memset
(仍然抛出 未定义引用)。
我不确定 -fno-builtin-*
是否如您所想。如果您使用这些标志,那么 GCC 将尝试调用外部函数。如果您不使用这些标志,GCC 将只插入内联代码而不是依赖库。
所以在我看来你不应该使用任何 -fno-builtin
标志。
几年前我遇到过类似的问题服务器并试图修复它,但事实证明我误解了 -fno-builtin
[1] 的含义,-fno-builtin
不能保证 GCC 不会调用 memcpy
、memmove
或 memset
隐含。
我想最简单的解决方案是,不要用 -flto
编译你的 libc.c
,或者换句话说,用 -fno-lto
编译 libc.c
。
这是我对发生的事情的猜测,我不知道如何重现你所看到的,所以它可能不正确,
- 在 LTO 的第一阶段,LTO 将收集您在程序中使用的任何符号
- 然后要求链接器提供这些文件,并丢弃任何未使用的符号。
- 然后将这些文件读入GCC并再次优化,此时gcc使用一些built-in函数来优化或代码生成,但它不是pull-in之前。
- 符号引用是在 LTO 阶段创建的,在当前 GCC LTO 流程中拉入任何符号都为时已晚,在这种情况下,memset 在早期阶段被丢弃...
所以您可能有疑问,为什么用 -fno-lto
编译 libc.c
会起作用?因为如果它不涉及到LTO流程,这意味着它不会在LTO流程中被丢弃。
即使您使用 -fno-builtin 编译,一些显示 gcc 的示例程序也会调用 memset,aarch64 gcc 和 riscv gcc 将生成对 memset 的函数调用。
// $ riscv64-unknown-elf-gcc x.c -o - -O3 -S -fno-builtin
struct bar {
int a[100];
};
struct bar y;
void foo(){
struct bar x = {{0}};
y = x;
}
这里是对应的 gcc 源代码[2]。
[1] https://gcc.gnu.org/pipermail/gcc-patches/2014-August/397382.html
[2] https://github.com/riscv/riscv-gcc/blob/riscv-gcc-10.2.0/gcc/expr.c#L3143