vak: (Default)
Serge Vakulenko ([personal profile] vak) wrote2023-04-19 06:39 pm

Софтверному архитектору на заметку

Представьте, что вы разрабатываете некую софтину, и хотите разделить её на части. Одна часть по жизни должна оставаться постоянной, а другая часть (или много частей) могут заменяться или подгружаться по потребности. Типичный пример: ядро операционной системы и программы юзера, или графический редактор и его плагины.

Между частями софтины имеется определённый программный интерфейс: обычно набор процедур/функций, заданный на некоем языке, скажем Си. Постоянная часть реализует эти функции и "экспортирует" их для потребления переменными частями. То есть имеем некоторый include-файл, скажем "api.h".

Переменные части, или плагины, удобно компилировать в виде разделяемых библиотек в позиционно независимом коде, чтобы можно было подгрузить и выполнить в любом удобном месте.

Внимание, вопрос: как скомпилировать такой плагин? Ведь его не с чем линковать. У нас нет никакой библиотеки типа "api.so", которую можно было бы подсунуть линкеру.

Разберём на примере. Пусть основной код предоставляет плагину всего одну функцию: tell_user(string). Вот такой файл api.h:
void tell_user(const char *);
Простейший плагин, выдающий юзеру сообщение:
#include "api.h"
void _start()
{
tell_user("Hi!");
}
Пытаемся компилировать, естественно, получаем ошибку:
$ cc -shared -fPIC plugin.c -o plugin.elf
plugin.c: undefined reference to `tell_user'
Линкеру нужно подсунуть некую разделяемую библиотеку api.so, чтобы он увидел там внешний символ tell_user и настроил связи на него. Как сделать такую библиотеку?

Оказывается, есть способ. Компилятор clang умеет извлекать из Си-шных исходников описание интерфейса в формате YAML. А отдельная утилита llvm-ifs может делать из этого описания бинарник разделяемой библиотеки для линкера.

Делаем раз, получаем описание интерфейса:
$ clang -c -emit-interface-stubs api.h
$ cat api.ifs
--- !ifs-v1
IfsVersion: 3.0
Target: x86_64-apple-macosx13.0.0
Symbols:
- { Name: "tell_user", Type: Func }
...
Делаем два, компилируем интерфейс в бинарник api.so:
$ llvm-ifs api.ifs --output-elf=api.so
$ file api.so
api.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), no program header, stripped
Теперь можем скомпилировать наш плагин:
$ cc -shared -fPIC plugin.c api.so -o plugin.elf
$ size plugin.elf
text data bss dec hex filename
677 344 80 1101 44d plugin.elf
Дело сделано, бинарник плагина готов. Теперь можно заняться реализацией собственно основной софтины, загружающей и выполняющей этот бинарник. 

Подробная статья про внутреннее устройство ELF-файла с динамическим связыванием: https://mcuoneclipse.com/2021/06/05/position-independent-code-with-gcc-for-arm-cortex-m/

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org