Что такое двоичный интерфейс приложения (ABI)? - PullRequest
405 голосов
/ 31 января 2010

Я никогда не понимал, что такое ABI. Пожалуйста, не указывайте мне статью в Википедии. Если бы я мог это понять, я бы не стал публиковать такие длинные сообщения.

Это мой взгляд на различные интерфейсы:

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

Интерфейс: Это слой "существующий объект" между functionality и consumer этой функциональности. Интерфейс сам по себе ничего не делает Это просто вызывает функциональность, лежащую позади.

Теперь, в зависимости от того, кто пользователь, существуют различные типы интерфейсов.

Интерфейс командной строки (CLI) команды являются существующими объектами, потребитель - это пользователь, а функциональность - позади.

functionality: моя функциональность программного обеспечения, которая решает некоторые цель, для которой мы описываем этот интерфейс.

existing entities: команды

consumer: пользователь

Графический интерфейс пользователя (GUI) Окно, кнопки и т. Д. Являются существующими сущности, и снова потребитель является пользователем, а функциональность отстает.

functionality: моя программная функциональность, которая решает проблему, для которой мы описываем этот интерфейс.

existing entities: окно, кнопки и т.д ..

consumer: пользователь

Интерфейс прикладного программирования (API) функции (или быть более правильно) интерфейсы (в интерфейсном программировании) являются Существующие объекты, потребитель здесь другая программа, а не пользователь, и снова функциональность лежит за этим слоем.

functionality: моя функциональность программного обеспечения, которая решает некоторые Проблема, к которой мы описываем этот интерфейс.

existing entities: функций, интерфейсов (массив функций).

consumer: другая программа / приложение.

Двоичный интерфейс приложения (ABI) Здесь начинается моя проблема.

functionality: ???

existing entities: ???

consumer: ???

* * 1068 Я написал программное обеспечение на разных языках и предоставил различные виды интерфейсов (CLI, GUI и API), но я не уверен, что когда-либо предоставлял какой-либо ABI.

Википедия говорит:

ABI, такие как

  • тип данных, размер и выравнивание;
  • соглашение о вызовах, которое управляет аргументами функций переданные и возвращенные значения получены;
  • номера системных вызовов и как приложение должно выполнять системные вызовы к операционной системе;

Другие ABI стандартизируют детали, такие как

  • искажение имени в C ++,
  • распространение исключений и
  • Соглашение о вызовах между компиляторами на одной платформе, но не требует кроссплатформенной совместимости.
  • Кому нужны эти данные? Пожалуйста, не говорите ОС. Я знаю ассемблерное программирование. Я знаю, как работают ссылки и загрузка. Я точно знаю, что происходит внутри.

  • Почему в C ++ появилось искажение имен? Я думал, что мы говорим на двоичном уровне. Почему языки приходят?

В любом случае, я скачал двоичный интерфейс приложения [PDF] System V Edition 4.1 (1997-03-18) , чтобы увидеть, что именно в нем содержится. Ну, большая часть этого не имела никакого смысла.

  • Почему он содержит две главы (4-ю и 5-ю) для описания формата файла ELF ? На самом деле, это только две важные главы этой спецификации. Остальные главы посвящены «процессору». Во всяком случае, я думаю, что это совершенно другая тема. Пожалуйста, не говорите, что спецификации формата файлов ELF являются ABI. Он не может быть интерфейсом в соответствии с определением.

  • Я знаю, поскольку мы говорим на таком низком уровне, он должен быть очень конкретным. Но я не уверен, как это специфично для "архитектуры набора команд (ISA)"?

  • Где я могу найти ABI Microsoft Windows?

Итак, это основные запросы, которые меня беспокоят.

Ответы [ 15 ]

436 голосов
/ 16 марта 2010

Один простой способ понять «ABI» - это сравнить его с «API».

Вы уже знакомы с концепцией API.Если вы хотите использовать функции, скажем, некоторой библиотеки или вашей ОС, вы будете использовать API.API состоит из типов / структур данных, констант, функций и т. Д., Которые вы можете использовать в своем коде для доступа к функциональности этого внешнего компонента.

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

ABI важны, когда речь идет о приложениях, которые используют внешние библиотеки,Если программа создана для использования определенной библиотеки, и эта библиотека позднее обновляется, вам не нужно перекомпилировать это приложение (и с точки зрения конечного пользователя у вас может не быть исходного кода).Если в обновленной библиотеке используется тот же ABI, ваша программа не нуждается в изменении.Интерфейс библиотеки (который действительно заботится о вашей программе) остается тем же, хотя внутренняя работа могла измениться.Две версии библиотеки, имеющие один и тот же ABI, иногда называют «бинарно-совместимыми», поскольку они имеют один и тот же низкоуровневый интерфейс (вы должны иметь возможность заменить старую версию новой и не иметь никаких серьезных проблем).

Иногда изменения ABI неизбежны.Когда это происходит, любые программы, использующие эту библиотеку, не будут работать, если они не будут перекомпилированы для использования новой версии библиотеки.Если ABI изменяется, а API - нет, то старые и новые версии библиотеки иногда называют «совместимыми с исходным кодом».Это означает, что, хотя программа, скомпилированная для одной версии библиотеки, не будет работать с другой, исходный код, написанный для одной версии, будет работать для другой, если перекомпилируется.

По этой причине авторы библиотек стремятся сохранитьих ABI стабильный (чтобы минимизировать нарушение).Поддержание стабильности ABI означает не изменение интерфейсов функций (возвращаемый тип и число, типы и порядок аргументов), определения типов данных или структур данных, определенные константы и т. Д. Можно добавлять новые функции и типы данных, но существующие должны оставатьсятот же самый.Если развернуть, скажем, 16-битное поле структуры данных в 32-битное поле, то уже скомпилированный код, использующий эту структуру данных, не будет правильно обращаться к этому полю (или к любому последующему за ним).Доступ к элементам структуры данных преобразуется в адреса памяти и смещения во время компиляции, и если структура данных изменяется, то эти смещения не будут указывать на то, на что ожидает их код, и результаты в лучшем случае непредсказуемы.

ABI - это не обязательно то, что вы будете явно предоставлять, если только вы не ожидаете, что люди будут взаимодействовать с вашим кодом с использованием ассемблера.Это также не зависит от языка, поскольку (например) приложение C и приложение Pascal будут использовать один и тот же ABI после компиляции.

Редактировать: Относительно вашего вопроса о главах, касающихся формата файла ELF в документах SysV ABI: причина, по которой эта информация включена, заключается в том, что формат ELF определяет интерфейс между операционной системой и приложением. Когда вы указываете ОС запускать программу, она ожидает, что программа будет отформатирована определенным образом, и (например) ожидает, что первый раздел двоичного файла будет заголовком ELF, содержащим определенную информацию с определенными смещениями памяти. Так приложение передает важную информацию о себе в операционную систему. Если вы создаете программу в двоичном формате, отличном от ELF (например, a.out или PE), то ОС, которая ожидает приложения в формате ELF, не сможет интерпретировать двоичный файл или запустить приложение. Это одна из основных причин, по которой приложения Windows не могут быть запущены непосредственно на компьютере с Linux (или наоборот) без их повторной компиляции или запуска на каком-либо уровне эмуляции, который может переводиться из одного двоичного формата в другой.

IIRC, Windows в настоящее время использует формат Portable Executable (или, PE). В разделе «Внешние ссылки» этой страницы Википедии есть ссылки с дополнительной информацией о формате PE.

Кроме того, что касается вашего замечания об искажении имен в C ++: ABI может определить «стандартизированный» способ компиляции имен в C ++ для совместимости. То есть, если я создаю библиотеку, а вы разрабатываете программу, которая использует библиотеку, вы должны иметь возможность использовать другой компилятор, чем я, и не беспокоиться о несовместимости получаемых двоичных файлов из-за различных схем искажения имен. Это действительно полезно, только если вы определяете новый двоичный формат файла или пишете компилятор или компоновщик.

125 голосов
/ 31 января 2010

Если вы знаете сборку и как все работает на уровне ОС, вы соответствуете определенному ABI. ABI управляет такими вещами, как передача параметров и размещение возвращаемых значений. Для многих платформ есть только один ABI на выбор, и в этих случаях ABI просто «как все работает».

Однако ABI также управляет такими вещами, как классы / объекты, расположенные в C ++. Это необходимо, если вы хотите иметь возможность передавать ссылки на объекты через границы модуля или если вы хотите смешивать код, скомпилированный с разными компиляторами.

Кроме того, если у вас есть 64-разрядная ОС, которая может выполнять 32-разрядные двоичные файлы, у вас будут различные ABI для 32- и 64-разрядного кода.

Как правило, любой код, который вы ссылаете на один и тот же исполняемый файл, должен соответствовать одному и тому же ABI. Если вы хотите обмениваться данными между кодами, используя разные интерфейсы ABI, вы должны использовать протокол RPC или сериализацию.

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

ABI могут быть (частично) ISA-независимыми. Некоторые аспекты (такие как соглашения о вызовах) зависят от ISA, в то время как другие аспекты (такие как расположение классов C ++) не зависят.

Хорошо определенный ABI очень важен для людей, пишущих компиляторы. Без четко определенного ABI было бы невозможно генерировать совместимый код.

РЕДАКТИРОВАТЬ: Некоторые примечания для уточнения:

  • «Двоичный» в ABI не исключает использование строк или текста. Если вы хотите связать DLL, экспортирующую класс C ++, где-то в нем должны быть закодированы методы и сигнатуры типов. Вот тут-то и происходит сортировка имен в C ++.
  • Причина, по которой вы никогда не предоставляли ABI, заключается в том, что подавляющее большинство программистов никогда этого не сделают. ABI предоставляются теми же людьми, которые проектируют платформу (то есть операционную систему), и очень немногие программисты когда-либо будут иметь привилегию разрабатывать широко используемый ABI.
28 голосов
/ 30 декабря 2016

Вам на самом деле не нужно вообще нужен АБИ, если -

  • Ваша программа не имеет функций, и -
  • Ваша программа - это отдельный исполняемый файл, который выполняется один (то есть встроенная система), где он работает буквально единственно и ему не нужно ни с чем разговаривать.

упрощенное резюме:

API: «Вот все функции, которые вы можете вызвать.»

ABI: "Это как вызывать функцию."

ABI - это набор правил, которых придерживаются компиляторы и компоновщики, чтобы скомпилировать вашу программу, чтобы она работала правильно. ABI охватывают несколько тем:

  • Пожалуй, самой большой и важной частью ABI является стандарт вызова процедур , иногда называемый "соглашением о вызовах". Соглашения о вызовах стандартизируют перевод «функций» в код сборки.
  • ABI также определяют способ представления имен открытых функций в библиотеках, чтобы другой код мог вызывать эти библиотеки и знать, какие аргументы следует передать. Это называется "искажение имени".
  • ABI также определяют, какой тип данных может использоваться, как они должны быть выровнены и другие низкоуровневые детали.

Более подробно рассмотрим соглашение о вызовах, которое я считаю основой ABI:

Сама машина не имеет понятия "функции". Когда вы пишете функцию на языке высокого уровня, таком как c, компилятор генерирует строку кода ассемблера, например, _MyFunction1:. Это метка , которая в конечном итоге будет преобразована ассемблером в адрес. Эта метка отмечает «начало» вашей «функции» в коде сборки. В высокоуровневом коде, когда вы «вызываете» эту функцию, то, что вы действительно делаете, заставляет ЦП перейти на адрес этой метки и продолжить выполнение там.

При подготовке к прыжку компилятор должен сделать кучу важных вещей. Соглашение о вызовах похоже на контрольный список, который следует компилятору для выполнения всего этого:

  • Сначала компилятор вставляет немного ассемблерного кода для сохранения текущего адреса, чтобы, когда ваша «функция» была выполнена, ЦП мог вернуться назад в нужное место и продолжить выполнение.
  • Затем компилятор генерирует ассемблерный код для передачи аргументов.
    • Некоторые соглашения о вызовах требуют, чтобы аргументы помещались в стек ( в определенном порядке , конечно).
    • Другие соглашения предписывают, что аргументы должны быть помещены в конкретные регистры ( в зависимости от их типов данных , конечно).
    • В соответствии с другими соглашениями необходимо использовать определенную комбинацию стека и регистров.
  • Конечно, если раньше в этих регистрах было что-то важное, эти значения теперь перезаписываются и теряются навсегда, поэтому некоторые соглашения о вызовах могут предписывать компилятору сохранять некоторые из этих регистров до помещения в них аргументов. *
  • Теперь компилятор вставляет инструкцию перехода, указывающую ЦПУ перейти на ту метку, которую он сделал ранее (_MyFunction1:). В этот момент вы можете считать, что процессор находится «в» вашей «функции».
  • В конце функции компилятор помещает некоторый код сборки, который заставит CPU записать возвращаемое значение в правильное место. Соглашение о вызовах определяет, следует ли возвращать возвращаемое значение в конкретный регистр (в зависимости от его типа) или в стеке.
  • Теперь пришло время для очистки. Соглашение о вызовах будет определять, где компилятор размещает код сборки очистки.
    • В некоторых соглашениях говорится, что вызывающая сторона должна очистить стек. Это означает, что после того, как «функция» будет выполнена, и ЦП перейдет туда, где он был раньше, следующий код, который должен быть выполнен, должен быть очень специфическим кодом очистки.
    • В других соглашениях говорится, что некоторые конкретные части кода очистки должны находиться в конце «функции» до перехода назад.

Существует много различных ABI / соглашений о вызовах. Некоторые основные из них:

  • Для процессора x86 или x86-64 (32-разрядная среда):
    • Cdecl
    • STDCALL
    • азЬсаИ
    • VECTORCALL
    • THISCALL
  • Для процессора x86-64 (64-разрядная среда):
    • SystemV
    • MSNATIVE
    • VECTORCALL
  • Для процессора ARM (32-разрядный)
    • AAPCS
  • Для процессора ARM (64-разрядный)
    • AAPCS64

Здесь - отличная страница, которая фактически показывает различия в сборке, сгенерированной при компиляции для различных ABI.

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

Ваш компилятор понимает, как вызывать библиотечные функции, чрезвычайно важно. На размещенной платформе (то есть той, на которой ОС загружает программы), ваша программа не может даже мигать без вызова ядра.

17 голосов
/ 23 марта 2010

Двоичный интерфейс приложения (ABI) похож на API, но функция недоступна для вызывающей стороны на уровне исходного кода. Только двоичное представление доступно / доступно.

ABI могут быть определены на уровне архитектуры процессора или на уровне ОС. ABI - это стандарты, которым должна следовать фаза генерации кода компилятора. Стандарт устанавливается либо ОС, либо процессором.

Функциональность: Определите механизм / стандарт, чтобы сделать вызовы функций независимыми от языка реализации или конкретного компилятора / компоновщика / цепочки инструментов. Укажите механизм, который позволяет использовать JNI или интерфейс Python-C и т. Д.

Существующие объекты: функции в форме машинного кода.

Потребитель: другая функция (в том числе на другом языке, скомпилированная другим компилятором или связанная с другим компоновщиком).

10 голосов
/ 23 марта 2010

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

Существующие сущности: расположение параметров, семантика функций, распределение регистров. Например, архитектуры ARM имеют множество ABI (APCS, EABI, GNU-EABI, не говоря уже о куче исторических случаев) - использование смешанного ABI приведет к тому, что ваш код просто не будет работать при вызове через границы.

Потребитель: Компилятор, сборщики, операционная система, специфичная для процессора архитектура.

Кому нужны эти детали? Компилятор, сборщики, компоновщики, которые выполняют генерацию кода (или требования выравнивания), операционную систему (обработка прерываний, интерфейс syscall). Если вы занимались программированием на ассемблере, вы соответствовали ABI!

C ++ - искажение имен - это особый случай - проблема, связанная с компоновщиком и динамическим компоновщиком, - если искажение имени не стандартизировано, динамическое связывание не будет работать. Отныне C ++ ABI называется именно так, C ++ ABI. Это не проблема уровня компоновщика, а проблема генерации кода. Если у вас есть двоичный файл C ++, его невозможно совместить с другим ABI C ++ (искажение имени, обработка исключений) без перекомпиляции из исходного кода.

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

Все ABI являются специфическими для набора команд. ARM ABI не имеет смысла для процессора MSP430 или x86_64.

Windows имеет несколько ABI - например, fastcall и stdcall - два ABI общего назначения. Системный вызов ABI снова отличается.

7 голосов
/ 31 января 2010

Позвольте мне хотя бы ответить на часть вашего вопроса. С примером того, как Linux ABI влияет на системные вызовы, и почему это полезно.

Системный вызов - это способ для программы пользовательского пространства запросить что-либо в пространстве ядра. Он работает, помещая числовой код для вызова и аргумента в определенный регистр и вызывая прерывание. Затем происходит переключение в пространство ядра, и ядро ​​ищет числовой код и аргумент, обрабатывает запрос, помещает результат обратно в регистр и запускает переключение обратно в пространство пользователя. Это необходимо, например, когда приложение хочет выделить память или открыть файл (системные вызовы "brk" и "open").

Теперь системные вызовы имеют короткие имена "brk" и т. Д. И соответствующие коды операций, которые определены в системном заголовочном файле. Пока эти коды операций остаются неизменными, вы можете запускать одни и те же скомпилированные пользовательские программы с разными обновленными ядрами без необходимости перекомпиляции. Таким образом, у вас есть интерфейс, используемый предварительно скомпилированными бинарными файлами, отсюда и ABI.

4 голосов
/ 25 февраля 2014

Лучший способ различить ABI и API - это знать, для чего и для чего он используется:

Для x86-64 обычно есть один ABI (а для x86 32-bit есть другой набор):

http://www.x86 -64.org / документация / abi.pdf

https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/140-x86-64_Function_Calling_Conventions/x86_64.html

http://people.freebsd.org/~obrien/amd64-elf-abi.pdf

Linux + FreeBSD + MacOSX следуют за ним с некоторыми небольшими изменениями. И Windows x64 имеет свой собственный ABI:

http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/

Зная ABI и предполагая, что за ним также следует другой компилятор, тогда двоичные файлы теоретически знают, как вызывать друг друга (в частности, API библиотек) и передавать параметры через стек или через регистры и т. Д. Или какие регистры будут изменены при вызове функции и т. д. По существу эти знания помогут программному обеспечению интегрироваться друг с другом. Зная порядок расположения регистров / стека, я могу легко собрать воедино различные программы, написанные на сборках.

Но API разные:

Это имена функций высокого уровня с определенным аргументом, так что если различные части программного обеспечения строятся с использованием этих API, МОГУТ иметь возможность вызывать друг друга. Но необходимо соблюдать дополнительное требование ЖЕ АБИ.

Например, Windows раньше была POSIX API-совместимой:

https://en.wikipedia.org/wiki/Windows_Services_for_UNIX

https://en.wikipedia.org/wiki/POSIX

И Linux также совместим с POSIX. Но двоичные файлы нельзя просто переместить и запустить сразу. Но поскольку они использовали одинаковые NAMES в POSIX-совместимом API, вы можете взять одно и то же программное обеспечение на C, перекомпилировать его в другой ОС и сразу запустить его.

API предназначены для облегчения интеграции программного обеспечения - этап предварительной компиляции. Таким образом, после компиляции программное обеспечение может выглядеть совершенно иначе - если ABI отличаются.

ABI предназначен для определения точной интеграции программного обеспечения на уровне двоичного кода / сборки.

3 голосов
/ 22 апреля 2017

Резюме

Существуют различные толкования и точные мнения о точном слое, определяющем ABI (двоичный интерфейс приложения).

На мой взгляд, ABI является субъективным соглашением того, что считается данной / платформой для конкретного API. ABI - это «остальная часть» соглашений, которые «не изменятся» для конкретного API или будут решаться средой выполнения: исполнителями, инструментами, компоновщиками, компиляторами, jvm и ОС.

Определение интерфейса : ABI, API

Если вы хотите использовать такую ​​библиотеку, как joda-time, вы должны объявить зависимость от joda-time-<major>.<minor>.<patch>.jar. Библиотека следует передовым методам и использует Семантическое управление версиями . Это определяет совместимость API на трех уровнях:

  1. Патч - вам вообще не нужно менять свой код. Библиотека просто исправляет некоторые ошибки.
  2. Незначительный - вам не нужно менять код, так как дополнения
  3. Major - Интерфейс (API) изменен, и вам может потребоваться изменить код.

Чтобы вы могли использовать новый основной выпуск той же библиотеки, необходимо соблюдать множество других соглашений:

  • Бинарный язык, используемый для библиотек (в случаях Java целевая версия JVM, которая определяет байт-код Java)
  • Соглашения о вызовах
  • Соглашения JVM
  • Соглашения о связывании
  • Соглашения во время выполнения Все они определяются и управляются используемыми нами инструментами.

Примеры

Пример использования Java

Например, Java стандартизировала все эти соглашения не в инструменте, а в формальной спецификации JVM. Спецификация позволила другим поставщикам предоставлять другой набор инструментов, которые могут выводить совместимые библиотеки.

Java предоставляет два других интересных примера для ABI: версии Scala и Dalvik виртуальная машина.

Виртуальная машина Dalvik сломала ABI

Виртуальной машине Dalvik необходим байт-код другого типа, чем байт-код Java. Библиотеки Dalvik получены путем преобразования байт-кода Java (с тем же API) для Dalvik. Таким образом, вы можете получить две версии одного и того же API: определены исходным joda-time-1.7.2.jar. Мы могли бы позвонить мне joda-time-1.7.2.jar и joda-time-1.7.2-dalvik.jar. Они используют другой ABI, один для стекового ориентированного стандартного Java vms: Oracle, IBM, open Java или любой другой; а второй ABI - тот, что вокруг Далвика.

Последовательные версии Scala несовместимы

Scala не имеет бинарной совместимости между второстепенными версиями Scala: 2.X. По этой причине один и тот же API "io.reactivex" %% "rxscala"% "0.26.5" имеет три версии (в будущем больше): для Scala 2.10, 2.11 и 2.12. Что изменилось? Пока не знаю , но двоичные файлы несовместимы. Вероятно, в последних версиях добавлены вещи, которые делают библиотеки непригодными для использования на старых виртуальных машинах, возможно, вещи, связанные с соглашениями о связывании / именовании / параметрах.

Последовательные версии Java несовместимы

У Java также есть проблемы с основными выпусками JVM: 4,5,6,7,8,9. Они предлагают только обратную совместимость. Jvm9 знает, как запускать код, скомпилированный / нацеленный (опция javac -target) для всех других версий, в то время как JVM 4 не знает, как запустить код, нацеленный на JVM 5. Все это, пока у вас есть одна библиотека joda. Эта несовместимость вылетает из радара благодаря различным решениям:

  1. Семантическое управление версиями: когда библиотеки ориентированы на более высокую JVM, они обычно меняют основную версию.
  2. Используйте JVM 4 в качестве ABI, и вы в безопасности.
  3. Java 9 добавляет спецификацию о том, как вы можете включить байт-код для конкретной целевой JVM в той же библиотеке.

Почему я начал с определения API?

API и ABI - это просто соглашения о том, как вы определяете совместимость. Нижние уровни являются общими для множества семантики высокого уровня. Вот почему легко сделать некоторые соглашения. Первый тип соглашений касается выравнивания памяти, байтового кодирования, соглашений о вызовах, кодировок с прямым и прямым порядком байтов и т. Д. Помимо них вы получаете исполняемые соглашения, подобные описанным выше, связывающие соглашения, промежуточный байт-код , например тот, который используется Java или LLVM IR, используемый GCC. В-третьих, вы получаете соглашения о том, как искать библиотеки, как их загружать (см. Загрузчики классов Java). По мере того, как вы будете подниматься выше и выше в концепциях, у вас появляются новые соглашения, которые вы рассматриваете как данное. Вот почему они не добрались до семантической версии . Они скрыты или свернуты в версии major . Мы можем изменить семантическое управление версиями с помощью <major>-<minor>-<patch>-<platform/ABI>. Это то, что на самом деле уже происходит: платформа уже является rpm, dll, jar (байт-код JVM), war (веб-сервер jvm +), apk, 2.11 (конкретная версия Scala) и так далее. Когда вы говорите APK, вы уже говорите об определенной части ABI вашего API.

API можно портировать на разные ABI

Верхний уровень абстракции (источники, написанные на основе самого высокого API, могут быть перекомпилированы / перенесены на любую другую низкоуровневую абстракцию.

Допустим, у меня есть несколько источников для rxscala. Если инструменты Scala будут изменены, я могу перекомпилировать их. Если JVM изменится, у меня могут быть автоматические преобразования из старой машины в новую, не заботясь о концепциях высокого уровня. Хотя портировать может быть сложно, поможет любой другой клиент. Если новая операционная система создается с использованием совершенно другого ассемблерного кода, можно создать переводчик.

API-интерфейсы, портированные на языки

Существуют API, которые портированы на несколько языков, например реактивные потоки . В целом они определяют сопоставления с конкретными языками / платформами. Я бы сказал, что API - это основная спецификация, формально определенная на человеческом языке или даже на конкретном языке программирования. Все остальные «отображения» в некотором смысле являются ABI, иначе API, чем обычный ABI. То же самое происходит с интерфейсами REST.

3 голосов
/ 01 февраля 2010

Чтобы вызвать код в общих библиотеках или код вызова между блоками компиляции, объектный файл должен содержать метки для вызовов. C ++ изменяет имена меток методов для обеспечения скрытия данных и допускает перегруженные методы. Вот почему вы не можете смешивать файлы из разных компиляторов C ++, если они явно не поддерживают один и тот же ABI.

1 голос

Пример использования совместно используемой библиотеки Linux с минимальным объемом запуска

В контексте разделяемых библиотек наиболее важным следствием «наличия стабильного ABI» является то, что вам не нужно перекомпилировать свои программы после изменения библиотеки.

Так, например:

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

  • если вы продаете программу с закрытым исходным кодом, которая зависит от общей библиотеки, присутствующей в дистрибутиве пользователя, вы можете выпустить и протестировать меньше предварительных сборок, если уверены, что ABI стабилен в определенных версиях целевой ОС.

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

Теперь я хочу привести минимальный конкретный пример этого.

main.c

#include <assert.h>
#include <stdlib.h>

#include "mylib.h"

int main(void) {
    mylib_mystruct *myobject = mylib_init(1);
    assert(myobject->old_field == 1);
    free(myobject);
    return EXIT_SUCCESS;
}

mylib.c

#include <stdlib.h>

#include "mylib.h"

mylib_mystruct* mylib_init(int old_field) {
    mylib_mystruct *myobject;
    myobject = malloc(sizeof(mylib_mystruct));
    myobject->old_field = old_field;
    return myobject;
}

mylib.h

#ifndef MYLIB_H
#define MYLIB_H

typedef struct {
    int old_field;
} mylib_mystruct;

mylib_mystruct* mylib_init(int old_field);

#endif

Компилируется и работает нормально с:

cc='gcc -pedantic-errors -std=c89 -Wall -Wextra'
$cc -fPIC -c -o mylib.o mylib.c
$cc -L . -shared -o libmylib.so mylib.o
$cc -L . -o main.out main.c -lmylib
LD_LIBRARY_PATH=. ./main.out

Теперь предположим, что для v2 библиотеки мы хотим добавить в mylib_mystruct новое поле с именем new_field.

Если мы добавили поле до old_field как в:

typedef struct {
    int new_field;
    int old_field;
} mylib_mystruct;

и перестроить библиотеку, но не main.out, тогда утверждение не выполняется!

Это потому, что строка:

myobject->old_field == 1

сгенерировал сборку, которая пытается получить доступ к самой первой int структуры, которая теперь new_field вместо ожидаемого old_field.

Поэтому это изменение нарушило ABI.

Если, однако, мы добавим new_field после old_field:

typedef struct {
    int old_field;
    int new_field;
} mylib_mystruct;

тогда старая сгенерированная сборка все еще обращается к первой int структуры, и программа все еще работает, потому что мы сохранили ABI стабильным.

Вот полностью автоматизированная версия этого примера на GitHub .

Другим способом сохранения стабильности ABI было бы рассматривать mylib_mystruct как непрозрачную структуру и обращаться к ее полям только через помощники методов. Это упростит поддержание стабильности ABI, но приведет к снижению производительности, поскольку мы будем выполнять больше вызовов функций.

API против ABI

В предыдущем примере интересно отметить, что добавление new_field перед old_field только нарушило ABI, но не API.

Это означает, что если бы мы перекомпилировали нашу main.c программу с библиотекой, она бы работала независимо.

Однако мы бы также нарушили API, если бы изменили, например, сигнатуру функции:

mylib_mystruct* mylib_init(int old_field, int new_field);

, поскольку в этом случае main.c вообще перестанет компилироваться.

Семантический API против API программирования

Мы также можем классифицировать изменения API по третьему типу: семантические изменения.

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

Поэтому возможно нарушить семантический API, не нарушая саму сборку программы.

Например, если мы изменили

myobject->old_field = old_field;

до:

myobject->old_field = old_field + 1;

тогда это не нарушило бы ни API программирования, ни ABI, но main.c семантический API сломался бы.

Существует два способа программной проверки API контракта:

  • проверить кучу угловых чехлов. Это легко сделать, но вы всегда можете пропустить один.
  • формальная проверка . Сложнее сделать, но производит математическое доказательство правильности, по существу объединяя документацию и тесты в «человеческий» / машинно проверяемый способ! Пока, конечно, в вашем официальном описании нет ошибки; -)

Список всего, что нарушает ABI общей библиотеки C / C ++

TODO: найти / создать окончательный список:

Пример минимального запуска Java

Что такое двоичная совместимость в Java?

Протестировано в Ubuntu 18.10, GCC 8.2.0.

...