Що таке бінарний інтерфейс програми (ABI)?


493

Я ніколи чітко не розумів, що таке ІПС. Будь ласка, не вказуйте мені на статтю у Вікіпедії. Якби я міг це зрозуміти, я б тут не розміщував такого довгого повідомлення.

Це мій погляд на різні інтерфейси:

Телевізійний пульт - це інтерфейс між користувачем та телевізором. Це існуюча сутність, але марна (не забезпечує жодної функціональності) сама по собі. Вся функціональність для кожної з цих кнопок на пульті реалізована в телевізорі.

Інтерфейс: це шар "існуючої сутності" між функціоналом 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 ++ mangling? Я думав, що ми говоримо на бінарному рівні. Чому мови входять?

У будь-якому випадку я завантажив [PDF] System V Binary Interface Edition 4.1 (1997-03-18), щоб побачити, що саме він містить. Ну, більшість із цього не мала жодного сенсу.

  • Чому він містить дві глави (4 та 5) для опису формату файлу ELF ? Насправді це єдині дві значущі глави цієї специфікації. Решта розділів "специфічні для процесора". У всякому разі, я хоч, що це зовсім інша тема. Не кажіть, що специфікація формату файлів ELF - це ABI. Це не може бути інтерфейсом відповідно до визначення.

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

  • Де я можу знайти ABI Microsoft Windows?

Отже, це основні запити, які мене клопочуть.


7
"Будь ласка, не кажіть, ОС" Компілятори повинні знати ABI. Лінкерам потрібно знати ABI. Ядро повинно знати ABI для того, щоб налаштувати програму в оперативній пам'яті, щоб вона нормально працювала. Що стосується C ++, див. Нижче, він навмисно перетворює мітки у безглуздість через перевантаження та приватні методи, а для того, щоб працювати з ним, лінкер та будь-який інший компілятор повинні мати сумісне ім'я, яке керується, іншими словами той самий ABI.
Джастін Сміт

8
Я думаю, що питання настільки чітке; точно описуючи очікуваний формат відповідей, але ще не було єдиної задовільної відповіді, яку можна прийняти.
legends2k

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

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

2
Я був неосвічений. Останнім часом працюю з усіма цими речами. Я зрозумів, що насправді є ABI. Так, я згоден, що мій шаблон несправний. Це не доречно, щоб вписати ABI в мій шаблон. Дякуємо @ JasperE. Просто знадобився досвід роботи, щоб зрозуміти вашу відповідь.
кігті

Відповіді:


534

Один простий спосіб зрозуміти "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 у документах SysV ABI: причина цієї інформації включена в тому, що формат ELF визначає інтерфейс між операційною системою та додатком. Коли ви говорите ОС запускати програму, вона очікує, що програма буде певним чином відформатована, і (наприклад) очікує, що перший розділ двійкового файлу буде заголовком ELF, що містить певну інформацію при конкретних зміщеннях пам'яті. Ось так додаток передає важливу інформацію про себе операційній системі. Якщо ви будуєте програму у двійковому форматі не ELF (наприклад, a.out або PE), ОС, яка очікує, що програми, відформатовані ELF, не зможуть інтерпретувати двійковий файл або запустити додаток.

IIRC, Windows в даний час використовує портативний виконуваний (або PE) формат. У розділі "зовнішні посилання" на цій сторінці Вікіпедії є посилання з додатковою інформацією про формат PE.

Крім того, щодо вашої примітки щодо керування іменем C ++: Під час знаходження функції у файлі бібліотеки функція зазвичай шукається за назвою. C ++ дозволяє перевантажувати імена функцій, тому одне ім'я недостатньо для ідентифікації функції. Компілятори C ++ мають свої власні способи вирішення цього питання, звані ім'ям mangling . ABI може визначити стандартний спосіб кодування імені функції, щоб програми, побудовані з іншої мови або компілятора, могли знайти те, що їм потрібно. Під час використання extern "c"програми C ++ ви доручаєте компілятору використовувати стандартизований спосіб запису імен, зрозумілий іншим програмним забезпеченням.


2
@bta, дякую за чудову відповідь. Чи є виклик конвенції своєрідним ABI? Спасибі
camino

37
Гарна відповідь. За винятком цього не є ABI. ABI - це сукупність правил, що визначають конвенцію про виклики та правила складання структур. Pascal передає аргументи на стек у зворотному порядку від програм C, тому компілятори Pascal і C НЕ компілюються в один і той же ABI. Відповідні стандарти для компіляторів C і Pascal явно гарантують, що так і буде. Компілятори C ++ не можуть визначити "стандартний" спосіб маніпулювання іменами, оскільки немає стандартного способу. Конвенції щодо керування іменами C ++ не були сумісні між компіляторами C ++, коли конкурували компілятори C ++ у Windows.
Робін Девіс

1
Однозначно також дивіться autotools.io/libtool/version.html та fedoramagazine.org/…
Pacerier

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

Чи можу я сказати, що файли obj, створені компілятором C, містять ABI?
Mitu Raj

144

Якщо ви знаєте збірку і як все працює на рівні ОС, ви відповідаєте певній ABI. ABI регулює такі речі, як, як передаються параметри, де розміщуються значення повернення. Для багатьох платформ на вибір є лише один ABI, і в цих випадках ABI - це просто "як все працює".

Однак ABI також керує такими речами, як, як класи / об'єкти викладені в C ++. Це необхідно, якщо ви хочете мати можливість передавати посилання на об'єкти через межі модулів або якщо ви хочете змішати код, складений з різними компіляторами.

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

Загалом, будь-який код, який ви пов'язуєте в один і той же виконуваний файл, повинен відповідати тому ж ABI. Якщо ви хочете спілкуватися між кодом за допомогою різних ABI, ви повинні використовувати певні форми протоколів RPC або серіалізації.

Я думаю, ви занадто сильно намагаєтесь втиснути різні типи інтерфейсів у фіксований набір характеристик. Наприклад, інтерфейс не обов'язково повинен бути розділений на споживачів та виробників. Інтерфейс - це лише умова, за якою взаємодіють дві сутності.

ІБС можуть бути (частково) ISA-агностичними. Деякі аспекти (наприклад, виклики конвенцій) залежать від ISA, тоді як інші аспекти (наприклад, макет класу C ++) не мають.

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

EDIT: Деякі примітки для уточнення:

  • "Бінарний" в ABI не виключає використання рядків або тексту. Якщо ви хочете пов’язати DLL, експортуючи клас C ++, десь у ньому методи та тип підписів повинні бути закодовані. Ось звідки входить C ++-менеджмент імен.
  • Причина, чому ви ніколи не надавали ABI, полягає в тому, що переважна більшість програмістів ніколи цього не зробить. ABI надаються тими ж людьми, що проектували платформу (тобто операційну систему), і дуже мало програмістів коли-небудь матимуть привілей проектувати широко використовуваний ABI.

Я зовсім не впевнений, що мій шаблон несправний. Тому що кожен, де цей шаблон для інтерфейсу відповідає дійсності. Так, так, я хочу, що я очікую, що ABI також впишеться в цей шаблон, але це не все. ВАЖЛИВА річ, я все ще не розумію. Я не знаю, чи я такий німий чи щось інше, але це просто не впадає мені в голову. Я не в змозі реалізувати відповіді та статті у вікі.
кігті

2
@jesperE, "ABI регулює такі речі, як, як передаються параметри, де розміщуються значення повернення". Посилається на "cdecl, stdcall, fastcall, pascal", правильно?
camino

3
Так. Власна назва - "конвенція про виклик", яка є частиною АБІ. en.wikipedia.org/wiki/X86_calling_conventions
JesperE

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

Я рекомендую написати трохи складання. Це допоможе людям зрозуміти ABI більш відчутним чином.
KunYu Tsai

40

Насправді вам взагалі не потрібен ABI, якщо--

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

Попрощений підсумок:

API: "Ось усі функції, які ви можете зателефонувати."

ABI: "Це як викликати функцію".

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

  • Можливо, найбільша і найважливіша частина ABI - це стандарт виклику процедури, який іноді називають "конвенцією про дзвінки". Конвенції про виклики стандартизують, як "функції" переводяться на код складання.
  • ABI також диктують, як повинні бути представлені назви відкритих функцій у бібліотеках, щоб інший код міг викликати ці бібліотеки та знати, які аргументи слід передавати. Це називається "ім'я манлінг".
  • ABI також диктують, який тип даних можна використовувати, як вони повинні бути вирівняні та інші деталі низького рівня.

Поглибивши глибший погляд на конвенцію про виклик, яку я вважаю стрижнем ABI:

Сама машина не має поняття "функції". Коли ви пишете функцію мовою високого рівня, як c, компілятор генерує рядок коду складання, як _MyFunction1:. Це мітка , яка врешті-решт буде вирішена на адресу асемблером. Ця мітка позначає "старт" вашої "функції" в коді складання. У коді високого рівня, коли ви "зателефонуєте" на цю функцію, те, що ви дійсно робите, змушує процесор стрибнути на адресу цієї мітки і продовжувати виконувати там.

Готуючись до стрибка, компілятор повинен зробити купу важливих речей. Конвенція про виклики - це як контрольний список, якого компілятор виконує для цього:

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

Існує багато різних конвенцій про ABI / виклики. Деякі основні з них:

  • Для процесора x86 або x86-64 (32-бітове середовище):
    • CDECL
    • STDCALL
    • ШВИДКО
    • ВЕКТОРКАЛ
    • ЦЕКАЛЬНО
  • Для процесора x86-64 (64-бітове середовище):
    • СИСТЕМВ
    • MSNATIVE
    • ВЕКТОРКАЛ
  • Для процесора ARM (32-розрядний)
    • AAPCS
  • Для процесора ARM (64-розрядний)
    • AAPCS64

Ось чудова сторінка, яка фактично показує відмінності в складі, що генерується при компілюванні для різних ABI.

Інша справа згадки про те , що ABI має значення не тільки всередині модуля вашої програми. Він також використовується лінкером, щоб переконатися, що ваша програма викликів бібліотеки працює правильно. У вас на комп’ютері працює декілька спільних бібліотек, і поки ваш компілятор знає, яким ABI вони користуються, він може викликати функції з них належним чином, не підірвавши стек.

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


19

Бінарний інтерфейс програми (ABI) схожий на API, але функція недоступна для абонента на рівні вихідного коду. Доступне / доступне лише бінарне представлення.

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

Функціональність: Визначте механізм / стандарт для здійснення функціональних викликів незалежно від мови реалізації або конкретного компілятора / linker / ланцюжка інструментів. Надайте механізм, що дозволяє JNI, або інтерфейс Python-C тощо.

Існуючі сутності: Функції у формі машинного коду.

Споживач: Ще одна функція (включаючи одну іншою мовою, складена іншим компілятором або пов'язана іншим лінкером).


Чому ABI визначається архітектурою? Чому різні ОС на одній архітектурі не зможуть визначити різні ABI?
Андреас Хафербург

10

Функціональність: набір контрактів, які впливають на компілятор, автори складання, лінкер та операційну систему. У контрактах визначено, як викладаються функції, де передаються параметри, як передаються параметри, як функціонує повернення функції. Вони, як правило, характерні для (архітектури процесора, операційної системи) кортежу.

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

Споживач: компілятор, автори складання, операційна система, специфічна архітектура процесора.

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

Управління іменами C ++ є окремим випадком - питання, пов'язане з лінкером і динамічним лінкером, - якщо керування іменами не стандартизоване, динамічне посилання не працюватиме. Відтепер C ++ ABI називається саме так, C ++ ABI. Це не питання рівня лінкера, а натомість питання генерації коду. Якщо у вас є двійковий файл C ++, неможливо зробити його сумісним з іншим C ++ ABI (керування іменами, обробка винятків) без перекомпіляції з джерела.

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

Усі ІВС конкретні набору інструкцій. ARI ABI не матиме сенсу для процесора MSP430 або x86_64.

У Windows є декілька ABI - наприклад, швидкий виклик та stdcall - це два розповсюджені ABI. Системний ABI знову інший.


9

Дозвольте мені хоча б відповісти на частину вашого запитання. На прикладі того, як Linux ABI впливає на системні дзвінки, і чому це корисно.

Системний виклик - це програма програми користувача, щоб запитати у просторі ядра щось. Він працює, вводячи числовий код для виклику та аргументу в певний реєстр і викликаючи переривання. Після чого відбувається перемикання на простір ядра, і ядро ​​шукає числовий код і аргумент, обробляє запит, повертає результат назад в реєстр і запускає перемикання назад в область користувачів. Це потрібно, наприклад, коли програма хоче виділити пам'ять або відкрити файл (sscalls "brk" і "open").

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


4

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


4

Найкращий спосіб розмежувати 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 призначені для визначення точної інтеграції програмного забезпечення на рівні бінарного / складання.


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

дякую за ваш коментар, я змінив свій коментар, щоб краще відповісти на різницю між 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 з розділеною бібліотекою для мінімуму, який можна виконати

У контексті спільних бібліотек найважливішим наслідком "мати стабільний ABI" є те, що вам не потрібно перекомпілювати свої програми після зміни бібліотеки.

Так, наприклад:

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

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

    Це особливо важливо для стандартної бібліотеки С, на яку посилається багато програм у вашій системі.

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

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_mystructcall 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 .

Іншим способом збереження цього ІБС було б трактувати 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

Список усього, що порушує CI + + спільну бібліотеку ABI

TODO: знайти / створити остаточний список:

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

Що таке бінарна сумісність у Java?

Випробувано в Ubuntu 18.10, GCC 8.2.0.


3

ABI повинно бути узгодженим між абонентом і абонентом, щоб бути впевненим, що виклик проходить успішно. Використання стека, використання регістрів, спливаюче коло стека. Все це - найважливіші частини АБІ.


3

Підсумок

Існують різні інтерпретації та сильні думки про точний шар, який визначає ABI (бінарний інтерфейс програми).

На мій погляд, ABI - це суб'єктивна умова того, що вважається заданою / платформою для конкретного API. ABI - це "решта" конвенцій, які "не зміняться" для конкретного API, або які будуть вирішені середовищем виконання: виконавцями, інструментами, посиланнями, компіляторами, jvm та ОС.

Визначення інтерфейсу : ABI, API

Якщо ви хочете використовувати бібліотеку типу joda-часу, ви повинні оголосити залежність 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 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 Java: Oracle, IBM, відкритого Java чи будь-якого іншого; а другий ABI - той навколо Дальвіка.

Послідовні випуски Scala несумісні

Scala не має бінарної сумісності між незначними версіями Scala: 2.X. З цієї причини той самий API "io.reactivex" %% "rxscala"% "0.26.5" має три версії (надалі більше): для Scala 2.10, 2.11 та 2.12. Що змінилося? Я зараз не знаю , але бінарні файли не сумісні. Ймовірно, в останніх версіях додаються речі, які роблять бібліотеки непридатними для старих віртуальних машин, ймовірно, речі, пов'язані зі зв'язуванням / іменуванням / умовами параметрів.

Послідовні випуски Java несумісні

У Java також є проблеми з основними версіями JVM: 4,5,6,7,8,9. Вони пропонують лише зворотну сумісність. Jvm9 знає, як запустити компільований / націлений код ( -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, ви вже говорите про конкретну частину API вашого ABI.

API можна переносити на різні ABI

Верхній рівень абстракції (джерела, написані проти найвищого API, можна перекомпілювати / перенести на будь-яку іншу нижню ступінь абстракції.

Скажімо, у мене є джерела для rxscala. Якщо інструменти Scala змінені, я можу перекомпілювати їх до цього. Якщо JVM зміниться, я міг би зробити автоматичні перетворення зі старої машини на нову, не турбуючись з концепціями високого рівня. Хоча перенесення може бути складним, допоможе будь-який інший клієнт. Якщо нова операційна система створена за допомогою зовсім іншого коду асемблера, може бути створений перекладач.

API, перенесені на різних мовах

Є API, які переносяться на декількох мовах, як реактивні потоки . Загалом вони визначають відображення певних мов / платформ. Я заперечую, що API - це головна специфікація, формально визначена людською мовою або навіть конкретною мовою програмування. Усі інші "відображення" є певним чином ABI, інакше більше API, ніж звичайний ABI. Те саме відбувається з інтерфейсами REST.


1

Якщо коротко, так і у філософії, тільки речі подібного роду можуть добре ладити, і ABI можна розглядати як вид програмного забезпечення, що працює разом.


1

Я також намагався зрозуміти ABI, і відповідь JesperE була дуже корисною.

З дуже простого погляду ми можемо спробувати зрозуміти ABI, розглядаючи бінарну сумісність.

Вікі KDE визначає бібліотеку як бінарну сумісну, "якщо програма, динамічно пов'язана з колишньою версією бібліотеки, продовжує працювати з новішими версіями бібліотеки без необхідності перекомпілювати". Докладніше про динамічне з'єднання див. У статті Статичне з'єднання проти динамічного з'єднання

Тепер спробуємо розглянути лише найосновніші аспекти, необхідні для бібліотеки для бінарної сумісності (якщо припустити, що в бібліотеці немає змін вихідного коду):

  1. Однакова / назад сумісна архітектура наборів інструкцій (інструкції процесора, структура файлів реєстру, організація стека, типи доступу до пам'яті, а також розміри, компонування та вирівнювання основних типів даних, до яких процесор може безпосередньо отримувати доступ)
  2. Ті самі заклики до виклику
  3. Це ж ім'я, що керується чоловіком (це може знадобитися, якщо скажімо, що програмі Fortran потрібно викликати деяку функцію бібліотеки 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- Час виконання - Swift runtimeі standard librariesє частинами ОС, і вони не повинні включатися в кожен пакет (наприклад, додаток, рамки). Це те саме, що і у використанні Objective-C

  • Library- час складанняModule Stability випадку - ви зможете імпортувати рамку, яка була побудована за допомогою іншої версії компілятора 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.