linux 64ビットアセンブリと32ビットアセンブリの違いと互換性

6309 ワード

32ビットアセンブリ「Linuxアセンブリ入門-32ビット」を参照
一、違う
  • システム呼び出し番号が異なります.例えばx 86のsys_writeは4,sys_exitは1です.x 86_64のsys_writeは1、sys_exitは60です.linuxシステム呼び出し番号は、実際には/usr/include/asm/unistd_に定義されています.32.hおよび/usr/include/asm/unistd_64.h中.
  • システム呼び出しで使用されるレジスタが異なり、x 86_64ではeaxに対応するraxを使用してシステム呼び出し番号を渡すが、x 86_64では、x 86のebx/ecx/edxではなく、rdi/rsi/rdxを使用して最初の3つのパラメータが伝達される.
  • 32ビットプログラムの場合、int$0 x 80を呼び出してシステム呼び出しに入り、システム呼び出し番号をeaxに入力し、各パラメータはebx、ecx、edxの順序でレジスタに渡され、システム呼び出し戻り値はeaxレジスタに格納される.
  • 64ビットプログラムの場合、syscallを呼び出してシステム呼び出しに入り、システム呼び出し番号をraxに入力し、各パラメータはrdi、rsi、rdxの順序でレジスタに渡され、システム呼び出し戻り値はraxレジスタに格納される.
  • システムコール「int 80」
  • ではなく「syscall」を使用
  • 32ビットと64ビットプログラムのアドレス空間範囲は異なる.

  • 例:
    32ビット
    global _start  
    _start:  
            jmp short ender  
    starter:  
            xor eax, eax    ;clean up the registers  
            xor ebx, ebx  
            xor edx, edx  
            xor ecx, ecx  
      
            mov al, 4       ;syscall write  
            mov bl, 1       ;stdout is 1  
            pop ecx         ;get the address of the string from the stack  
            mov dl, 5       ;length of the string  
            int 0x80  
      
            xor eax, eax  
            mov al, 1       ;exit the shellcode  
            xor ebx,ebx  
            int 0x80  
    ender:  
            call starter    ;put the address of the string on the stack  
            db 'hello',0x0a  
    

    64ビット
    global _start           ; global entry point export for ld  
    _start:  
        jump short string   ; get message addr  
    code:  
        ; sys_write(stdout, message, length)  
        pop     rsi         ; message address  
        mov     rax, 1      ; sys_write  
        mov     rdi, 1      ; stdout  
        mov     rdx, 13     ; message string length + 0x0a  
        syscall  
      
        ; sys_exit(return_code)  
        mov     rax, 60     ; sys_exit  
        mov     rdi, 0      ; return 0 (success)  
        syscall  
      
    string:  
        call    code  
        db 'Hello!',0x0a    ; message and newline
    

    二、互換性
    ハードウェア命令の互換性のため、32ビットのプログラムはユーザー状態で何の影響も受けずに実行され、カーネルは0 x 80番割り込みを32ビットプログラムのシステム呼び出しサービスとして保持しているため、32ビットプログラムは安全に0 x 80番割り込みをトリガーしてシステム呼び出しを使用することができ、カーネルは0 x 80割り込みのために別の新しいシステム呼び出しテーブルを配置しているため、したがって、データ型を一貫した64ビットタイプに安全に変換することができ、アプリケーションレベルに加えて2つのcライブラリが提供され、64ビットと32ビットプログラムを異なるライブラリにリンクすることができる.
    三、インラインアセンブリ
    1.プログラムコードと問題
    まず、以下の簡単なCプログラム(test.c)を見ます.
    #include 
    int main(){
        char str[] = "Hello
    "; write(0, str, 6); return 0; }

    このプログラムはwrite関数を呼び出し、インタフェースは次のとおりです.
    int write(int fd /*      */, const char* src /*     */, int len /*  */)
    

    fdが0であるとコンソールに出力されることを示す.したがって、上記のプログラムの実行結果は、6の長さの文字列「Hello」をコンソールに出力することである.
    コンソールでgcc testを呼び出す.c、正しく出力できます.
    アセンブリコードの下でのシステム呼び出しプロセスをよりよく理解するために、上記のコードをインラインアセンブリのフォーマットに書き換えることができる(『asmインラインアセンブリでシステム呼び出しを実現する』)
    test_asm_A.c
    int main(){
        char str[] = "Hello
    "; asm volatile( "int $0x80
    \t" : :"a"(4), "b"(0), "c"(str), "d"(6) ); return 0; }

    ここで、4はwrite関数のシステム呼び出し番号であり、ebx/ecx/edxはシステム呼び出しの最初の3つのパラメータである.
    しかしながら、gcc test_を実行するasm_A.cコンパイル後、プログラムを実行すると、プログラムに出力がないことがわかります.奇妙な問題は、次のようなtestを採用するとasm_B.cの書き方は、プログラムが正常に出力できる.
    test_asm_B.c
    #include 
    
    int main(){
        char *str = (char*)malloc(7 * sizeof(char));
        strcpy(str, "Hello
    "); asm volatile( "int $0x80
    \t" : :"a"(4), "b"(0), "c"(str), "d"(6) ); free(str); return 0; }

    出力は次のとおりです.
    [root@tsinghua-pcm C]# gcc test_asm_B.c
    test_asm_B.c:    ‘main’ :
    test_asm_B.c:4:2:   :         ‘strcpy’    [    ]
      strcpy(str, "Hello
    "); ^ [root@tsinghua-pcm C]# ./a.out Hello [root@tsinghua-pcm C]# cp inline_assembly.c test_asm_A.c [root@tsinghua-pcm C]# gcc test_asm_A.c [root@tsinghua-pcm C]# ./a.out [root@tsinghua-pcm C]#

    2つのコードの唯一の違いはtestです.asm_A.cのstrはスタック空間に格納され、test_asm_B.cのstrはスタック空間に格納される.
    では、なぜ格納場所の違いが全く異なる結果をもたらすのでしょうか.
    2.原因分析
    上記コードを32ビットでコンパイルする、すなわちgcc test_asm_A.c-m 32とgcc test_asm_B.c-m 32では、2つのコードが正しく出力されていることがわかります.これは,上記のコードを32ビットでコンパイルすることで,正しい結果が得られることを示している.
    [root@tsinghua-pcm C]# gcc test_asm_A.c -m32
    [root@tsinghua-pcm C]# ./a.out 
    Hello
    [root@tsinghua-pcm C]# gcc test_asm_B.c -m32
    test_asm_B.c:    ‘main’ :
    test_asm_B.c:4:2:   :         ‘strcpy’    [    ]
      strcpy(str, "Hello
    "); ^ [root@tsinghua-pcm C]# ./a.out Hello

    -m 32フラグがない場合、gccはデフォルトで64ビットでコンパイルされます.32ビットと64ビットのプログラムは、コンパイル時に次のような違いがあります(上記).
  • 32ビットと64ビットプログラムのアドレス空間範囲は異なる.
  • 32ビットと64ビットプログラムのシステム呼び出し番号は異なり、本例のwriteのように32ビットシステムでは呼び出し番号4、64ビットシステムでは1である.
  • 32ビットプログラムの場合、int$0 x 80を呼び出してシステム呼び出しに入り、システム呼び出し番号をeaxに入力し、各パラメータはebx、ecx、edxの順序でレジスタに渡され、システム呼び出し戻り値はeaxレジスタに格納される.
  • 64ビットプログラムの場合、syscallを呼び出してシステム呼び出しに入り、システム呼び出し番号をraxに入力し、各パラメータはrdi、rsi、rdxの順序でレジスタに渡され、システム呼び出し戻り値はraxレジスタに格納される.

  • 上の2つのコードを見ると、int$0 x 80を呼び出してシステム呼び出しに入りますが、64ビットでコンパイルすると、次のような異常が発生します.
  • プログラムのアドレス空間は64ビットアドレス空間である.
  • 0 x 80番割り込みは32ビットシステム呼び出し関数であるため、システム呼び出しは32ビット方式で解釈される.すなわち、すべてのレジスタは32ビット低い値のみを考慮する.

  • プログラムに入力された各パラメータを見ると、システム呼び出し番号(4)、1番目と3番目のパラメータ(0と6)は32ビット以内であるが、strのアドレスは64ビットアドレスであり、0 x 80システム呼び出しでは32ビット低いのみが考慮される.
    これでtest_asm_A.cは正しく実行できませんがtest_asm_B.cが正しく実行できる理由は明らかです.
  • test_asm_A.cではstrはスタック空間に格納されているが,スタック空間はシステムの高位から32ビット低いアドレスのみをとり,エラーアドレスが得られる.
  • test_asm_B.cでは、strはスタック空間に格納され、スタック空間はシステムの下位で開始される.このような小さなプログラムでは、strアドレスの上位32ビットは0であり、下位32ビットのみがゼロ値ではないため、遮断エラーは発生しない.

  • メモリスタックの場合は、Computer_を表示できます.Architecture/x 86/命令システム/下内容
    表示、test_asm_B.cが正しく実行されるのは偽物にすぎない.スタック空間は低位から始まるため,開拓空間が多すぎてスタック空間も高位に入ると,このコードも同様にエラーになる可能性がある.
    3.64ビットシステムのシステム呼び出しコード
    64ビットシステムで正しく出力できるasmシステム呼び出しコードを与えます.
    //test_asm_C.c
    int main(){
        char str[] = "Hello
    "; // :64 ,write 1 asm volatile( "mov %2, %%rsi
    \t" "syscall" : :"a"(1), "D"(0), "b"(str), "d"(6) ); return 0; }