Версионирование символов позволяет разделяемой библиотеке предоставлять разные версии одной и той же функции. Каждая программа использует функцию той версии, которая была текущей на момент ее (статической) компоновки с библиотекой. Благодаря этому можно вносить в библиотеку несовместимые изменения, не увеличивая номер ее мажорной версии. В крайнем случае версионирование символов может заменить собой традиционный механизм назначения мажорных и минорных версий. Такое применение данной технологии практикуется в библиотеке glibc 2.3 и выше; это позволило сделать все ее версии, начиная с 2.0, совместимыми в рамках единого мажорного номера (libc.so.6).
Применение версионирования символов показано в следующем примере. Начнем с создания первой версии разделяемой библиотеки с помощью версионного сценария:
$ cat sv_lib_v1.c
#include
void xyz(void) { printf("v1 xyz\n"); }
$ cat sv_v1.map
VER_1 {
global: xyz;
local: *; # Скрываем любые другие символы
};
$ gcc — g — c — fPIC — Wall sv_lib_v1.c
$ gcc — g — shared — o libsv.so sv_lib_v1.o — Wl, — version-script,sv_v1.map
В версионных сценариях знак решетки (#) обозначает начало комментария.
Чтобы упростить этот пример, мы не станем вручную указывать имена soname и номера мажорных версий библиотеки.
На данном этапе версионный сценарий sv_v1.map используется только для управления видимостью символов разделяемой библиотеки; экспортируется лишь функция xyz(), тогда как все остальные символы (а таковых в нашем небольшом примере просто нет) скрываются. Теперь создадим программу p1, которая будет применять эту библиотеку:
$ cat sv_prog.c
#include
int
main(int argc, char *argv[])
{
void xyz(void);
xyz();
exit(EXIT_SUCCESS);
}
$ gcc — g — o p1 sv_prog.c libsv.so
Запустив эту программу, мы увидим ожидаемый результат:
$ LD_LIBRARY_PATH=. /p1
v1 xyz
Теперь предположим, что нам нужно изменить определение xyz() внутри библиотеки, но при этом программа p1 должна иметь возможность использовать старую версию данной функции. Необходимо определить в библиотеке две версии xyz():
$ cat sv_lib_v2.c
#include
__asm__(".symver xyz_old,xyz@VER_1");
__asm__(".symver xyz_new,xyz@@VER_2");
void xyz_old(void) { printf("v1 xyz\n"); }
void xyz_new(void) { printf("v2 xyz\n"); }
void pqr(void) { printf("v2 pqr\n"); }
Две версии xyz() доступны с помощью функций xyz_old() и xyz_new(). Первая из них соответствует оригинальной версии xyz(), которая будет и дальше использоваться программой p1. Вторая предоставляет определение xyz(), доступное программам, скомпонованным с новой версией библиотеки.
Две директивы. symver связывают эти функции с разными версионными метками в модифицированном версионном сценарии, который мы задействуем для создания новой версии разделяемой библиотеки (показан чуть ниже). Первая директива говорит о том, что xyz_old() является реализацией функции xyz(), которая будет использоваться в программах, скомпонованных с версионной меткой VER_1 (в нашем примере это программа p1), и что xyz_new() — реализация новой версии xyz(), доступной для программ, скомпонованных с меткой VER_2.
Во второй директиве. symver применяется обозначение @@ вместо @; это говорит о том, что приложения, скомпонованные с библиотекой статически, должны быть привязаны по умолчанию именно к данному определению xyz(). Только одна из директив. symver должна быть помечена с помощью символов @@.
Версионный сценарий для нашей измененной библиотеки будет выглядеть так:
$ cat sv_v2.map
VER_1 {
global: xyz;
local: *; # Скрываем любые другие символы
};
VER_2 {
global: pqr;
} VER_1;
Данный сценарий содержит новую версионную метку, VER_2, которая зависит от метки VER_1. Эта зависимость обозначена следующей строчкой:
} VER_1;
Зависимости версионных меток отражают связи между соседними версиями библиотеки. С точки зрения семантики единственный эффект от зависимостей версионных меток в Linux состоит в том, что версионный раздел наследует ключевые слова global и local от раздела, от которого он зависит.
Зависимости можно объединять. К примеру, мы могли бы создать еще один версионный раздел VER_3, зависящий от VER_2, и т. д.
Имена версионных меток сами по себе ничего не значат. Их связи друг с другом определяются только их зависимостями; имена VER_1 и VER_2 были выбраны только для того, чтобы эти связи были более наглядными. Для упрощения администрирования в имена версионных меток рекомендуется включать имя пакета и номер версии. Например, версионные метки в glibc имеют следующий вид: GLIBC_2.0, GLIBC_2.1 и т. д.