Программная загрузка библиотек

Последнее обновление: 29.09.2023

В общем случае загрузчик автоматически загружает все необходимые библиотеки при запуске приложения. Но мы также можем загружать их вручную. Для этого предназначены функции dlopen и dlsym. Функция dlopen указывает имя файла общей библиотеки, которую нужно открыть, а также параметр флагов для открытия библиотеки. Он возвращает указатель на дескриптор общей библиотеки, которую можно использовать для поиска символов с помощью dlsym. Функция dlsym ищет в общей библиотеке указанный символ и, если находит, возвращает его значение (обычно указатель). Подобные функции часто используются для добавления плагинов в код.

Например, пусть у нас есть файл exponent.s, который содержит функцию возведения числа в степень:

.globl exponent

.text
# Функция возвращает число в определенной степени
# Принимает два параметра:
# %rdi - число, возводимое в степень
# %rsi - степень, в которую надо возвести число
# Результат функции возвращается через регистр %rax
exponent:
    movq $1, %rax      # результат в %rax
    jmp condition      # переход к проверке условия
mainloop:
    mulq %rdi          # %rax = %rax *%rdi
    decq %rsi
condition:
    cmpq $0, %rsi
    jnz mainloop
    ret

Через регистр %rdi функция получает число, а через регистр %rsi - степень, в которую надо возвести число. Результат возвращается через регистр %rax. Для упрощения примера пусть наша функция работает только положительными показателями степеней.

Скомпилируем файл в общую библиотеку "libexp.so":

gcc -shared exponent.s -o libexp.so

И пусть у нас есть файл hello.s, который использует эту библиотеку:

.globl main
 
.data
file_name: .ascii "libexp.so\0"
function_name: .asciz "exponent"
message: .asciz "%d^%d = %d\n"  # строка форматирования
num: .quad 2
power: .quad 4

.text
main:
    subq $8, %rsp           # выравнивание должно быть по 16 байтам

    movq $file_name, %rdi
    movq $1, %rsi           # флаг для отложенной загрузки
    call dlopen             # загружаем библиотеку
 
    movq %rax, %rdi         # помещаем дескриптор в %rdi
    movq $function_name, %rsi   # помещаем имя функции в %rsi
    call dlsym              # получаем указатель на функцию - в регистре %rax

    movq num, %rdi     # первый параметр функции exponent - возводимое число
    movq power, %rsi   # второй параметр функции exponent - степень
    call *%rax         # выхываем функцию exponent через указатель в %rax

    # вывод результата
    movq $message, %rdi     # первый параметр функции printf - строка форматирования
    movq num, %rsi          # второй параметр функции printf - первое число
    movq power, %rdx        # третий параметр функции printf - второе число
    movq %rax, %rcx         # четвертый параметр функции printf - третье число - результат
    call printf

    addq $8, %rsp
    ret

Весь код разбивается на четыре части. Вначале открываем библиотеку, название которой хранится в переменной file_name:

movq $file_name, %rdi
movq $1, %rsi           # флаг для отложенной загрузки
call dlopen             # загружаем библиотеку

После вызова в регистре %rax будет дескриптор библиотеки. Далее вызываем функцию dlsym, которой передается полученный дескриптор и имя функции:

movq %rax, %rdi         # помещаем дескриптор в %rdi
movq $function_name, %rsi   # помещаем имя функции в %rsi
call dlsym              # получаем указатель на функцию - в регистре %rax

После вызова в регистр %rax помещается указатель на найденную функцию - в данном случае на функцию exponent. Используя полученный указатель, мы можем вызвать функцию:

movq num, %rdi     # первый параметр функции exponent - возводимое число
movq power, %rsi   # второй параметр функции exponent - степень
call *%rax         # выхываем функцию exponent через указатель в %rax

Через регистры %rdi и %rsi функции передаются параметры. Поскольку в регистре %rax хранится адрес функции, то с помошью выражения call *%rax вызываем код, который располагается по хранимому адресу.

В последней части программы с помощью функции printf выводим полученный результат на консоль.

Для доступа к функциям dlopen и dlsym нам нужно загрузить библиотеку libdl.so. Скомпилируем приложение:

gcc -rdynamic -no-pie hello.s -ldl -o hello

Для упрощения примера здесь применяется флаг -no-pie, которое создает зависимое от позиции приложение. Затем запустим приложение. Полный вывод компиля

root@Eugene:~/asm# gcc -shared exponent.s -o libexp.so
root@Eugene:~/asm# gcc -rdynamic -no-pie hello.s -ldl -o hello
root@Eugene:~/asm# ./hello
2^4 = 16
root@Eugene:~/asm#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850