Обычно загрузчик автоматически загружает все необходимые библиотеки при запуске приложения. Но при необходимости также можно загружать библиотеки вручную. Для этого предназначены встроенные функции dlopen и dlsym. Функция dlopen указывает имя файла общей библиотеки, которую нужно открыть, а также параметр флагов для открытия библиотеки. Эта функция возвращает указатель на дескриптор общей библиотеки, который можно использовать для поиска символов с помощью dlsym. Функция dlsym ищет в общей библиотеке указанный символ и, если находит, возвращает его значение (обычно указатель). Подобные функции часто используются для добавления плагинов в код.
Например, пусть у нас есть файл print.asm, который содержит функцию для вывода строки на консоль:
global print:function ; Функция print выводит текст на консоль ; Параметры ; RDI - количество символов ; RSI - ссылка на строку section .text print: mov rdx, rdi mov rdi, 1 mov rax, 1 syscall ret
Через регистр rdi функция получает количество символов, а через регистр rsi - адрес выводимой строки. Для вывода строки выполняется системный вызов номер 1 - функция write.
Скомпилируем файл в общую библиотеку "print.so":
nasm -felf64 print.asm -o print.o ld -shared print.o -o print.so
И пусть у нас есть файл app.asm, который использует эту библиотеку:
global _start extern dlopen extern dlsym section .data file_name: db "print.so", 0 function_name: db "print",0 message: db "Hello METANIT.COM",10, 0 ; строка для вывода на консоль len equ $-message section .text _start: lea rdi, [rel file_name] mov rsi, 1 ; флаг для отложенной загрузки call dlopen ; загружаем библиотеку mov rdi, rax ; помещаем дескриптор библиотеки в rdi lea rsi, [rel function_name] ; помещаем имя функции в rsi call dlsym ; получаем указатель на функцию - в регистре rax mov rdi, len ; первый параметр функци print - количество символов lea rsi, [rel message] ; второй параметр функции print - выводимая строка call rax ; вызываем функцию print через указатель в rax mov rax, 60 syscall
Весь код разбивается на три части. Вначале открываем библиотеку, название которой хранится в переменной file_name:
lea rdi, [rel file_name] mov rsi, 1 ; флаг для отложенной загрузки call dlopen ; загружаем библиотеку
После вызова в регистре rax будет дескриптор библиотеки. Далее вызываем функцию dlsym
, которой передается полученный дескриптор и имя функции:
mov rdi, rax ; помещаем дескриптор библиотеки в rdi lea rsi, [rel function_name] ; помещаем имя функции в rsi call dlsym ; получаем указатель на функцию - в регистре rax
После вызова в регистр rax помещается указатель на найденную функцию - в данном случае на функцию print. Используя полученный указатель, мы можем вызвать функцию:
mov rdi, len ; первый параметр функци print - количество символов lea rsi, [rel message] ; второй параметр функции print - выводимая строка call rax ; вызываем функцию print через указатель в rax
Через регистры rdi и rsi функции передаются параметры. Поскольку в регистре rax
хранится адрес функции, то с помошью выражения
call rax
вызываем код, который располагается по хранимому адресу.
Для доступа к функциям dlopen
и dlsym
нам нужно загрузить библиотеку libc.so. Скомпилируем приложение:
nasm -felf64 app.asm -o app.o ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -lc app.o -o app
Полный консольный вывод:
root@Eugene:~/asm# nasm -felf64 print.asm -o print.o root@Eugene:~/asm# ld -shared print.o -o print.so root@Eugene:~/asm# nasm -felf64 app.asm -o app.o root@Eugene:~/asm# ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -lc app.o -o app root@Eugene:~/asm# ./app Hello METANIT.COM root@Eugene:~/asm#