banner
Zein

Zein

x_id

アセンブリ/マシン命令

アセンブリは命令セットの助記符であり、コンパイラ + スタイル + 命令セットアーキテクチャ(ISA)の組み合わせによって言語の特性が決まりますが、統一された標準はありません:
コンパイラ:
1)MASM:Windows プラットフォームのみをサポートし、オンデマンドコンパイルを完全にサポートする唯一のコンパイラであり、bin 形式の出力はサポートしていません。
2)NASM:クロスプラットフォームで、さまざまな出力形式(bin/coff/omf/elf/…)をサポートしています。

スタイル:
1)Intel スタイル
2)AT&T スタイル

主流の命令セットアーキテクチャ:
1)x86-64/x64/amd64/Intel64
2)ARM64/AArch64
3)RISC-V
4)MIPS

x86 アセンブリ#

Intel スタイルのアセンブラを例にとります。

メモリとアドレッシングモード#

.DATA 静的データ領域の宣言#

データ型修飾原語:
DB: バイト,1 バイト
DW: ワード,2 バイト
DD: ダブルワード,4 バイト
アセンブリには一次元配列のみが存在し、二次元および多次元配列は存在しません。一次元配列は実際にはメモリ内の連続した領域です。さらに、DUP と文字列定数も配列を宣言する 2 つの方法です。

.DATA
var     DB 64    ; 1バイトサイズの変数 var を宣言し、64で初期化
var2    DB ?     ; 初期化されていない1バイトサイズの変数 var2 を宣言し、その初期値はプログラム内で明示的に設定されるまで未定義
        DB 10    ; 値が10の1バイトサイズの定数を宣言します。バイトの位置は var2 + 1 です。ラベルがないため、この値はラベルを介してアクセスできません。
X       DW ?     ; 初期化されていない1バイトサイズ(16ビット)の変数 X を宣言し、初期値はプログラム内で明示的に設定されるまで未定義です。
Y       DD 30000 ; 4バイトの値を宣言し、Yという位置を参照し、30000で初期化します。

Z       DD 1, 2, 3      ; 配列Zを宣言し、各要素は4バイトサイズで、1, 2, 3で初期化されます。Zラベルはデータストレージの開始位置を示し、Z+インデックス*4が要素のアドレスです。
bytes   DB 10 DUP(?)    ; 配列bytesを宣言し、各要素は1バイトサイズです;10 DUP(?)は10個の初期化されていないバイトを宣言します。
arr     DD 100 DUP(0)   ; 配列arrを宣言し、各要素は4バイトサイズです;100 DUP(0)は100個の初期値が0の要素を宣言します。
str     DB 'hello',0    ; アドレスstrから始まる6バイトを宣言し、helloとnull(0)バイトで初期化します。

メモリアドレッシング#

MOV はメモリとレジスタ間でデータを移動します(デフォルトで 32 ビットデータを移動します)。2 つのパラメータを受け取ります:最初のパラメータは宛先、2 番目はソースです。ソースと宛先は実際にはアドレスです;[レジスタ] はレジスタの値をアドレスとして参照します;[var] はシンボル var が示すアドレスを参照します;

;合法的なアドレッシングの例:加[]はアドレスを格納していることを示し、参照アドレスはメモリの内容を指します
mov eax, [ebx]        ; EBXに含まれるアドレスのメモリ内の4バイトをEAXに移動します
mov [var], ebx        ; EBXの内容をvarのメモリアドレスの4バイトに移動します。(注意、varは32ビット定数です)。
mov eax, [esi-4]      ; ESI + (-4)のメモリアドレスの4バイトをEAXに移動します
mov [esi+eax], cl     ; CLの内容をESI+EAXのアドレスのバイトに移動します
mov edx, [esi+4*ebx]  ; ESI+4*EBXのアドレスの4バイトのデータをEDXに移動します
;不正なアドレッシングの例:
mov eax, [ebx-ecx]      ; レジスタの値を加算することしかできず、減算はできません
mov [eax+esi+edi], ebx  ; アドレス計算に参加できるレジスタは最大で2つです

データ型(サイズ)原語(サイズ指示子)#

ポインタ型を修飾します:データのビット数を示します
BYTE PTR - 1 バイト
WORD PTR - 2 バイト
DWORD PTR - 4 バイト

mov BYTE PTR [ebx], 2   ; EBXに格納されているアドレスの単一バイトに2を移動します。
mov WORD PTR [ebx], 2   ; EBXのアドレスから始まる2バイトに2の16ビット整数表現を移動します。
mov DWORD PTR [ebx], 2  ; EBXのアドレスから始まる4バイトに2の32ビット整数表現を移動します。

命令#

分類命令説明
データ移動movソースオペランドの値をターゲットオペランドにコピーしますmov ax, bxbx レジスタの値を ax レジスタにコピーします
pushデータをスタックにプッシュしますpush axax レジスタの値をスタックにプッシュします
popスタックのトップデータをターゲットオペランドにポップしますpop bx ; スタックのトップの値をポップして bx レジスタに保存します
lea有効アドレスをロードし、アドレスをターゲットレジスタに保存しますlea ax, [bx + 4][bx + 4] の有効アドレスを ax に保存します
算術 / 論理演算add加算操作を実行しますadd ax, bxbxax レジスタに加えます
sub減算操作を実行しますsub ax, bxax から bx の値を減算します
incオペランドに 1 を加えますinc axax レジスタの値を 1 増加させます
decオペランドから 1 を減算しますdec bxbx レジスタの値を 1 減少させます
imul符号付き乗算imul ax, bxax = ax * bx、符号付き乗算
idiv符号付き除算idiv bxaxbx で除算し、結果を axdx に保存します
andビットごとの AND 操作and ax, bxaxbx のビットごとの AND 操作の結果を ax に戻します
orビットごとの OR 操作or ax, bxaxbx のビットごとの OR 操作の結果を ax に戻します
xorビットごとの XOR 操作xor ax, bxaxbx のビットごとの XOR 操作の結果を ax に戻します
notビットごとの反転操作not axax レジスタのすべてのビットを反転させます
neg負の操作neg axax レジスタの値を反転させます(すなわち、反対数を加えます)
shl左シフト操作shl ax, 1ax を 1 ビット左にシフトし、シフトされたビットは破棄されます
shr右シフト操作shr bx, 1bx を 1 ビット右にシフトし、シフトされたビットは破棄されます
制御フローjmp無条件ジャンプjmp labellabel タグにジャンプします
je / jz等しい場合はジャンプします(je:equal の場合にジャンプ、jz:zero の場合にジャンプ)je label ; ゼロフラグが設定されている場合(等しいことを示す)、label にジャンプします
jne等しくない場合はジャンプします(not equal の場合にジャンプ)jne label ; ゼロフラグが設定されていない場合(等しくないことを示す)、label にジャンプします
jg大きい場合はジャンプします(greater の場合にジャンプ)jg label ; 大きい場合は label にジャンプします
jl小さい場合はジャンプします(less の場合にジャンプ)jl label ; 小さい場合は label にジャンプします
cmp2 つのオペランドを比較します(フラグレジスタを設定します)cmp ax, bxaxbx の値を比較します(フラグレジスタを設定します)
callサブルーチンを呼び出し、戻りアドレスをスタックにプッシュしますcall subroutinesubroutine サブルーチンを呼び出します
retプロシージャから戻り、戻りアドレスをポップして呼び出し点にジャンプしますret ; 現在のプロシージャから呼び出し点に戻ります

呼び出し規約#

サブルーチン(関数)呼び出しは、どのように呼び出し、どのようにプロセスから戻るかを規定する一連の共通のプロトコルに従う必要があります。たとえば、特定の呼び出し規約ルールが与えられると、プログラマーはサブ関数の定義を確認することなく、パラメータをどのように渡すかを決定できます。さらに、特定の呼び出し規約ルールが与えられると、高級言語コンパイラはこれらのルールに従うだけで、アセンブリ関数と高級言語関数が相互に呼び出すことができます。

C 言語呼び出し規約#

呼び出し規約にはさまざまな種類があります。C 言語呼び出し規約が最も広く使用されています。この規約に従うことで、アセンブリコードは安全に C/C++ から呼び出され、アセンブリコードから C 関数ライブラリを呼び出すこともできます。

1)ハードウェアスタックのサポートに強く依存します(hardwared-supported stack)
2)push, pop, call, ret 命令に基づいています
3)サブルーチンパラメータはスタックを介して渡されます:レジスタはスタックに保存され、サブルーチンで使用されるローカル変数もスタックに置かれます

ほとんどのプロセッサで実装されているほとんどの高級手続き型言語は、これに似た呼び出し慣行を使用しています。呼び出し慣行は 2 つの部分に分かれています。最初の部分は呼び出し元caller)用で、2 番目の部分は呼び出し先callee)用です。これらのルールを誤って使用すると、スタックが破損し、プログラムがすぐにエラーを引き起こすことになるため、自分のサブルーチンで呼び出し規約を実装する際には特に注意が必要です。

image

呼び出し元ルール#

呼び出し元は、サブルーチンを呼び出す前にコンテキストを保存する必要があります:
1)呼び出し元が保存するレジスタ
caller-saved registers:EAX, ECX, EDX; これらのレジスタは callee によって変更される可能性があるため、呼び出し終了後にスタックの状態を復元するために保存します。

2)サブルーチンに渡すパラメータをスタックにプッシュします:パラメータは逆順でスタックにプッシュされます(最後のパラメータが最初に入ります)。スタックは下に成長するため、最初のパラメータは最低アドレスに保存されます(この特性により可変長パラメータリストが可能になります)。

3)call 命令を使用してサブルーチン(関数)を呼び出します:call は自動的に戻りアドレスをスタックにプッシュし、サブルーチンコードの実行を開始します。サブルーチンが戻ると(call が終了した後)、呼び出し先は戻り値を EAX レジスタに置き、呼び出し元はそこから読み取ることができます。マシンの状態を復元するために、呼び出し元は次のことを行う必要があります:
a.スタックから渡されたパラメータを削除します
b.呼び出し元が保存したレジスタを復元しますEAX, ECX, EDX)—— スタックからポップします;呼び出し元は、これら 3 つ以外の他のレジスタの値は変更されていないと考えることができます。

;コンテキストを保存
push eax
push ecx
push edx

push [var] ; 最後のパラメータを最初にプッシュ
push 216   ; 2番目のパラメータをプッシュ
push eax   ; 最初のパラメータを最後にプッシュ

call _myFunc ; 関数を呼び出します(C命名を仮定します);callは自動的に戻りアドレスをスタックにプッシュします

;コンテキストを復元します。
add esp, 24  ; スタックスペースをクリーンアップし、スタックポインタを復元し、渡された6つのパラメータを削除します。スタックポインタは高アドレスに移動します。
mov result, eax   ; 戻り値をresult変数に読み取ります

呼び出し先ルール#

1)元のスタックフレームベースレジスタ**EBPの値をスタックにプッシュし、次にESPEBP**にコピーします;サブルーチンスタックフレームの新しいベースとして
2)ローカル変数のためにスタック上にスペースを割り当てます;スタックは上から下に成長するため、変数の割り当てに伴い、スタックトップポインタはどんどん小さくなります。
3)呼び出し元が保存したレジスタ
callee-saved
—— それらをスタックにプッシュします。EBX, EDI, ESIを含みます;これらのレジスタは呼び出し先が保存および復元する責任があります。

)サブルーチンのコードを実行し、戻り値を**EAXに保存します;サブルーチンが戻ると:
a. 呼び出し先が保存すべきレジスタを復元しますEDI, ESI)—— スタックからポップします
b. ローカル変数を解放します
c. 呼び出し元のベースポインタ
EBP**を復元します —— スタックからポップします
d. 最後に、
ret**を実行し、呼び出し元に戻ります(caller)

.486           ; アセンブラにどの命令セットアーキテクチャを使用してコンパイルするかを指示します
.MODEL FLAT   ; フラットメモリモデル

.CODE         ; アセンブリファイル内のコードセクションの開始を示します

PUBLIC _myFunc ; 外部から見える/リンク可能/このファイルに私有でない関数
_myFunc PROC
  ;1)
  push ebp
  mov  ebp, esp
  ;2)
  sub esp, 4      ; ローカル変数のためにスペースを割り当てます(4バイト)
  ;3)
  push edi        ; レジスタ値を保存します。EDIは変更されます
  push esi        ; レジスタ値を保存します。ESIは変更されます
  ;)サブルーチン本体
  mov eax, [ebp+8]   ; 最初のパラメータの値をEAXに移動します
  mov esi, [ebp+12]  ; 2番目のパラメータの値をESIに移動します  
  mov edi, [ebp+16]  ; 3番目のパラメータの値をEDIに移動します
  
  mov [ebp-4], edi   ; EDIをローカル変数に保存します
  add [ebp-4], esi   ; ESIをローカル変数に加えます
  add eax, [ebp-4]   ; ローカル変数の内容をEAXに加え、最終結果とします
  ;)a
  pop esi          ; ESIのレジスタ値を復元します
  pop edi          ; EDIのレジスタ値を復元します
  ;)b
  mov esp, ebp     ; ローカル変数を解放します
  ;)c
  pop ebp           ; 呼び出し元のベースポインタ値を復元します
  ;)d
  ret               ; スタックの戻りアドレスをポップし、戻りアドレスにジャンプして呼び出し元のコードを続行します
_myFunc ENDP

END             ; プログラムの終了を示します


riscV アセンブリ#

arm アセンブリ#

Intel/AT&T 構文スタイルの違い#

AT&T 構文Intel 構文
insn source, destinationinsn destination, source

メモリ操作#

AT&T メモリアドレッシングは ()
Intel メモリアドレッシングは []

AT&T 構文Intel 構文
movl -12(%rbp), %eaxmov eax, DWORD PTR -12[rbp]

アドレッシング#

AT&T 構文:disp (base, index, scale)
Intel 構文:[base + index*scale + disp]

最終アドレスは base + disp + index * scale です。

AT&T 構文Intel 構文
movl -12(%rbp), %eaxmov eax, DWORD PTR -12[rbp]
leaq 0(,%rax,4), %rdxlea rdx, 0[0+rax*8]

特定のアセンブリ形式で出力#

objdump 逆アセンブル:Linux で objdump はデフォルトで AT&T 形式で逆アセンブルし、-M を追加して出力形式を指定します。

gcc -c test.c               // まずgccでバイナリファイルにコンパイルします
objdump -d test.o           // デフォルトでAT&T形式を出力します
objdump -M intel -d test.o  // intel形式を指定して出力できます

特定のスタイルのアセンブリコンパイラを選択して、アセンブリファイルを生成して確認することもできます。

gcc -S test.c              //  デフォルトでAT&T形式を出力します
gcc -S -masm=intel test.c  //  intelの構文形式を出力します

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。