Что такое двоичный интерфейс приложения (ABI)?


493

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

Это мое мышление о различных интерфейсах:

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

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

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

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

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

existing entities: команды

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

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

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

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

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

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

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

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

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

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

functionality: ???

existing entities: ???

consumer: ???

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

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

ABI охватывает такие детали, как

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

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

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

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

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

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

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

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

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


7
«Пожалуйста, не говорите, ОС» Компиляторы должны знать ABI. Линкеры должны знать ABI. Ядро должно знать ABI, чтобы настроить программу в ОЗУ для правильной работы. Что касается C ++, см. Ниже, он намеренно превращает метки в тарабарщину из-за перегрузки и закрытых методов, и компоновщик и любой другой компилятор должны иметь совместимое искажение имен для работы с ним, другими словами, тот же ABI.
Джастин Смит

8
Я думаю, что вопрос так ясен; точное описание ожидаемого формата ответа и, тем не менее, ни одного удовлетворительного ответа, который можно принять.
legends2k

3
@ legends2k Я считаю, что OP действительно знает, что такое ABI, но не осознает этого. Подавляющее большинство программистов никогда не будут проектировать или предоставлять ABI, потому что это работа дизайнеров ОС / платформ.
JesperE

4
@JesperE: Я согласен с вашей точкой зрения. Но, вероятно, ОП хочет знать это четко в формате, который он / она считает нужным, даже если ему / ей может не потребоваться предоставление ABI.
legends2k

2
Я был в неведении. Недавно во время работы со всеми этими вещами. Я понял, что такое ABI на самом деле. Да, я согласен, что мой шаблон неисправен. Это не подходит, чтобы вписать ABI в мой шаблон. Спасибо @ Джаспер. Просто потребовался опыт работы, чтобы понять твой ответ.
когти

Ответы:


536

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

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

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

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

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

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

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

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

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

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


2
@bta, спасибо за отличный ответ. Является ли соглашение о вызовах своего рода ABI? Спасибо
camino

37
Хороший ответ. За исключением того, что это не то, что ABI. ABI - это набор правил, которые определяют соглашение о вызовах и правила для разметки структур. Паскаль передает аргументы в стеке в обратном порядке от приложений на C, поэтому компиляторы pascal и C НЕ компилируются в один и тот же ABI. Соответствующие стандарты для компиляторов C и Pascal неявно гарантируют, что это будет так. Компиляторы C ++ не могут определить «стандартный» способ искажения имен, поскольку стандартного способа не существует. Соглашения об обработке имен в C ++ не были совместимы между компиляторами C ++, когда в Windows были конкурирующие компиляторы C ++.
Робин Дэвис,

1
Определенно также увидеть autotools.io/libtool/version.html и fedoramagazine.org/...
Pacerier

1
@RobinDavies: На платформах, где компиляторы Pascal вызывали бы функции pop аргументы, заданные их вызывающими, компиляторы C, как правило, определяют средства, с помощью которых программист может указать, что определенные функции должны использовать или должны использовать те же соглашения о вызовах, что и Компиляторы Pascal, хотя компиляторы C обычно по умолчанию используют соглашение, согласно которому вызываемые функции оставляют в стеке все, что там размещают их вызывающие.
суперкат

Могу ли я сказать, что файлы obj, созданные компилятором C, содержат ABI?
Миту Радж

144

Если вы знаете сборку и как все работает на уровне ОС, вы соответствуете определенному 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.

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

2
@jesperE, "ABI управляет такими вещами, как, как параметры передаются, где возвращаются значения."
camino

3
Да. Собственное имя - «соглашение о вызовах», которое является частью ABI. ru.wikipedia.org/wiki/X86_calling_conventions
JesperE

4
это правильный и точный ответ без многословия (а шум )!
Наваз

Я рекомендую написать немного сборки. Это поможет людям понять ABI более осязаемым образом.
КунЮ Цай

40

Тебе вообще не нужен ABI, если ...

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

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

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 они используют, он может правильно вызывать функции из них, не разрушая стек.

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


19

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

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

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

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

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


Почему ABI определяется архитектурой? Почему разные ОС на одной архитектуре не могут определять разные ABI?
Андреас Хефербург

10

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

Существующие сущности: расположение параметров, семантика функций, распределение регистров. Например, архитектура 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 снова отличается.


9

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

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

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


4

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


4

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

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

http://www.x86-64.org/documentation/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 предназначены для определения точной интеграции программного обеспечения на уровне двоичного кода / сборки.


Соглашение о вызовах Windows x86-64 не использует соглашение о вызовах SysV, которое используют все другие операционные системы x86-64. Linux / OS X / FreeBSD имеют одинаковое соглашение о вызовах, но не имеют полного ABI. ABI ОС включает номера системных вызовов. например, freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/… говорит, что SYS_execve11 на 32-битной Linux, но 59 на FreeBSD.
Питер Кордес

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

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

«Уровень совместимости с FreeBSD», никогда не слышал об этом. Можете ли вы указать на соответствующий исходный код ядра Linux? Но существует обратное: freebsd.org/doc/en_US.ISO8859-1/books/handbook/linuxemu.html .
Питер Теох

Это не то, что я использую. Я думал, что что- то подобное существует, но, может быть, это больше не так. tldp.org/HOWTO/Linux+FreeBSD-6.html говорит, что он не поддерживается, и что это с 2000 года. xD. unix.stackexchange.com/questions/172038/… подтверждает, что оно было заброшено и никогда не было сделано заново (поскольку никто не хотел этого достаточно сильно, чтобы сделать это). personality(2)можно установить PER_BSD. Я думаю, что помню, как все время видел personality(PER_LINUX)в straceвыводе, но современные 64-битные двоичные файлы Linux больше этого не делают.
Питер Кордес

4

Пример минимально работающей ABI для общей библиотеки 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 контракта:

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

    Эта концепция тесно связана с формализацией самой математики: /math/53969/what-does-formal-mean/3297537#3297537

Список всего, что нарушает C / C ++ разделяемые библиотеки ABI

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

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

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

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


3

ABI должен быть согласованным между вызывающим абонентом и вызываемым абонентом, чтобы быть уверенным, что вызов успешен. Использование стека, использование регистра, всплывающее окно в конце процедуры. Все это самые важные части ABI.


3

Резюме

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

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

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

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

  1. Патч - вам вообще не нужно менять свой код. Библиотека просто исправляет некоторые ошибки.
  2. Незначительный - Вам не нужно менять код, так как дополнения
  3. Основные - Интерфейс (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 - тот, что вокруг Dalvik.

Последовательные выпуски 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 знает, как запускать код, скомпилированный / нацеленный ( -targetопция javac ) для всех других версий, в то время как JVM 4 не знает, как запускать код, нацеленный на JVM 5. Все это, пока у вас есть одна библиотека joda. Эта несовместимость вылетает из радара благодаря различным решениям:

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

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

API и ABI - это просто соглашения о том, как вы определяете совместимость. Нижние уровни являются общими для множества семантики высокого уровня. Вот почему легко сделать некоторые соглашения. Первый тип соглашений касается выравнивания памяти, байтового кодирования, соглашений о вызовах, кодировок с прямым и прямым порядком байтов и т. Д. Помимо них вы получаете исполняемые соглашения, подобные описанным выше, соглашения о связывании, промежуточный байт-код, такой как тот, который используется Java или LLVM IR используется GCC. В-третьих, вы получаете соглашения о том, как найти библиотеки, как их загрузить (см. Загрузчики классов Java). По мере того, как вы будете подниматься выше и выше в концепциях, у вас появляются новые соглашения, которые вы рассматриваете как данность. Вот почему они не добрались до семантического контроля версий .версия. Мы могли бы изменить семантическое управление версиями с помощью <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.


1

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


1

Я также пытался понять ABI, и ответ JesperE был очень полезным.

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

Вики KDE определяет библиотеку как двоичную, совместимую «если программа, динамически связанная с предыдущей версией библиотеки, продолжает работать с более новыми версиями библиотеки без необходимости перекомпиляции». Для получения дополнительной информации о динамическом связывании см. Статическое связывание против динамического связывания.

Теперь давайте попробуем взглянуть только на самые основные аспекты, необходимые для двоичной совместимости библиотеки (при условии, что в библиотеке нет изменений исходного кода):

  1. Одинаковая / обратно совместимая архитектура набора команд (инструкции процессора, структура файла регистра, организация стека, типы доступа к памяти, а также размеры, компоновка и выравнивание основных типов данных, к которым процессор может напрямую обращаться)
  2. Те же соглашения о вызовах
  3. Соглашение об одноименном названии (это может понадобиться, если, скажем, программе на Фортране нужно вызвать некоторую библиотечную функцию C ++).

Конечно, есть много других деталей, но это в основном то, что ABI также охватывает.

Более конкретно, чтобы ответить на ваш вопрос, из вышесказанного можно сделать вывод:

Функциональность ABI: двоичная совместимость

существующие объекты: существующие программы / библиотеки / ОС

потребитель: библиотеки, ОС

Надеюсь это поможет!


1

Двоичный интерфейс приложения (ABI)

Функциональные возможности:

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

Существующие объекты:

  • Логические блоки, которые непосредственно участвуют в выполнении программы: ALU, регистры общего назначения, регистры для отображения памяти / ввода-вывода ввода-вывода и т. Д.

потребитель:

  • Языковые процессоры, компоновщик, ассемблер ...

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

Искаженное имя в C ++, потому что в вашем приложении могут потребоваться ссылки на объектные файлы из разных языков высокого уровня. Рассмотрите возможность использования стандартной библиотеки GCC для выполнения системных вызовов Windows, созданных с помощью Visual C ++.

ELF - одно из возможных ожиданий компоновщика от объектного файла для интерпретации, хотя у JVM может быть другая идея.

Для приложения Windows RT Store попробуйте поискать ARM ABI, если вы действительно хотите, чтобы какая-то сборка инструментов работала вместе.


1

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

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

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

Изменения в библиотеке могут нарушить ABI, не нарушая API. Рассмотрим, например, библиотеку с интерфейсом вроде.

void initfoo(FOO * foo)
int usefoo(FOO * foo, int bar)
void cleanupfoo(FOO * foo)

и прикладной программист пишет код как

int dostuffwithfoo(int bar) {
  FOO foo;
  initfoo(&foo);
  int result = usefoo(&foo,bar)
  cleanupfoo(&foo);
  return result;
}

Программист приложения не заботится о размере или структуре FOO, но двоичный файл приложения заканчивается жестко заданным размером foo. Если программист библиотеки добавляет дополнительное поле в foo, и кто-то использует новый двоичный файл библиотеки со старым двоичным файлом приложения, то библиотека может получить доступ к памяти за пределами границ.

OTOH, если автор библиотеки разработал свой API как.

FOO * newfoo(void)
int usefoo(FOO * foo, int bar)
void deletefoo((FOO * foo, int bar))

и прикладной программист пишет код как

int dostuffwithfoo(int bar) {
  FOO * foo;
  foo = newfoo();
  int result = usefoo(foo,bar)
  deletefoo(foo);
  return result;
}

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


0

ABI- Application Binary Interfaceоб обмене машинным кодом во время выполнения между двумя частями двоичной программы, такими как: приложение, библиотека, ОС ... ABIописывает, как объекты сохраняются в памяти и как вызываются функции ( calling convention)

Хорошим примером API и ABI является экосистема iOS с языком Swift .

  • Application- При создании приложения на разных языках. Например , вы можете создать приложение , используя Swiftи Objective-C[Mixing Swift и Objective-C]

  • Application - OS- runtime - Swift runtimeи standard librariesявляются частями ОС, и их не следует включать в каждый пакет (например, приложение, инфраструктуру). Это то же самое, что и в Objective-C

  • Library- Module Stabilitycase - время компиляции - вы сможете импортировать фреймворк, который был собран с другой версией компилятора Swift. Это означает, что безопасно создавать двоичный файл с закрытым исходным кодом (перед сборкой), который будет использоваться другой версией компилятора ( .swiftinterfaceиспользуется с .swiftmodule), и вы не получите

    Module compiled with _ cannot be imported by the _ compiler
    
  • Library- Library Evolutionчехол

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

[API против ABI]

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.