TL:DR: -static
不是默认值,请使用它来制作仅运行您的 ELF 可执行文件_start
.
-no-pie -nostdlib
还将生成静态可执行文件,因为它不是 PIE 并且没有要链接的动态库。
还有这样的事情-static-pie
内核会将您的可执行文件加载到随机基地址,但是not首先运行 ld.so (我认为),但这不是你得到的-static
.
只是要明确的是,我们正在谈论的是dynamic指令数(实际上有多少executed在用户空间中,perf stat -e instructions:u
),而不是静态计数(有多少作为可执行文件的一部分位于磁盘/内存中)。静态计数仅对循环内的指令进行一次计数,并且仍然对从未执行的指令进行计数。
或者至少我是这么回答的。这使得其他部分中的元数据以及不执行的代码变得无关紧要。
根据gdb,来自ld-linux-x86-64.so.2的代码被映射到程序地址空间。鉴于我禁用了 vdso 并且不包含任何库,运行该程序是否需要此文件?
您仍然构建了位置无关的可执行文件 (PIE)。这是一个带有入口点的 ELF 共享对象,因此它仍然是动态链接的。所以ld.so ELF解释器就在它上面运行。它没有什么可做的,因为您实际上没有使用任何共享库,但 17k 用户空间指令听起来是正确的。我在我的 Arch Linux 系统 (glibc 2.31) 上收到了 32606 或 7 个关于你的程序的指令。
ld.so
作为二进制文件的“解释器”启动,其方式类似于/bin/sh
开始解释以以下内容开头的可执行文本文件#!/bin/sh
。 (虽然Linux的ELF程序加载器仍然根据可执行文件的程序头完成一些将程序段映射到内存的工作,所以ld.so不必通过系统调用手动完成这些工作。)
你可以通过运行来看到这一点gdb ./foo5
并使用starti
代替run
在第一个用户空间指令之前停止。你会发现你在ld.so
's _start
.
Reading symbols from ./foo5...
(No debugging symbols found in ./foo5)
Cannot access memory at address 0x1024 ### note this isn't a real address,
### just an offset relative to the base address / start of the file.
### That's another clue this is a PIE
(gdb) starti
Program stopped.
0x00007ffff7fd3100 in _start () from /lib64/ld-linux-x86-64.so.2
你也可以运行strace ./foo5
查看它所做的系统调用,表明发生了很多事情:
$ strace ./foo5
execve("./foo5", ["./foo5"], 0x7ffc12394d90 /* 50 vars */) = 0
brk(NULL) = 0x55741b4b7000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffca69312b0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d4fc4b000
arch_prctl(ARCH_SET_FS, 0x7f1d4fc4ba80) = 0
mprotect(0x557419622000, 4096, PROT_READ) = 0
strace: [ Process PID=303809 runs in 32 bit mode. ]
exit(0) = ?
(注意“以 32 位模式运行”;它没有,但 strace 检测到您使用了 32 位int $0x80
ABI代替正常的syscall
ld.so 使用的 ABI。)
Use -static
-nostdlib
用于暗示-static
,在 GCC 中默认配置为不生成 PIE。但出于安全原因,现代发行版确实会配置 GCC 来制作 PIE。看x86-64 Linux 中不再允许使用 32 位绝对地址?
$ file foo5
foo5: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1ac0a9af247fefebde100695805e5b73f06e891c, not stripped
构建后-static
, OTOH:
$ file foo5
foo5: ELF 64-bit LSB executable ...
$ perf stat --all-user ./foo5
Performance counter stats for './foo5':
0.03 msec task-clock # 0.151 CPUs utilized
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
1 page-faults # 0.030 M/sec
1,930 cycles # 0.058 GHz
12 instructions # 0.01 insn per cycle
4 branches # 0.121 M/sec
0 branch-misses # 0.00% of all branches
0.000219151 seconds time elapsed
0.000284000 seconds user
0.000000000 seconds sys
(奇怪的是 perf 不打印:u
对于您使用时的事件--all-user
。我的系统有/proc/sys/kernel/perf_event_paranoid
= 0 所以如果我不使用它,它也会计算内核内执行的指令。运行与运行之间的差异很大,但此静态可执行文件的总数约为 60k。)
我只计算了 11 条执行的用户空间指令,但显然我的 i7-6700k 在该事件中计算了 12 条。 (硬件支持对任何事件计数器屏蔽用户、内核或两者。这就是 perf 使用的功能。)
GDB 也确认了成功:
Reading symbols from ./foo5...
(No debugging symbols found in ./foo5)
Cannot access memory at address 0x401024
(gdb) starti
Starting program: /tmp/foo5
Program stopped.
0x0000000000401000 in _start ()
(gdb)
以及反汇编窗口layout reg
shows:
│ >0x401000 <_start> call 0x40100e <main>
│ 0x401005 <_start+5> mov eax,0x1
│ 0x40100a <_start+10> xor ebx,ebx
│ 0x40100c <_start+12> int 0x80
│ 0x40100e <main> push rbp
│ 0x40100f <main+1> mov rbp,rsp
│ 0x401012 <main+4> lea rax,[rip+0xfe7] # 0x402000
│ 0x401019 <main+11> mov QWORD PTR [rbp-0x8],rax
│ 0x40101d <main+15> mov eax,0x0
│ 0x401022 <main+20> pop rbp
│ 0x401023 <main+21> ret
你可以编译-O2
来优化您的main
减少到只有一个xor eax,eax
/ ret
,或者根本不调用它,因此只需执行 3 个用户空间指令。
或者在仍然使用 C 的同时优化用户空间指令计数,请参阅@mosvy 的回答关于写作_start
在 C 中,以及一个内联汇编_exit(2)
可以内联到其中。)
请注意,您的 _start 无法将 argc 和 argv 传递给 main,尽管它在函数调用之前确实具有正确 16 字节对齐的 RSP。 (因为 x86-64 SysV ABI 保证进程条目在堆栈对齐的情况下发生)。您可以使用 mov load 和 LEA 来做到这一点。请注意,由于您没有初始化 libc,因此即使静态链接 libc,您也无法调用它的函数。
See 如何在不使用 Glibc 的情况下使用 C 中的内联汇编获取参数值?对于一些黑客。 (基本上是独立的asm_start
写在一个asm()
全局范围内的声明,或者我的答案是对调用约定的彻底修改。)