Как изменить путь интерпретатора и передать аргументы командной строки в" исполняемую " общую библиотеку в 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));

в начало