アプリケーションバイナリインターフェイス(ABI)とは何ですか?


493

私はABIが何であるかを明確に理解したことがありません。ウィキペディアの記事を紹介しないでください。理解できたらここまでそんなに長い投稿はしないでしょう。

これは、さまざまなインターフェイスに関する私の考え方です。

テレビのリモコンは、ユーザーとテレビの間のインターフェースです。これは既存のエンティティですが、それ自体では役に立ちません(機能を提供しません)。リモコンのこれらの各ボタンのすべての機能は、テレビに実装されています。

インターフェース:それは間の「既存のエンティティ」層であり、 functionalityかつconsumerその機能の。インターフェイス自体は何もしません。背後にある機能を呼び出すだけです。

ユーザーが誰であるかに応じて、さまざまなタイプのインターフェースがあります。

コマンドラインインターフェイス(CLI)コマンドは既存のエンティティであり、コンシューマがユーザーであり、機能は背後にあります。

functionality: 私たちがこのインターフェースを説明しているいくつかの目的を解決する私のソフトウェア機能。

existing entities: コマンド

consumer: ユーザー

グラフィカルユーザーインターフェイス(GUI)ウィンドウ、ボタンなどは既存のエンティティであり、コンシューマがユーザーであり、機能は背後にあります。

functionality: このインターフェイスについて説明している問題を解決するソフトウェア機能。

existing entities: ウィンドウ、ボタンなど

consumer: ユーザー

アプリケーションプログラミングインターフェイス(API)関数(またはより正確には)のインターフェイス(インターフェイスベースのプログラミングの場合)は既存のエンティティであり、コンシューマーはユーザーではなく別のプログラムであり、機能はこのレイヤーの背後にあります。

functionality: このインターフェイスについて説明している問題を解決するソフトウェア機能。

existing entities: 関数、インターフェイス(関数の配列)。

consumer: 別のプログラム/アプリケーション。

Application Binary Interface(ABI)ここからが私の問題の始まりです。

functionality: ???

existing entities: ???

consumer: ???

  • ソフトウェアをさまざまな言語で作成し、さまざまな種類のインターフェース(CLI、GUI、API)を提供しましたが、ABIを提供したことがあるかどうかはわかりません。

ウィキペディアは言う:

ABIは次のような詳細をカバーします

  • データ型、サイズ、および配置。
  • 関数の引数の受け渡し方法と戻り値の取得方法を制御する呼び出し規約。
  • システムコール番号、およびアプリケーションがオペレーティングシステムに対してシステムコールを行う方法。

他のABIは次のような詳細を標準化します

  • C ++名のマングリング、
  • 例外の伝播、および
  • 同じプラットフォーム上のコンパイラ間の呼び出し規約。ただし、プラットフォーム間の互換性は必要ありません。
  • 誰がこれらの詳細を必要としますか?OSは言わないでください。私はアセンブリプログラミングを知っています。リンクと読み込みの仕組みを知っています。私は内部で何が起こるかを正確に知っています。

  • なぜC ++の名前マングリングが登場したのですか?バイナリレベルで話していると思いました。なぜ言語が入ってくるのですか?

とにかく、私は[PDF] System V Application Binary Interface Edition 4.1(1997-03-18)をダウンロードして、正確に何が含まれているかを確認しました。まあ、それのほとんどは意味がありませんでした。

  • ELFファイル形式を説明する2つの章(4番目と5番目)が含まれているのはなぜですか?実際、これらはその仕様の2つの重要な章のみです。残りの章は「プロセッサ固有」です。とにかく、全く別の話題だと思います。ELFファイル形式の仕様 ABIであるとは言わないでください。定義によると、インターフェースとしての資格はありません。

  • 私たちは知っている、私たちはそのような低いレベルで話しているので、それは非常に具体的でなければならない。しかし、「命令セットアーキテクチャ(ISA)」がどのように特定されているのかはわかりません。

  • Microsoft WindowsのABIはどこにありますか?

したがって、これらは私を悩ませている主要なクエリです。


7
「OSは言わないでください」コンパイラはABIを知る必要があります。リンカーはABIを知る必要があります。カーネルは、プログラムを適切に実行するためにRAMにプログラムをセットアップするために、ABIを知る必要があります。C ++については、以下を参照してください。オーバーロードとプライベートメソッドのために意図的にラベルを意味不明なものに変換します。リンカーと他のコンパイラーは、ラベルを操作するために互換性のある名前のマングル、つまり同じABIを持っている必要があります。
ジャスティン・スミス

8
質問はとても明確だと思います。期待される回答形式は何であるかを正確に説明しているが、受け入れ可能な単一の満足できる回答ではない。
legends2k 2010

3
@ legends2kこの問題についての私の見方は、OPは確かにABIが何であるかを知っているが、それを認識していないということです。ほとんどのプログラマーがABIを設計または提供することは決してありません。それはOS /プラットフォームデザイナーの仕事だからです。
JesperE 2010

4
@JesperE:私はあなたの意見に同意します。しかし、おそらくOPは、ABIを提供する必要がない場合でも、適切と思われる形式で、それを明確に知りたいと考えています。
legends2k 2010

2
私は無知でした。最近、これらすべてのものを扱っている。ABIが実際に何であるか私は気づきました。ええ、私は私のテンプレートに欠陥があることに同意します。ABIを私のテンプレートに合わせるのは適切ではありません。@ JasperEに感謝します。あなたの答えを実現するには、実務経験が必要でした。

回答:


535

「ABI」を理解する簡単な方法の1つは、「API」と比較することです。

あなたはすでにAPIの概念に精通しています。たとえば、一部のライブラリやOSの機能を使用したい場合は、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は変更されない場合、新旧のライブラリバージョンは「ソース互換」と呼ばれることがあります。これは、1つのライブラリバージョン用にコンパイルされたプログラムは他のバージョンでは機能しないが、1つ用に作成されたソースコードは再コンパイルすると他のバージョンで機能することを意味します。

このため、開発者は(中断を最小限に抑えるために)ABIを安定させようとする傾向があります。ABIを安定に保つことは、関数インターフェース(戻りの型と数、型、引数の順序)、データ型またはデータ構造の定義、定義された定数などを変更しないことを意味します。新しい関数とデータ型を追加できますが、既存のものはそのままでなければなりません同じ。たとえば、ライブラリが32ビット整数を使用して関数のオフセットを示し、64ビット整数に切り替えると、そのライブラリを使用するコンパイル済みのコードはそのフィールド(またはその後のフィールド)に正しくアクセスしません。データ構造メンバーにアクセスすると、コンパイル中にメモリアドレスとオフセットに変換され、データ構造が変更された場合は、

ABIは、非常に低レベルのシステム設計作業を行わない限り、必ずしも明示的に提供するものではありません。(たとえば)CアプリケーションとPascalアプリケーションは、コンパイル後に同じABIを使用できるため、言語固有ではありません。

編集:SysV ABIドキュメントのELFファイル形式に関する章に関する質問について:この情報が含まれているのは、ELF形式がオペレーティングシステムとアプリケーション間のインターフェイスを定義しているためです。OSにプログラムを実行するように指示すると、プログラムは特定の方法でフォーマットされていることを期待し、(たとえば)バイナリの最初のセクションが特定のメモリオフセットで特定の情報を含むELFヘッダーであることを期待します。これは、アプリケーションがそれ自体に関する重要な情報をオペレーティングシステムに伝達する方法です。ELF以外のバイナリ形式(a.outやPEなど)でプログラムをビルドすると、ELF形式のアプリケーションを想定しているOSは、バイナリファイルを解釈したり、アプリケーションを実行したりできません。

IIRC、Windowsは現在、Portable Executable(またはPE)形式を使用しています。そのWikipediaページの「外部リンク」セクションに、PE形式に関する詳細情報を含むリンクがあります。

また、C ++の名前のマングリングに関する注意:ライブラリファイルで関数を見つける場合、通常、関数は名前で検索されます。C ++では関数名をオーバーロードできるため、関数を識別するには名前だけでは不十分です。C ++コンパイラには、内部でこれに対処するための独自の方法があり、名前マングルと呼ばれています。ABIは、関数の名前をエンコードする標準的な方法を定義して、異なる言語またはコンパイラーでビルドされたプログラムが必要なものを見つけられるようにすることができます。あなたが使用する場合はextern "c"C ++プログラムでは、あなたが他のソフトウェアが理解できるのです名前を記録する標準化された方法を使用するようにコンパイラに指示しています。


2
@bta、素晴らしい答えをありがとう。呼び出し規約はABIの一種ですか?ありがとう
camino

37
いい答えだ。ただし、これはABIとは異なります。ABIは、呼び出し規約を決定する一連のルールと、構造をレイアウトするためのルールです。PascalはCアプリケーションから逆の順序でスタックの引数を渡すため、pascalとCコンパイラは同じABIにコンパイルされません。CおよびPascalコンパイラのそれぞれの標準は、これが事実であることを暗黙的に保証します。C ++コンパイラは、標準的な方法がないため、名前をマングルする「標準的な」方法を定義できません。Windowsで競合するC ++コンパイラが存在する場合、C ++名のマングリング規則はC ++コンパイラ間で互換性がありませんでした。
ロビンデイビス


1
@RobinDavies:Pascalコンパイラーが呼び出し元から与えられた関数pop引数を呼び出すプラットフォームでは、Cコンパイラーは通常、プログラマーが特定の関数がPascalコンパイラーは、Cコンパイラーは通常、デフォルトで、呼び出された関数が呼び出し元によってそこに配置されたものをスタックに残すという規則を使用しますが、
スーパーキャット2017年

Cコンパイラによって生成されたobjファイルにABIが含まれていると言えますか?
Mitu Raj

144

アセンブリとOSレベルでの動作を知っている場合は、特定のABIに準拠しています。ABIは、戻り値が配置されるパラメーターの受け渡し方法などを管理します。多くのプラットフォームでは、選択できるABIは1つだけです。その場合、ABIは「物事の仕組み」にすぎません。

ただし、ABIは、クラス/オブジェクトがC ++でどのようにレイアウトされるかなども管理します。これは、モジュールの境界を越えてオブジェクト参照を渡せるようにしたい場合、または異なるコンパイラーでコンパイルされたコードを混合したい場合に必要です。

また、32ビットのバイナリを実行できる64ビットのOSを使用している場合、32ビットと64ビットのコードで異なるABIが使用されます。

一般に、同じ実行可能ファイルにリンクするコードはすべて、同じABIに準拠している必要があります。異なるABIを使用してコード間で通信する場合は、何らかの形式のRPCまたはシリアル化プロトコルを使用する必要があります。

さまざまなタイプのインターフェースを固定された特性のセットに詰め込もうとすると、あなたは懸命に努力しすぎていると思います。たとえば、インターフェースを必ずしもコンシューマーとプロデューサーに分割する必要はありません。インターフェイスは、2つのエンティティが相互作用する規則にすぎません。

ABIは(部分的に)ISAに依存しない場合があります。一部の側面(呼び出し規則など)はISAに依存していますが、他の側面(C ++クラスレイアウトなど)は依存していません。

明確に定義されたABIは、コンパイラーを作成する人々にとって非常に重要です。明確に定義されたABIがなければ、相互運用可能なコードを生成することは不可能です。

編集:明確にするためのいくつかのメモ:

  • ABIの「バイナリ」は、文字列またはテキストの使用を除外しません。C ++クラスをエクスポートするDLLをリンクする場合は、そのどこかにメソッドと型シグネチャをエンコードする必要があります。そこで登場するのがC ++の名前マングリングです。
  • ABIを提供しなかった理由は、大多数のプログラマーがABIを提供しないためです。ABIは、プラットフォーム(つまり、オペレーティングシステム)を設計する同じ人々によって提供され、広く使用されているABIを設計する特権を持つプログラマーはほとんどいません。

私のテンプレートに欠陥があるとはまったく確信していません。なぜなら、このインターフェースのテンプレートはどこにでも当てはまるからです。だから、はい、私はABIもこのテンプレートに適合することを期待していますが、そうではありません。重要なことは、私はまだ理解していません。私がそんなに馬鹿なのか他の何かなのかは分かりませんが、頭の中に入れません。答えとウィキの記事を理解できません。

2
@jesperE、「ABIは、パラメーターがどのように渡され、戻り値が配置されるかなどを管理します。」は「cdecl、stdcall、fastcall、pascal」を指しますか?
camino

3
はい。適切な名前は「呼び出し規約」で、これはABIの一部です。en.wikipedia.org/wiki/X86_calling_conventions
JesperE 2014年

4
これは冗長(かなりノイズ)のない正しい正確な答えです!
Nawaz 2017年

少しアセンブリを書くことをお勧めします。これは、人々がより具体的な方法でABIを理解するのに役立ちます。
KunYu Tsai

40

実際に、ABI はまったく必要ありません。

  • あなたのプログラムには関数がありません、そして-
  • あなたのプログラムは、単独で実行されている単一の実行可能ファイル(つまり、組み込みシステム)であり、文字通り実行されている唯一のものであり、他のものと通信する必要はありません。

単純化した要約:

API: 「呼び出すことができるすべての関数は次のとおりです。」

ABI: 「これは関数を呼び出す方法です。」

ABIは、プログラムが正しく機能するようにプログラムをコンパイルするためにコンパイラとリンカーが準拠する一連のルールです。ABIは複数のトピックをカバーしています:

  • 間違いなく、ABIの最大かつ最も重要な部分は、「呼び出し規約」として知られる手続き呼び出し標準です。呼び出し規約は、「関数」がアセンブリコードに変換される方法を標準化します。
  • ABIは、他のコードがそれらのライブラリーを呼び出して、どの引数を渡す必要があるかを知ることができるように、ライブラリー内の公開された関数の名前をどのように表すかを指示します。これは「名前のマングリング」と呼ばれます。
  • また、ABIは、使用できるデータ型のタイプ、それらの整列方法、およびその他の低レベルの詳細も指示します。

私がABIの中核であると考える呼び出し規約をより深く見てみましょう。

機械自体には「機能」という概念はありません。cのような高級言語で関数を書くと、コンパイラはのようなアセンブリコードの行を生成します_MyFunction1:。これはラベルであり、最終的にはアセンブラによってアドレスに解決されます。このラベルは、アセンブリコード内の「関数」の「開始」を示します。高レベルのコードでは、その関数を「呼び出す」と、CPU がそのラベルのアドレスにジャンプしてそこで実行を継続することになります。

ジャンプの準備として、コンパイラは一連の重要なことを行わなければなりません。呼び出し規約は、コンパイラーがこのすべてを行うために従うチェックリストのようなものです。

  • 最初に、コンパイラはアセンブリコードを少し挿入して現在のアドレスを保存するため、「関数」が完了すると、CPUは適切な場所にジャンプして実行を継続できます。
  • 次に、コンパイラは引数を渡すためのアセンブリコードを生成します。
    • 一部の呼び出し規約では、引数は(もちろん特定の順序で)スタックに置く必要があります。
    • 他の規則では、引数は特定のレジスターに入れる必要があると規定されています(もちろん、それらのデータ型によって異なります)。
    • さらに他の規約では、スタックとレジスタの特定の組み合わせを使用する必要があると規定されています。
  • もちろん、以前にそれらのレジスターに重要なものがあった場合、それらの値は上書きされて永久に失われるため、一部の呼び出し規則では、コンパイラーが引数をレジスターに入れる前にそれらのレジスターのいくつかを保存するように指示する場合があります。
  • これで、コンパイラはジャンプ命令を挿入し、以前に作成したラベルに移動するようCPUに指示します(_MyFunction1:)。この時点で、CPUは「機能」の中にあると考えることができます。
  • 関数の最後に、コンパイラーは、CPUが戻り値を正しい場所に書き込むようにするアセンブリー・コードをいくつか配置します。呼び出し規約により、戻り値を特定のレジスター(そのタイプに応じて)に入れるか、スタックに入れるかが決まります。
  • さて、クリーンアップの時間です。呼び出し規約により、コンパイラがクリーンアップアセンブリコードを配置する場所が決まります。
    • 一部の規則では、呼び出し元はスタックをクリーンアップする必要があるとしています。これは、「関数」が実行され、CPUが以前の場所にジャンプした後、次に実行されるコードは、非常に具体的なクリーンアップコードである必要があることを意味します。
    • 他の慣例では、クリーンアップコードの特定の部分は、ジャンプするの「関数」の最後に置く必要があるとしています。

多くの異なるABI /呼び出し規約があります。主なものは次のとおりです。

  • x86またはx86-64 CPU(32ビット環境)の場合:
    • CDECL
    • STDCALL
    • ファーストコール
    • ベクトル
    • この呼び出し
  • x86-64 CPU(64ビット環境)の場合:
    • SYSTEMV
    • MSNATIVE
    • ベクトル
  • ARM CPU(32ビット)
    • AAPCS
  • ARM CPU(64ビット)の場合
    • AAPCS64

これは、さまざまなABI用にコンパイルしたときに生成されるアセンブリの違いを実際に示す優れたページです。

もう1つ言及する必要があるのは、ABIはプログラムの実行可能モジュールだけに関係があるわけではないということです。また、プログラムがライブラリ関数を正しく呼び出すようにするために、リンカによって使用されます。コンピューターで複数の共有ライブラリーを実行していて、コンパイラーがそれぞれが使用するABIを知っている限り、スタックを爆破することなく、それらのライブラリーから適切に関数を呼び出すことができます。

ライブラリ関数を呼び出す方法を理解するコンパイラは非常に重要です。ホストされたプラットフォーム(つまり、OSがプログラムをロードするプラットフォーム)では、カーネル呼び出しを行わなければ、プログラムを点滅させることさえできません。


19

アプリケーションバイナリインターフェイス(ABI)はAPIに似ていますが、ソースコードレベルでは呼び出し元が関数にアクセスできません。バイナリ表現のみがアクセス可能/利用可能です。

ABIは、プロセッサアーキテクチャレベルまたはOSレベルで定義できます。ABIは、コンパイラーのコード生成フェーズが従うべき標準です。この規格は、OSまたはプロセッサによって修正されています。

機能性:実装言語または特定のコンパイラー/リンカー/ツールチェーンから独立した関数呼び出しを行うためのメカニズム/標準を定義します。JNIを可能にするメカニズム、またはPython-Cインターフェースなどを提供します。

既存のエンティティ:マシンコード形式の関数。

消費者:別の関数(別の言語の関数を含む、別のコンパイラーによってコンパイルされた、または別のリンカーによってリンクされた)。


ABIがアーキテクチャによって定義されるのはなぜですか?同じアーキテクチャ上の異なるOSが異なるABIを定義できないのはなぜですか?
Andreas Haferburg 2018

10

機能:コンパイラ、アセンブリライター、リンカー、およびオペレーティングシステムに影響を与える一連のコントラクト。コントラクトは、関数のレイアウト、パラメーターの受け渡し、パラメーターの受け渡し、関数の戻りの動作を指定します。これらは一般に(プロセッサアーキテクチャ、オペレーティングシステム)タプルに固有です。

既存のエンティティ:パラメータのレイアウト、関数のセマンティクス、レジスタの割り当て。たとえば、ARMアーキテクチャには多数のABI(APCS、EABI、GNU-EABI、歴史的なケースの束を気にしないでください)があります-混合ABIを使用すると、境界を越えて呼び出すときにコードが機能しなくなります。

消費者:コンパイラ、アセンブリライター、オペレーティングシステム、CPU固有のアーキテクチャ。

誰がこれらの詳細を必要としますか?コンパイラ、アセンブリライター、コード生成(またはアライメント要件)を実行するリンカー、オペレーティングシステム(割り込み処理、syscallインターフェイス)。あなたがアセンブリプログラミングをしたなら、あなたはABIに準拠していました!

C ++の名前のマングリングは特殊なケースです-そのリンカーと動的リンカーが中心の問題-名前のマングリングが標準化されていない場合、動的リンクは機能しません。以降、C ++ ABIはC ++ ABIと呼ばれます。これはリンカレベルの問題ではなく、コード生成の問題です。C ++バイナリを取得すると、ソースから再コンパイルしない限り、別のC ++ ABI(名前のマングリング、例外処理)と互換性を持たせることはできません。

ELFは、ローダーとダイナミックリンカーを使用するためのファイル形式です。ELFは、バイナリコードとデータのコンテナー形式であり、コードのABIを指定します。PE実行可能ファイルはABIではないため、ELFを厳密な意味でABIとは見なしません。

すべてのABIは命令セット固有です。ARM ABIは、MSP430またはx86_64プロセッサでは意味がありません。

WindowsにはいくつかのABIがあります。たとえば、fastcallとstdcallは2つの一般的な用途のABIです。syscall ABIもまた異なります。


9

少なくともあなたの質問の一部に答えさせてください。Linux ABIがシステムコールにどのように影響するか、そしてそれがなぜ有用であるかの例を示します。

システムコールは、ユーザー空間プログラムがカーネル空間に何かを要求する方法です。これは、呼び出しの数値コードと引数を特定のレジスターに入れ、割り込みをトリガーすることによって機能します。カーネル空間への切り替えが発生し、カーネルが数値コードと引数を検索して要求を処理し、結果をレジスターに戻し、ユーザー空間への切り替えをトリガーします。これは、たとえば、アプリケーションがメモリを割り当てたり、ファイルを開いたりする場合に必要です(syscallの「brk」と「open」)。

現在、syscallには「brk」などの短い名前と対応するオペコードがあり、これらはシステム固有のヘッダーファイルで定義されています。これらのオペコードが同じである限り、再コンパイルする必要なしに、異なる更新済みカーネルで同じコンパイル済みユーザーランドプログラムを実行できます。つまり、プリコンパイルされたバイナリ、つまりABIが使用するインターフェースがあります。


4

共有ライブラリのコードを呼び出す、またはコンパイルユニット間でコードを呼び出すには、オブジェクトファイルに呼び出しのラベルを含める必要があります。C ++は、データの非表示を強制し、オーバーロードされたメソッドを許可するために、メソッドラベルの名前をマングルします。そのため、明示的に同じABIをサポートしない限り、異なるC ++コンパイラのファイルを混在させることはできません。


4

ABIとAPIを区別する最良の方法は、その理由と用途を知ることです。

x86-64の場合、一般に1つのABIがあります(x86 32ビットの場合、別のセットがあります)。

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がこれらのAPIを使用してビルドされた場合、相互に呼び出すことができる場合があります。ただし、同じABIの追加要件を遵守する必要があります。

たとえば、Windowsは以前はPOSIX APIに準拠していました。

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

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

LinuxもPOSIXに準拠しています。ただし、バイナリを移動してすぐに実行することはできません。ただし、POSIX準拠のAPIで同じ名前を使用しているため、同じソフトウェアをCで取得し、別のOSで再コンパイルして、すぐに実行できます。

APIは、ソフトウェアの統合を容易にすることを目的としています-プリコンパイル段階。したがって、コンパイル後、ソフトウェアは完全に異なって見える可能性があります-ABIが異なる場合。

ABIは、バイナリー/アセンブリーレベルでのソフトウェアの正確な統合を定義するためのものです。


Windows x86-64呼び出し規約は、他のすべてのx86-64 OSが使用するSysV呼び出し規約を使用しません。Linux / OS X / FreeBSDはすべて同じ呼び出し規約を共有しますが、完全なABIを共有しません。OSのABIには、システム呼び出し番号が含まれています。例:freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/…は、SYS_execve32ビットLinuxでは11、FreeBSDでは59であると述べています。
Peter Cordes

コメントをありがとう、ABIとAPIの違いによりよく答えるためにコメントを変更しました。
Peter Teoh

呼び出し規約と完全なABI(システムコールとすべて)の違いがまだありません。Linux(カーネル)はFreeBSD互換性レイヤーを提供するため、一部のFreeBSDバイナリをLinuxで実行できます。それでも、これはLinuxが提供しないFreeBSD ABIの一部を使用しようとしないバイナリに限定されます。(例:FreeBSDのみのシステムコール)。ABI互換とは、同じようにコンパイルされるだけでなく、両方のシステムで同じバイナリを実行できることを意味します。
Peter Cordes

「FreeBSD互換レイヤー」、それについて聞いたことがない。関連するLinuxカーネルのソースコードを指摘できますか?しかし、その逆が存在します: freebsd.org/doc/en_US.ISO8859-1/books/handbook/linuxemu.html
Peter Teoh

それは私が使うものではありません。私は思っ存在していたようなものが、多分それはもうしません。 tldp.org/HOWTO/Linux+FreeBSD-6.htmlメンテナンスされていないと言っており、そのハウツーは2000年のものです。xD。 unix.stackexchange.com/questions/172038/…は、それが放棄され、再実行されなかったことを確認します(誰もそれを達成するのに十分なほど望んでいないため)。 personality(2)設定できますPER_BSD。私はいつも出力で見personality(PER_LINUX)たのを覚えていると思いますstraceが、最新の64ビットLinuxバイナリではもうそれができません。
Peter Cordes

4

Linux共有ライブラリの最小実行可能ABIの例

共有ライブラリのコンテキストでは、「安定したABIを持つ」ことの最も重要な含意は、ライブラリの変更後にプログラムを再コンパイルする必要がないことです。

だから例えば:

  • 共有ライブラリを販売している場合、新しいリリースごとにライブラリに依存するすべてのものを再コンパイルする煩わしさをユーザーに与えません。

  • ユーザーのディストリビューションに存在する共有ライブラリに依存するクローズドソースプログラムを販売している場合、ターゲットOSの特定のバージョン間で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_mystructcalled に追加するとし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_fieldbefore を追加するとold_field、ABIだけが破壊され、APIは破壊されなかったことに注意してください。

これが意味することはmain.c、ライブラリに対してプログラムを再コンパイルした場合、関係なく機能したということです。

また、たとえば関数のシグネチャを変更した場合、APIも壊れていました。

mylib_mystruct* mylib_init(int old_field, int new_field);

その場合、main.cコンパイルは完全に停止します。

セマンティックAPIとプログラミングAPI

APIの変更を3番目のタイプ、つまりセマンティックの変更に分類することもできます。

セマンティックAPIは通常、APIが実行するはずの自然言語による説明であり、通常はAPIドキュメントに含まれています。

したがって、プログラムのビルド自体を壊すことなく、セマンティックAPIを壊すことが可能です。

たとえば、変更した場合

myobject->old_field = old_field;

に:

myobject->old_field = old_field + 1;

その後、これはプログラミングAPIもABIも破壊しませんでしたがmain.c、セマンティックAPIは破壊しました。

プログラムでコントラクトAPIを確認する方法は2つあります。

  • 一連のコーナーケースをテストします。簡単ですが、いつでも見逃してしまうかもしれません。
  • 正式な確認。行うのは難しいですが、正確さを数学的に証明し、本質的にドキュメントとテストを「人間の」/機械で検証可能な方法に統合します!もちろん、正式な説明にバグがない限り、;-)

    この概念は、数学自体の形式化と密接に関連しています:https : //math.stackexchange.com/questions/53969/what-does-formal-mean/3297537#3297537

C / C ++共有ライブラリABIを壊すすべてのリスト

TODO:究極のリストを見つけて作成する:

Java最小実行可能例

Javaのバイナリ互換性とは何ですか?

Ubuntu 18.10、GCC 8.2.0でテスト済み。


3

ABIは、呼び出しが成功したことを確認するために、呼び出し元と呼び出し先の間で一貫している必要があります。スタックの使用、レジスタの使用、ルーチンの終わりのスタックポップ。これらはすべて、ABIの最も重要な部分です。


3

概要

ABI(アプリケーションバイナリインターフェイス)を定義する正確な層については、さまざまな解釈と強い意見があります。

私の見解では、ABIは特定のAPIの所定のプラットフォームと見なされるものの主観的な規約です。ABIは、特定のAPIに対して「変更されない」、またはランタイム環境(エグゼキューター、ツール、リンカー、コンパイラー、jvm、およびOS)によって対処される規則の「残りの部分」です。

インターフェイスの定義:ABI、API

joda-timeのようなライブラリを使用する場合は、への依存関係を宣言する必要がありますjoda-time-<major>.<minor>.<patch>.jar。ライブラリはベストプラクティスに従い、セマンティックバージョニングを使用します。これにより、APIの互換性が3つのレベルで定義されます。

  1. パッチ-コードをまったく変更する必要はありません。ライブラリはいくつかのバグを修正しました。
  2. マイナー-追加以降、コードを変更する必要はありません
  3. メジャー-インターフェース(API)が変更され、コードの変更が必要になる場合があります。

同じライブラリの新しいメジャーリリースを使用するために、他の多くの規則が引き続き尊重されます。

  • ライブラリに使用されるバイナリ言語(Javaの場合、Javaバイトコードを定義するJVMターゲットバージョン)
  • 呼び出し規約
  • JVMの規則
  • リンク規約
  • ランタイム規約これらはすべて、使用するツールによって定義および管理されます。

Javaケーススタディ

たとえば、Javaはツールではなく正式なJVM仕様でこれらすべての規則を標準化しました。この仕様により、他のベンダーは、互換性のあるライブラリを出力できるさまざまなツールセットを提供できました。

Javaは、ABIの興味深い2つのケーススタディを提供します。ScalaバージョンとDalvik仮想マシンです。

Dalvik仮想マシンがABIを破壊

Dalvik VMには、Javaバイトコードとは異なるタイプのバイトコードが必要です。Dalvikライブラリは、DalvikのJavaバイトコード(同じAPIを使用)を変換することによって取得されます。このようにして、同じAPIの2つのバージョンを取得できますjoda-time-1.7.2.jar。私は私joda-time-1.7.2.jarと呼ぶことができますjoda-time-1.7.2-dalvik.jar。それらは、スタック指向の標準Java VM用の異なるABIを使用します。Oracleのもの、IBMのもの、オープンJavaなどです。2番目のABIは、Dalvikの周りのものです。

Scalaの連続リリースには互換性がありません

Scalaには、Scalaのマイナーバージョン2.Xとのバイナリ互換性がありません。このため、同じAPI "io.reactivex" %% "rxscala"% "0.26.5"には、Scala 2.10、2.11、2.12の3つのバージョン(将来的にはさらに)があります。何が変更されますか?現時点はわかりませんが、バイナリには互換性がありません。おそらく最新バージョンでは、ライブラリを古い仮想マシンで使用できなくするもの、おそらくリンク/命名/パラメータの慣習に関連するものが追加されます。

Javaの連続リリースには互換性がありません

Javaは、JVMのメジャーリリース(4、5、6、7、8、9)にも問題があります。下位互換性のみを提供します。Jvm9は-target他のすべてのバージョンでコンパイル/ターゲット(javacのオプション)されたコードを実行する方法を知っていますが、JVM 4はJVM 5をターゲットにしたコードを実行する方法を認識していません。この非互換性は、さまざまなソリューションのおかげでレーダーの下を飛んでいます:

  1. セマンティックバージョニング:ライブラリがより高いJVMをターゲットとする場合、通常、それらはメジャーバージョンを変更します。
  2. JVM 4をABIとして使用すれば安全です。
  3. Java 9では、特定のターゲットJVMのバイトコードを同じライブラリに含める方法に関する仕様が追加されています。

なぜAPI定義から始めたのですか?

APIとABIは、互換性の定義方法に関する単なる慣例です。下層は、大量の高レベルのセマンティクスに関して一般的です。そのため、いくつかの規則を作成するのは簡単です。最初の種類の規則は、メモリアライメント、バイトエンコーディング、呼び出し規則、ビッグエンディアンおよびリトルエンディアンエンコーディングなどに関するものです。その上に、他の記述と同様の実行可能な規則、リンク規則、Javaで使用されるような中間バイトコード、またはGCCが使用するLLVM IR。3番目に、ライブラリの検索方法、ライブラリのロード方法に関する規則を取得します(Javaクラスローダーを参照)。概念をどんどん高くしていくと、当然のことと考える新しい規則があります。これが、セマンティックバージョニングに到達しなかった理由です。バージョン。セマンティックバージョニングをで修正でき<major>-<minor>-<patch>-<platform/ABI>ます。これは実際にはすでに何が起こっているかである:プラットフォームがすでにあるrpmdlljar(JVMバイトコード)、war(JVM + Webサーバ)、 、apk2.11特定のScala版)などがあります。APKと言うときは、APIの特定のABI部分について既に話し合っています。

APIは別のABIに移植できます

抽象化のトップレベル(最高のAPIに対して記述されたソースは、他の下位レベルの抽象化に再コンパイル/移植できます。

rxscalaのソースがいくつかあるとします。Scalaツールが変更された場合は、それに再コンパイルできます。JVMが変更された場合、高レベルの概念を気にすることなく、古いマシンから新しいマシンに自動変換することができます。移植は難しいかもしれませんが、他のクライアントには役立ちます。まったく異なるアセンブラコードを使用して新しいオペレーティングシステムを作成すると、トランスレータを作成できます。

言語間で移植されたAPI

リアクティブストリームのような複数の言語で移植されるAPIがあります。一般に、特定の言語/プラットフォームへのマッピングを定義します。APIは、人間の言語または特定のプログラミング言語で正式に定義されたマスター仕様であると私は主張します。他のすべての「マッピング」は、ある意味ではABIであり、それ以外の場合は通常のABIよりも多くのAPIです。RESTインターフェースでも同じことが起こります。


1

要するに、哲学では、ある種のものだけがうまくいくことができ、ABIは、ソフトウェアがどのように連携するのと見なすことができます。


1

私もABIを理解しようとしていましたが、JesperEの回答は非常に役に立ちました。

非常に単純な観点から、バイナリ互換性を検討することでABIを理解しようとする場合があります。

KDE wikiは、ライブラリをバイナリ互換として定義しています。「以前のバージョンのライブラリに動的にリンクされたプログラムが、再コンパイルする必要なしに、新しいバージョンのライブラリで引き続き実行される場合」動的リンクの詳細については、静的リンクと動的リンクを参照してください。

次に、ライブラリをバイナリ互換にするために必要な最も基本的な側面だけを見てみましょう(ライブラリのソースコードに変更がない場合)。

  1. 同じ/下位互換性のある命令セットアーキテクチャ(プロセッサ命令、レジスタファイル構造、スタック構成、メモリアクセスタイプ、およびサイズ、レイアウト、プロセッサが直接アクセスできる基本データタイプの配置)
  2. 同じ呼び出し規約
  3. 同じ名前の変換規則(これは、FortranプログラムがC ++ライブラリ関数を呼び出す必要がある場合に必要になることがあります)。

確かに、他にも多くの詳細がありますが、これは主にABIもカバーしています。

より具体的には、あなたの質問に答えるために、上記から、私たちは推測することができます:

ABI機能:バイナリ互換性

既存のエンティティ:既存のプログラム/ライブラリ/ OS

消費者:ライブラリ、OS

お役に立てれば!


1

アプリケーションバイナリインターフェイス(ABI)

機能性:

  • プログラマーのモデルから、基盤となるシステムのドメインデータタイプ、サイズ、配置、呼び出し規則への変換。これは、関数の引数の受け渡し方法と戻り値の取得方法を制御します。システムコール番号、およびアプリケーションがオペレーティングシステムに対してシステムコールを行う方法。高水準言語コンパイラーの名前マングリング方式、例外伝搬、および同じプラットフォーム上のコンパイラー間の呼び出し規約。ただし、プラットフォーム間の互換性は必要ありません...

既存のエンティティ:

  • プログラムの実行に直接関与する論理ブロック:ALU、汎用レジスター、I / Oのメモリー/ I / Oマッピング用レジスターなど

消費者:

  • 言語プロセッサのリンカ、アセンブラ...

これらは、ビルドツールチェーンが全体として機能することを確認する必要がある人が必要とします。アセンブリ言語で1つのモジュールを記述し、Pythonで別のモジュールを記述し、独自のブートローダーではなくオペレーティングシステムを使用する場合、「アプリケーション」モジュールは「バイナリ」境界を越えて機能しており、そのような「インターフェース」の同意が必要です。

異なる高水準言語のオブジェクトファイルをアプリケーションでリンクする必要がある場合があるため、C ++名のマングリング。Visual C ++で構築されたWindowsへのシステムコールを行うGCC標準ライブラリの使用を検討してください。

ELFは、解釈のためのオブジェクトファイルからのリンカーの予想される1つの可能性ですが、JVMには他の考えがあるかもしれません。

Windows RTストアアプリの場合、ビルドツールチェーンを一緒に動作させたい場合は、ARM ABIを検索してみてください。


1

ABIという用語は、2つの異なるが関連する概念を指すために使用されます。

コンパイラについて話すときは、ソースレベルの構成からバイナリ構成に変換するために使用される規則を指します。データ型はどのくらい大きいですか?スタックはどのように機能しますか?関数にパラメーターを渡すにはどうすればよいですか?呼び出し元と呼び出し先でどちらのレジスタを保存する必要がありますか?

ライブラリについて話すときは、コンパイルされたライブラリによって提供されるバイナリインターフェイスを指します。このインターフェイスは、ライブラリのソースコード、コンパイラが使用するルール、場合によっては他のライブラリから取得した定義など、いくつかの要因の結果です。

ライブラリを変更すると、APIを壊すことなくABIを壊す可能性があります。たとえば、次のようなインターフェイスを持つライブラリを考えてみましょう。

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- アプリケーション、ライブラリ、OSなどの2つのバイナリプログラムパーツ間のランタイムのApplication Binary Interfaceマシンコード通信に関するものです... オブジェクトがメモリに保存される方法と関数が呼び出される方法を説明します()ABIcalling convention

APIとABIの良い例は、Swift言語を使用したiOSエコシステムです

  • Application-異なる言語を使用してアプリケーションを作成する場合。たとえばSwift[SwiftとObjective-Cの混合]を使用してアプリケーションを作成できますObjective-C

  • Application - OS-ランタイム- Swift runtimeおよびstandard librariesOSの一部であり、各バンドル(アプリ、フレームワークなど)に含めないでください。Objective-Cが使用するのと同じです

  • Library- Module Stabilityケース- コンパイル時 - 別のバージョンのSwiftコンパイラーでビルドされたフレームワークをインポートできます。これは、異なるバージョンのコンパイラー.swiftinterfaceで使用されるクローズドソース(ビルド前)バイナリを作成することが安全であることを意味します(で使用されます.swiftmodule)。

    Module compiled with _ cannot be imported by the _ compiler
    
  • Library- Library Evolutionケース

    1. コンパイル時間-依存関係が変更された場合、クライアントを再コンパイルする必要はありません。
    2. ランタイム-システムライブラリまたは動的フレームワークを新しいものにホットスワップできます。

[APIとABI]

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.