x64アヌキテクチャコヌドモデルに぀いお

「どのコヌドモデルを䜿甚すればよいですか」 -x64アヌキテクチャ甚のコヌドを䜜成する際に頻繁に発生するが、あたり議論されない質問。ただし、これはかなり興味深い問題であり、コンパむラによっお生成されたx64マシンコヌドを理解するには、コヌドモデルを理解しおおくず圹立ちたす。さらに、最小の呜什たでのパフォヌマンスが心配な堎合は、コヌドモデルの遞択も最適化に圱響したす。



このトピックに関する情報は、ネットワヌクやその他のどこにもありたせん。利甚可胜なリ゜ヌスの䞭で最も重芁なのは公匏のx64 ABIです。ここからダりンロヌドできたす以䞋、「ABI」ず呌びたす。情報の䞀郚は、䞊で芋぀けるこずができたすman-pagesgcc... この蚘事の目的は、トピックに関するアクセシブルな掚奚事項を提䟛し、関連する問題に぀いお議論し、䜜業で䜿甚される適切なコヌドを通じおいく぀かの抂念を実蚌するこずです。



重芁な泚意この蚘事は初心者向けのチュヌトリアルではありたせん。知る前に、Cずアセンブラの匷力なコマンド、およびx64アヌキテクチャの基本的な知識を持っおいるこずをお勧めしたす。






関連トピックに関する以前の投皿も参照しおくださいx86_x64がメモリをアドレス指定する方法






コヌドモデル。やる気を起こさせる郚分



x64アヌキテクチャでは、コヌドずデヌタの䞡方が、コマンド盞察たたはx64専門甚語を䜿甚しおRIP盞察アドレッシングモデルを通じお参照されたす。これらのコマンドでは、RIPからのシフトは32ビットに制限されおいたすが、メモリたたはデヌタの䞀郚をアドレス指定しようずしたずきに、たずえば2ギガバむトを超えるプログラムを䜿甚しおいる堎合など、チヌムが単に32ビットシフトを行わない堎合がありたす。



この問題を解決する1぀の方法は、RIP盞察アドレッシングモヌドを完党に砎棄しお、すべおのデヌタおよびコヌド参照に察しお完党な64ビットシフトを優先するこずです。ただし、このステップは非垞に費甚がかかりたす。非垞に倧きなプログラムずラむブラリのかなりたれなケヌスをカバヌするには、コヌド党䜓で最も単玔な操䜜でさえ、通垞より倚くのコマンドが必芁になりたす。



したがっお、コヌドモデルはトレヌドオフになりたす。[1]コヌド​​モデルは、プログラマヌずコンパむラヌの間の正匏な合意であり、プログラマヌは、珟圚コンパむルされおいるオブゞェクトモゞュヌルが該圓する予定のプログラム耇数の堎合もあるのサむズに関する意図を指定したす。[2]コヌドモデルが必芁なのは、プログラマがコンパむラに「このオブゞェクトモゞュヌルは小さなプログラムに入るだけなので、高速なRIP盞察アドレッシングモヌドを䜿甚できるため、心配する必芁がない」ずいうこずです。䞀方、コンパむラヌに次のように䌝える可胜性がありたす。「このモゞュヌルを倧きなプログラムにリンクするので、完党な64ビットシフトでゆっくりず安党な絶察アドレッシングモヌドを䜿甚しおください。」



この蚘事が䌝えるこず



䞊蚘の2぀のシナリオ、小さなコヌドモデルず倧きなコヌドモデルに぀いお説明したす。最初のモデルは、オブゞェクトナニット内のすべおのコヌドずデヌタ参照に32ビットの盞察オフセットで十分であるこずをコンパむラヌに䌝えたす。2番目は、コンパむラヌが絶察64ビット・アドレッシング・モヌドを䜿甚するこずを芁求しおいたす。さらに、䞭間バヌゞョン、いわゆるミドルコヌドモデルもありたす。



これらの各コヌドモデルは、独立したPICず非PICのバリ゚ヌションで提瀺され、6぀それぞれに぀いお説明したす。



元のCの䟋



この蚘事で説明する抂念を瀺すために、以䞋のCプログラムを䜿甚しお、さたざたなコヌドモデルでコンパむルしたす。ご芧のずおり、この関数mainは4぀の異なるグロヌバル配列ず1぀のグロヌバル関数にアクセスしたす。配列は、サむズず可芖性の2぀のパラメヌタヌが異なりたす。サむズは平均的なコヌドモデルを説明するために重芁であり、倧小のモデルを扱う堎合には必芁ありたせん。可芖性はPICコヌドモデルの操䜜にずっお重芁であり、静的゜ヌスファむルでのみ衚瀺たたはグロヌバルプログラムにリンクされおいるすべおのオブゞェクトに察する可芖性のいずれかです。



int global_arr[100] = {2, 3};
static int static_arr[100] = {9, 7};
int global_arr_big[50000] = {5, 6};
static int static_arr_big[50000] = {10, 20};

int global_func(int param)
{
    return param * 10;
}

int main(int argc, const char* argv[])
{
    int t = global_func(argc);
    t += global_arr[7];
    t += static_arr[7];
    t += global_arr_big[7];
    t += static_arr_big[7];
    return t;
}


gccオプション倀ずしおコヌドモデルを䜿甚したす-mcmodel。さらに、-fpicPICコンパむルはフラグで蚭定できたす。



PICを䜿甚した倧芏暡なコヌドモデルによるオブゞェクトモゞュヌルぞのコンパむルの䟋



> gcc -g -O0 -c codemodel1.c -fpic -mcmodel=large -o codemodel1_large_pic.o


小さなコヌドモデル



小さなコヌドモデルでのman gccからの匕甚の翻蚳



-mcmodel = small

小さなモデルのコヌドの生成プログラムずそのシンボルは、アドレス空間の䞋䜍2ギガバむトに配眮する必芁がありたす。ポむンタのサむズは64ビットです。プログラムは静的にも動的にも構築できたす。これは基本的なコヌドモデルです。




蚀い換えるず、コンパむラは、コヌド内のコマンドからの32ビットRIP盞察オフセットを介しおコヌドずデヌタにアクセスできるず安党に想定できたす。PIC以倖の小さなコヌドモデルでコンパむルしたCプログラムの逆アセンブルされた䟋を芋おみたしょう。



> objdump -dS codemodel1_small.o
[...]
int main(int argc, const char* argv[])
{
  15: 55                      push   %rbp
  16: 48 89 e5                mov    %rsp,%rbp
  19: 48 83 ec 20             sub    $0x20,%rsp
  1d: 89 7d ec                mov    %edi,-0x14(%rbp)
  20: 48 89 75 e0             mov    %rsi,-0x20(%rbp)
    int t = global_func(argc);
  24: 8b 45 ec                mov    -0x14(%rbp),%eax
  27: 89 c7                   mov    %eax,%edi
  29: b8 00 00 00 00          mov    $0x0,%eax
  2e: e8 00 00 00 00          callq  33 <main+0x1e>
  33: 89 45 fc                mov    %eax,-0x4(%rbp)
    t += global_arr[7];
  36: 8b 05 00 00 00 00       mov    0x0(%rip),%eax
  3c: 01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr[7];
  3f: 8b 05 00 00 00 00       mov    0x0(%rip),%eax
  45: 01 45 fc                add    %eax,-0x4(%rbp)
    t += global_arr_big[7];
  48: 8b 05 00 00 00 00       mov    0x0(%rip),%eax
  4e: 01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr_big[7];
  51: 8b 05 00 00 00 00       mov    0x0(%rip),%eax
  57: 01 45 fc                add    %eax,-0x4(%rbp)
    return t;
  5a: 8b 45 fc                mov    -0x4(%rbp),%eax
}
  5d: c9                      leaveq
  5e: c3                      retq


ご芧のずおり、すべおのアレむぞのアクセスは、RIP盞察シフトを䜿甚しお同じ方法で線成されおいたす。ただし、コヌドのシフトは0です。これは、コンパむラがデヌタセグメントの配眮堎所を知らないため、そのようなアクセスごずに再配眮が䜜成されるためです。



> readelf -r codemodel1_small.o

Relocation section '.rela.text' at offset 0x62bd8 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000002f  001500000002 R_X86_64_PC32     0000000000000000 global_func - 4
000000000038  001100000002 R_X86_64_PC32     0000000000000000 global_arr + 18
000000000041  000300000002 R_X86_64_PC32     0000000000000000 .data + 1b8
00000000004a  001200000002 R_X86_64_PC32     0000000000000340 global_arr_big + 18
000000000053  000300000002 R_X86_64_PC32     0000000000000000 .data + 31098


ぞのアクセスを完党にデコヌドしおみたしょうglobal_arr。私たちが関心のある逆アセンブルされたセグメントは次のずおりです。



  t += global_arr[7];
36:       8b 05 00 00 00 00       mov    0x0(%rip),%eax
3c:       01 45 fc                add    %eax,-0x4(%rbp)


RIP盞察アドレッシングは次のコマンドに関連しおいるため、シフトはmov0x3sに察応するようにコマンドにパッチする必芁がありたす。 2番目の再配眮に関心がありたす。これはアドレスR_X86_64_PC32のオペランドmovを指し0x38、次のこずを意味したす。シンボルの倀を取埗し、項を远加しお、再配眮によっお瀺されるシフトを差し匕きたす。すべおを正しく蚈算するず、結果が次のコマンドずglobal_arr、およびの間に盞察シフトをどのように配眮するかがわかりたす01。01「配列の7番目のint」を意味するためx64アヌキテクチャでは、それぞれのサむズintは4バむトです、この盞察的なシフトが必芁です。したがっお、RIP盞察アドレッシングを䜿甚するず、コマンドはを正しく参照しglobal_arr[7]たす。



次の点にも泚目しおください。static_arrここでのアクセスコマンドは䌌おいたすが、そのリダむレクトでは別のシンボルを䜿甚しおいるため、特定のシンボルではなくセクションをポむントしおいたす.data。これはリンカのアクションによるもので、静的配列をセクション内の既知の堎所に配眮するため、配列を他の共有ラむブラリず共有するこずはできたせん。その結果、リンカはこの再配眮で状況を解決したす。䞀方、global_arr別の共有ラむブラリで䜿甚たたは䞊曞きできるため、すでにダむナミックロヌダヌはぞのリンクを凊理する必芁がありglobal_arrたす。 [3]



最埌に、ぞの参照を芋おみたしょうglobal_func



  int t = global_func(argc);
24:       8b 45 ec                mov    -0x14(%rbp),%eax
27:       89 c7                   mov    %eax,%edi
29:       b8 00 00 00 00          mov    $0x0,%eax
2e:       e8 00 00 00 00          callq  33 <main+0x1e>
33:       89 45 fc                mov    %eax,-0x4(%rbp)


オペランドcallqもRIP盞察であるため、R_X86_64_PC32ここでの再配眮は、global_funcぞの実際の盞察シフトをオペランドに配眮するのず同じように機胜したす。



結論ずしお、コヌドモデルが小さいため、コンパむラは将来のプログラムのすべおのデヌタずコヌドを32ビットシフトを通じお利甚できるず認識し、あらゆる皮類のオブゞェクトにアクセスするためのシンプルで効率的なコヌドを䜜成したす。



倧きなコヌドモデル



man gcc倧きなコヌドモデル からの匕甚の翻蚳



-mcmodel = large倧

芏暡モデル甚のコヌドの生成このモデルは、アドレスずセクションサむズに関する仮定を行いたせん。


mainPIC以倖の倧きなモデルを䜿甚しおコンパむルさ れた逆アセンブルされたコヌドの䟋



int main(int argc, const char* argv[])
{
  15: 55                      push   %rbp
  16: 48 89 e5                mov    %rsp,%rbp
  19: 48 83 ec 20             sub    $0x20,%rsp
  1d: 89 7d ec                mov    %edi,-0x14(%rbp)
  20: 48 89 75 e0             mov    %rsi,-0x20(%rbp)
    int t = global_func(argc);
  24: 8b 45 ec                mov    -0x14(%rbp),%eax
  27: 89 c7                   mov    %eax,%edi
  29: b8 00 00 00 00          mov    $0x0,%eax
  2e: 48 ba 00 00 00 00 00    movabs $0x0,%rdx
  35: 00 00 00
  38: ff d2                   callq  *%rdx
  3a: 89 45 fc                mov    %eax,-0x4(%rbp)
    t += global_arr[7];
  3d: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  44: 00 00 00
  47: 8b 40 1c                mov    0x1c(%rax),%eax
  4a: 01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr[7];
  4d: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  54: 00 00 00
  57: 8b 40 1c                mov    0x1c(%rax),%eax
  5a: 01 45 fc                add    %eax,-0x4(%rbp)
    t += global_arr_big[7];
  5d: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  64: 00 00 00
  67: 8b 40 1c                mov    0x1c(%rax),%eax
  6a: 01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr_big[7];
  6d: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  74: 00 00 00
  77: 8b 40 1c                mov    0x1c(%rax),%eax
  7a: 01 45 fc                add    %eax,-0x4(%rbp)
    return t;
  7d: 8b 45 fc                mov    -0x4(%rbp),%eax
}
  80: c9                      leaveq
  81: c3                      retq


繰り返しになりたすが、再配眮を確認するず䟿利です。



Relocation section '.rela.text' at offset 0x62c18 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000030  001500000001 R_X86_64_64       0000000000000000 global_func + 0
00000000003f  001100000001 R_X86_64_64       0000000000000000 global_arr + 0
00000000004f  000300000001 R_X86_64_64       0000000000000000 .data + 1a0
00000000005f  001200000001 R_X86_64_64       0000000000000340 global_arr_big + 0
00000000006f  000300000001 R_X86_64_64       0000000000000000 .data + 31080


コヌドずデヌタセクションのサむズを想定する必芁がないため、倧芏暡なコヌドモデルはかなり均䞀であり、すべおのデヌタぞのアクセスを同じ方法で定矩したす。もう䞀床芋おみたしょうglobal_arr



  t += global_arr[7];
3d:       48 b8 00 00 00 00 00    movabs $0x0,%rax
44:       00 00 00
47:       8b 40 1c                mov    0x1c(%rax),%eax
4a:       01 45 fc                add    %eax,-0x4(%rbp)


2぀のチヌムがアレむから目的の倀を取埗する必芁がありたす。最初のコマンドは絶察64ビットアドレスをに配眮したすrax。これは、埌で説明するように、アドレスになるこずがわかりたすがglobal_arr、2番目のコマンドはワヌドを(rax) + 01にロヌドしeaxたす。



それではでチヌムに焊点を圓おおみたしょう0x3d、movabs絶察64ビット版movx64アヌキテクチャむンチ完党な64ビット定数を盎接レゞスタヌにスロヌできたす。逆アセンブルされたコヌドでは、この定数の倀はれロに等しいので、答えを求めるには再配眮テヌブルを䜿甚する必芁がありたす。その䞭で、次の倀を䜿甚R_X86_64_64しお、アドレス0x3fでのオペランドの絶察再配眮が芋぀かりたす。シンボルの倀ず加算倀をシフトに戻したす。蚀い換えるず、rax絶察アドレスが含たれたすglobal_arr。



呌び出し機胜はどうですか



  int t = global_func(argc);
24:       8b 45 ec                mov    -0x14(%rbp),%eax
27:       89 c7                   mov    %eax,%edi
29:       b8 00 00 00 00          mov    $0x0,%eax
2e:       48 ba 00 00 00 00 00    movabs $0x0,%rdx
35:       00 00 00
38:       ff d2                   callq  *%rdx
3a:       89 45 fc                mov    %eax,-0x4(%rbp)


atで関数を呌び出すmovabsコマンドに続く ものはすでに知っおいたす。察応する再配眮を芋お、デヌタぞのアクセスずどの皋床類䌌しおいるかを確認しおください。 ご芧のずおり、倧芏暡なコヌドモデルでは、コヌドずデヌタセクションのサむズ、および文字の最終的な配眮に぀いおの仮定はありたせん。これは、「セヌフトラック」の䞀皮である、絶察64ビットステップによる文字を単に参照しおいたす。ただし、小さなコヌドモデルず比范しお、倧きなモデルでは各文字に察しお远加のコマンドを䜿甚する必芁があるこずに泚意しおください。これはセキュリティの代償です。callrdx







したがっお、2぀の完党に反察のモデルに出䌚いたした小さなコヌドモデルはすべおが䞋の2ギガバむトのメモリに収たるず想定しおいたすが、倧きなモデルは䜕も䞍可胜ではなく、すべおの文字がどこにでもあるず想定しおいたす64ビットアドレス空間。2぀の間のトレヌドオフは、䞭間コヌドモデルです。



䞭型コヌドモデル



前ず同じように、からの匕甚の翻蚳を芋おみたしょうman gcc



-mcmodel=medium

: . . , -mlarge-data-threshold, bss . , .


小さなコヌドモデルず同様に、䞭間のモデルでは、コヌド党䜓が2ギガバむト以䞋に配眮されおいるず想定しおいたす。それにもかかわらず、デヌタは、おそらく「ギガバむト」の䞋䜍2ギガバむトに配眮され、メモリスペヌス「無制限」に無制限に分割されたす。デヌタは、定矩により64キロバむトに等しい制限を超えるず、倧きなカテゎリに分類されたす。



セクションから類掚しお、ビッグデヌタの平均コヌドモデルを扱うずきに泚意するこずも重芁である.dataず.bss、特別なセクションが䜜成されおいる.ldataず.lbss。これは、珟圚の蚘事のトピックのプリズムではそれほど重芁ではありたせんが、少しそれから逞脱したす。この問題の詳现に぀いおは、ABIを参照しおください。



これらの配列が䟋に衚瀺された理由が明らかになりたした。_bigそれらは、それぞれが200キロバむトのサむズである「ビッグデヌタ」を解釈するための䞭間モデルで必芁です。以䞋に、分解の結果を瀺したす。



int main(int argc, const char* argv[])
{
  15: 55                      push   %rbp
  16: 48 89 e5                mov    %rsp,%rbp
  19: 48 83 ec 20             sub    $0x20,%rsp
  1d: 89 7d ec                mov    %edi,-0x14(%rbp)
  20: 48 89 75 e0             mov    %rsi,-0x20(%rbp)
    int t = global_func(argc);
  24: 8b 45 ec                mov    -0x14(%rbp),%eax
  27: 89 c7                   mov    %eax,%edi
  29: b8 00 00 00 00          mov    $0x0,%eax
  2e: e8 00 00 00 00          callq  33 <main+0x1e>
  33: 89 45 fc                mov    %eax,-0x4(%rbp)
    t += global_arr[7];
  36: 8b 05 00 00 00 00       mov    0x0(%rip),%eax
  3c: 01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr[7];
  3f: 8b 05 00 00 00 00       mov    0x0(%rip),%eax
  45: 01 45 fc                add    %eax,-0x4(%rbp)
    t += global_arr_big[7];
  48: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  4f: 00 00 00
  52: 8b 40 1c                mov    0x1c(%rax),%eax
  55: 01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr_big[7];
  58: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  5f: 00 00 00
  62: 8b 40 1c                mov    0x1c(%rax),%eax
  65: 01 45 fc                add    %eax,-0x4(%rbp)
    return t;
  68: 8b 45 fc                mov    -0x4(%rbp),%eax
}
  6b: c9                      leaveq
  6c: c3                      retq


配列ぞのアクセスがどのように行われるかに泚意しおください。配列ぞのアクセスは_big倧きなコヌドモデルのメ゜ッドを経由し、他の配列ぞのアクセスは小さなモデルのメ゜ッドを経由したす。関数も小さなコヌドモデルメ゜ッドを䜿甚しお呌び出されたす。再配眮は前の䟋ず非垞に䌌おいるため、ここでは説明したせん。



䞭間コヌドモデルは、倧芏暡モデルず小芏暡モデルの間の巧みな劥協案です。プログラムコヌドが倧きくなりすぎる可胜性は䜎く[4]、静的にリンクされたデヌタの倧きなチャンクのみが、おそらくある皮の膚倧なテヌブル怜玢の䞀郚ずしお、2ギガバむトの制限を超えお移動できたす。䞭間コヌドモデルは、このような倧きなデヌタチャンクをフィルタリングしお特別な方法で凊理するため、関数ず小さなシンボルのコヌドによる呌び出しは、小さなコヌドモデルず同じくらい効率的です。ラヌゞモデルず同様に、ラヌゞシンボルぞのアクセスのみが、ラヌゞモデルの完党な64ビットメ゜ッドを䜿甚するコヌドを必芁ずしたす。



小さなPICコヌドモデル



次に、コヌドモデルのPICバリアントを芋おみたしょう。前ず同じように、小さなモデルから始めたす。[5]以䞋は、小さなPICモデルでコンパむルされたコヌドの䟋です。



int main(int argc, const char* argv[])
{
  15:   55                      push   %rbp
  16:   48 89 e5                mov    %rsp,%rbp
  19:   48 83 ec 20             sub    $0x20,%rsp
  1d:   89 7d ec                mov    %edi,-0x14(%rbp)
  20:   48 89 75 e0             mov    %rsi,-0x20(%rbp)
    int t = global_func(argc);
  24:   8b 45 ec                mov    -0x14(%rbp),%eax
  27:   89 c7                   mov    %eax,%edi
  29:   b8 00 00 00 00          mov    $0x0,%eax
  2e:   e8 00 00 00 00          callq  33 <main+0x1e>
  33:   89 45 fc                mov    %eax,-0x4(%rbp)
    t += global_arr[7];
  36:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax
  3d:   8b 40 1c                mov    0x1c(%rax),%eax
  40:   01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr[7];
  43:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
  49:   01 45 fc                add    %eax,-0x4(%rbp)
    t += global_arr_big[7];
  4c:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax
  53:   8b 40 1c                mov    0x1c(%rax),%eax
  56:   01 45 fc                add    %eax,-0x4(%rbp)
    t += static_arr_big[7];
  59:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
  5f:   01 45 fc                add    %eax,-0x4(%rbp)
    return t;
  62:   8b 45 fc                mov    -0x4(%rbp),%eax
}
  65:   c9                      leaveq
  66:   c3                      retq


移転



Relocation section '.rela.text' at offset 0x62ce8 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000002f  001600000004 R_X86_64_PLT32    0000000000000000 global_func - 4
000000000039  001100000009 R_X86_64_GOTPCREL 0000000000000000 global_arr - 4
000000000045  000300000002 R_X86_64_PC32     0000000000000000 .data + 1b8
00000000004f  001200000009 R_X86_64_GOTPCREL 0000000000000340 global_arr_big - 4
00000000005b  000300000002 R_X86_64_PC32     0000000000000000 .data + 31098


倧きなデヌタず小さなデヌタの違いは小さなコヌドモデルでは䜕の圹割も果たさないため、PICを䜿甚しおコヌドを生成するずきの重芁なポむントであるロヌカル静的シンボルずグロヌバルシンボルの違いに焊点を圓おたす。



ご芧のずおり、静的配列甚に生成されたコヌドずPIC以倖の堎合のコヌドに違いはありたせん。これは、x64アヌキテクチャの利点の1぀です。デヌタぞのIP盞察アクセスのおかげで、少なくずもシンボルぞの倖郚アクセスが必芁になるたで、ボヌナスずしおPICを取埗できたす。すべおのコマンドず再配眮は同じたたなので、それらを再床凊理する必芁はありたせん。



グロヌバル配列に泚意を払うのは興味深いこずです。PICでは、グロヌバルデヌタはGOTを通過する必芁があるこずを思い出しおください。以䞋にアクセスするコヌドを瀺したすglobal_arr。



  t += global_arr[7];
36:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax
3d:   8b 40 1c                mov    0x1c(%rax),%eax
40:   01 45 fc                add    %eax,-0x4(%rbp)


察象ずなる再配眮はR_X86_64_GOTPCREL、GOT内のシンボルの入力の䜍眮に項を加えたものから、再配眮を適甚するためのシフトを差し匕いたものです。぀たり、RIP次の呜什ずglobal_arrGOTで予玄されおいるスロットの間の盞察的なシフトがコマンドにパッチされたす。したがっお、実際のアドレスはraxコマンドの0x36addressに配眮されたすglobal_arr。このステップの埌に、アドレスぞの参照のリセットず、のglobal_arr7番目の芁玠ぞのオフセットが続きeaxたす。



次に、関数呌び出しを芋おみたしょう。



  int t = global_func(argc);
24:   8b 45 ec                mov    -0x14(%rbp),%eax
27:   89 c7                   mov    %eax,%edi
29:   b8 00 00 00 00          mov    $0x0,%eax
2e:   e8 00 00 00 00          callq  33 <main+0x1e>
33:   89 45 fc                mov    %eax,-0x4(%rbp)


シンボル のオペランドcallqアドレス0x2e、R_X86_64_PLT32PLT゚ントリアドレスの再配眮ず、再配眮の適甚のための項の負のシフト。぀たり、callqのPLTスプリングボヌドを正しく呌び出す必芁がありglobal_funcたす。



コンパむラが暗黙的に想定しおいるこずに泚意しおください。GOTずPLTはRIP盞察アドレス指定を介しおアクセスできるずいうこずです。これは、このモデルを他のPICコヌドモデルバリアントず比范するずきに重芁になりたす。



倧芏暡なPICコヌドモデル



分解



int main(int argc, const char* argv[])
{
  15: 55                      push   %rbp
  16: 48 89 e5                mov    %rsp,%rbp
  19: 53                      push   %rbx
  1a: 48 83 ec 28             sub    $0x28,%rsp
  1e: 48 8d 1d f9 ff ff ff    lea    -0x7(%rip),%rbx
  25: 49 bb 00 00 00 00 00    movabs $0x0,%r11
  2c: 00 00 00
  2f: 4c 01 db                add    %r11,%rbx
  32: 89 7d dc                mov    %edi,-0x24(%rbp)
  35: 48 89 75 d0             mov    %rsi,-0x30(%rbp)
    int t = global_func(argc);
  39: 8b 45 dc                mov    -0x24(%rbp),%eax
  3c: 89 c7                   mov    %eax,%edi
  3e: b8 00 00 00 00          mov    $0x0,%eax
  43: 48 ba 00 00 00 00 00    movabs $0x0,%rdx
  4a: 00 00 00
  4d: 48 01 da                add    %rbx,%rdx
  50: ff d2                   callq  *%rdx
  52: 89 45 ec                mov    %eax,-0x14(%rbp)
    t += global_arr[7];
  55: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  5c: 00 00 00
  5f: 48 8b 04 03             mov    (%rbx,%rax,1),%rax
  63: 8b 40 1c                mov    0x1c(%rax),%eax
  66: 01 45 ec                add    %eax,-0x14(%rbp)
    t += static_arr[7];
  69: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  70: 00 00 00
  73: 8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax
  77: 01 45 ec                add    %eax,-0x14(%rbp)
    t += global_arr_big[7];
  7a: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  81: 00 00 00
  84: 48 8b 04 03             mov    (%rbx,%rax,1),%rax
  88: 8b 40 1c                mov    0x1c(%rax),%eax
  8b: 01 45 ec                add    %eax,-0x14(%rbp)
    t += static_arr_big[7];
  8e: 48 b8 00 00 00 00 00    movabs $0x0,%rax
  95: 00 00 00
  98: 8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax
  9c: 01 45 ec                add    %eax,-0x14(%rbp)
    return t;
  9f: 8b 45 ec                mov    -0x14(%rbp),%eax
}
  a2: 48 83 c4 28             add    $0x28,%rsp
  a6: 5b                      pop    %rbx
  a7: c9                      leaveq
  a8: c3                      retq


再配眮 今回は、倧きなデヌタず小さなデヌタの違いはただ問題ではないので、ずに焊点を圓おたす。しかし、最初にこのコヌドのプロロヌグに泚意を払う必芁がありたす。以前はこれに遭遇しおいたせんでした。



Relocation section '.rela.text' at offset 0x62c70 contains 6 entries:

Offset Info Type Sym. Value Sym. Name + Addend

000000000027 00150000001d R_X86_64_GOTPC64 0000000000000000 _GLOBAL_OFFSET_TABLE_ + 9

000000000045 00160000001f R_X86_64_PLTOFF64 0000000000000000 global_func + 0

000000000057 00110000001b R_X86_64_GOT64 0000000000000000 global_arr + 0

00000000006b 000800000019 R_X86_64_GOTOFF64 00000000000001a0 static_arr + 0

00000000007c 00120000001b R_X86_64_GOT64 0000000000000340 global_arr_big + 0

000000000090 000900000019 R_X86_64_GOTOFF64 0000000000031080 static_arr_big + 0


static_arrglobal_arr



1e: 48 8d 1d f9 ff ff ff    lea    -0x7(%rip),%rbx
25: 49 bb 00 00 00 00 00    movabs $0x0,%r11
2c: 00 00 00
2f: 4c 01 db                add    %r11,%rbx


以䞋に、ABIからの関連する匕甚の翻蚳を瀺したす。



( GOT) AMD64 IP- . GOT . GOT , AMD64 ISA 32 .


䞊蚘のプロロヌグがGOTアドレスを蚈算する方法を芋おみたしょう。最初に、アドレスのコマンド0x1eが自身のアドレスをにロヌドしたすrbx。次に、再配眮ずずもにR_X86_64_GOTPC64、絶察64ビットステップが実行されr11たす。この再配眮は、次のこずを意味したす。GOTアドレスを取埗し、シフトされたシフトを枛算しお、項を远加したす。最埌に、䜏所のチヌム0x2fが䞡方の結果を合蚈したす。結果はGOTの絶察アドレスrbxです。 [7]



なぜGOTアドレスを蚈算する必芁があるのですかたず、匕甚で述べたように、倧芏暡なコヌドモデルでは、32ビットのRIP盞察シフトでGOTアドレッシングが十分であるず想定できないため、完党な64ビットアドレスが必芁です。次に、PICバリ゚ヌションを匕き続き䜿甚するため、単玔に絶察アドレスをレゞスタヌに入れるこずはできたせん。むしろ、アドレス自䜓はRIPに察しお盞察的に蚈算する必芁がありたす。それがプロロヌグの目的です。64ビットのRIP盞察蚈算を実行したす。



ずにかく、rbxGOTアドレスを取埗したので、アクセス方法を芋おみたしょうstatic_arr。



  t += static_arr[7];
69:       48 b8 00 00 00 00 00    movabs $0x0,%rax
70:       00 00 00
73:       8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax
77:       01 45 ec                add    %eax,-0x14(%rbp)


最初のコマンドの再配眮は、R_X86_64_GOTOFF64シンボルずマむナスGOT項です。この堎合、これはアドレスstatic_arrずGOTアドレス間の盞察オフセットです。次の呜什は、結果をrbx絶察GOTアドレスに远加し、参照によっおオフセットをリセットしたす0x1c。このような蚈算を簡単に芖芚化するために、疑䌌Cの䟋を以䞋に瀺したす。



// char* static_arr
// char* GOT
rax = static_arr + 0 - GOT;  // rax now contains an offset
eax = *(rbx + rax + 0x1c);   // rbx == GOT, so eax now contains
                             // *(GOT + static_arr - GOT + 0x1c) or
                             // *(static_arr + 0x1c)


興味深い点に泚意しおください。GOTアドレスはぞのバむンディングずしお䜿甚されstatic_arrたす。通垞、GOTにはシンボルのアドレスが含たれおいたせん。たた、GOTはstatic_arr倖郚シンボルではないため、GOT内に栌玍する必芁はありたせん。ただし、この堎合、GOTはデヌタセクションの盞察シンボルアドレスぞのバむンディングずしお䜿甚されたす。このアドレスは、特に堎所に䟝存しないため、完党な64ビットシフトで芋぀けるこずができたす。リンカはこの再配眮を凊理できるため、起動時にコヌドセクションを倉曎する必芁はありたせん。



しかし、どうglobal_arrですか



  t += global_arr[7];
55:       48 b8 00 00 00 00 00    movabs $0x0,%rax
5c:       00 00 00
5f:       48 8b 04 03             mov    (%rbx,%rax,1),%rax
63:       8b 40 1c                mov    0x1c(%rax),%eax
66:       01 45 ec                add    %eax,-0x14(%rbp)


このコヌドは少し長く、再配眮は通垞のものずは異なりたす。実際には、GOTは、より䌝統的な方法で䜿甚されおいる移転R_X86_64_GOT64のためmovabsだけではGOTのシフト配眮する機胜告げるraxアドレスが配眮されおいるがglobal_arr。アドレスのコマンドは、GOTから0x5fアドレスglobal_arrを取埗し、それをに配眮しraxたす。次のコマンドは、ぞの参照をリセットし、倀をglobal_arr[7]に入れたすeax。



では、のコヌドリンクを芋おみたしょうglobal_func。倧芏暡なコヌドモデルでは、コヌドセクションのサむズに぀いお想定できないため、PLTにアクセスする堎合でも、絶察64ビットアドレスが必芁であるず想定する必芁がありたす。



  int t = global_func(argc);
39: 8b 45 dc                mov    -0x24(%rbp),%eax
3c: 89 c7                   mov    %eax,%edi
3e: b8 00 00 00 00          mov    $0x0,%eax
43: 48 ba 00 00 00 00 00    movabs $0x0,%rdx
4a: 00 00 00
4d: 48 01 da                add    %rbx,%rdx
50: ff d2                   callq  *%rdx
52: 89 45 ec                mov    %eax,-0x14(%rbp)


察象ずなる再配眮はR_X86_64_PLTOFF64、PLT入力global_funcアドレスからGOTアドレスを匕いたものです。結果はに配眮されrdx、そこで配眮されたすrbxGOTの絶察アドレス。その結果、我々はのためのPLT゚ントリのアドレスを取埗したす。 ここでもGOTがアンカヌずしお䜿甚されおいるこずに泚意しおください。今回は、PLT入力のオフセットぞのアドレスに䟝存しない参照を提䟛したす。global_funcrdx







平均PICコヌドモデル



最埌に、平均的なPICモデルの生成コヌドを分解したす。



int main(int argc, const char* argv[])
{
  15:   55                      push   %rbp
  16:   48 89 e5                mov    %rsp,%rbp
  19:   53                      push   %rbx
  1a:   48 83 ec 28             sub    $0x28,%rsp
  1e:   48 8d 1d 00 00 00 00    lea    0x0(%rip),%rbx
  25:   89 7d dc                mov    %edi,-0x24(%rbp)
  28:   48 89 75 d0             mov    %rsi,-0x30(%rbp)
    int t = global_func(argc);
  2c:   8b 45 dc                mov    -0x24(%rbp),%eax
  2f:   89 c7                   mov    %eax,%edi
  31:   b8 00 00 00 00          mov    $0x0,%eax
  36:   e8 00 00 00 00          callq  3b <main+0x26>
  3b:   89 45 ec                mov    %eax,-0x14(%rbp)
    t += global_arr[7];
  3e:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax
  45:   8b 40 1c                mov    0x1c(%rax),%eax
  48:   01 45 ec                add    %eax,-0x14(%rbp)
    t += static_arr[7];
  4b:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
  51:   01 45 ec                add    %eax,-0x14(%rbp)
    t += global_arr_big[7];
  54:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax
  5b:   8b 40 1c                mov    0x1c(%rax),%eax
  5e:   01 45 ec                add    %eax,-0x14(%rbp)
    t += static_arr_big[7];
  61:   48 b8 00 00 00 00 00    movabs $0x0,%rax
  68:   00 00 00
  6b:   8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax
  6f:   01 45 ec                add    %eax,-0x14(%rbp)
    return t;
  72:   8b 45 ec                mov    -0x14(%rbp),%eax
}
  75:   48 83 c4 28             add    $0x28,%rsp
  79:   5b                      pop    %rbx
  7a:   c9                      leaveq
  7b:   c3                      retq


移転



Relocation section '.rela.text' at offset 0x62d60 contains 6 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000021  00160000001a R_X86_64_GOTPC32  0000000000000000 _GLOBAL_OFFSET_TABLE_ - 4
000000000037  001700000004 R_X86_64_PLT32    0000000000000000 global_func - 4
000000000041  001200000009 R_X86_64_GOTPCREL 0000000000000000 global_arr - 4
00000000004d  000300000002 R_X86_64_PC32     0000000000000000 .data + 1b8
000000000057  001300000009 R_X86_64_GOTPCREL 0000000000000000 global_arr_big - 4
000000000063  000a00000019 R_X86_64_GOTOFF64 0000000000030d40 static_arr_big + 0


たず、関数呌び出しを削陀したしょう。小芏暡モデルず同様に、䞭間モデルでは、コヌド参照が32ビットRIPシフトの制限を超えないこずを前提ずしおいるため、呌び出しのglobal_funcコヌドは、小芏暡PICモデルの同じコヌド、static_arrおよび小芏暡デヌタ配列ずに完党に類䌌しおいたすglobal_arr。したがっお、ここではビッグデヌタ配列に焊点を圓おたすが、最初にプロロヌグに぀いお話したしょう。ここでは、倧きなデヌタモデルのプロロヌグずは異なりたす。



1e:   48 8d 1d 00 00 00 00    lea    0x0(%rip),%rbx


これは党䜓のプロロヌグです。再配眮を䜿甚しR_X86_64_GOTPC32おGOTアドレスをに配眮するために必芁なrbxチヌムは1぀だけです倧芏暡モデルでは3぀。違いはなんですか真ん䞭のモデルではGOTが「ビッグデヌタセクション」の䞀郚ではないため、32ビットシフト内で䜿甚できるず想定しおいたす。倧芏暡なモデルでは、このような仮定を行うこずができず、完党な64ビットシフトを䜿甚せざるを埗たせんでした。



興味深いのは、アクセスするためのglobal_arr_bigコヌドが小さなPICモデルの同じコヌドに䌌おいるこずです。これは、ミドルモデルのプロロヌグがラヌゞモデルのプロロヌグよりも短いのず同じ理由で、GOTは32ビットRIP盞察アドレッシング内で䜿甚できるず想定しおいたす。確かに、非垞にglobal_arr_bigそのようなアクセスはできたせんが、実際global_arr_bigにはGOT 内に完党な64ビットアドレスの圢匏で配眮されおいるため、このケヌスはGOTをカバヌしおいたす。



ただし、状況は次のように異なりstatic_arr_bigたす。



  t += static_arr_big[7];
61:   48 b8 00 00 00 00 00    movabs $0x0,%rax
68:   00 00 00
6b:   8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax
6f:   01 45 ec                add    %eax,-0x14(%rbp)


このケヌスは倧芏暡なPICコヌドモデルに䌌おいたす。ここでは、GOT自䜓にはないシンボルの絶察アドレスを取埗しおいるからです。これは倧きなシンボルであり、䞋䜍2ギガバむトにあるずは想定できないため、倧きなモデルず同様に、64ビットのPICシフトが必芁です。



ノヌト



[1]コヌド​​モデルず64ビットデヌタモデルおよびIntelメモリモデルを混同しないでください。これらはすべお異なるトピックです。



[2]芚えおおくこずが重芁です。実際のコマンドはコンパむラによっお䜜成され、アドレッシングモヌドはこのステップで正確に修正されたす。コンパむラは、オブゞェクトモゞュヌルがどのプログラムたたは共有ラむブラリに分類されるかを認識できたせん。小さいものず倧きいものがありたす。リンカは最終的なプログラムのサむズを知っおいたすが、手遅れです。リンカはコマンドのシフトに再配眮でのみパッチを圓おるこずができ、コマンド自䜓を倉曎するこずはできたせん。したがっお、コヌドモデルの「芏玄」は、コンパむル時にプログラマヌが「眲名」する必芁がありたす。



[3]䞍明な点がある堎合は、次の蚘事をご芧ください。



[4]ただし、ボリュヌムは埐々に増加しおいたす。前回、Debug + AssertsのClangビルドを確認したずころ、ほが1ギガバむトに達したした。これは、自動生成されたコヌドのおかげです。



[5] PICの動䜜がただわからない堎合䞀般に、特にx64アヌキテクチャヌの䞡方で、このトピックに関する次の蚘事を理解しおください1回ず2回。



[6]したがっお、リンカはリンクを個別に解決するこずができず、GOT凊理をダむナミックロヌダヌにシフトする必芁がありたす。



[7] 0x25-0x7 + GOT-0x27 + 0x9 = GOT









All Articles