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


Answers

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

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

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

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

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

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

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

EDIT: Некоторые примечания для уточнения:

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

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

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

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

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

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

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

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

existing entities: команды

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

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

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

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

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

Функции API прикладного программирования (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)»?

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

Итак, это основные запросы, которые меня прослушивают.




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




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

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

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




Application binary interface (ABI)

Functionality:

  • Translation from the programmer's model to the underlying system's domain data type, size, alignment, the calling convention, which controls how functions' arguments are passed and return values retrieved; the system call numbers and how an application should make system calls to the operating system; the high-level language compilers' name mangling scheme, exception propagation, and calling convention between compilers on the same platform, but do not require cross-platform compatibility...

Existing entities:

  • Logical blocks that directly participate in program's execution: ALU, general purpose registers, registers for memory/ I/O mapping of I/O, etc...

consumer:

  • Language processors linker, assembler...

These are needed by whoever has to ensure that build tool-chains work as a whole. If you write one module in assembly language, another in Python, and instead of your own boot-loader want to use an operating system, then your "application" modules are working across "binary" boundaries and require agreement of such "interface".

C++ name mangling because object files from different high-level languages might be required to be linked in your application. Consider using GCC standard library making system calls to Windows built with Visual C++.

ELF is one possible expectation of the linker from an object file for interpretation, though JVM might have some other idea.

For a Windows RT Store app, try searching for ARM ABI if you really wish to make some build tool-chain work together.




The ABI needs to be consistent between caller and callee to be certain that the call succeeds. Stack use, register use, end-of-routine stack pop. All these are the most important parts of the ABI.




Вы вообще не нуждаетесь в ABI, если ...

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

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

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

ABI: «Вот как вызвать функцию».

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

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

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

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

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

  • Во-первых, компилятор вставляет немного кода сборки, чтобы сохранить текущий адрес, так что, когда ваша «функция» будет выполнена, процессор может вернуться в нужное место и продолжить выполнение.
  • Затем компилятор генерирует код сборки для передачи аргументов.
    • Некоторые соглашения о вызовах диктуют, что аргументы должны быть помещены в стек ( в определенном порядке, конечно).
    • Другие соглашения диктуют, что аргументы должны быть помещены в конкретные регистры (в зависимости от их типов данных, конечно).
    • В других конвенциях диктуется необходимость использования конкретной комбинации стека и регистров.
  • Конечно, если раньше в этих регистрах было что-то важное, эти значения теперь перезаписываются и теряются навсегда, поэтому некоторые соглашения о вызове могут диктовать, что компилятор должен сохранять некоторые из этих регистров до размещения в них аргументов.
  • Теперь компилятор вставляет инструкцию перехода, сообщающую 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

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

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

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




Related