Как изменить путь интерпретатора и передать аргументы командной строки в" исполняемую " общую библиотеку в Linux?
вот минимальный пример для" исполняемой " общей библиотеки (предполагаемое имя файла:mini.c
):
// Interpreter path is different on some systems
//+definitely different for 32-Bit machines
const char my_interp[] __attribute__((section(".interp")))
= "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#include <stdio.h>
#include <stdlib.h>
int entry() {
printf("WooFoo!n");
exit (0);
}
если его компилировать, например:gcc -fPIC -o mini.so -shared -Wl,-e,entry mini.c
. "Бег" в результате .so
будет выглядеть так:
confus@confusion:~$ ./mini.so
WooFoo!
мой вопрос теперь:
как мне изменить вышеуказанную программу, чтобы передать аргументы командной строки вызову .so
-файл? пример сеанса оболочки после изменения может, например, выглядеть так это:
confus@confusion:~$ ./mini.so 2 bar
1: WooFoo! bar!
2: WooFoo! bar!
confus@confusion:~$ ./mini.so 3 bla
1: WooFoo! bla!
2: WooFoo! bla!
3: WooFoo! bla!
5: WooFoo! Bar!
было бы неплохо обнаружение во время компиляции, когда целью является 32-разрядный или 64-разрядный двоичный чтобы изменить строку интерпретатора соответствующим образом. В противном случае получается "доступ к поврежденной общей библиотеке" предупреждение. Что-то вроде:
#ifdef SIXTY_FOUR_BIT
const char my_interp[] __attribute__((section(".interp"))) = "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#else
const char my_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.2";
#endif
или даже лучше, чтобы обнаружить соответствующий путь полностью автоматически, чтобы убедиться, что он подходит для системы, на которой скомпилирована библиотека.
2 ответов
как мне изменить вышеуказанную программу, чтобы передать аргументы командной строки вызову .Итак-файл?
при запуске общей библиотеки,argc
и argv
будет передано вашей функции ввода в стеке.
проблема в том, что соглашение о вызовах, используемое при компиляции вашей общей библиотеки в x86_64 linux, будет System V AMD64 ABI, который не принимает аргументы в стеке, но в реестры.
вам понадобится код клея ASM, который извлекает аргумент из стека и помещает их в правильные регистры.
вот простой .ASM-файл можно сохранить как запись.asm и просто ссылка с:
global _entry
extern entry, _GLOBAL_OFFSET_TABLE_
section .text
BITS 64
_entry:
mov rdi, [rsp]
mov rsi, rsp
add rsi, 8
call .getGOT
.getGOT:
pop rbx
add rbx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc
jmp entry wrt ..plt
этот код копирует аргументы из стека в соответствующие регистры, а затем называет свой entry
функция в положении-независимый путь.
вы можете просто написать свой entry
как будто это был обычный main
функция:
// Interpreter path is different on some systems
//+definitely different for 32-Bit machines
const char my_interp[] __attribute__((section(".interp")))
= "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#include <stdio.h>
#include <stdlib.h>
int entry(int argc, char* argv[]) {
printf("WooFoo! Got %d args!\n", argc);
exit (0);
}
и вот как вы затем скомпилируете свою библиотеку:
nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o
преимущество в том, что у вас не будет встроенных операторов asm, смешанных с вашим кодом C, вместо этого ваша реальная точка входа чисто абстрагируется в стартовом файле.
было бы также неплохо обнаружить во время компиляции, когда цель является 32-разрядным или 64-разрядным двоичным файлом, чтобы соответственно изменить строку интерпретатора.
к сожалению, есть нет полностью чистого, надежного способа сделать это. Лучшее, что вы можете сделать, это положиться на ваш предпочтительный компилятор, имеющий правильные определения.
поскольку вы используете GCC, вы можете написать свой код C следующим образом:
#if defined(__x86_64__)
const char my_interp[] __attribute__((section(".interp")))
= "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#elif defined(__i386__)
const char my_interp[] __attribute__((section(".interp")))
= "/lib/ld-linux.so.2";
#else
#error Architecture or compiler not supported
#endif
#include <stdio.h>
#include <stdlib.h>
int entry(int argc, char* argv[]) {
printf("%d: WooFoo!\n", argc);
exit (0);
}
и имеют два разных файла запуска.
Для 64бит:
global _entry
extern entry, _GLOBAL_OFFSET_TABLE_
section .text
BITS 64
_entry:
mov rdi, [rsp]
mov rsi, rsp
add rsi, 8
call .getGOT
.getGOT:
pop rbx
add rbx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc
jmp entry wrt ..plt
и один для 32bit:
global _entry
extern entry, _GLOBAL_OFFSET_TABLE_
section .text
BITS 32
_entry:
mov edi, [esp]
mov esi, esp
add esi, 4
call .getGOT
.getGOT:
pop ebx
add ebx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc
push edi
push esi
jmp entry wrt ..plt
что означает, что теперь у вас есть два немного разных способа компиляции библиотеки для каждого цель.
для 64bit:
nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64
и для 32bit:
nasm entry32.asm -f elf32
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32
Итак, чтобы подвести итог, у вас теперь есть два файла запуска entry.asm
и entry32.asm
набор определяет в mini.c
это автоматически выбирает правильный интерпретатор и два немного разных способа компиляции вашей библиотеки в зависимости от цели.
Итак, если мы действительно хотим пройти весь путь, все, что осталось, это создать Makefile, который обнаруживает правильную цель и создает ваш библиотека соответственно.
давайте так и сделаем:
ARCH := $(shell getconf LONG_BIT)
all: build_$(ARCH)
build_32:
nasm entry32.asm -f elf32
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32
build_64:
nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64
и мы закончили. Просто запустите make
построить свою библиотеку, и пусть волшебство произойдет.
добавить
int argc;
char **argv;
asm("mov 8(%%rbp), %0" : "=&r" (argc));
asm("mov %%rbp, %0\n"
"add , %0" : "=&r" (argv));
в начало