В качестве быстрого и грязного хака , вы можете создать исполняемый файл с скомпилированной функцией C в качестве точки входа ELF. Просто убедитесь, что вы используете exit
или _exit
вместо возврата.
Если он динамически связан, вы можете по-прежнему использовать функции glibc в Linux (поскольку динамический компоновщик выполняет функции инициализации glibc). Не все системы такие, например, в cygwin вы определенно не можете вызывать функции libc, если вы (или стартовый код CRT) не вызвали функции инициализации libc в правильном порядке. Я не уверен, что даже гарантируется, что это работает в Linux, поэтому не зависите от него, за исключением экспериментов на вашей собственной системе.
Я использовал _start
+ _exit
для создания статического исполняемого файла для микробенчмаркинга сгенерированного компилятором кода с меньшими накладными расходами при запуске для perf stat ./a.out
. _exit
можно использовать, даже если glibc не был инициализирован, или использовать встроенный asm для запуска xor %edi,%edi
/ mov $60, %eax
/ syscall
(sys_exit (0) в Linux), поэтому вам не нужно даже статически связывать libc .
С еще более грязным хакерством вы можете получить доступ к argc и argv, зная ABI System V x86-64, для которого вы компилируете (см. Ответ @ zwol для цитаты из документа ABI), и Чем состояние запуска процесса отличается от соглашения о вызове функции:
argc
- это адрес возврата для нормальной функции (на что указывает RSP). GNU C имеет встроенную функцию доступа к обратному адресу текущей функции (или для перемещения по стеку).
argv[0]
- это место, где должен быть 7-й аргумент целого числа / указателя (первый аргумент стека , чуть выше адреса возврата). Это происходит / кажется, работает, чтобы взять его адрес и использовать его в качестве массива!
// Works only for the x86-64 SystemV ABI; only tested on Linux.
// DO NOT USE THIS EXCEPT FOR EXPERIMENTS ON YOUR OWN COMPUTER.
#include <stdio.h>
#include <stdlib.h>
// tell gcc *this* function is called with a misaligned RSP
__attribute__((force_align_arg_pointer))
void _start(int dummy1, int dummy2, int dummy3, int dummy4, int dummy5, int dummy6, // register args
char *argv0) {
int argc = (int)(long)__builtin_return_address(0); // load (%rsp), casts to silence gcc warnings.
char **argv = &argv0;
printf("argc = %d, argv[argc-1] = %s\n", argc, argv[argc-1]);
printf("%f\n", 1.234); // segfaults if RSP is misaligned
exit(0);
//_exit(0); // without flushing stdio buffers!
}
# with a version without the FP printf
peter@volta:~/src/SO$ gcc -nostartfiles _start.c -o bare_start
peter@volta:~/src/SO$ ./bare_start
argc = 1, argv[argc-1] = ./bare_start
peter@volta:~/src/SO$ ./bare_start abc def hij
argc = 4, argv[argc-1] = hij
peter@volta:~/src/SO$ file bare_start
bare_start: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=af27c8416b31bb74628ef9eec51a8fc84e49550c, not stripped
# I could have used -fno-pie -no-pie to make a non-PIE executable
Работает с оптимизацией или без, с gcc7.3. Меня беспокоило, что без оптимизации адрес argv0
будет ниже rbp
, куда он копирует arg, а не его первоначальное местоположение. Но, видимо, это работает.
gcc -nostartfiles
связывает glibc, но не стартовые файлы CRT.
gcc -nostdlib
пропускает обе библиотеки и файлы запуска CRT.
Очень мало из этого гарантировано для работы, но на практике он работает с текущей версией gcc на текущей x86-64 Linux и работал в прошлом в течение многих лет. Если он сломается, вы сохраните обе части. IDK, какие функции C нарушены, если пропустить код запуска CRT и просто полагаться на динамический компоновщик для запуска функций инициализации glibc. Кроме того, если взять адрес аргумента arg и получить доступ к указателям над ним, это UB, чтобы вы могли получить неработающий код-ген. gcc7.3 делает то, что вы ожидаете в этом случае.
gcc -mincoming-stack-boundary=3
(то есть 2 ^ 3 = 8 байт) - это еще один способ заставить gcc перестроить стек, потому что значение по умолчанию -mpreferred-stack-boundary=4
2 ^ 4 = 16 все еще остается в силе. Но это заставляет gcc предполагать недопустимый RSP для всех функций, а не только для _start
, поэтому я заглянул в документы и нашел атрибут, предназначенный для 32- бит, когда ABI перешел от только требующего выравнивания стека 4 байта к текущему требованию выравнивания 16 байтов для ESP
в 32-битном режиме.
Требование SysV ABI для 64-битного режима всегда было 16-байтовым выравниванием, но опции gcc позволяют создавать код, который не следует ABI.
// test call to a function the compiler can't inline
// to see if gcc emits extra code to re-align the stack
// like it would if we'd used -mincoming-stack-boundary=3 to assume *all* functions
// have only 8-byte (2^3) aligned RSP on entry, with the default -mpreferred-stack-boundary=4
void foo() {
int i = 0;
atoi(NULL);
}
С -mincoming-stack-boundary=3
мы получаем код выравнивания стека там, где он нам не нужен. Код перестановки стека в gcc довольно неуклюж, поэтому мы бы хотели этого избежать. (Не то, чтобы вы когда-либо использовали это для составления важной программы, в которой вы заботитесь об эффективности, используйте этот глупый компьютерный трюк только в качестве учебного эксперимента.)
Но в любом случае, см. Код в проводнике компилятора Godbolt с -mpreferred-stack-boundary=3
.
и без него.