コンパイル。 10:ELFでのコンパイル

前回は、jスクリプトを独自の形式のファイルにコンパイルすることに限定していました。これには特別なローダーが必要でした。 さらに、隣接するコマンドの分析が必要な実行可能コードの最適化がいくつかあると考えました。



さらに投稿:

  1. のぞき穴の最適化
  2. 標準機能
  3. ELFへの出力
  4. どのように機能しますか?
  5. どうしたの?

のぞき穴の最適化



計画された最適化を実装するために、最初に、一般的なベクトルコードを各コマンドの生成されたマシンコードの小さなベクトルに分割し、最後にそれらを結合します。



次に、2回のパスでは不十分です。最初のパスでは各コマンドのマシンコードを生成し、2回目では最適化を実行し、3回目では「研磨」してジャンプと行のオフセットを埋めます。 ジャンプオフセットの計算と同時に、可能であれば、 近いジャンプを短いジャンプに置き換えます。これにより、3回目のパスでコードがさらに削減され、さらに1回のパスが必要になります。 その上で、コードの削減により変更されたジャンプオフセットを再び修正します。



生成されたPOP



の数を各チームに保存する必要があります。マシンコードだけでは、最後のバイトがPOP



命令であるか、単に値で一致するかを理解するのが難しいからです。 生成されたPUSH



の数を保存する必要はありません。マシンコードの最初のバイトは明確に解読されます。

  struct commandn { // ... int offset; //    int needfixat; //  JZ, ECHO int popcnt; // INPUT, ECHO std::vector<unsigned char> code;//   // ... void pop_back(int c = 1) { code.resize(code.size()-c); } void pop_front(int c = 1) { code.erase(code.begin(), code.begin()+c); } }; // ... // ""    JZ: // ,     case command::jz: if(!i->cmd.dest) // JMP off i->emit(0xe9); else { // OR dst, dst / JZ off i->emit(0x0b, 0xc0|(i->cmd.dest-1)<<3|(i->cmd.dest-1)); i->emit(0x0f, 0x84); } i->needfixat = i->code.size(); i->emit4(0); break; // ... //  ECHO  INPUT   popcnt; //   --   case command::input: foreach(rp, i->onenterp) if(*rp!=4) i->emit(0x50|(*rp-1)); i->emit(0xff, 0x55, 0); if(i->cmd.dest!=1) i->emit(0x90|(i->cmd.dest-1)); foreachr(rp, i->onenterp) if(*rp!=4) { i->emit(0x58|(*rp-1)); i->popcnt++; } break; // ... //  :   ,    int offset = 0; foreach2(i,pcode,next) { i->offset = offset; //  "POP-PUSH" while(i->popcnt && ((next->code[0]&0xfc) == 0x50) && ((i->code.back()&3) == (next->code[0]&3)) && //  :     !((next->cmd.opcode==command::echo) && (next->cmd.dest==(next->code[0]&3)+1))) { i->pop_back(); next->pop_front(); i->popcnt--; if(next->needfixat) next->needfixat--; } //  "-JZ" if((i->cmd.opcode>=command::eq) && (next->cmd.opcode==command::jz) && (i->cmd.dest==next->cmd.dest) && !next->onexitp.count((physreg)next->cmd.dest)) { char cc = i->code[i->code.size()-5]; // cond code i->pop_back(6); // SETcc / MOVZX next->code.clear(); //    next->emit(0x0f, cc^0x11); next->needfixat = next->code.size(); next->emit4(0); } offset += i->code.size(); } //  :    offset = 0; foreach(i, pcode) { i->offset = offset; if(i->cmd.opcode==command::jz) { int joffset = i->tgt->offset-(i->offset+i->code.size()); if((joffset>=-128) && (joffset<128)) { //   if(!i->cmd.dest) { // JMP SHORT i->code.clear(); i->emit(0xeb, (char)joffset); } else if(i->code[0]==0x0b && i->code[1]==0xc9) { // OR ECX, ECX i->code.clear(); i->emit(0xe3, (char)joffset); // JECXZ } else { char cc = i->code[i->code.size()-5]; // cond code i->pop_back(6); i->emit(cc^0xf0, (char)joffset);// Jcc SHORT } i->needfixat = i->code.size()-1; //     } } offset += i->code.size(); } //  :     foreach(i, pcode) if(i->needfixat) if(i->cmd.opcode==command::jz) { int joffset = i->tgt->offset-(i->offset+i->code.size()); switch(i->code[i->needfixat]-1) { case 0xeb: case 0xe3: case 0x74: case 0x75: case 0x7c: case 0x7d: case 0x7e: case 0x7f: i->code[i->needfixat] = (char)joffset; break; // short default: i->fix4(joffset); } } else if (i->cmd.opcode==command::echo) i->fix4(offsets[i->cmd.imm]);
      
      







標準機能



前回は、標準関数input,echoi,echos



の実装がブートローダーにありました。 今回はブートローダーはありません。 機能はどこにありますか?



原則として、生成された各バイナリにコードを埋め込むことができます。 しかし、それはいです。 代わりに、それらを個別の.o



ファイルにコンパイルし、ファイルにリンクします。 このアプローチの利点は、プラットフォーム依存性の分離です:コンパイラーの観点からは、関数の呼び出しの方法にあるx86とx64のすべての違いは、プラットフォーム依存ライブラリに隠されており、プラットフォームに依存しないコードを生成します。



標準関数がESI



パラメーターを受け入れ、結果をEAX



返すと便利です。 やってみましょう。 x64の実装は次のとおりです。

 .global input,echoi,echos .text fd: .asciz "%d" fs: .asciz "%s" input: push %rax lea fd, %edi xor %eax, %eax mov %rsp, %rsi call scanf pop %rax ret echoi: lea fd, %edi echo: xor %eax, %eax jmp printf echos: movslq %esi, %rsi add %rbp, %rsi lea fs, %edi jmp echo
      
      





また、x86の場合は次のとおりです。

 .global input,echoi,echos .text fd: .asciz "%d" fs: .asciz "%s" input: push %eax push %esp push $fd call scanf pop %eax pop2: pop %eax pop %eax ret echoi: push %esi push $fd echo: call printf jmp pop2 echos: add %ebp, %esi push %esi push $fs jmp echo
      
      







ELFへの出力



実行可能コードはほぼ準備ができています。 OSがリンクして実行できるように、「パック」するだけです。



メモリでは、コードは以前と同じ部分で構成されます:コード、データ、標準関数を呼び出すための通信領域。 .o



ファイル形式と直接実行可能ファイルの違いは、 .o



ではメモリ内のプログラム部分( セクション )のアライメントが不明であることです。 したがって、コンパイラは、あるセクションから別のセクションのアドレスへのリンクを生成できません。 代わりに、コンパイラはリンカに指示を生成します-リンク時に計算するアドレスとその計算方法を示す再配置 。 したがって、 ELF形式仕様の .o



ファイルは 再配置可能と呼ばれます。



データを2つのセクションに分割することが可能です。個別に初期化され、不変(通信エリアとライン)、個別に初期化されず、可変(注がれたレジスターのセル)。 しかし、x86では、両方のデータセクションのベースアドレスを常に保存するのに十分なレジスタがありません。つまり、呼び出しごとに再配置を生成しなければなりません。 あなたの人生を簡素化し、データの1つのセクションでうまくいきます。

同じ理由で、通信エリアを使用して標準関数を呼び出します。直接呼び出すには、呼び出しごとに再配置を作成する必要があります。 x86とx64で通信エリアを24バイトにしましょう-繰り返しますが、シンプルさとクロスプラットフォームです。 x64では、3つのポインター( input,echoi,echos



へ)の配列になります。 x86では、各ポインターの後に4バイトのスタブが続きます。



常に4つの再配置があることがわかります。通信の分野における標準機能の3つのアドレス、およびプログラムの最初のコマンドRBP/EBP



データのベースアドレスのRBP/EBP



へのロード。 その結果、「再配置可能な」コードの生成は、前回の「ソリッドピース」の生成とほとんど変わりません。 いくつかの点で、それはさらに単純化されます:行がコードとは別に保存されるようになったため、解析段階でオフセットを記憶できます。 このようにして、コンパイルされたコードの出力の前に、「一時行識別子」とそれらのバインディングの段階を完全に取り除きます。 さらに、標準関数を呼び出すメソッドはプラットフォームに依存しなくなりました。

  typedef std::map<std::string,int> stringmap; stringmap strings; int laststr = 24; //       std::vector<stringmap::iterator> strdata; //   // ... //    VAL: ID '(' ARGS ')' if (!$1.compare("echo")) { if(!$3.size()) yyerror("Input: too many arguments"); $$ = 0; foreach(i, $3) if(!i->dest) // string if(strings.count(i->str)) emit(command::echo, 0, strings[i->str]); else { strdata.push_back(strings.insert(stringmap::value_type(i->str,laststr)).first); emit(command::echo, 0, laststr); laststr += i->str.length()+1; } else emit(command::echo, i->dest, 0); } // ... // (    -) case command::hlt: i->emit(0x5b, 0x5e, 0x5f, 0x5d); // POP EBX / POP ESI / POP EDI / POP EBP i->emit(0xc3); // RET break; case command::echo: // PUSH live / MOV EDI, dst / CALL [EBP+?] / POP live foreach(rp, i->onexitp) if(*rp!=4) i->emit(0x50|(*rp-1)); if(!i->cmd.dest) { // imm / [EBP+16] i->emit14(0xbe, i->cmd.imm); i->emit(0xff, 0x55, 16); } else { if(i->known.count(i->cmd.dest)) // imm / [EBP+4] i->emit14(0xbe, i->known[i->cmd.dest]); else // dst / [EBP+8] i->emit(0x8b, 0xf0|(i->cmd.dest-1)); i->emit(0xff, 0x55, 8); } foreachr(rp, i->onexitp) if(*rp!=4) { i->emit(0x58|(*rp-1)); i->popcnt++; } break;
      
      





ELFの再配置には、 rel



またはrela



2つの形式があります。 rel



形式はよりコンパクトですが、リンカーはrela



方が簡単です。 そのため、x64プラットフォームへの移行時に、 rel



deprecatedと宣言されました 。 それでも、私のバージョンのld



は64ビットコードのrel



をサポートしているため、 rel



正確に生成します。



8つの標準セクションを生成します:empty、 .shstrtab, .strtab, .symtab, .rel.text, .rel.data, .text, .data



。 ELFヘッダーと最初の6つのセクションには、事前定義されたコンテンツがあります。 生成されたコードに応じて、 .text



.data



のみ.data



入力されます。

 //  ELF #include <linux/elf.h> #if ELF_CLASS == ELFCLASS32 #define Elf_Shdr Elf32_Shdr #define Elf_Sym Elf32_Sym #define Elf_Rel Elf32_Rel #define ELF_R_INFO(s,t) (((s)<<8)+(unsigned char)(t)) #define R_32 R_386_32 #else #define Elf_Shdr Elf64_Shdr #define Elf_Sym Elf64_Sym #define Elf_Rel Elf64_Rel #define ELF_R_INFO(s,t) (((unsigned long)(s)<<32)+(t)) #define R_32 R_X86_64_32 #endif #define ST_GLOBAL_NOTYPE STB_GLOBAL<<4 // ... //    : //       //     // : PUSH EBP / PUSH EDI / PUSH ESI / PUSH EBX / MOV RBP, ... const char prolog[] = {0x55,0x57,0x56,0x53,0x48,0xc7,0xc5,0,0,0,0}; offset += sizeof(prolog); int alignment = ((offset+3)&~3) - offset; //   dword offset += alignment; const struct { elfhdr hdr; Elf_Shdr Shdr[8]; char shstrtab[64]; char strtab[24]; Elf_Sym symtab[6]; Elf_Rel reltext[1]; Elf_Rel reldata[3]; } elf = {{{ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, ELF_CLASS, ELF_DATA, EV_CURRENT}, // identification ET_REL, ELF_ARCH, EV_CURRENT, 0, 0, sizeof(elfhdr), 0, sizeof(elfhdr), 0, 0, sizeof(Elf_Shdr),8, 1}, {{0, SHT_NULL}, {1, SHT_STRTAB, 0, 0, (char*)&elf.shstrtab-(char*)&elf, sizeof(elf.shstrtab), 0, 0, 1, 0}, {11, SHT_STRTAB, 0, 0, (char*)&elf.strtab-(char*)&elf, sizeof(elf.strtab), 0, 0, 1, 0}, {19, SHT_SYMTAB, 0, 0, (char*)&elf.symtab-(char*)&elf, sizeof(elf.symtab), 2, 2, 8,sizeof(Elf_Sym)}, {27, SHT_REL, 0, 0, (char*)&elf.reltext-(char*)&elf, sizeof(elf.reltext), 3, 6, 8, sizeof(Elf_Rel)}, {37, SHT_REL, 0, 0, (char*)&elf.reldata-(char*)&elf, sizeof(elf.reldata), 3, 7, 8, sizeof(Elf_Rel)}, {47, SHT_PROGBITS, SHF_ALLOC|SHF_EXECINSTR, 0, sizeof(elf), offset, 0, 0, 4, 0}, {53, SHT_PROGBITS, SHF_ALLOC|SHF_WRITE, 0, sizeof(elf)+offset, laststr+lastspill*4, 0, 0, 4, 0}}, "\0.shstrtab\0.strtab\0.symtab\0.rel.text\0.rel.data\0.text\0.data", // shstrtab "\0main\0input\0echoi\0echos", // strtab {{}, #if ELF_CLASS == ELFCLASS32 {0, 0, 0, STT_SECTION, 0, 7}, {1, 0, 0, ST_GLOBAL_NOTYPE, 0, 6}, // main {6, 0, 0, ST_GLOBAL_NOTYPE, 0, 0}, // input {12, 0, 0, ST_GLOBAL_NOTYPE, 0, 0}, // echoi {18, 0, 0, ST_GLOBAL_NOTYPE, 0, 0}}, // echos #else {0, STT_SECTION, 0, 7, 0, 0}, {1, ST_GLOBAL_NOTYPE, 0, 6, 0, 0}, // main {6, ST_GLOBAL_NOTYPE, 0, 0, 0, 0}, // input {12, ST_GLOBAL_NOTYPE, 0, 0, 0, 0}, // echoi {18, ST_GLOBAL_NOTYPE, 0, 0, 0, 0}}, // echos #endif {{7, ELF_R_INFO(1,R_32)}}, // reltext {{0, ELF_R_INFO(3,1)}, // input {8, ELF_R_INFO(4,1)}, // echoi {16, ELF_R_INFO(5,1)}} // echos }; write(1, &elf, sizeof(elf)); //   write(1, prolog, sizeof(prolog)); foreach(i, pcode) write(1, &*i->code.begin(), i->code.size()); //   dword const char zero[24] = {}; write(1, zero, alignment); //   write(1, zero, 24); //   foreach(i, strdata) write(1, (*i)->first.c_str(), (*i)->first.length()+1); //     ftruncate(1, sizeof(elf)+offset+laststr+lastspill*4);
      
      







どのように機能しますか?



生成されたコードに追加する不変のヘッダーで、不明瞭な数字の束。 それらはすべてどういう意味ですか?

順番に行きましょう。 プログラマを混乱させるために、 Elf32_Sym



およびElf64_Sym



同じフィールドで定義されていますが、順序は異なります。 コードの2つのバージョンを記述し、 #ifdef



#ifdef



してそのうちの1つを選択する以外に方法はありません。



rel



タイプの再配置は、「再配置されたフィールド、シンボル番号、バインディングタイプコードのオフセット」のトリプルで構成されます。 最初の再配置( {7, ELF_R_INFO(1,R_32)}



)-プロローグ内、コードの先頭からのオフセット7 EBP/RBP



ロードされたデータのベースアドレスを設定します。 この再配置は、シンボルNo. 1、つまり .data



セクションへ。 どのアーキテクチャでも、サイズは32ビットです。 バインディングタイプのコードは異なりますR_386_32=1



、x64 R_X86_64_32=10



です。

データには他の3つの再配置があり、インポートされた3つの関数のアドレスをオフセット0.8.16で取得します。 これらの3つの再配置は、インポートされた文字(3、4、5)を参照し、常にマシンワード( R_386_32,R_X86_64_64



)と等しいアンカーコード1を使用します。



完全なコンパイラコード: tyomitch.net.ru/jsk.y.elf.html



バイナリを手動で収集することに興味がある場合は、実用的なトリックからクレイジーなハックまで、ELFファイルのサイズを削減する方法を検討することをお勧めします。 そして同時に完全に縮小したプログラムの例

結果のファイルのサイズは45バイトです。アセンブラーの5倍、Cの50倍です。 ファイルからできることはすべて捨てました。 そして、捨てられなかったものを、2つまたは3つの目的に同時に使用します。



このファイルの値の約半分は、何らかの形でELF標準に違反しています。 通常のプログラマーは、そのようなプログラムが彼の手によるものであることを認めて恥ずかしいでしょう。 驚くべきことに、Linuxはこの悪夢にPIDを割り当てることに同意します。



一方、このファイルの各バイトについて、なぜ必要なのかを説明できます。 多くの場合、コンパイルしたファイルについて同じことを言うことができますか?



どうしたの?



これで、個別にコンパイルできる2つの独立したコンポーネントから最終的なバイナリが取得されます。

 [tyomitch@home ~]$ as jskstd.s -o jskstd.o 
      

[tyomitch@home ~]$

[tyomitch@home ~]$ bison jsk.y

[tyomitch@home ~]$ c++ jsk.tab.c lex.yy.c -o jskc

[tyomitch@home ~]$

[tyomitch@home ~]$ ./jskc < test.jsk > code.o

[tyomitch@home ~]$ cc jskstd.o code.o

[tyomitch@home ~]$ ./a.out

0 1000,

500? (1=, 2=, 3=) 1

249? (1=, 2=, 3=) 3

! !

[tyomitch@home ~]$ objdump -d code.o

code.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push %rbp 1: 57 push %rdi 2: 56 push %rsi 3: 53 push %rbx 4: 48 c7 c5 00 00 00 00 mov $0x0,%rbp b: 33 c0 xor %eax,%eax d: b9 e8 03 00 00 mov $0x3e8,%ecx 12: 50 push %rax 13: 51 push %rcx 14: be 18 00 00 00 mov $0x18,%esi 19: ff 55 10 callq *0x10(%rbp) 1c: 59 pop %rcx 1d: 58 pop %rax 1e: 50 push %rax 1f: 51 push %rcx 20: be 00 00 00 00 mov $0x0,%esi 25: ff 55 08 callq *0x8(%rbp) 28: be 38 00 00 00 mov $0x38,%esi 2d: ff 55 10 callq *0x10(%rbp) 30: 59 pop %rcx 31: 51 push %rcx 32: be e8 03 00 00 mov $0x3e8,%esi 37: ff 55 08 callq *0x8(%rbp) 3a: be 3f 00 00 00 mov $0x3f,%esi 3f: ff 55 10 callq *0x10(%rbp) 42: 59 pop %rcx 43: 58 pop %rax 44: 3b c1 cmp %ecx,%eax 46: 0f 8f 6c 00 00 00 jg b8 <main+0xb8> 4c: 8d 14 01 lea (%rcx,%rax,1),%edx 4f: d1 fa sar %edx 51: 50 push %rax 52: 51 push %rcx 53: 52 push %rdx 54: be 64 00 00 00 mov $0x64,%esi 59: ff 55 10 callq *0x10(%rbp) 5c: 5a pop %rdx 5d: 52 push %rdx 5e: 8b f2 mov %edx,%esi 60: ff 55 08 callq *0x8(%rbp) 63: be 6c 00 00 00 mov $0x6c,%esi 68: ff 55 10 callq *0x10(%rbp) 6b: ff 55 00 callq *0x0(%rbp) 6e: 93 xchg %eax,%ebx 6f: 5a pop %rdx 70: 59 pop %rcx 71: 58 pop %rax 72: 89 85 04 01 00 00 mov %eax,0x104(%rbp) 78: 83 fb 01 cmp $0x1,%ebx 7b: 75 0b jne 88 <main+0x88> 7d: 8b 85 04 01 00 00 mov 0x104(%rbp),%eax 83: 8d 4a ff lea 0xffffffffffffffff(%rdx),%ecx 86: eb bc jmp 44 <main+0x44> 88: 83 fb 02 cmp $0x2,%ebx 8b: 75 05 jne 92 <main+0x92> 8d: 8d 42 01 lea 0x1(%rdx),%eax 90: eb b2 jmp 44 <main+0x44> 92: 8b 85 04 01 00 00 mov 0x104(%rbp),%eax 98: 83 fb 03 cmp $0x3,%ebx 9b: 75 0d jne aa <main+0xaa> 9d: be 9f 00 00 00 mov $0x9f,%esi a2: ff 55 10 callq *0x10(%rbp) a5: 5b pop %rbx a6: 5e pop %rsi a7: 5f pop %rdi a8: 5d pop %rbp a9: c3 retq aa: 50 push %rax ab: 51 push %rcx ac: be bb 00 00 00 mov $0xbb,%esi b1: ff 55 10 callq *0x10(%rbp) b4: 59 pop %rcx b5: 58 pop %rax b6: eb 8c jmp 44 <main+0x44> b8: be dd 00 00 00 mov $0xdd,%esi bd: ff 55 10 callq *0x10(%rbp) c0: 5b pop %rbx c1: 5e pop %rsi c2: 5f pop %rdi c3: 5d pop %rbp c4: c3 retq

, , , llvm. — .








[tyomitch@home ~]$ as jskstd.s -o jskstd.o

[tyomitch@home ~]$

[tyomitch@home ~]$ bison jsk.y

[tyomitch@home ~]$ c++ jsk.tab.c lex.yy.c -o jskc

[tyomitch@home ~]$

[tyomitch@home ~]$ ./jskc < test.jsk > code.o

[tyomitch@home ~]$ cc jskstd.o code.o

[tyomitch@home ~]$ ./a.out

0 1000,

500? (1=, 2=, 3=) 1

249? (1=, 2=, 3=) 3

! !

[tyomitch@home ~]$ objdump -d code.o

code.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push %rbp 1: 57 push %rdi 2: 56 push %rsi 3: 53 push %rbx 4: 48 c7 c5 00 00 00 00 mov $0x0,%rbp b: 33 c0 xor %eax,%eax d: b9 e8 03 00 00 mov $0x3e8,%ecx 12: 50 push %rax 13: 51 push %rcx 14: be 18 00 00 00 mov $0x18,%esi 19: ff 55 10 callq *0x10(%rbp) 1c: 59 pop %rcx 1d: 58 pop %rax 1e: 50 push %rax 1f: 51 push %rcx 20: be 00 00 00 00 mov $0x0,%esi 25: ff 55 08 callq *0x8(%rbp) 28: be 38 00 00 00 mov $0x38,%esi 2d: ff 55 10 callq *0x10(%rbp) 30: 59 pop %rcx 31: 51 push %rcx 32: be e8 03 00 00 mov $0x3e8,%esi 37: ff 55 08 callq *0x8(%rbp) 3a: be 3f 00 00 00 mov $0x3f,%esi 3f: ff 55 10 callq *0x10(%rbp) 42: 59 pop %rcx 43: 58 pop %rax 44: 3b c1 cmp %ecx,%eax 46: 0f 8f 6c 00 00 00 jg b8 <main+0xb8> 4c: 8d 14 01 lea (%rcx,%rax,1),%edx 4f: d1 fa sar %edx 51: 50 push %rax 52: 51 push %rcx 53: 52 push %rdx 54: be 64 00 00 00 mov $0x64,%esi 59: ff 55 10 callq *0x10(%rbp) 5c: 5a pop %rdx 5d: 52 push %rdx 5e: 8b f2 mov %edx,%esi 60: ff 55 08 callq *0x8(%rbp) 63: be 6c 00 00 00 mov $0x6c,%esi 68: ff 55 10 callq *0x10(%rbp) 6b: ff 55 00 callq *0x0(%rbp) 6e: 93 xchg %eax,%ebx 6f: 5a pop %rdx 70: 59 pop %rcx 71: 58 pop %rax 72: 89 85 04 01 00 00 mov %eax,0x104(%rbp) 78: 83 fb 01 cmp $0x1,%ebx 7b: 75 0b jne 88 <main+0x88> 7d: 8b 85 04 01 00 00 mov 0x104(%rbp),%eax 83: 8d 4a ff lea 0xffffffffffffffff(%rdx),%ecx 86: eb bc jmp 44 <main+0x44> 88: 83 fb 02 cmp $0x2,%ebx 8b: 75 05 jne 92 <main+0x92> 8d: 8d 42 01 lea 0x1(%rdx),%eax 90: eb b2 jmp 44 <main+0x44> 92: 8b 85 04 01 00 00 mov 0x104(%rbp),%eax 98: 83 fb 03 cmp $0x3,%ebx 9b: 75 0d jne aa <main+0xaa> 9d: be 9f 00 00 00 mov $0x9f,%esi a2: ff 55 10 callq *0x10(%rbp) a5: 5b pop %rbx a6: 5e pop %rsi a7: 5f pop %rdi a8: 5d pop %rbp a9: c3 retq aa: 50 push %rax ab: 51 push %rcx ac: be bb 00 00 00 mov $0xbb,%esi b1: ff 55 10 callq *0x10(%rbp) b4: 59 pop %rcx b5: 58 pop %rax b6: eb 8c jmp 44 <main+0x44> b8: be dd 00 00 00 mov $0xdd,%esi bd: ff 55 10 callq *0x10(%rbp) c0: 5b pop %rbx c1: 5e pop %rsi c2: 5f pop %rdi c3: 5d pop %rbp c4: c3 retq

, , , llvm. — .











All Articles