TL: DR: -static
не по умолчанию, используйте его для создания исполняемого файла ELF, который запускает только ваш _start
.
-no-pie -nostdlib
также сделает c исполняемый файл просто потому, что он не является P IE и нет никаких динамических c библиотек для связи.
Также существует такая вещь, как -static-pie
, когда ядро загружает ваш исполняемый файл в случайный базовый адрес, но не сначала запустите ld.so (я думаю), но это не то, что вы получаете с -static
.
Просто чтобы прояснить, мы Вы говорите о Dynami c Количество команд (сколько фактически выполнено в пользовательском пространстве, perf stat -e instructions:u
), а не подсчет c ( сколько находится на диске / в памяти как часть исполняемого файла). Stati c count подсчитывает только инструкции внутри циклов и по-прежнему считает команды, которые никогда не выполняются.
Или, по крайней мере, я отвечаю. Это делает метаданные в других разделах и код, который не выполняется, не имеет значения.
Согласно GDB, код из ld- linux -x86-64.so.2 отображается в адрес программы пространство. Учитывая, что я отключил vdso и не включаю библиотеки, нужен ли этот файл для запуска программы?
Вы все еще создали независимый от позиции исполняемый файл (P IE). Это общий объект ELF с точкой входа, поэтому он все еще динамически связан. Так что на нем работает интерпретатор ld.so ELF. Ничего не поделаешь, потому что вы на самом деле не используете какие-либо общие библиотеки, но инструкции из 17k пространства пользователя звучат правильно. Я получаю 32606 или 7 инструкций для вашей программы в моей системе Arch Linux (glib c 2.31).
ld.so
запускается как "интерпретатор" для вашего двоичного файла аналогично тому, как /bin/sh
запускается для интерпретации исполняемого текстового файла, начинающегося с #!/bin/sh
. (Хотя загрузчик программ ELF Linux все еще выполняет некоторую работу по отображению сегментов программы в память в соответствии с заголовком программы исполняемого файла, поэтому ld.so не должен делать это вручную с помощью системных вызовов.)
Вы можете увидеть это, запустив под gdb ./foo5
и используя starti
вместо run
для остановки перед первой инструкцией пространства пользователя. Вы увидите, что вы находитесь в ld.so
_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
ABI который использовался ld.so.)
Использовать -static
-nostdlib
, используемый для обозначения -static
, в G CC, сконфигурированном, чтобы не делать PIEs по умолчанию. Но современные дистрибутивы действительно настраивают G CC, чтобы сделать пироги из соображений безопасности. См. 32-разрядные абсолютные адреса, больше не разрешенные в x86-64 Linux?
$ 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, поэтому, если я не использую это, он также считает команды, выполняемые внутри ядра. Это значительно варьируется от запуска к запуску, но всего около 60 тыс. для этого исполняемого файла stati c.
Я считаю только 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
показывает:
│ >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's answer о написании _start
в C и встроенный asm _exit(2)
, который может встраиваться в него.)
Обратите внимание, что вашему _start не удается передать arg c и argv в main, хотя RSP правильно выровнен на 16 байт перед вызовом функции. (Поскольку x86-64 SysV ABI гарантирует, что вход в процесс происходит с выровненным стеком). Вы можете сделать это с помощью MOV Load и LEA. Обратите внимание, что, поскольку вы не инициализируете lib c, даже если вы статически связали lib c, вы не можете вызывать ее функции.
См. Как получить значение аргументов с помощью встроенной сборки в C без Glib c? для некоторых хаков. (В основном автономный asm _start
, написанный в операторе asm()
в глобальном масштабе, или мой ответ - полный взлом соглашения о вызовах.)