LINUX.ORG.RU

ABI internals


0

1

В случае стабильного ABI библиотек, не должны меняться например размеры объявленных структур или смещения полей внутри структур. Простой пример из 20 строк программа+библиотека легко демонстрирует это - в .h файле библиотеки liba объявляем структуру, в .cpp - её простую реализацию; линкуем программу с этой liba, в которой используется структура и выводятся на консоль значения её полей ("1" и "2" например); меняем ABI библиотеки, добавив скажем первым полем в структуру char a[32], компилим библиотеку; программу заново с этой библиотекой не линкуем; теперь программа выводит не 1 2 а значения из массива [32]. Очевидно, что обращения данным идут по адресу, а не по символу. Это всё вроде понятно, и разжёвано много кем (тем же Саттером с Pimpl или Qt-шниками с d-pointers). Теперь самое интересное - КАК происходит обращение к полям структуры в памяти? Интересен полностью подробно весь механизм, начиная от компиляции программы (не библиотеки) и обращения к полям структуры в памяти, почему происходит именно так и не иначе, и т.д. Я что-то обломался найти такую инфомацию, не подскажете?

Спасибо.

Ты всё это серьезно, или в качестве прикола "смотрите, какие тупые вопросы задают студни"?

tailgunner ★★★★★
()
Ответ на: комментарий от alex_custov

> серьёзно конечно

[херассе... чему сейчас в универах учат? O_O]

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

> почему происходит именно так и не иначе

Потому что поле структуры в Си - это по определению нечто, лежащее по фиксированному смещению от начала структуры.

> Я что-то обломался найти такую инфомацию, не подскажете?

Здравый смысл, какой-нибудь учебник по компиляторам и gcc -S. Еще можно пошарить в сети насчет спецификаций реальных ABI.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

>>Концептуально, обращение к полю структуры происходит по смещению от базового адреса, которым является адрес самой структуры (обычно архитектура прддерживает режим адресации база:смещение). Компилируется библиотека или приложение - без разницы.

>>Потому что поле структуры в Си - это по определению нечто, лежащее по фиксированному смещению от начала структуры.


я же написал - всё это и так понятно, мне нужен уровень ниже, где-то в районе ld и ld-linux.so или

>>какой-нибудь учебник по компиляторам

alex_custov ★★★★★
() автор топика
Ответ на: комментарий от alex_custov

> я же написал - всё это и так понятно, мне нужен уровень ниже, где-то в районе ld и ld-linux.so

google "Linkers and loaders"

anonymous
()
Ответ на: комментарий от alex_custov

> я же написал - всё это и так понятно

Ты написал "КАК происходит обращение к полям структуры в памяти?", на что и получил ответ.

> мне нужен уровень ниже, где-то в районе ld и ld-linux.so или

Спецификация ELF, книга "Linkers and loaders" (ссылка проскакивала на ЛОРе).

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

+ выравнивание структур учесть.

anonymous
()
Ответ на: комментарий от tailgunner

поэтому я написал "КАК", а не "как" ;) То что машина заводится повортом ключа и так понятно, мне интересно КАК она заводится. Нужен детальный механизм, а не "по адресу". То что "по адресу" и так понятно. В частности - загрузка программы, слинкованной с библиотекой, где лежит реализация структуры или класса.

"Linkers and loaders" что-то близко к этому, спасибо.

alex_custov ★★★★★
() автор топика
Ответ на: комментарий от alex_custov

Ты задешь два НЕсвязанных друг с другом вопроса:

1. Загрузка программы в память
2. Обращение к элементам структуры или класса

На оба из них даны ответы в этом треде.

anonymous
()

> Теперь самое интересное - КАК происходит обращение к полям структуры в памяти? Интересен полностью подробно весь механизм, начиная от компиляции программы (не библиотеки) и обращения к полям структуры в памяти, почему происходит именно так и не иначе, и т.д.

как-то так:
1. в исходниках описывается структура (определение типа) и обращение к структуре (через поле или указатель на структуру(поле, метод класса, итп))
2. компилятор генерирует ассемблерный файл, с размещёнными в BSS секции структурами, выравненными на границу выравнивания.
3. Линкер "подверстывает" нужные объектные файлы/функции/библиотеки, удаляет ненужный код, получает исполнимый образ бинарника.
4. Загрузчик грузит этот образ, при загрузке делает релокацию смещений на перемещаемый код, инициализирует секцию данных, запускает код из образа
5. При запуске в runtime программа создаёт указатель на структуру, обращается к нему, передаёт его в другие "плагины" /нити, копирует в другие процессы (см. про mmap файлы, подгрузку .so)
6. Создание объектов делается менеджером памяти (программы). Который грузится из библиотеки, берёт память от ядра (kmap), выдаёт указатели на объекты для userlevel приложений. Можно сказать, разные менеджеры памяти задают разные отображения (и стратегии отображений) памяти ядра на userlevel память, делая это безопасным для нескольких процессов способом.

Теперь вопросы. Почему у одной сущности (объекта приложения) есть 2-3 разных указателя? Указатель в userlevel памяти, указатель с точки зрения ядра, и какой-то свой OID, Object ID внутри приложения.
Почему сериализация объекта с диска в память и наоборот делается непрозрачным способом, сложнее чем загрузка исполняемого бинарника и релокация (подгрузка образа), или mmap файла?
Почему на разных местах этой цепочки столько посредников? Когда можно было бы сделать просто GUID-"адреса" объектов, и грузить/выгружать их тем же самым механизмом, что и loader ELF-файла?

anonymous
()
Ответ на: комментарий от anonymous

>Почему сериализация объекта с диска в память и наоборот делается непрозрачным способом, сложнее чем загрузка исполняемого бинарника и релокация (подгрузка образа), или mmap файла?

Если под объектом подразумевается С-структура, выделенная через malloc, то эта структура может содержать поинтеры на объекты размещенные на стеке. Как такую структуру сериализуешь?

anonymous
()
Ответ на: комментарий от anonymous

да, то есть сериализация объекта подразумевает сериализацию транзитивных ему, доступных из него по ссылкам. Это понятно. Если указатели -- временные, времени жизни приложения, то понадобится воссоздавать объекты по ссылкам, с сопоставимым временем жизни. Это тоже понятно. Чего не понятно, так что мешает расширить контекст ссылки, её "время жизни". Сделать указатели большей ширины, GUIDы, 64-128 бит. Тогда Эти объекты по ссылкам воссоздавать не нужно будет, достаточно просто обеспечить уникальность разных указателей, и доступность конкретных указателей в соответствии с их временем жизни. Но вместо неявного, мы вынесем его явно, нумератором, который будет создавать уникальные GUID (а значит, любая хеш-функция от такого уникального GUID тоже скорее всего будет уникальной. Разные хеш-функции задают разные "соотношения" между GUID-указателями).

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

anonymous
()
Ответ на: комментарий от anonymous

да, то есть сериализация объекта подразумевает сериализацию транзитивных ему, доступных из него по ссылкам. Это понятно.
Если указатели -- временные, времени жизни приложения, то понадобится воссоздавать объекты по ссылкам, с сопоставимым временем жизни. Это тоже понятно.
Чего не понятно, так что мешает расширить контекст ссылки, её "время жизни". Сделать указатели большей ширины, GUIDы, 64-128 бит. Тогда Эти объекты по ссылкам воссоздавать не нужно будет, достаточно просто обеспечить уникальность разных указателей, и доступность конкретных указателей в соответствии с их временем жизни. Но вместо неявного, мы вынесем его явно, нумератором, который будет создавать уникальные GUID (а значит, любая хеш-функция от такого уникального GUID тоже скорее всего будет уникальной. Разные хеш-функции задают разные "соотношения" между GUID-указателями).

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

anonymous
()
Ответ на: комментарий от anonymous

> структура может содержать поинтеры на объекты размещенные на стеке.

воот. То есть, не все поинтеры одинаково полезны. У них может быть разное время жизни, области видимости. Нужны нормальные ссылки, что-то в духе Cyclone C, которые знают о контексте объявления и разыменования. Тогда этот неявный контекст можно вынести наружу, если обеспечить соотв. механизм целостности контекстов. Тогда вообще не нужно будет заботиться относительно проблем вроде срыва стека, просто если ссылка в этом контексте имеет смысл, она есть, если нет -- то это NULL.

anonymous
()
Ответ на: комментарий от anonymous

вот это что-то похожее на ответ, а не "по адресу". Вопросы ещё остались, но дальше уже сам разберусь.

alex_custov ★★★★★
() автор топика
Ответ на: комментарий от anonymous

вообще эта проблема наверное вылезла от отсутствия нормальных замыканий. Вот взяли символ и разорвали пополам на две части: указатель(клиент) и контекст(сервер). Механизм замыканий обеспечивает "целостность контекста" автоматически, а с половиной концепции, "указателем" безотносительно контекста так не получается. Поэтому хочется считать их тегами в какой-то другой структуре, в которой есть полноценные контексты и замыкания.

anonymous
()
Ответ на: комментарий от alex_custov

ОК, сформулируй вопросы, если есть, для определения того места, которое не четко ясно. Но в целом про "Linkers and loaders" и кросс-компиляцию должно навести резкость в какой-то степени.

anonymous
()


прочитал тред три раза. долго думал. по отдельности слова вроде знакомые, но попытка протрассировать пучок мыслей упирается в отсыревшую траву.

граждане, вы о чем вообще :-?

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от alex_custov

>>Потому что поле структуры в Си - это по определению нечто, лежащее по фиксированному смещению от начала структуры.

> я же написал - всё это и так понятно, мне нужен уровень ниже, где-то в районе ld и ld-linux.so или


о чём вообще топик ? %) линкер ничего не знает о структурах, это задача компилятора (хинт: заголовочные файлы). например простой пример:

$ cat a.c
typedef struct my_struct
{
int field1;
char field2;
void *field3;
} my_struct;

my_struct my_struct_instance;

$ gcc -c a.c
$ objdump -t a.o

a.o: file format elf32-i386

SYMBOL TABLE:
00000000 l df *ABS* 00000000 a.c
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l d .note.GNU-stack 00000000 .note.GNU-stack
00000000 l d .comment 00000000 .comment
0000000c O *COM* 00000004 my_struct_instance


как можно заметить - в .o файле есть информация только о _переменной_, о самой структуре (my_struct) как и о её алиасе (typedef ... my_struct) нет ни слова.

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

линкер здесь вообще не в тему...

Eshkin_kot ★★
()
Ответ на: комментарий от Eshkin_kot

> именно поэтому версии заголовочных файлов с которыми компилировались библиотеки должны совпадать с версиями заголовочных файлов с которыми компилировалась основная программа. потому что только в .h файлах и содержится информация о структурах и их полях.

это личные грабли компилятора Си, у которого нет нормальной таблицы символов. Поэтому функции вызываются с теми прототипами, которые описаны на клиенте (в хедерах), а не с теми, которыми собирался сервер (библиотека). Просто они оказываются совместимы, поэтому код линкуется. Как совместимы int main(int,char**) и void main(). Более строгий язык мог бы такое и не слинковать.

> линкер здесь вообще не в тему...

по-твоему, в .so нет кода иницализации, который вызывается при dlopen()? То есть, что, .so не экспортирует данных?

anonymous
()
Ответ на: комментарий от anonymous

правда, топикстартер спрашивал не об этом. А (как я понял) о том, как обеспечить стабильный ABI с учетом изменения структур. И да, вопрос про стабильный ABI уходит, если есть исходники на всё и можно просто пересобрать и клиента и сервера с новой версией с нуля.

anonymous
()
Ответ на: комментарий от anonymous

>о том, как обеспечить стабильный ABI с учетом изменения структур

А чего тут думать-то? Достаточно посмотреть на используемый в Win32 вариант, то есть хранить размер структуры в ней самой и добавлять новые поля в конец. Тогда сервер (винда) в зависимости от того, какой размер передаст клиент, будет учитывать только нужные поля.

anonymous
()

Лучший выход, используемый на практике -- это обращение к чужим записям по указателям (opaque pointers). Если надо изменить что-то в записи, вызывается процедура, если нужно считать что-нибудь из записи, вызывается функция. Таким образом, вся работа с записью происходит только в одном модуле, целостность которого гарантирует компилятор. В принципе, можно и не по указателям. Коль скоро размер структуры постоянен во время выполнения программы, этот размер можно узнать при инициализации и использовать для определения типа:

package Some_Lib_Binding is

...

type Some_Type is private;

...

private

function Get_Size_Of_Some_Type return Integer;

pragma Import (...);

Size_Of_Some_Type : constant Natural := Get_Size_Of_Some_Type;

type Some_Type is array (1 .. Size_Of_Some_Type) of aliased Character;

pragma Pack (Some_Type);

end Some_Lib_Binding;

но такие трюки, кроме как в Аде я не знаю, где ещё можно запрограммировать. Может быть, в C99, там вроде были какие-то послабления насчёт вычислимости размера массива на этапе компиляции. В целом, по сравнению с Адой, такие языки, как C и C++, не могут считаться системными.

P. S. Ещё под тему топика частично попадают Google Protocol Buffers.

Nihilist
()
Ответ на: комментарий от Nihilist

> В целом, по сравнению с Адой, такие языки, как C и C++, не могут считаться системными.

Ada фанбой детектед O_O

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.