我正在尝试使用gcc和clang来查看它们是否可以优化
#define SCOPE static
SCOPE const struct wrap_ { const int x; } ptr = { 42 /*==0x2a*/ };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
返回一个中间常量。
事实证明他们可以:
0000000000000010 <ret_global>:
10: b8 2a 00 00 00 mov $0x2a,%eax
15: c3 retq
但令人惊讶的是,移除静态会产生相同的装配输出。这让我很好奇,因为如果全局不是static
它应该是可插入的并且用中间体替换引用应该防止全局变量上的位置。
确实如此:
#!/bin/sh -eu
: ${CC:=gcc}
cat > lib.c <<EOF
int ret_42(void) { return 42; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 42 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
int ret_fn_result(void) { return ret_42()+1; }
EOF
cat > lib_override.c <<EOF
int ret_42(void) { return 50; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 60 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
EOF
cat > main.c <<EOF
#include <stdio.h>
int ret_42(void), ret_global(void), ret_fn_result(void);
struct wrap_ { const int x; };
extern struct wrap { const struct wrap_ *ptr; } const w;
int main(void)
{
printf("ret_42()=%d\n", ret_42());
printf("ret_fn_result()=%d\n", ret_fn_result());
printf("ret_global()=%d\n", ret_global());
printf("w.ptr->x=%d\n",w.ptr->x);
}
EOF
for c in *.c; do
$CC -fpic -O2 $c -c
#$CC -fpic -O2 $c -c -fno-semantic-interposition
done
$CC lib.o -o lib.so -shared
$CC lib_override.o -o lib_override.so -shared
$CC main.o $PWD/lib.so
export LD_LIBRARY_PATH=$PWD
./a.out
LD_PRELOAD=$PWD/lib_override.so ./a.out
输出
ret_42()=42
ret_fn_result()=43
ret_global()=42
w.ptr->x=42
ret_42()=50
ret_fn_result()=51
ret_global()=42
w.ptr->x=60
编译器可以用中间件替换refs到extern全局变量吗?这些也不应该是可插入的吗?
编辑:
Gcc不优化外部函数调用(除非使用-fno-semantic-interposition
编译)
比如在ret_42()
中对int ret_fn_result(void) { return ret_42()+1; }
的调用,尽管如同对extern global const
变量的引用一样,对符号定义的唯一改变方法是通过插入。
0000000000000020 <ret_fn_result>:
20: 48 83 ec 08 sub $0x8,%rsp
24: e8 00 00 00 00 callq 29 <ret_fn_result+0x9>
29: 48 83 c4 08 add $0x8,%rsp
2d: 83 c0 01 add $0x1,%eax
我一直认为这是为了允许符号插入的可能性。顺便说一句,clang确实优化了它们。
我想知道在哪里(如果有的话)它说extern const w
中对ret_global()
的引用可以优化为中间体,而在ret_42()
中对ret_fn_result
的调用不能。
无论如何,除非你建立翻译单元边界,否则看起来符号iterposition在不同的编译器中是非常不一致和不可靠的。 :/(如果只是所有的全局变量一直是可插入的,除非-fno-semantic-interposition
打开,但是人们只能希望。)
根据What is the LD_PRELOAD trick?
,LD_PRELOAD
是一个环境变量,允许用户在加载任何其他库之前加载库,包括libc.so
。
从这个定义来看,它意味着两件事:
LD_PRELOAD
中指定的库可以重载其他库中的符号。在这里你指定LD_PRELOAD
为lib_override.so
,它定义了int ret_42(void)
和全局变量ptr
和w
,但它没有定义int ret_global(void)
。
因此int ret_global(void)
将从lib.so
加载,并且此函数将直接返回42
,因为编译器看不到ptr
的w
和lib.c
可能在运行时被修改(它们将被放入data
中的int const elf
部分,linux
保证它们不能通过硬件内存保护在运行时进行修改),因此编译器优化了直接返回42
。
编辑 - 测试:
所以我对你的脚本进行了一些修改:
#!/bin/sh -eu
: ${CC:=gcc}
cat > lib.c <<EOF
int ret_42(void) { return 42; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 42 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
EOF
cat > lib_override.c <<EOF
int ret_42(void) { return 50; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 60 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
EOF
cat > main.c <<EOF
#include <stdio.h>
int ret_42(void), ret_global(void);
struct wrap_ { const int x; };
extern struct wrap { const struct wrap_ *ptr; } const w;
int main(void)
{
printf("ret_42()=%d\n", ret_42());
printf("ret_global()=%d\n", ret_global());
printf("w.ptr->x=%d\n",w.ptr->x);
}
EOF
for c in *.c; do gcc -fpic -O2 $c -c; done
$CC lib.o -o lib.so -shared
$CC lib_override.o -o lib_override.so -shared
$CC main.o $PWD/lib.so
export LD_LIBRARY_PATH=$PWD
./a.out
LD_PRELOAD=$PWD/lib_override.so ./a.out
而这一次,它打印:
ret_42()=42
ret_global()=42
w.ptr->x=42
ret_42()=50
ret_global()=60
w.ptr->x=60
编辑 - 结论:
所以事实证明你要么重载所有相关的部分或者什么都不过载,否则你会得到这样棘手的行为。另一种方法是在标题中定义int ret_global(void)
,而不是在动态库中定义,因此当您尝试重载某些功能以进行某些测试时,您不必担心这一点。
编辑 - 解释为什么int ret_global(void)
可以超载,ptr
和w
不是。
首先,我想指出你定义的符号类型(使用How do I list the symbols in a .so file 的技术:
文件lib.so
:
Symbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000001110 6 FUNC GLOBAL DEFAULT 12 ret_global
6: 0000000000001120 17 FUNC GLOBAL DEFAULT 12 ret_fn_result
7: 000000000000114c 0 FUNC GLOBAL DEFAULT 14 _fini
8: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
9: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
10: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
Symbol table '.symtab' contains 28 entries:
Num: Value Size Type Bind Vis Ndx Name
23: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
24: 0000000000001110 6 FUNC GLOBAL DEFAULT 12 ret_global
25: 0000000000001120 17 FUNC GLOBAL DEFAULT 12 ret_fn_result
26: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
27: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
文件lib_override.so
:
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
6: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
7: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
8: 0000000000001108 0 FUNC GLOBAL DEFAULT 13 _init
9: 0000000000001120 0 FUNC GLOBAL DEFAULT 14 _fini
10: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
Symbol table '.symtab' contains 26 entries:
Num: Value Size Type Bind Vis Ndx Name
23: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
24: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
25: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
你会发现,尽管两者都是GLOBAL
符号,所有函数都标记为类型FUNC
,它是可重载的,而所有变量都有类型OBJECT
。类型OBJECT
意味着它不可重载,因此编译器不需要使用符号解析来获取数据。
有关这方面的更多信息,请查看:What Are "Tentative" Symbols? 。
您可以使用LD_DEBUG=bindings
来跟踪符号绑定。在这种情况下,它打印(除其他外):
17570: binding file /tmp/lib.so [0] to /tmp/lib_override.so [0]: normal symbol `ptr'
17570: binding file /tmp/lib_override.so [0] to /tmp/lib_override.so [0]: normal symbol `ptr'
17570: binding file ./a.out [0] to /tmp/lib_override.so [0]: normal symbol `ret_42'
17570: binding file ./a.out [0] to /tmp/lib_override.so [0]: normal symbol `ret_global'
所以ptr
中的lib.so
对象确实插入了,但主程序从未在原始库中调用ret_global
。调用从预加载的库转到ret_global
,因为函数也是插入的。
编辑:问题:I wonder where (if anywhere) it says that the reference to extern const w in ret_global() can be optimized to an intermediate while the call to ret_42() in ret_fn_result cannot.
-fvisibility=hidden
标志,则导出所有函数。由于导出了任何已定义的函数,因此无法对其进行内联。所以在ret_42
中调用ret_fn_result
无法内联。打开-fvisibility=hidden
,结果如下。linker
创建有时以一种方式工作的代码(内联),有时候工作重写(插入),有时工作直接在单个加载和执行生成的可执行文件的范围内。-Bsymbolic
,-Bsymbolic-functions
和--dynamic-list
为per SO。
-fno-semantic-interposition
当然是优化标志当ret_fn_result
被隐藏时,函数ret_42
,不导出然后内联。
0000000000001110 <ret_fn_result>:
1110: b8 2b 00 00 00 mov $0x2b,%eax
1115: c3 retq
步骤#1,主题在lib.c
中定义:
SCOPE const struct wrap_ { const int x; } ptr = { 42 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
当编译lib.c
时,w.ptr->x
被优化为const
。因此,通过不断折叠,它会导致:
$ object -T lib.so
lib.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
0000000000001110 g DF .text 0000000000000006 Base ret_42
0000000000002000 g DO .rodata 0000000000000004 Base ptr
0000000000001120 g DF .text 0000000000000006 Base ret_global
0000000000001130 g DF .text 0000000000000011 Base ret_fn_result
0000000000003e18 g DO .data.rel.ro 0000000000000008 Base w
ptr
和w
分别被用于rodata
和data.rel.ro
(因为const
指针)。常量折叠会产生以下代码:
0000000000001120 <ret_global>:
1120: b8 2a 00 00 00 mov $0x2a,%eax
1125: c3 retq
另一部分是:
int ret_42(void) { return 42; }
int ret_fn_result(void) { return ret_42()+1; }
这里ret_42
是一个函数,因为没有隐藏,它是导出函数。所以这是一个code
。两者都导致:
0000000000001110 <ret_42>:
1110: b8 2a 00 00 00 mov $0x2a,%eax
1115: c3 retq
0000000000001130 <ret_fn_result>:
1130: 48 83 ec 08 sub $0x8,%rsp
1134: e8 f7 fe ff ff callq 1030 <ret_42@plt>
1139: 48 83 c4 08 add $0x8,%rsp
113d: 83 c0 01 add $0x1,%eax
1140: c3 retq
考虑到,编译器确实只知道lib.c
,我们就完成了。把lib.so
放在一边。
第2步,编译lib_override.c
:
int ret_42(void) { return 50; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 60 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
这很简单:
$ objdump -T lib_override.so
lib_override.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
00000000000010f0 g DF .text 0000000000000006 Base ret_42
0000000000002000 g DO .rodata 0000000000000004 Base ptr
0000000000003e58 g DO .data.rel.ro 0000000000000008 Base w
导出函数ret_42
,然后ptr
和w
分别放到rodata
和data.rel.ro
(因为const
指针)。常量折叠会产生以下代码:
00000000000010f0 <ret_42>:
10f0: b8 32 00 00 00 mov $0x32,%eax
10f5: c3 retq
第3步,编译main.c
,让我们先看看对象:
$ objdump -t main.o
# SKIPPED
0000000000000000 *UND* 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000 *UND* 0000000000000000 ret_42
0000000000000000 *UND* 0000000000000000 printf
0000000000000000 *UND* 0000000000000000 ret_fn_result
0000000000000000 *UND* 0000000000000000 ret_global
0000000000000000 *UND* 0000000000000000 w
我们所有符号都未定义。所以他们必须来自某个地方。
然后我们默认用lib.so
链接,代码是(printf和其他的省略):
0000000000001070 <main>:
1074: e8 c7 ff ff ff callq 1040 <ret_42@plt>
1089: e8 c2 ff ff ff callq 1050 <ret_fn_result@plt>
109e: e8 bd ff ff ff callq 1060 <ret_global@plt>
10b3: 48 8b 05 2e 2f 00 00 mov 0x2f2e(%rip),%rax # 3fe8 <w>
现在我们手中有lib.so
,lib_override.so
和a.out
。
我们简单地打电话给a.out
:
现在让我们用lib_override.so
预加载:
1:main
从ret_42
调用lib_override.so
,因为它是预装的,ret_42
现在解析为lib_override.so
中的一个。
2:main
从ret_fn_result
调用lib.so
,ret_42
,但是来自lib_override.so
,因为它现在解析为lib_override.so
中的一个。
3:main
从ret_global
调用lib.so
,返回折叠常数42。
对于4:main
读取指向lib_override.so
的extern指针,因为它是预加载的。
最后,一旦使用内联的折叠常数生成lib.so
,就不能要求它们“可重写”。如果有意图拥有可覆盖的数据结构,则应该以其他方式定义它(提供操作它们的函数,不使用常量等)。因为在将某些东西定义为常量时,意图是明确的,编译器会做它所做的事情。然后即使在main.c
或其他地方将相同的符号定义为不恒定,它也不能在unfolded
中回到lib.c
。
#!/bin/sh -eu
: ${CC:=gcc}
cat > lib.c <<EOF
int ret_42(void) { return 42; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 42 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
int ret_fn_result(void) { return ret_42()+1; }
EOF
cat > lib_override.c <<EOF
int ret_42(void) { return 50; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 60 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
EOF
cat > main.c <<EOF
#include <stdio.h>
int ret_42(void), ret_global(void), ret_fn_result(void);
struct wrap_ { const int x; };
extern struct wrap { const struct wrap_ *ptr; } const w;
int main(void)
{
printf("ret_42()=%d\n", ret_42());
printf("ret_fn_result()=%d\n", ret_fn_result());
printf("ret_global()=%d\n", ret_global());
printf("w.ptr->x=%d\n",w.ptr->x);
}
EOF
for c in *.c; do gcc -fpic -O2 $c -c; done
$CC lib.o -o lib.so -shared
$CC lib_override.o -o lib_override.so -shared
$CC main.o $PWD/lib.so
export LD_LIBRARY_PATH=$PWD
./a.out
LD_PRELOAD=$PWD/lib_override.so ./a.out