6

だから私は楽しみのために私のMacでx86_64 nasmアセンブリを学んでいます。hello world といくつかの基本的な計算の後、このサイトから少し高度な hello world プログラムをコピーして 64 ビット Intel 用に変更しようとしましたが、この 1 つのエラー メッセージを取り除くことができません: hello.s:53: error: Mach-O 64-bit format does not support 32-bit absolute addresses. アセンブルとリンクに使用するコマンドは次のとおりnasm -f macho64 hello.s && ld -macosx_version_min 10.6 hello.oです。そして、ここに関連する行があります:

cmp rsi, name+8

rsi はループ内のインデックスに使用しているレジスタであり、name はユーザー入力用に予約されているクワッド ワードであり、この時点で既に書き込まれている名前です。

コードの一部を次に示します (残りを表示するには、リンクをクリックして一番下に移動します。唯一の違いは、64 ビット レジスタを使用していることです)。

loopAgain:
mov al, [rsi]           ; al is a 1 byte register
cmp al, 0x0a            ; if al holds an ascii newline...
je exitLoop             ; then jump to label exitLoop

; If al does not hold an ascii newline...
mov rax, 0x2000004      ; System call write = 4
mov rdi, 1              ; Write to stdout = 1
mov rdx, 1              ; Size to write
syscall

inc rsi

cmp rsi, name+8         ; LINE THAT CAUSES ERROR
jl loopAgain
4

2 に答える 2

4

このcmp命令は、64 ビットの即値オペランドをサポートしていません。そのため、オペランドの 1 つに 64 ビットの即時アドレス参照を入れることはできませんname+8。レジスタにロードしてから、そのレジスタと比較してください。

どの命令エンコーディングが許可されているかは、Intel ISA のマニュアル(警告: 巨大な PDF) で確認できます。CMP のエントリでわかるように、32 ビットの即値を 32 ビットおよび 64 ビットの両方のレジスタと比較できますがCMP r/m32, imm32、. ただし、エンコーディングがあります。CMP r/m64, imm32CMP r/m64, imm64MOV r64, imm64

または、RIP 相対 LEA を使用することをお勧めします: default relthenを使用しlea r64, [name+8]ます。これは よりも効率的で小さいですmov r64, imm64


nasm がクラッシュしているので、 の失敗MOV rcx, name+8は単なる nasm のバグです。nasm 開発者に報告してください (nasm の最新バージョンを使用していることを確認した後、また、このパッチで問題が解決しないことを確認してください)。ただし、いずれにせよ、1 つの回避策は、末尾に記号を追加することですname

name:
    resb 8
name_end:

を使用するだけMOV rcx, name_endです。これには、サイズが変更されたときに参照先を更新する必要がないという利点がありnameます。あるいは、clang や GNU binutils アセンブラーなどの別のアセンブラーを使用することもできます。


コメントでの議論は、Linux が 32 ビットの即値としてシンボル アドレスを使用できることを指摘しています。これは、仮想アドレス空間の下位 2GiB のベース アドレスにリンクされている非 PIE 実行可能ファイルにのみ当てはまります。ただし、MacOS はイメージのベース アドレスを 4GiB より上に配置することを選択するため、シンボル アドレスと一緒にmov r32, imm32orを使用することはできません。cmp r64, sign_extended_imm32

于 2011-07-05T02:38:21.557 に答える
3

あなたが直面している問題は単純だと思います.Mach-Oフォーマットは再配置可能なコードを義務付けています。つまり、絶対アドレスではなく相対アドレスでデータにアクセスする必要があります。つまり、データは任意のアドレスにある可能性があるnameため、アセンブラは定数に解決できません。

データのアドレスがコードのアドレスに対して相対的であることがわかったので、GCC からの出力を理解できるかどうかを確認してください。例えば、

static unsigned global_var;
unsigned inc(void)
{
    return ++global_var;
}

_inc:
    mflr r0                                           ; Save old link register
    bcl 20,31,"L00000000001$pb"                       ; Jump
"L00000000001$pb":
    mflr r10                                          ; Get address of jump
    mtlr r0                                           ; Restore old link register
    addis r2,r10,ha16(_global_var-"L00000000001$pb")  ; Add offset to address
    lwz r3,lo16(_global_var-"L00000000001$pb")(r2)    ; Load global_var
    addi r3,r3,1                                      ; Increment global_var
    stw r3,lo16(_global_var-"L00000000001$pb")(r2)    ; Store global_var
    blr                                               ; Return

x86-64 用の Mach-O ABI がわからないため、これは PowerPC 上にあることに注意してください。PowerPC では、プログラム カウンターを保存してジャンプし、その結果に対して演算を行います。x86-64 では、まったく異なることが起こると思います。

(注:GCCのアセンブリ出力を見る場合は、で見てみてください。冗長すぎて理解しにくいので、-O2気にしません。)-O0

私の推薦? コンパイラを作成する場合を除き (場合によってはコンパイラを作成する場合も)、次の 2 つの方法のいずれかでアセンブリ関数を記述します。

  • 必要なすべてのポインタを引数として関数に渡します。または、
  • アセンブリを C 関数内のインライン アセンブリとして記述します。

これは、ABI の特定の詳細にあまり依存しないため、一般に移植性も高くなります。しかし、ABI は依然として重要です。ABI を知らずにそれに従うと、検出がかなり困難なエラーが発生します。たとえば、何年も前に、LibSDL アセンブリ コードにバグがあり、libc memcpy(アセンブリも) が特定の状況下で間違ったデータをコピーする原因となりました。

于 2011-07-05T03:00:35.947 に答える