1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
## Общие соображения
В библиотеку добавлены реализации символов, появившихся в `libc.so.6` Ubuntu
14.04 с момента Ubuntu 12.04.
Если какой-либо объектный файл ссылается на один из таких символов, то при
компоновке этот символ будет взят из нашей библиотеки. В противном случае,
компоновщик сослался бы на такой символ из `libc.so.6`, добавив к исполняемому
файлу зависимость от новой версии динамической библиотеки (`GLIBC_2.16`,
`GLIBC_2.17` или `GLIBC_2.18`). Такой исполняемый файл не может быть запущен на
Ubuntu 12.04, даже если ни один из новых символов не используется в runtime.
На Ubuntu 14.04 или более новых, в случае если процесс загружает `libc.so.6`, мы
будем в runtime иметь две реализации символов, нашу и libc. Но все добавленные
функции не имеют какого-либо состояния и никакими деталями внутренней реализации
не связаны с другими. Нет разницы, какую из реализаций использовать, и даже
попеременное использование различных реализаций в одном и том же контексте не
должно приводить к некорректной работе.
В какой-то момент этот слой совместимости будет отключен: https://st.yandex-team.ru/DEVTOOLS-7436
### Разделяемая реализация
Была идея оформить новые реализации так, чтобы в случае их наличия и в
загруженной libc динамический компоновщик выбирал для всех ссылок одну
реализацию.
По всей видимости, для этого требуется собирать исполняемый файл как PIE. Только
в этом случае для наших реализаций будут сгенерированы дополнительные PLT
прослойки и вызовы наших реализаций будут проходить через них.
Но в этом случае вообще все вызовы будут происходить таким образом и это
повлияет на производительность. Ухудшения производительности, наверное, можно
избежать, явно указав, какие символы в исполняемом файле должны быть публичными,
но сейчас нет способа сделать это в одном месте, учитывая, что в некоторых
случаях этот список должен дополняться.
## `getauxval` и `secure_getenv`
Функция `getauxval` требует загрузки и хранения «Auxiliary Vector» (см.
[здесь](https://refspecs.linuxfoundation.org/LSB_1.3.0/IA64/spec/auxiliaryvector.html)).
Эти данные доступны в момент запуска процесса. libc из новых Ubuntu сохраняет
эти данные и предоставляет реализацию `getauxval`. В этом случае наша реализация
перенаправляет вызовы `getauxval` в libc, получив при старте исполняемого файла
соответствующий указатель.
Если реализация libc недоступна (на старых Ubuntu или если libc не загружена),
то эти данные можно получить, прочитав `/proc/self/auxv`. Это также делается
один раз при старте.
В обоих случаях, статически инициализируется синглтон `NUbuntuCompat::TGlibc`,
который производит эти действия в конструкторе.
`secure_getenv` использует одно из значений `getauxval`, поэтому к нему всё это
также относится.
Каждый метод новой libc реализован в отдельном объектом файле. `TGlibc` также
находится в отдельном файле, и ссылки на неё стоят только в местах использования.
Если при компоновке не понадобились ни `getauxval`, ни `secure_getenv`, то
объектный файл с `TGlibc` тоже не будет выбран компоновщиком, и в этом случае
никакой лишней статической инициализации выполняться не будет.
## Патч libc.so
Чтобы иметь возможность использовать `getauxval` (точнее его реализацию
`__getauxval`) из новой libc, если таковая уже используется процессом,
библиотека совместимости объявляет этот символ у себя как внешний и слабый. При
загрузке динамический компоновщик устанавливает значение этого символа из libc
или `nullptr`, если его там нет. Наша реализация `getauxval` проверяет
доступность реализации libc, просто сравнивая указатель с `nullptr`.
В Аркадии также есть код, который подобным образом работает с символом
`__cxa_thread_atexit_impl`.
Однако, если компоновать такую программу с использованием новой libc, то к таким
символам и самой программе будет приписано требование соответствующей (новой)
версии libc. Чтобы этого не произошло, при сборке с этой библиотекой
совместимости используется патченный вариант `libc.so.6`, где у таких символов
удалена версия.
Также, файлы, проверяющие, что доступна реализация из libc, должны быть собраны
как PIC. В противном случае вместо значения, заполненного динамическим
компоновщиком, компилятор ещё на стадии компиляции использует `nullptr` и
проверка никогда не срабатывает.
## Упоминания
Идея о возможности добавить слой совместимости была взята из ClickHouse.
* [https://clickhouse.tech/](https://clickhouse.tech/)
* [https://wiki.yandex-team.ru/clickhouse/](https://wiki.yandex-team.ru/clickhouse/)
|