Я никогда не понимал, что такое ABI. Пожалуйста, не указывайте мне статью в Википедии. Если бы я мог это понять, я бы не стал размещать такой длинный пост.
Это мое мышление о разных интерфейсах:
Пульт телевизора - это интерфейс между пользователем и телевизором. Это уже существующая сущность, но бесполезная (не предоставляет никакой функциональности) сама по себе. Все функции для каждой из этих кнопок на пульте дистанционного управления реализованы в телевизоре.
Интерфейс:. Это слой "существующий объект" между
functionality
иconsumer
этого функциональность. Интерфейс сам по себе не делает ничего. Это просто вызывает функциональность, лежащую позади.Теперь, в зависимости от того, кто пользователь это разные типы интерфейсов.
Команды командной строки (CLI) - это существующие сущности, потребитель - пользователь и функциональность лежит позади.
functionality:
мое программное обеспечение функциональность, которая решает некоторые цель, которую мы описываем этот интерфейс.
existing entities:
команды
consumer:
пользовательГрафический интерфейс пользователя (GUI), кнопки и т.д. - это существующие объектов, и снова потребитель является пользователем и функциональность лежит позади.
functionality:
мое программное обеспечение функциональность, которая решает некоторые цель, которую мы описываем этот интерфейс.
existing entities:
окно, кнопки и т.д..
consumer:
пользовательИнтерфейс прикладного программирования (API) или более правильные, интерфейсы (в сопряженное программирование) являются существующие субъекты, потребитель здесь другая программа, а не пользователь, и снова функциональность лежит за этим слоем.
functionality:
мое программное обеспечение функциональность, которая решает некоторые цель, которую мы описываем этот интерфейс.
existing entities:
функции, Интерфейсы (массив функций).
consumer:
другой программы/приложения.Бинарный интерфейс приложения (ABI) Здесь начинается моя проблема.
functionality:
existing entities:
consumer:
ABI охватывают такие детали, как
- тип, размер и выравнивание данных;
- вызывающее соглашение, которое определяет, как аргументы функций переданы и возвращены полученные значения;
- номера системных вызовов и как приложение должно выполнять системные вызовы к операционной системе;
Другие ABI стандартизируют такие данные, как
- имя ключа С++,
- распространение исключений и
- вызывать соглашение между компиляторами на той же платформе, но делать не требуется межплатформенная совместимость.
Кому нужны эти данные? Пожалуйста, не говорите ОС. Я знаю программирование сборки. Я знаю, как работают ссылки и загрузка. Я знаю, что именно происходит внутри.
Почему появилось имя С++ name? Я думал, что мы говорим на двоичном уровне. Почему появляются языки?
В любом случае, я загрузил [PDF] System V Application Binary Interface Edition 4.1 (1997-03-18), чтобы узнать, что именно это содержит. Ну, большинство из них не имеет никакого смысла.
Почему он содержит две главы (4-й и 5-й) для описания формата файла ELF? Фактически, это только две важные главы этой спецификации. Остальные главы "специфичны для процессора". Во всяком случае, я думал, что это совершенно другая тема. Пожалуйста, не говорите, что спецификации формата файлов ELF - это ABI. Он не может быть интерфейсом в соответствии с определением.
Я знаю, поскольку мы говорим на таком низком уровне, он должен быть очень конкретным. Но я не уверен, как это специфическая "архитектура набора инструкций (ISA)?
Где я могу найти ABI Microsoft Windows?
Итак, это основные запросы, которые меня прослушивают.
Один простой способ понять "ABI" - сравнить его с "API".
Вы уже знакомы с концепцией API. Если вы хотите использовать функции, скажем, какой-либо библиотеки или вашей ОС, вы будете использовать API. API состоит из типов данных/структур, констант, функций и т.д., Которые вы можете использовать в своем коде для доступа к функциям этого внешнего компонента.
ABI очень похож. Подумайте об этом как скомпилированную версию API (или как API на уровне машинного языка). Когда вы пишете исходный код, вы получаете доступ к библиотеке, хотя API. После компиляции кода ваше приложение обращается к двоичным данным в библиотеке через ABI. ABI определяет структуры и методы, которые ваше скомпилированное приложение будет использовать для доступа к внешней библиотеке (как это сделал API), только на более низком уровне.
ABI важны, когда речь идет о приложениях, использующих внешние библиотеки. Если программа создана для использования определенной библиотеки, и эта библиотека позже обновляется, вы не хотите перекомпилировать это приложение (и с точки зрения конечного пользователя у вас может не быть источника). Если обновленная библиотека использует один и тот же ABI, то ваша программа не нуждается в изменении. Интерфейс к библиотеке (который все ваши программы действительно заботятся) одинаково, хотя внутренние работы могут быть изменены. Две версии библиотеки, имеющие один и тот же ABI, иногда называются "двоично-совместимыми", поскольку они имеют один и тот же интерфейс низкого уровня (вы должны иметь возможность заменить старую версию на новую и не иметь серьезных проблем).
Иногда изменения ABI неизбежны. Когда это произойдет, любые программы, которые используют эту библиотеку, не будут работать, если они не будут скомпилированы для использования новой версии библиотеки. Если ABI изменяется, но API нет, то старые и новые версии библиотеки иногда называются "совместимыми с исходниками". Это означает, что, хотя программа, скомпилированная для одной версии библиотеки, не будет работать с другой, исходный код, написанный для одного, будет работать для другого, если он скомпилирован.
По этой причине разработчики библиотек, как правило, стараются сохранить свою стабильную ABI (чтобы свести к минимуму разрушение). Сохранение стабильности ABI означает не изменение функциональных интерфейсов (тип и число возвращаемых данных, типы и порядок аргументов), определения типов данных или структуры данных, определенные константы и т.д. Новые функции и типы данных могут быть добавлены, но существующие должны оставаться тоже самое. Если вы развернете, скажем, 16-битовое поле структуры данных в 32-битовое поле, то уже скомпилированный код, который использует эту структуру данных, не будет правильно обращаться к этому полю (или к любому последующему ему). Доступ к элементам структуры данных преобразуется в адреса и смещения памяти во время компиляции, и если структура данных изменяется, то эти смещения не будут указывать на то, что код ожидает, чтобы они указывали, а результаты в лучшем случае непредсказуемы.
ABI не обязательно то, что вы явно укажете, если вы не ожидаете, что люди будут взаимодействовать с вашим кодом с помощью сборки. Это не зависит от языка, так как (например) приложение C и приложение Pascal будут использовать тот же ABI после их компиляции.
Изменить: Что касается вашего вопроса о главах относительно формата файла ELF в документах SysV ABI: причина, по которой эта информация включена, заключается в том, что формат ELF определяет интерфейс между операционной системой и приложением. Когда вы укажете ОС на запуск программы, она ожидает, что программа будет отформатирована определенным образом и (например) ожидает, что первый раздел двоичного файла будет заголовком ELF, содержащим определенную информацию при определенных смещениях памяти. Именно так приложение передает важную информацию о себе в операционную систему. Если вы создаете программу в двоичном формате, отличном от ELF (например, a.out или PE), тогда ОС, ожидающая отформатированных в ELF приложений, не сможет интерпретировать двоичный файл или запустить приложение. Это одна из главных причин, по которым приложения Windows не могут запускаться непосредственно на машине Linux (или наоборот), не будучи повторно скомпилированными или выполняемыми внутри некоторого типа уровня эмуляции, который может переводить из одного двоичного формата в другой.
IIRC, Windows в настоящее время использует Portable Executable (или PE). В разделе "внешние ссылки" на этой странице в Википедии есть ссылки с дополнительной информацией о формате PE.
Кроме того, в отношении вашей заметки о когниции имени С++: ABI может определить "стандартизированный" способ для компилятора С++ для обработки имени с целью обеспечения совместимости. То есть, если я создаю библиотеку и вы разрабатываете программу, которая использует библиотеку, вы должны иметь возможность использовать другой компилятор, чем я, и не нужно беспокоиться о том, что результирующие двоичные файлы несовместимы из-за разных схем переключения имен. Это действительно полезно, если вы определяете новый формат двоичного файла или пишете компилятор или компоновщик.
Если вы знаете сборку и как все работает на уровне ОС, вы соответствуете определенному ABI. ABI управляет такими вещами, как параметры передаются, где размещаются значения возврата. Для многих платформ существует только один ABI, и в этих случаях ABI - это просто "как все работает".
Однако ABI также управляет такими вещами, как, например, как классы/объекты выкладываются на С++. Это необходимо, если вы хотите иметь возможность передавать ссылки на объекты через границы модулей или если вы хотите смешать код, скомпилированный с разными компиляторами.
Кроме того, если у вас есть 64-разрядная ОС, которая может выполнять 32-разрядные двоичные файлы, у вас будут разные ABI для 32- и 64-разрядного кода.
В общем, любой код, который вы связываете с тем же исполняемым файлом, должен соответствовать одному и тому же ABI. Если вы хотите связываться между кодом с использованием различных ABI, вы должны использовать некоторую форму протоколов RPC или сериализации.
Я думаю, вы слишком стараетесь сжимать разные типы интерфейсов в фиксированный набор характеристик. Например, интерфейс необязательно должен быть разделен на потребителей и производителей. Интерфейс - это просто соглашение, с помощью которого взаимодействуют два объекта.
ABI могут быть (частично) ISA-агностиками. Некоторые аспекты (например, соглашения о вызовах) зависят от ISA, в то время как другие аспекты (например, макет класса С++) не работают.
Хорошо определенная ABI очень важна для людей, пишущих компиляторы. Без четко определенного ABI было бы невозможно создать интероперабельный код.
РЕДАКТИРОВАТЬ: Некоторые примечания для пояснения:
Бинарный интерфейс приложения (ABI) аналогичен API, но функция недоступна для вызывающего абонента на уровне исходного кода. Доступно/доступно только двоичное представление.
ABI могут быть определены на уровне архитектуры процессора или на уровне ОС. ABI - это стандарты, которым должна следовать фаза кода-генератора компилятора. Стандарт фиксируется либо ОС, либо процессором.
Функциональность: Определите механизм/стандарт, чтобы выполнять вызовы функций независимо от языка реализации или конкретного компилятора/компоновщика/инструментальной цепочки. Предоставьте механизм, который позволяет JNI, или интерфейс Python-C, и т.д.
Существующие объекты: функции в форме машинного кода.
Потребитель: другая функция (в том числе одна на другом языке, скомпилированная другим компилятором или связанная другим компоновщиком).
Функциональность: набор контрактов, которые влияют на компилятор, сборщики, компоновщик и операционную систему. Контракты определяют, как распределяются функции, где передаются параметры, как передаются параметры, как функция возвращается. Они обычно специфичны для кортежей (процессорной архитектуры, операционной системы).
Существующие объекты: расположение параметров, семантика функций, распределение регистров. Например, архитектуры 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 снова отличается.
Вам вообще не нужен ABI, если -
Упрощенное резюме:
API:. Вот все функции, которые вы можете назвать.
ABI:. Это как для вызова функции. "
ABI - это набор правил, которым придерживаются компиляторы и компоновщики, чтобы скомпилировать вашу программу, чтобы она работала правильно. ABI охватывают несколько тем:
Более глубокий взгляд на вызов конвенции, который я считаю ядром ABI:
Сама машина не имеет понятия "функции". Когда вы пишете функцию на высокоуровневом языке, таком как c, компилятор создает строку ассемблерного кода, например _MyFunction1:
. Это ярлык, который в конечном итоге будет разрешен в адрес ассемблером. Этот ярлык обозначает "начало" вашей "функции" в коде сборки. В высокоуровневом коде, когда вы "вызываете" эту функцию, то, что вы действительно делаете, заставляет CPU перейти к адресу этой метки и продолжать выполнять там.
В рамках подготовки к прыжку компилятор должен сделать кучу важных вещей. Вызывающее соглашение похоже на контрольный список, который компилятор выполняет для всего этого:
_MyFunction1:
). На этом этапе вы можете считать CPU "включенным" в вашу "функцию".Существует много различных соглашений ABI/call. Вот некоторые из них:
Здесь - отличная страница, на самом деле показывающая различия в сборке, сгенерированной при компиляции для разных ABI.
Еще одна вещь, которая стоит упомянуть, заключается в том, что ABI не только имеет значение внутри исполняемого модуля программы. Он также, используемый компоновщиком, чтобы убедиться, что ваша программа правильно выполняет функции библиотеки. У вас есть несколько разделяемых библиотек, запущенных на вашем компьютере, и пока ваш компилятор знает, что каждый из них использует ABI, он может вызывать функции из них должным образом, не взорвав стек.
Ваш компилятор, понимающий, как вызвать библиотечные функции, чрезвычайно. На размещенной платформе (то есть, где OS загружает программы), ваша программа даже не может мигать без вызова ядра.
Позвольте мне хотя бы ответить на часть вашего вопроса. На примере того, как Linux ABI влияет на системные символы и почему это полезно.
Systemcall - это способ для программы пользовательского пространства запросить ядерное пространство для чего-то. Он работает, помещая числовой код для вызова и аргумент в определенный регистр и вызывая прерывание. Чем переключается на kernelspace, и ядро просматривает числовой код и аргумент, обрабатывает запрос, возвращает результат обратно в регистр и запускает переход обратно в пользовательское пространство. Это необходимо, например, когда приложение хочет выделить память или открыть файл (syscalls "brk" и "open" ).
Теперь в syscalls указаны короткие имена "brk" и т.д. и соответствующие коды операций, они определены в системном файле заголовка. Пока эти коды операций остаются неизменными, вы можете запускать одни и те же скомпилированные программы пользовательских программ с разными обновленными ядрами без перекомпиляции. Таким образом, у вас есть интерфейс, используемый прекомпилированными двоичными файлами, следовательно, ABI.
Лучший способ разграничения между ABI и API - знать, почему и для чего он используется:
Для x86-64 обычно есть один ABI (а для 32-разрядного x86 - другой набор):
http://www.x86-64.org/documentation/abi.pdf
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 предназначены для определения точной интеграции программного обеспечения на уровне двоичного/сборочного уровня.
SYS_execve
- 11 для 32-битного Linux, но 59 для FreeBSD.
Чтобы вызвать код в разделяемых библиотеках или вызвать код между единицами компиляции, объектный файл должен содержать метки для вызовов. С++ управляет именами меток, чтобы обеспечить скрытие данных и разрешить перегруженные методы. Вот почему вы не можете смешивать файлы из разных компиляторов С++, если они явно не поддерживают тот же ABI.
Существуют различные интерпретации и сильные мнения о точном слое, которые определяют ABI (двоичный интерфейс приложения).
На мой взгляд, ABI - это субъективное соглашение того, что считается конкретной/платформой для конкретного API. ABI - это "отдых" соглашений, которые "не изменятся" для конкретного API или которые будут рассмотрены средой выполнения: исполнителями, инструментами, компоновщиками, компиляторами, jvm и ОС.
Если вы хотите использовать библиотеку типа joda-time, вы должны объявить зависимость от joda-time-<major>.<minor>.<patch>.jar
. Библиотека следует лучшим практикам и использует Semantic Versioning. Это определяет совместимость API на трех уровнях:
Для того, чтобы вы могли использовать новый основной выпуск одной и той же библиотеки, необходимо соблюдать множество других соглашений:
Например, Java стандартизовал все эти соглашения, а не в инструменте, а в формальной спецификации JVM. Спецификация позволила другим поставщикам предоставить другой набор инструментов, которые могут выводить совместимые библиотеки.
Java предоставляет два других интересных примера для ABI: версии Scala и Dalvik виртуальная машина.
Для 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: 2.X. По этой причине один и тот же API "io.reactivex" %% "rxscala" % "0.26.5" имеет три версии (в будущем больше): для Scala 2.10, 2.11 и 2.12. Что изменилось? Я пока не знаю, но двоичные файлы несовместимы. Вероятно, в последних версиях добавляются вещи, которые делают библиотеки непригодными для использования на старых виртуальных машинах, возможно, связанные с привязкой/назначением имен/параметров.
Java также имеет проблемы с основными выпусками JVM: 4,5,6,7,8,9. Они предлагают только обратную совместимость. Jvm9 знает, как запустить компилируемый/целевой код (javac -target
) для всех других версий, в то время как JVM 4 не знает, как запускать код, предназначенный для JVM 5. Все это, пока у вас есть одна joda-библиотека. Эта несовместимость пролетает над радаром благодаря различным решениям:
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, могут быть перекомпилированы/перенесены на любую другую абстракцию нижнего уровня.
Скажем, у меня есть некоторые источники для rxscala. Если инструменты Scala изменены, я могу их перекомпилировать. Если JVM-изменения меняются, у меня могут быть автоматические преобразования от старой машины к новой, не беспокоясь о концепциях высокого уровня. Хотя перенос может быть затруднен, это поможет любому другому клиенту. Если новая операционная система создается с использованием совершенно другого кода ассемблера, может быть создан переводчик.
Есть API, которые переносятся на несколько языков, например реактивные потоки. В общем случае они определяют отображения на определенные языки/платформы. Я бы сказал, что API - это основная спецификация, формально определенная на человеческом языке или даже на конкретном языке программирования. Все другие "отображения" - это ABI в некотором смысле, а еще больше API, чем обычный ABI. То же самое происходит с интерфейсами REST.
Бинарный интерфейс приложения (ABI)
Функциональность:
Существующие объекты:
потребитель:
Это необходимо тем, кто должен обеспечить, чтобы сборка цепей инструментов работала в целом. Если вы пишете один модуль на ассемблере, другой в Python, а вместо своего собственного загрузчика хотите использовать операционную систему, то ваши "прикладные" модули работают через "двоичные" границы и требуют согласования такого "интерфейса".
С++ name mangling, потому что в вашем приложении могут быть связаны объектные файлы с разных языков высокого уровня. Подумайте о том, как использовать стандартную библиотеку GCC для системных вызовов Windows, созданных с помощью Visual С++.
ELF - одно из возможных ожиданий компоновщика из объектного файла для интерпретации, хотя у JVM может быть и другая идея.
Для приложения Windows RT Store попробуйте выполнить поиск ARM ABI, если вы действительно хотите, чтобы какая-то команда сборки инструментов работала вместе.
ABI должен быть согласован между вызывающим и вызываемым, чтобы быть уверенным, что вызов преуспевает. Использование стека, использование регистров, поэтапный запуск стека. Все это самые важные части ABI.
Я также пытался понять, что ответ ABI и JesperEs был очень полезным.
С очень простой точки зрения мы можем попытаться понять ABI, рассматривая двоичную совместимость.
KDE wiki определяет библиотеку как бинарную совместимость "если динамически связанная программа с прежней версией библиотеки продолжает работать с более новыми версиями библиотеки без необходимости перекомпиляции". Подробнее о динамической компоновке см. Статическая связь или динамическая компоновка
Теперь давайте попробуем рассмотреть только самые основные аспекты, необходимые для того, чтобы библиотека была совместимой с двоичным кодом (при условии, что в библиотеке нет изменений исходного кода):
Конечно, есть много других деталей, но это в основном то, что охватывает ABI.
Более конкретно, чтобы ответить на ваш вопрос, из вышесказанного, можно сделать вывод:
Функциональность ABI: двоичная совместимость
существующие объекты: существующая программа/библиотеки/ОС
потребитель: библиотеки, ОС
Надеюсь, это поможет!
Вкратце и в философии только хорошие вещи могут хорошо ладить, и ABI можно рассматривать как вид, с которым работают программные средства.