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個の初期化されていない要素を宣言します。
str     DB 'hello',0    ; アドレスstrから始まる6バイトを宣言し、helloとヌル(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つのレジスタしか参加できません。

データ型(サイズ)原語(Size Directives)#

ポインタ型を修飾します:データのビット数を示します。
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 操作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等しくない場合にジャンプ(jump if not equal)jne label ; ゼロフラグが設定されていない場合(等しくないことを示す)、label にジャンプします
jg大きい場合にジャンプ(jump if greater)jg label ; 大きい場合は label にジャンプします
jl小さい場合にジャンプ(jump if 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

呼び出し元の規則 Caller Rules#

呼び出し元は、サブルーチンを呼び出す前にコンテキストを保存する必要があります:
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変数に読み取ります。

呼び出し先の規則 Callee Rules#

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の構文形式で出力します。

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