PSk*_*cik 6 c compiler-construction optimization linker elf
我正在尝试使用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; }
Run Code Online (Sandbox Code Playgroud)
返回一个中间常量.
事实证明他们可以:
0000000000000010 <ret_global>:
10: b8 2a 00 00 00 mov $0x2a,%eax
15: c3 retq
Run Code Online (Sandbox Code Playgroud)
但令人惊讶的是,移除静态会产生相同的装配输出.这让我很好奇,因为如果全局不是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
Run Code Online (Sandbox Code Playgroud)
输出
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
Run Code Online (Sandbox Code Playgroud)
编译器可以用中间件替换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
Run Code Online (Sandbox Code Playgroud)
我一直认为这是为了允许符号插入的可能性.顺便说一句,clang确实优化了它们.
我想知道在哪里(如果有的话)它表示对extern const win 的引用ret_global()可以优化为中间,而对ret_42()in 的调用ret_fn_result则不能.
无论如何,除非你建立翻译单元边界,否则看起来符号iterposition在不同编译器之间是非常不一致和不可靠的.:/(如果只是所有全局变量一直是可插入的,除非-fno-semantic-interposition打开,否则会很好.但是人们只能希望.)
编辑:问题: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.
编译器常量折叠优化能够内联复杂的 const 变量和结构
函数的编译器默认行为是导出。如果-fvisibility=hidden未使用标志,则导出所有函数。因为任何定义的函数都是导出的,所以不能内联。所以对ret_42in的调用ret_fn_result不能内联。打开-fvisibility=hidden,结果如下。
比方说,如果可以同时出于优化目的导出和内联函数,则将导致linker创建有时以一种方式工作(内联)、有时被覆盖(插入)、有时直接工作的代码在生成的可执行文件的单次加载和执行范围内。
还有其他对此主题有效的标志。最值得注意的:
-Bsymbolic,-Bsymbolic-functions并--dynamic-list按照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
Run Code Online (Sandbox Code Playgroud)
步骤#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; }
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
其中ptr和分别w放置到rodata和data.rel.ro(因为const指针)。常量折叠会产生以下代码:
0000000000001120 <ret_global>:
1120: b8 2a 00 00 00 mov $0x2a,%eax
1125: c3 retq
Run Code Online (Sandbox Code Playgroud)
另一部分是:
int ret_42(void) { return 42; }
int ret_fn_result(void) { return ret_42()+1; }
Run Code Online (Sandbox Code Playgroud)
这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
Run Code Online (Sandbox Code Playgroud)
考虑到编译器只知道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 };
Run Code Online (Sandbox Code Playgroud)
这很简单:
$ 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
Run Code Online (Sandbox Code Playgroud)
导出函数ret_42,然后ptr和分别w放入rodata和data.rel.ro(因为const指针)。常量折叠会产生以下代码:
00000000000010f0 <ret_42>:
10f0: b8 32 00 00 00 mov $0x32,%eax
10f5: c3 retq
Run Code Online (Sandbox Code Playgroud)
STEP 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
Run Code Online (Sandbox Code Playgroud)
我们所有的符号都未定义。所以他们必须来自某个地方。
然后我们默认链接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>
Run Code Online (Sandbox Code Playgroud)
现在我们手里有lib.so、lib_override.so和。a.out
让我们简单地调用a.out:
现在让我们预加载lib_override.so:
对于 1:main调用ret_42fromlib_override.so因为它是预加载的,ret_42现在解析为 1 in lib_override.so。
对于 2:main调用ret_fn_resultfrom lib.sowhich 调用ret_42但 from lib_override.so,因为它现在解析为 1 in lib_override.so。
对于3:main调用返回折叠常量 42。ret_globallib.so
对于4:main读取指向 的外部指针lib_override.so,因为它是预加载的。
最后,一旦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
Run Code Online (Sandbox Code Playgroud)