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

Я никогда не понимал, что такое 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?

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

Вам вообще не нужен ABI, если -

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

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

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

ABI:. Это как для вызова функции. "

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

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

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

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

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

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

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

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

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

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

В Windows есть несколько ABI - например, fastcall и stdcall - это два распространенных ABI. Сценарий ABI снова отличается.

Ответ 6

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

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

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

Ответ 7

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

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

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, МОГУТ иметь возможность звонить друг другу. Но необходимо соблюдать дополнительное требование SAME ABI.

Например, Windows была совместима с POSIX API:

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

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

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

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

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

Ответ 8

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

Ответ 9

Резюме

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

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

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

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

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

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

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

Примеры

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

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

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

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

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

  • Семантическое управление версиями: когда библиотеки нацелены на более высокую JVM, они обычно меняют основную версию.
  • Используйте JVM 4 как ABI, и вы в безопасности.
  • В 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.

Ответ 10

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

Ответ 11

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

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

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

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

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

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

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

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

Ответ 12

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

Ответ 13

Бинарный интерфейс приложения (ABI)

Функциональность:

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

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

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

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

  • Компилятор языка, сборщик...

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

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

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

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

Ответ 14

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

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

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

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

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

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

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

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

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

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

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

Ответ 15

Термин 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, которая может быть скрыта внутри библиотеки. Однако цена, которую вы платите за это, заключается в том, что в этом участвует куча операций.

Ответ 16

  ABI - это интерфейс между двумя двоичными программными модулями.

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

ABI обычно охватывает следующие вещи:

  • Инструкции процессора (регистры, организация стека, тип доступа к памяти)
  • размеры, макеты и выравнивания типов данных
  • соглашение о вызовах (как вызывать функции, передавать аргументы, возвращать значения)
  • системные вызовы в ОС

ABI и iOS

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

Стабильность ABI
Время выполнения, стандартные библиотеки Swift

Стабильность ABI для операционных систем Apple означает, что приложениям, развертываемым в будущих выпусках этих операционных систем, больше не нужно будет встраивать стандартную библиотеку Swift. Среда выполнения Swift и стандартная библиотека будут поставляться вместе с ОС, как и среда выполнения Objective-C.

Стабильность модуля
время компиляции, библиотеки и фреймворки

Клиенты могут использовать модуль, не заботясь о том, с каким компилятором он был собран.

Эволюция библиотек

Отправка новой версии библиотеки без перекомпиляции ее клиентов.

[API против ABI]

Подробнее здесь, здесь, здесь