6

私は Mac での x64 アセンブリにかなり慣れていないので、32 ビット コードを 64 ビットに移植するのに混乱しています。プログラムは、C 標準ライブラリ
の関数を介して単純にメッセージを出力する必要があります。 私はこのコードから始めました:printf

section .data
    msg db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp
    mov     rbp, rsp       

    push    msg
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret

このようにnasmでコンパイルします:

$ nasm -f macho64 main.s

次のエラーが返されました:

main.s:12: error: Mach-O 64-bit format does not support 32-bit absolute addresses

コードを次のように変更して、その問題のバイトを修正しようとしました。

section .data
    msg db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp
    mov     rbp, rsp       

    mov     rax, msg    ; shouldn't rax now contain the address of msg?
    push    rax         ; push the address
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret

上記のコマンドで正常にコンパイルされましたが、オブジェクト ファイルを実際のプログラムにnasmコンパイルする際に警告が表示されます。gcc

$ gcc main.o
ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not
allowed in code signed PIE, but used in _main from main.o. To fix this warning,
don't compile with -mdynamic-no-pic or link with -Wl,-no_pie

エラーではなく警告なので、a.outファイルを実行しました:

$ ./a.out
Segmentation fault: 11

私が間違っていることを誰かが知っていることを願っています。

4

3 に答える 3

9

64ビットOSXABIは、SystemVABI-AMD64アーキテクチャプロセッササプリメントに広く準拠しています。そのコードモデルは、ここで説明されている違いを除いて、スモールポジション独立コードモデル(PIC)と非常によく似ています。そのコードモデルでは、すべてのローカルデータとスモールデータにRIP相対アドレス指定を使用して直接アクセスします。Z bosonのコメントに記載されているように、64ビットMach-O実行可能ファイルのイメージベースは仮想アドレス空間の最初の4 GiBを超えているため、のアドレスをスタックに配置するための無効な方法であるだけでなく、 64ビットの即値をサポートしていないため、これも不可能です。コードは次のようになります。push msgmsgPUSH

   ; this is what you *would* do for later args on the stack
lea   rax, [rel msg]  ; RIP-relative addressing
push  rax

しかし、その特定のケースでは、スタックに値をプッシュする必要はまったくありません。64ビットの呼び出し規約では、最初の6つの整数/ポインター引数がレジスター、、、、、、、およびで正確にこの順序で渡されることが 義務付けられRDIていますRSI。最初の8つの浮動小数点またはベクトル引数は、、、... 、になります。使用可能なすべてのレジスタが使用された後、またはそれらのレジスタのいずれにも収まらない引数(たとえば、80ビット値)がある場合にのみ、スタックが使用されます。64ビットの即時プッシュは(バリアント)を使用して実行され、ではありません。単純な戻り値は、RDXRCXR8R9XMM0XMM1XMM7long doubleMOVQWORDPUSHRAX登録。呼び出し元は、呼び出し先がレジスタの一部を保存するためのスタックスペースも提供する必要があります。

printf可変数の引数を取るため、は特殊関数です。このような関数AL(RAXの下位バイト)を呼び出すときは、ベクトルレジスタに渡される浮動小数点引数の数に設定する必要があります。RIPまた、コードから2 GiB以内にあるデータには、相対アドレス指定が推奨されることにも注意してください。

OSXでのアセンブリへのgcc変換方法は次のとおりです。printf("This is a test\n");

    xorl    %eax, %eax             # (1)
    leaq    L_.str(%rip), %rdi     # (2)
    callq   _printf                # (3)

L_.str:
    .asciz   "This is a test\n"

(これはAT&Tスタイルのアセンブリであり、ソースは左、デスティネーションは右、レジスタ名のプレフィックスは%、データ幅は命令名のサフィックスとしてエンコードされます)

浮動小数点引数が渡されていないため、ゼロになります(部分レジスタの遅延を回避するRAX全体をゼロにすることによって)(1)。文字列のアドレスでロードされALます。値が実際には現在の値からのオフセットであることに注意してください。アセンブラはこの値が何であるかを知らないため、オブジェクトファイルに再配置要求を置きます。次に、リンカは再配置を確認し、リンク時に正しい値を入力します。(2)RDIRIP

私はNASMの第一人者ではありませんが、次のコードでそれを実行できると思います。

default rel             ; make [rel msg] the default for [msg]
section .data
    msg:  db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp                 ; re-aligns the stack by 16 before call
    mov     rbp, rsp       

    xor     eax, eax            ; al = 0 FP args in XMM regs
    lea     rdi, [rel msg]
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret
于 2012-10-26T18:51:48.097 に答える
5

NASMが報告する理由を説明する答えはまだありません

Mach-O 64-bit format does not support 32-bit absolute addresses

NASM がこれを行わない理由は、Agner Fog の Optimizing Assemblyマニュアルのセクション3.3 のアドレッシング モードで、彼が書いた 64 ビット モードでの 32 ビット絶対アドレッシングというサブセクションの下に説明されています。

Mac OS X では 32 ビットの絶対アドレスを使用できません。Mac OS X では、デフォルトでアドレスが 2^32 を超えています。

これは、Linux または Windows では問題になりません。実際、 static-linkage-with-glibc-without-calling-mainでこれが機能することをすでに示しました。この hello world コードは、elf64 で 32 ビットの絶対アドレス指定を使用しており、正常に動作します。

@HristoIliev は rip 相対アドレッシングの使用を提案しましたが、Linux での 32 ビット絶対アドレッシングも同様に機能するとは説明しませんでした。実際、それに変更lea rdi, [rel msg]するlea rdi, [msg]と、組み立てて正常に実行されnasm -efl64ますが、失敗しますnasm -macho64

このような:

section .data
    msg db 'This is a test', 10, 0    ; something stupid here

section .text
    global _main
    extern _printf

_main:
    push    rbp
    mov     rbp, rsp       

    xor     al, al
    lea     rdi, [msg]
    call    _printf

    mov     rsp, rbp
    pop     rbp
    ret

これが絶対 32 ビット アドレスであり、リッピング相対ではないことを確認できますobjdump。ただし、推奨される方法は引き続き rip 相対アドレス指定であることを指摘しておくことが重要です。同じマニュアルの Agner は次のように書いています。

単純なメモリオペランドに絶対アドレスを使用する理由はまったくありません。リップ相対アドレスにより、命令が短くなり、ロード時の再配置が不要になり、すべてのシステムで安全に使用できます。

では、いつ 64 ビット モードで 32 ビット絶対アドレスを使用するのでしょうか? 静的配列は良い候補です。次のサブセクション「64 ビット モードでの静的配列のアドレス指定」を参照してください。単純なケースは次のとおりです。

mov eax, [A+rcx*4]

ここで、A は静的配列の絶対 32 ビット アドレスです。これは Linux では問題なく動作しますが、デフォルトでイメージ ベースが 2^32 より大きいため、Mac OS X ではこれを行うことができません。Mac OS X でこれを行うには、Agner のマニュアルの例 3.11c と 3.11d を参照してください。例 3.11c では、次のことができます。

mov eax, [(imagerel A) + rbx + rcx*4]

Mach O からの extern 参照を使用し__mh_execute_headerてイメージ ベースを取得する場所。例 3.11c では、rip 相対アドレス指定を使用して、次のようにアドレスをロードします。

lea rbx, [rel A]; rel tells nasm to do [rip + A]
mov eax, [rbx + 4*rcx] ; A[i]
于 2014-10-16T14:56:04.727 に答える
2

x86 64 ビット命令セットのドキュメントによるとhttp://download.intel.com/products/processor/manual/325383.pdf

PUSH は、8、16、および 32 ビットの即値のみを受け入れます (ただし、64 ビット レジスタおよびレジスタ アドレス指定されたメモリ ブロックは許可されます)。

PUSH msg

msg が 64 ビットの即時アドレスである場合、ご存知のようにコンパイルされません。


64 ビット ライブラリで定義されている _printf の呼び出し規約は何ですか?

スタック上のパラメーターを期待していますか、それともパラメーターが登録されている高速呼び出し規則を使用していますか? x86-64 では、より汎用的なレジスタが使用できるようになるため、高速呼び出し規則がより頻繁に使用されます。

于 2012-10-26T18:11:11.410 に答える