Assembly-programming-quick-guide
組み立て-はじめに
アセンブリ言語とは何ですか?
各パーソナルコンピューターには、コンピューターの算術、論理、および制御アクティビティを管理するマイクロプロセッサーがあります。
プロセッサの各ファミリには、キーボードからの入力の取得、画面上の情報の表示、その他のさまざまなジョブの実行など、さまざまな操作を処理するための独自の命令セットがあります。 これらの命令セットは「マシン言語命令」と呼ばれます。
プロセッサは、1と0の文字列である機械語命令のみを理解します。 しかし、機械語はソフトウェア開発で使用するにはあまりにも曖昧で複雑です。 そのため、低レベルアセンブリ言語は、シンボリックコードおよびより理解しやすい形式でさまざまな命令を表す特定のプロセッサファミリ向けに設計されています。
アセンブリ言語の利点
アセンブリ言語を理解すると、次のことに気付くことができます-
- プログラムがOS、プロセッサ、およびBIOSとどのようにインターフェイスするか。
- メモリおよびその他の外部デバイスでデータがどのように表されるか。
- プロセッサが命令にアクセスして実行する方法。
- 命令がデータにアクセスして処理する方法。
- プログラムが外部デバイスにアクセスする方法。
アセンブリ言語を使用する他の利点は次のとおりです-
- メモリと実行時間が少なくて済みます。
- ハードウェア固有の複雑なジョブをより簡単な方法で許可します。
- タイムクリティカルなジョブに適しています。
- 割り込みサービスルーチンやその他のメモリ常駐プログラムの作成に最適です。
PCハードウェアの基本機能
PCの主要な内部ハードウェアは、プロセッサ、メモリ、およびレジスタで構成されています。 レジスタは、データとアドレスを保持するプロセッサコンポーネントです。 プログラムを実行するために、システムはそのプログラムを外部デバイスから内部メモリにコピーします。 プロセッサはプログラム命令を実行します。
コンピュータストレージの基本単位は少しです。 ON(1)またはOFF(0)になります。 関連する9ビットのグループで1バイトが作成され、そのうち8ビットがデータに使用され、最後の1ビットがパリティに使用されます。 パリティの規則に従って、各バイトでON(1)になっているビットの数は常に奇数である必要があります。
したがって、パリティビットは、バイトのビット数を奇数にするために使用されます。 パリティが偶数の場合、システムは、パリティエラー(まれではありますが)があったと想定します。パリティエラーは、ハードウェア障害または電気障害が原因で発生した可能性があります。
プロセッサは、次のデータサイズをサポートしています-
- Word:2バイトのデータ項目
- ダブルワード:4バイト(32ビット)データ項目
- クワッドワード:8バイト(64ビット)データ項目
- 段落:16バイト(128ビット)領域
- キロバイト:1024バイト
- メガバイト:1,048,576バイト
2進数システム
すべての番号システムは位置表記法を使用します。つまり、数字が書き込まれる各位置には異なる位置値があります。 各位置はベースの累乗であり、2進数システムでは2です。これらの累乗は0から始まり、1ずつ増加します。
次の表は、すべてのビットがオンに設定されている8ビットの2進数の位置値を示しています。
Bit value | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Position value as a power of base 2 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Bit number | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
2進数の値は、1ビットの存在とその位置値に基づいています。 だから、与えられた2進数の値は-
1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255
これは2 ^ 8 ^-1と同じです。
16進数システム
16進数システムでは、基数16が使用されます。 このシステムの数字の範囲は0〜15です。 慣例により、A〜Fの文字は、10〜15の10進値に対応する16進数字を表すために使用されます。
計算の16進数は、長いバイナリ表現を短縮するために使用されます。 基本的に、16進数システムは、各バイトを半分に分割し、各ハーフバイトの値を表現することにより、バイナリデータを表します。 次の表は、10進数、2進数、および16進数の同等物を提供します-
Decimal number | Binary representation | Hexadecimal representation |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
2進数を16進数に変換するには、右から順に4つの連続したグループのグループに分割し、16進数の対応する数字の上にそれらのグループを書き込みます。
例-2進数1000 1100 1101 0001は16進数と同等-8CD1
16進数を2進数に変換するには、各16進数を4桁の2進数に書き込みます。
例-16進数FAD8は2進数と同等-1111 1010 1101 1000
バイナリ演算
次の表は、バイナリ加算の4つの単純なルールを示しています-
(i) | (ii) | (iii) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
=0 | =1 | =10 | =11 |
ルール(iii)および(iv)は、次の左位置への1ビットのキャリーを示しています。
例
Decimal | Binary |
---|---|
60 | 00111100 |
+42 | 00101010 |
102 | 01100110 |
負のバイナリ値は、* 2の補数表記*で表されます。 この規則によれば、2進数を負の値に変換するには、ビット値を反転して1を加算します。
例
Number 53 | 00110101 |
Reverse the bits | 11001010 |
Add 1 | 00000001 |
Number -53 | 11001011 |
1つの値を別の値から減算するには、減算する数値を2の補数形式に変換し、数値を加算します。
例
53から42を引きます
Number 53 | 00110101 |
Number 42 | 00101010 |
Reverse the bits of 42 | 11010101 |
Add 1 | 00000001 |
Number -42 | 11010110 |
53 - 42 = 11 | 00001011 |
最後の1ビットのオーバーフローは失われます。
メモリ内のデータのアドレス指定
プロセッサが命令の実行を制御するプロセスは、*フェッチ-デコード-実行サイクル*または*実行サイクル*と呼ばれます。 それは3つの連続したステップで構成されています-
- メモリから命令を取得する
- 命令のデコードまたは識別
- 命令を実行する
プロセッサは、一度に1バイト以上のメモリにアクセスできます。 16進数の0725Hを考えてみましょう。 この数には2バイトのメモリが必要です。 上位バイトまたは最上位バイトは07で、下位バイトは25です。
プロセッサはデータを逆バイトシーケンスで保存します。つまり、下位バイトは下位メモリアドレスに、上位バイトは上位メモリアドレスに格納されます。 したがって、プロセッサがレジスタからメモリに値0725Hを持ってくると、最初に25を下位メモリアドレスに転送し、07を次のメモリアドレスに転送します。
x:メモリアドレス
プロセッサがメモリから数値データを取得して登録すると、再びバイトを反転します。 2種類のメモリアドレスがあります-
- 絶対アドレス-特定の場所の直接参照。
- セグメントアドレス(またはオフセット)-オフセット値を持つメモリセグメントの開始アドレス。
アセンブリ-環境設定
ローカル環境のセットアップ
アセンブリ言語は、命令セットとプロセッサのアーキテクチャに依存しています。 このチュートリアルでは、PentiumなどのIntel-32プロセッサに焦点を当てます。 このチュートリアルを実行するには、必要があります-
- IBM PCまたは同等の互換性のあるコンピューター
- Linuxオペレーティングシステムのコピー
- NASMアセンブラープログラムのコピー
など、多くの優れたアセンブラープログラムがあります-
- マイクロソフトアセンブラー(MASM)
- ボーランドターボアセンブラー(TASM)
- GNUアセンブラー(GAS)
NASMアセンブラをそのまま使用します-
- 無料です。 さまざまなWebソースからダウンロードできます。
- 十分に文書化されており、ネット上で多くの情報を入手できます。
- LinuxとWindowsの両方で使用できます。
NASMのインストール
Linuxのインストール中に「開発ツール」を選択すると、LinuxオペレーティングシステムとともにNASMがインストールされる場合があり、個別にダウンロードしてインストールする必要はありません。 NASMがすでにインストールされているかどうかを確認するには、次の手順を実行します-
- Linuxターミナルを開きます。
- whereis nasm と入力し、Enterキーを押します。
- すでにインストールされている場合は、_nasm:/usr/bin/nasm_のような行が表示されます。 それ以外の場合は、_nasm:_のみが表示されるため、NASMをインストールする必要があります。
NASMをインストールするには、次の手順を実行します-
- 最新バージョンについては、http://www.nasm.us/[Netwide assembler(NASM)] Webサイトを確認してください。
- Linuxソースアーカイブ「+ nasm-X.XX.ta.gz 」をダウンロードします。「 X.XX +」はアーカイブ内のNASMバージョン番号です。
- サブディレクトリ `+ nasm-Xを作成するディレクトリにアーカイブを解凍します。 XX + `。
- 「+ nasm-X.XX +」にcdして、*。/configure *と入力します。 このシェルスクリプトは、Makefileの使用とセットアップに最適なCコンパイラを見つけます。
- make と入力して、nasmおよびndisasmのバイナリをビルドします。
- make install と入力して、nasmおよびndisasmを/usr/local/binにインストールし、manページをインストールします。
これにより、NASMがシステムにインストールされます。 または、Fedora Linux用のRPMディストリビューションを使用できます。 このバージョンのインストールは簡単で、RPMファイルをダブルクリックするだけです。
アセンブリ-基本構文
アセンブリプログラムは3つのセクションに分けることができます-
- data セクション、
- bss セクション、および
- text セクション。
_data_セクション
*data* セクションは、初期化されたデータまたは定数を宣言するために使用されます。 このデータは実行時に変更されません。 このセクションでは、さまざまな定数値、ファイル名、またはバッファサイズなどを宣言できます。
データセクションを宣言するための構文は-
section.data
_bss_セクション
*bss* セクションは、変数の宣言に使用されます。 bssセクションを宣言するための構文は次のとおりです-
section.bss
_text_セクション
*text* セクションは、実際のコードを保持するために使用されます。 このセクションは、 *global _start* 宣言で始まる必要があります。これは、プログラムの実行が開始される場所をカーネルに通知します。
テキストセクションを宣言するための構文は次のとおりです-
section.text
global _start
_start:
コメント
アセンブリ言語のコメントはセミコロン(;)で始まります。 空白を含む印刷可能な文字を含めることができます。 それは、次のようにそれ自体で行に表示することができます-
; This program displays a message on screen
または、命令と同じ行で-のように
add eax, ebx ; adds ebx to eax
アセンブリ言語ステートメント
アセンブリ言語プログラムは、3種類のステートメントで構成されています-
- 実行可能な指示または指示、
- アセンブラーディレクティブまたは擬似操作
- マクロ
実行可能命令*または単に*命令*は、プロセッサに何をすべきかを伝えます。 各命令は、*操作コード(オペコード)で構成されています。 各実行可能命令は、1つの機械語命令を生成します。
アセンブラディレクティブ*または *pseudo-ops は、アセンブリプロセスのさまざまな側面についてアセンブラに通知します。 これらは実行可能ではなく、機械語命令を生成しません。
- マクロ*は、基本的にテキスト置換メカニズムです。
アセンブリ言語ステートメントの構文
アセンブリ言語ステートメントは、1行に1ステートメントずつ入力されます。 各ステートメントは次の形式に従います-
[label] mnemonic [operands] [;comment]
角括弧内のフィールドはオプションです。 基本的な命令には2つの部分があります。最初の部分は実行される命令(またはニーモニック)の名前で、2番目はコマンドのオペランドまたはパラメーターです。
以下は、典型的なアセンブリ言語ステートメントのいくつかの例です-
INC COUNT ; Increment the memory variable COUNT
MOV TOTAL, 48 ; Transfer the value 48 in the
; memory variable TOTAL
ADD AH, BH ; Add the content of the
; BH register into the AH register
AND MASK1, 128 ; Perform AND operation on the
; variable MASK1 and 128
ADD MARKS, 10 ; Add 10 to the variable MARKS
MOV AL, 10 ; Transfer the value 10 to the AL register
アセンブリのHello Worldプログラム
次のアセンブリ言語コードは、画面に文字列「Hello World」を表示します-
section .text
global _start ;must be declared for linker (ld)
_start: ;tells linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!', 0xa ;string to be printed
len equ $ - msg ;length of the string
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Hello, world!
NASMでのアセンブリプログラムのコンパイルとリンク
PATH環境変数に nasm および ld バイナリのパスが設定されていることを確認してください。 さて、上記のプログラムをコンパイルおよびリンクするには、次の手順を実行します-
- テキストエディターを使用して上記のコードを入力し、hello.asmとして保存します。
- hello.asm を保存したディレクトリと同じディレクトリにいることを確認してください。
- プログラムをアセンブルするには、 nasm -f elf hello.asm と入力します
- エラーがある場合は、この段階でそれについてプロンプトが表示されます。 それ以外の場合、 hello.o という名前のプログラムのオブジェクトファイルが作成されます。
- オブジェクトファイルをリンクし、helloという名前の実行可能ファイルを作成するには、 ld -m elf_i386 -s -o hello hello.o と入力します。
- *。/hello *と入力してプログラムを実行します
すべてを正しく実行すると、「Hello、world!」と表示されます。画面上。
アセンブリ-メモリセグメント
アセンブリプログラムの3つのセクションについては既に説明しました。 これらのセクションは、さまざまなメモリセグメントも表します。
興味深いことに、セクションキーワードをセグメントに置き換えると、同じ結果が得られます。 次のコードを試してください-
segment .text ;code segment
global_start ;must be declared for linker
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
segment .data ;data segment
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Hello, world!
メモリセグメント
セグメントメモリモデルは、システムメモリを、セグメントレジスタにあるポインターによって参照される独立したセグメントのグループに分割します。 各セグメントは、特定のタイプのデータを含めるために使用されます。 1つのセグメントは命令コードを含むために使用され、別のセグメントはデータ要素を保存し、3番目のセグメントはプログラムスタックを保持します。
上記の議論に照らして、さまざまなメモリセグメントを次のように指定できます-
- データセグメント- .data セクションと .bss で表されます。 .dataセクションは、プログラムのデータ要素が保存されるメモリ領域を宣言するために使用されます。 このセクションは、データ要素が宣言された後は展開できず、プログラム全体で静的のままです。 + .bssセクションは、プログラムで後で宣言されるデータ用のバッファを含む静的メモリセクションでもあります。 このバッファメモリはゼロでいっぱいです。
- コードセグメント- .text セクションで表されます。 これは、命令コードを格納するメモリ内の領域を定義します。 これも固定領域です。
- スタック-このセグメントには、プログラム内の関数とプロシージャに渡されるデータ値が含まれます。
アセンブリ-登録
プロセッサの操作には、主にデータの処理が含まれます。 このデータはメモリに保存され、そこからアクセスできます。 ただし、制御バスを介してメモリストレージユニットにデータ要求を送信し、同じチャネルを介してデータを取得するという複雑なプロセスを伴うため、メモリからデータを読み取り、メモリにデータを保存すると、プロセッサの速度が低下します。
プロセッサの動作を高速化するために、プロセッサには registers と呼ばれる内部メモリストレージの場所がいくつか含まれています。
レジスタには、メモリにアクセスすることなく処理するデータ要素が格納されます。 限られた数のレジスタがプロセッサチップに組み込まれています。
プロセッサレジスタ
IA-32アーキテクチャには、10個の32ビットと6個の16ビットプロセッサレジスタがあります。 レジスタは3つのカテゴリに分類されます-
- 汎用レジスタ、
- 制御レジスタ、および
- セグメントレジスタ。
汎用レジスタは、さらに次のグループに分けられます-
- データレジスタ、
- ポインターレジスター
- インデックスレジスタ。
データレジスタ
4つの32ビットデータレジスタは、算術演算、論理演算、その他の演算に使用されます。 これらの32ビットレジスタは、3つの方法で使用できます-
- 完全な32ビットデータレジスタとして:EAX、EBX、ECX、EDX。
- 32ビットレジスタの下半分は、4つの16ビットデータレジスタとして使用できます:AX、BX、CXおよびDX。
- 上記の4つの16ビットレジスタの下半分と上半分は、8つの8ビットデータレジスタとして使用できます:AH、AL、BH、BL、CH、CL、DH、およびDL。
これらのデータレジスタの一部は、算術演算で特定の用途があります。
- AXはプライマリアキュムレータです*。入出力およびほとんどの算術命令で使用されます。 たとえば、乗算演算では、オペランドのサイズに応じて1つのオペランドがEAXまたはAXまたはALレジスタに格納されます。
- BXは、インデックス付きアドレス指定で使用できるため、ベースレジスタとして知られています*。
- CXは、カウントレジスタとして知られています。ECX、CXレジスタは、反復演算でループカウントを格納します。
- DXはデータレジスタ*として知られています。 また、入出力操作でも使用されます。 また、大きな値を伴う乗算および除算演算のために、DXとともにAXレジスタとともに使用されます。
ポインタレジスタ
ポインタレジスタは、32ビットのEIP、ESP、およびEBPレジスタと、対応する16ビットの右部分IP、SP、およびBPです。 ポインタレジスタには3つのカテゴリがあります-
- 命令ポインタ(IP)-16ビットIPレジスタには、次に実行される命令のオフセットアドレスが格納されます。 CSレジスタに関連付けられたIP(CS:IPとして)は、コードセグメント内の現在の命令の完全なアドレスを提供します。
- スタックポインタ(SP)-16ビットSPレジスタは、プログラムスタック内のオフセット値を提供します。 SSレジスタに関連付けられたSP(SS:SP)は、プログラムスタック内のデータまたはアドレスの現在の位置を指します。
- ベースポインター(BP)-16ビットBPレジスタは、主にサブルーチンに渡されるパラメーター変数の参照に役立ちます。 SSレジスタのアドレスをBPのオフセットと組み合わせて、パラメーターの位置を取得します。 BPは、特別なアドレッシング用のベースレジスタとしてDIおよびSIと組み合わせることができます。
インデックスレジスタ
32ビットのインデックスレジスタ、ESIおよびEDI、およびそれらの16ビットの右端部分。 SIおよびDIは、インデックス付きアドレス指定に使用され、加算および減算で使用されることもあります。 インデックスポインタの2つのセットがあります-
- ソースインデックス(SI)-文字列操作のソースインデックスとして使用されます。
- 宛先インデックス(DI)-文字列操作の宛先インデックスとして使用されます。
制御レジスタ
32ビット命令ポインタレジスタと32ビットフラグレジスタの組み合わせは、制御レジスタと見なされます。
多くの命令は比較と数学的計算を含み、フラグのステータスを変更し、他の条件付き命令はこれらのステータスフラグの値をテストして、制御フローを他の場所に移動します。
一般的なフラグビットは次のとおりです。
- オーバーフローフラグ(OF)-符号付き算術演算後のデータの高位ビット(左端ビット)のオーバーフローを示します。
- 方向フラグ(DF)-文字列データを移動または比較するための左または右の方向を決定します。 DF値が0の場合、文字列操作は左から右の方向になり、値が1に設定されている場合、文字列操作は右から左の方向になります。
- 割り込みフラグ(IF)-キーボード入力などの外部割り込みを無視するか処理するかを決定します。 値が0の場合は外部割り込みを無効にし、1に設定されている場合は割り込みを有効にします。
- トラップフラグ(TF)-シングルステップモードでプロセッサの動作を設定できます。 使用したDEBUGプログラムはトラップフラグを設定するため、一度に1命令ずつ実行を進めることができます。
- * Sign Flag(SF)*-算術演算の結果の符号を示します。 このフラグは、算術演算に続くデータ項目の符号に従って設定されます。 符号は、左端ビットの上位で示されます。 正の結果はSFの値を0にクリアし、負の結果は1に設定します。
- ゼロフラグ(ZF)-算術演算または比較演算の結果を示します。 ゼロ以外の結果はゼロフラグを0にクリアし、ゼロの結果は1に設定します。
- 補助キャリーフラグ(AF)-算術演算後のビット3からビット4へのキャリーが含まれています。特殊な算術に使用されます。 AFは、1バイトの算術演算によりビット3からビット4へのキャリーが発生したときに設定されます。
- パリティフラグ(PF)-算術演算から得られた結果の1ビットの総数を示します。 偶数の1ビットはパリティフラグを0にクリアし、奇数の1ビットはパリティフラグを1に設定します。
- キャリーフラグ(CF)-算術演算後の上位ビット(左端)からの0または1のキャリーが含まれます。 また、_shift_または_rotate_操作の最後のビットの内容も保存します。
次の表は、16ビットフラグレジスタのフラグビットの位置を示しています。
Flag: | O | D | I | T | S | Z | A | P | C | |||||||
Bit no: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
セグメントレジスタ
セグメントは、データ、コード、およびスタックを含むためにプログラムで定義された特定の領域です。 3つの主要なセグメントがあります-
- コードセグメント-実行されるすべての命令が含まれています。 16ビットコードセグメントレジスタまたはCSレジスタは、コードセグメントの開始アドレスを格納します。
- データセグメント-データ、定数、および作業領域が含まれます。 16ビットのデータセグメントレジスタまたはDSレジスタは、データセグメントの開始アドレスを格納します。
- スタックセグメント-プロシージャまたはサブルーチンのデータと戻りアドレスが含まれています。 「スタック」データ構造として実装されます。 スタックセグメントレジスタまたはSSレジスタには、スタックの開始アドレスが格納されます。
DS、CS、およびSSレジスタとは別に、データを格納するための追加のセグメントを提供する、ES(追加セグメント)、FS、およびGSの追加のセグメントレジスタがあります。
アセンブリプログラミングでは、プログラムはメモリの場所にアクセスする必要があります。 セグメント内のすべてのメモリ位置は、セグメントの開始アドレスに関連しています。 セグメントは、16または16進数で10で割り切れるアドレスで始まります。 したがって、このようなすべてのメモリアドレスの右端の16進数字は0であり、通常はセグメントレジスタに格納されません。
セグメントレジスタには、セグメントの開始アドレスが格納されます。 セグメント内のデータまたは命令の正確な位置を取得するには、オフセット値(または変位)が必要です。 セグメント内のメモリロケーションを参照するために、プロセッサは、セグメントレジスタ内のセグメントアドレスとロケーションのオフセット値を組み合わせます。
例
次の簡単なプログラムを見て、アセンブリプログラミングでのレジスタの使用を理解してください。 このプログラムは、簡単なメッセージとともに画面に9つの星を表示します-
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,9 ;message length
mov ecx,s2 ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Displaying 9 stars',0xa ;a message
len equ $ - msg ;length of message
s2 times 9 db '*'
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Displaying 9 stars
*********
アセンブリ-システムコール
システムコールは、ユーザー空間とカーネル空間の間のインターフェイスのAPIです。 既にシステムコールを使用しています。 sys_writeおよびsys_exit、それぞれ画面への書き込みおよびプログラムの終了用。
Linuxシステムコール
アセンブリプログラムでLinuxシステムコールを使用できます。 あなたのプログラムでLinuxシステムコールを使用するには、次の手順を実行する必要があります-
- システムコール番号をEAXレジスタに入れます。
- システムコールへの引数をレジスタEBX、ECXなどに保存します。
- 関連する割り込み(80h)を呼び出します。 *通常、結果はEAXレジスタに返されます。
使用されるシステムコールの引数を格納する6つのレジスタがあります。 これらはEBX、ECX、EDX、ESI、EDI、およびEBPです。 これらのレジスタは、EBXレジスタから始まる連続した引数を取ります。 6つ以上の引数がある場合、最初の引数のメモリ位置はEBXレジスタに保存されます。
次のコードスニペットは、システムコールsys_exitの使用を示しています-
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
次のコードスニペットは、システムコールsys_writeの使用を示しています-
mov edx,4 ; message length
mov ecx,msg ; message to write
mov ebx,1 ; file descriptor (stdout)
mov eax,4 ; system call number (sys_write)
int 0x80 ; call kernel
すべてのsyscallは、その番号(int 80hを呼び出す前にEAXに入れる値)とともに_/usr/include/asm/unistd.h_にリストされています。
次の表は、このチュートリアルで使用されるシステムコールの一部を示しています-
%eax | Name | %ebx | %ecx | %edx | %esx | %edi |
---|---|---|---|---|---|---|
1 | sys_exit | int | - | - | - | - |
2 | sys_fork | struct pt_regs | - | - | - | - |
3 | sys_read | unsigned int | char* | size_t | - | - |
4 | sys_write | unsigned int | const char * | size_t | - | - |
5 | sys_open | const char* | int | int | - | - |
6 | sys_close | unsigned int | - | - | - | - |
例
次の例では、キーボードから数字を読み取り、画面に表示します-
section .data ;Data segment
userMsg db 'Please enter a number: ' ;Ask the user to enter a number
lenUserMsg equ $-userMsg ;The length of the message
dispMsg db 'You have entered: '
lenDispMsg equ $-dispMsg
section .bss ;Uninitialized data
num resb 5
section .text ;Code Segment
global _start
_start: ;User prompt
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
;Output the message 'The entered number is: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
;Output the number entered
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; Exit code
mov eax, 1
mov ebx, 0
int 80h
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Please enter a number:
1234
You have entered:1234
アセンブリ-アドレス指定モード
ほとんどのアセンブリ言語命令では、オペランドを処理する必要があります。 オペランドアドレスは、処理されるデータが保存される場所を提供します。 オペランドを必要としない命令もあれば、1つ、2つ、または3つのオペランドを必要とする命令もあります。
命令に2つのオペランドが必要な場合、通常、最初のオペランドはデスティネーションであり、レジスタまたはメモリの場所にデータが含まれ、2番目のオペランドはソースです。 ソースには、配信されるデータ(即時アドレス指定)またはデータのアドレス(レジスタまたはメモリ内)が含まれます。 通常、ソースデータは操作後も変更されません。
アドレッシングの3つの基本モードは次のとおりです-
- レジスタのアドレス指定
- 即時アドレス指定
- メモリアドレッシング
レジスタのアドレス指定
このアドレッシングモードでは、レジスタにオペランドが含まれています。 命令に応じて、レジスタは第1オペランド、第2オペランド、またはその両方になります。
例えば、
MOV DX, TAX_RATE ; Register in first operand
MOV COUNT, CX ; Register in second operand
MOV EAX, EBX ; Both the operands are in registers
レジスタ間のデータ処理にはメモリが関与しないため、データの処理が最速になります。
即時アドレス指定
即値オペランドには、定数値または式があります。 2つのオペランドを持つ命令が即時アドレス指定を使用する場合、最初のオペランドはレジスタまたはメモリの場所であり、2番目のオペランドは即時定数です。 最初のオペランドは、データの長さを定義します。
例えば、
BYTE_VALUE DB 150 ; A byte value is defined
WORD_VALUE DW 300 ; A word value is defined
ADD BYTE_VALUE, 65 ; An immediate operand 65 is added
MOV AX, 45H ; Immediate constant 45H is transferred to AX
ダイレクトメモリアドレッシング
オペランドがメモリアドレッシングモードで指定されている場合、通常はデータセグメントへのメインメモリへの直接アクセスが必要です。 このアドレス指定方法では、データの処理が遅くなります。 メモリ内のデータの正確な位置を特定するには、通常DSレジスタとオフセット値にあるセグメント開始アドレスが必要です。 このオフセット値は、*有効アドレス*とも呼ばれます。
直接アドレス指定モードでは、オフセット値は通常、変数名で示される命令の一部として直接指定されます。 アセンブラは、オフセット値を計算し、プログラムで使用されるすべての変数のオフセット値を格納するシンボルテーブルを維持します。
ダイレクトメモリアドレッシングでは、オペランドの1つがメモリ位置を参照し、他のオペランドがレジスタを参照します。
例えば、
ADD BYTE_VALUE, DL ; Adds the register in the memory location
MOV BX, WORD_VALUE ; Operand from the memory is added to register
直接オフセットアドレッシング
このアドレス指定モードでは、算術演算子を使用してアドレスを変更します。 たとえば、データのテーブルを定義する次の定義を見てください-
BYTE_TABLE DB 14, 15, 22, 45 ; Tables of bytes
WORD_TABLE DW 134, 345, 564, 123 ; Tables of words
次の操作は、メモリ内のテーブルからレジスタにデータにアクセスします-
MOV CL, BYTE_TABLE[2] ; Gets the 3rd element of the BYTE_TABLE
MOV CL, BYTE_TABLE + 2 ; Gets the 3rd element of the BYTE_TABLE
MOV CX, WORD_TABLE[3] ; Gets the 4th element of the WORD_TABLE
MOV CX, WORD_TABLE + 3 ; Gets the 4th element of the WORD_TABLE
間接メモリアドレス指定
このアドレス指定モードは、コンピューターの_Segment:Offset_アドレス指定機能を利用します。 一般に、メモリ参照用の角括弧内にコード化されたベースレジスタEBX、EBP(またはBX、BP)およびインデックスレジスタ(DI、SI)がこの目的に使用されます。
間接アドレス指定は通常、配列などのいくつかの要素を含む変数に使用されます。 アレイの開始アドレスは、たとえばEBXレジスタに保存されます。
次のコードスニペットは、変数のさまざまな要素にアクセスする方法を示しています。
MY_TABLE TIMES 10 DW 0 ; Allocates 10 words (2 bytes) each initialized to 0
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
ADD EBX, 2 ; EBX = EBX +2
MOV [EBX], 123 ; MY_TABLE[1] = 123
MOV命令
あるストレージスペースから別のストレージスペースにデータを移動するために使用されるMOV命令をすでに使用しています。 MOV命令は2つのオペランドを取ります。
構文
MOV命令の構文は-
MOV destination, source
MOV命令は、次の5つの形式のいずれかを持つことができます-
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register
次のことに注意してください-
- MOV操作のオペランドは両方とも同じサイズでなければなりません
- ソースオペランドの値は変更されません
MOV命令は、あいまいさを引き起こすことがあります。 たとえば、ステートメントを見てください-
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
110に相当するバイトを移動するのか、ワードに相当するのを移動するのかは明確ではありません。 そのような場合、 type specifier を使用するのが賢明です。
次の表は、一般的な型指定子の一部を示しています-
Type Specifier | Bytes addressed |
---|---|
BYTE | 1 |
WORD | 2 |
DWORD | 4 |
QWORD | 8 |
TBYTE | 10 |
例
次のプログラムは、上記で説明した概念の一部を示しています。 メモリのデータセクションに「Zara Ali」という名前を保存し、プログラムで値を別の名前「Nuha Ali」に変更し、両方の名前を表示します。
section .text
global_start ;must be declared for linker (ld)
_start: ;tell linker entry point
;writing the name 'Zara Ali'
mov edx,9 ;message length
mov ecx, name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov [name], dword 'Nuha' ; Changed the name to Nuha Ali
;writing the name 'Nuha Ali'
mov edx,8 ;message length
mov ecx,name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
name db 'Zara Ali '
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Zara Ali Nuha Ali
アセンブリ-変数
NASMは、変数のストレージスペースを予約するためのさまざまな* defineディレクティブ*を提供します。 define assemblerディレクティブは、ストレージスペースの割り当てに使用されます。 1つ以上のバイトを予約および初期化するために使用できます。
初期化されたデータ用のストレージスペースの割り当て
初期化されたデータのストレージ割り当てステートメントの構文は-
[variable-name] define-directive initial-value [,initial-value]...
ここで、_variable-name_は各ストレージスペースの識別子です。 アセンブラは、データセグメントで定義された各変数名のオフセット値を関連付けます。
defineディレクティブには5つの基本的な形式があります-
Directive | Purpose | Storage Space |
---|---|---|
DB | Define Byte | allocates 1 byte |
DW | Define Word | allocates 2 bytes |
DD | Define Doubleword | allocates 4 bytes |
DQ | Define Quadword | allocates 8 bytes |
DT | Define Ten Bytes | allocates 10 bytes |
以下は、定義ディレクティブの使用例です-
choice DB 'y'
number DW 12345
neg_number DW -12345
big_number DQ 123456789
real_number1 DD 1.234
real_number2 DQ 123.456
次のことに注意してください-
- 文字の各バイトは、ASCII値として16進数で保存されます。
- 各10進数値は、16ビットのバイナリに自動的に変換され、16進数として保存されます。
- プロセッサは、リトルエンディアンのバイト順を使用します。
- 負の数は、その2の補数表現に変換されます。
- 短い浮動小数点数と長い浮動小数点数は、それぞれ32ビットまたは64ビットを使用して表されます。
次のプログラムは、定義ディレクティブの使用を示しています-
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,1 ;message length
mov ecx,choice ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
choice DB 'y'
上記のコードをコンパイルして実行すると、次の結果が生成されます-
y
初期化されていないデータ用のストレージスペースの割り当て
reserveディレクティブは、初期化されていないデータ用のスペースを予約するために使用されます。 予約ディレクティブは、予約するスペースの単位数を指定する単一のオペランドを取ります。 各defineディレクティブには、関連する予約ディレクティブがあります。
予約ディレクティブの5つの基本的な形式があります-
Directive | Purpose |
---|---|
RESB | Reserve a Byte |
RESW | Reserve a Word |
RESD | Reserve a Doubleword |
RESQ | Reserve a Quadword |
REST | Reserve a Ten Bytes |
複数の定義
1つのプログラムに複数のデータ定義ステートメントを含めることができます。 たとえば-
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
アセンブラは、複数の変数定義に連続したメモリを割り当てます。
複数の初期化
TIMESディレクティブは、同じ値への複数の初期化を許可します。 たとえば、サイズ9のmarksという名前の配列を定義し、次のステートメントを使用してゼロに初期化することができます-
marks TIMES 9 DW 0
TIMESディレクティブは、配列とテーブルの定義に役立ちます。 次のプログラムは、画面に9アスタリスクを表示します-
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
mov edx,9 ;message length
mov ecx, stars ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
stars times 9 db '*'
上記のコードをコンパイルして実行すると、次の結果が生成されます-
*********
アセンブリ-定数
NASMが提供する、定数を定義するディレクティブがいくつかあります。 前の章ですでにEQUディレクティブを使用しました。 特に3つのディレクティブについて説明します-
- EQU
- %割り当てます
- 定義する
EQUディレクティブ
*EQU* ディレクティブは、定数の定義に使用されます。 EQUディレクティブの構文は次のとおりです-
CONSTANT_NAME EQU expression
例えば、
TOTAL_STUDENTS equ 50
その後、次のようにコードでこの定数値を使用できます-
mov ecx, TOTAL_STUDENTS
cmp eax, TOTAL_STUDENTS
EQUステートメントのオペランドは式にすることができます-
LENGTH equ 20
WIDTH equ 10
AREA equ length * width
上記のコードセグメントでは、AREAを200として定義します。
例
次の例は、EQUディレクティブの使用を示しています-
SYS_EXIT equ 1
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
mov eax,SYS_EXIT ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $ - msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
%assignディレクティブ
- %assign *ディレクティブを使用して、EQUディレクティブのような数値定数を定義できます。 このディレクティブは再定義を許可します。 たとえば、定数TOTALを次のように定義できます-
%assign TOTAL 10
コードの後半で、次のように再定義できます-
%assign TOTAL 20
このディレクティブは大文字と小文字を区別します。
%defineディレクティブ
- %define *ディレクティブを使用すると、数値定数と文字列定数の両方を定義できます。 このディレクティブは、Cの#defineに似ています。 たとえば、次のように定数PTRを定義できます-
%define PTR [EBP+4]
上記のコードは、_PTR_を[EBP + 4]に置き換えます。
このディレクティブは再定義も許可し、大文字と小文字が区別されます。
アセンブリ-算術命令
INC命令
INC命令は、オペランドを1インクリメントするために使用されます。 これは、レジスタまたはメモリ内にある単一のオペランドで機能します。
構文
INC命令には次の構文があります-
INC destination
オペランド_destination_は、8ビット、16ビット、または32ビットのオペランドにすることができます。
例
INC EBX ; Increments 32-bit register
INC DL ; Increments 8-bit register
INC [count] ; Increments the count variable
DEC命令
DEC命令は、オペランドを1減らすために使用されます。 これは、レジスタまたはメモリ内にある単一のオペランドで機能します。
構文
DEC命令には次の構文があります-
DEC destination
オペランド_destination_は、8ビット、16ビット、または32ビットのオペランドにすることができます。
例
segment .data
count dw 0
value db 15
segment .text
inc [count]
dec [value]
mov ebx, count
inc word [ebx]
mov esi, value
dec byte [esi]
ADDおよびSUB命令
ADDおよびSUB命令は、バイト、ワード、およびダブルワードサイズのバイナリデータの単純な加算/減算を実行するために、つまり、それぞれ8ビット、16ビット、または32ビットのオペランドを加算または減算するために使用されます。
構文
ADDおよびSUB命令には、次の構文があります-
ADD/SUB destination, source
ADD/SUB命令は次の間に実行できます-
- 登録して登録する
- 登録するメモリ
- メモリに登録する
- 定数データに登録する
- 定数データへのメモリ
ただし、他の命令と同様に、ADD/SUB命令を使用したメモリ間操作はできません。 ADDまたはSUB操作は、オーバーフローフラグとキャリーフラグを設定またはクリアします。
例
次の例では、ユーザーに2桁の数字を要求し、その数字をそれぞれEAXおよびEBXレジスタに保存し、値を追加し、結果をメモリロケーション「res」に保存し、最終的に結果を表示します。
SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1
msg2 db "Please enter a second digit", 0xA,0xD
len2 equ $- msg2
msg3 db "The sum is: "
len3 equ $- msg3
segment .bss
num1 resb 2
num2 resb 2
res resb 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
; moving the first number to eax register and second number to ebx
; and subtracting ascii '0' to convert it into a decimal number
mov eax, [num1]
sub eax, '0'
mov ebx, [num2]
sub ebx, '0'
; add eax and ebx
add eax, ebx
; add '0' to to convert the sum from decimal to ASCII
add eax, '0'
; storing the sum in memory location res
mov [res], eax
; print the sum
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80
exit:
mov eax, SYS_EXIT
xor ebx, ebx
int 0x80
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
- ハードコードされた変数を持つプログラム- *
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The sum is:
7
MUL/IMUL命令
バイナリデータを乗算するための2つの命令があります。 MUL(乗算)命令は符号なしデータを処理し、IMUL(整数乗算)は符号付きデータを処理します。 両方の命令は、キャリーおよびオーバーフローフラグに影響します。
構文
MUL/IMUL命令の構文は次のとおりです-
MUL/IMUL multiplier
両方の場合の被乗数は、被乗数と乗数のサイズに応じてアキュムレータに格納され、生成された積もオペランドのサイズに応じて2つのレジスタに格納されます。 次のセクションでは、3つの異なるケースを持つMUL命令について説明します-
Sr.No. | Scenarios |
---|---|
1 |
被乗数はALレジスタにあり、乗数はメモリまたは別のレジスタのバイトです。 製品はAXにあります。 製品の上位8ビットはAHに格納され、下位8ビットはALに格納されます。 |
2 |
When two one-word values are multiplied − 被乗数はAXレジスタにある必要があり、乗数はメモリ内のワードまたは別のレジスタです。 たとえば、MUL DXのような命令の場合、乗数をDXに、被乗数をAXに保存する必要があります。 結果の積はダブルワードであり、2つのレジスタが必要です。 上位(左端)の部分はDXに格納され、下位(右端)の部分はAXに格納されます。 |
3 |
When two doubleword values are multiplied − 2つのダブルワード値を乗算する場合、被乗数はEAXである必要があり、乗数はメモリまたは別のレジスタに格納されているダブルワード値です。 生成された製品はEDX:EAXレジスタに保存されます。つまり、上位32ビットがEDXレジスタに保存され、下位32ビットがEAXレジスタに保存されます。 |
例
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL
例
次の例では、3に2を掛けて結果を表示します-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al,'3'
sub al, '0'
mov bl, '2'
sub bl, '0'
mul bl
add al, '0'
mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The result is:
6
DIV/IDIV命令
除算演算は、2つの要素- quotient および remainder を生成します。 乗算の場合、積を保持するために倍長レジスタが使用されるため、オーバーフローは発生しません。 ただし、除算の場合、オーバーフローが発生する可能性があります。 オーバーフローが発生すると、プロセッサは割り込みを生成します。
DIV(除算)命令は符号なしデータに使用され、IDIV(整数除算)は符号付きデータに使用されます。
構文
DIV/IDIV命令の形式-
DIV/IDIV divisor
配当はアキュムレーターにあります。 どちらの命令も、8ビット、16ビット、または32ビットのオペランドで機能します。 この操作は、6つのステータスフラグすべてに影響します。 次のセクションでは、異なるオペランドサイズの除算の3つのケースについて説明します-
Sr.No. | Scenarios |
---|---|
1 |
When the divisor is 1 byte − 配当はAXレジスタ(16ビット)にあると想定されます。 除算後、商はALレジスタに行き、残りはAHレジスタに行きます。 |
2 |
When the divisor is 1 word − 配当は32ビット長で、DX:AXレジスタ内にあると想定されます。 上位16ビットはDXにあり、下位16ビットはAXにあります。 除算後、16ビットの商はAXレジスタに送られ、16ビットの余りはDXレジスタに送られます。 |
3 |
When the divisor is doubleword − 被除数は64ビット長で、EDX:EAXレジスタにあると想定されています。 上位32ビットはEDXにあり、下位32ビットはEAXにあります。 除算後、32ビットの商はEAXレジスタに送られ、32ビットの余りはEDXレジスタに送られます。 |
例
次の例では、8を2で割ります。 被除数8 *は 16ビットAXレジスタ*に格納され、除数2 *は 8ビットBLレジスタ*に格納されます。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax,'8'
sub ax, '0'
mov bl, '2'
sub bl, '0'
div bl
add ax, '0'
mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The result is:
4
アセンブリ-論理命令
プロセッサの命令セットは、AND、OR、XOR、TEST、およびNOTのブール論理命令を提供し、プログラムの必要性に応じてビットをテスト、設定、およびクリアします。
これらの指示の形式-
Sr.No. | Instruction | Format |
---|---|---|
1 | AND | AND operand1, operand2 |
2 | OR | OR operand1, operand2 |
3 | XOR | XOR operand1, operand2 |
4 | TEST | TEST operand1, operand2 |
5 | NOT | NOT operand1 |
すべての場合の最初のオペランドは、レジスタまたはメモリのいずれかにあります。 2番目のオペランドは、レジスタ/メモリまたは即値(定数)にあります。 ただし、メモリ間操作はできません。 これらの命令は、オペランドのビットを比較または一致させ、CF、OF、PF、SF、およびZFフラグを設定します。
AND命令
AND命令は、ビット単位のAND演算を実行して論理式をサポートするために使用されます。 両方のオペランドの一致するビットが1の場合、ビット単位のAND演算は1を返します。それ以外の場合は0を返します。 たとえば-
Operand1: 0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001
AND演算は、1つ以上のビットをクリアするために使用できます。 たとえば、BLレジスタに0011 1010が含まれているとします。 上位ビットをゼロにクリアする必要がある場合は、0FHとANDします。
AND BL, 0FH ; This sets BL to 0000 1010
別の例を取り上げましょう。 特定の数値が奇数か偶数かを確認する場合、簡単なテストは、数値の最下位ビットを確認することです。 これが1の場合、数値は奇数です。それ以外の場合、数値は偶数です。
番号がALレジスタにあると仮定すると、私たちは書くことができます-
AND AL, 01H ; ANDing with 0000 0001
JZ EVEN_NUMBER
次のプログラムはこれを示しています-
例
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax, 8h ;getting 8 in the ax
and ax, 1 ;and ax with 1
jz evnn
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, odd_msg ;message to write
mov edx, len2 ;length of message
int 0x80 ;call kernel
jmp outprog
evnn:
mov ah, 09h
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, even_msg ;message to write
mov edx, len1 ;length of message
int 0x80 ;call kernel
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
even_msg db 'Even Number!' ;message showing even number
len1 equ $ - even_msg
odd_msg db 'Odd Number!' ;message showing odd number
len2 equ $ - odd_msg
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Even Number!
のように、奇数レジスタでaxレジスタの値を変更します-
mov ax, 9h ; getting 9 in the ax
プログラムは以下を表示します。
Odd Number!
同様に、レジスタ全体をクリアするには、00HでANDできます。
OR命令
OR命令は、ビット単位のOR演算を実行して論理式をサポートするために使用されます。 一方または両方のオペランドの一致するビットが1である場合、ビット単位のOR演算子は1を返します。 両方のビットがゼロの場合、0を返します。
例えば、
Operand1: 0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111
OR演算を使用して、1つ以上のビットを設定できます。 たとえば、ALレジスタに0011 1010が含まれている場合、4つの下位ビットを設定する必要があり、値0000 1111、つまりFHとORすることができます。
OR BL, 0FH ; This sets BL to 0011 1111
例
次の例は、OR命令を示しています。 値5と3をそれぞれALレジスタとBLレジスタに保存してから、命令、
OR AL, BL
ALレジスタに7を保存する必要があります-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al, 5 ;getting 5 in the al
mov bl, 3 ;getting 3 in the bl
or al, bl ;or al and bl registers, result should be 7
add al, byte '0' ;converting decimal to ascii
mov [result], al
mov eax, 4
mov ebx, 1
mov ecx, result
mov edx, 1
int 0x80
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
result resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
7
XOR命令
XOR命令は、ビット単位のXOR演算を実装します。 XOR演算は、オペランドのビットが異なる場合にのみ、結果のビットを1に設定します。 オペランドからのビットが同じ場合(両方とも0または両方1)、結果のビットは0にクリアされます。
例えば、
Operand1: 0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110
オペランドとそれ自身の XORing は、オペランドを 0 に変更します。 これは、レジスタをクリアするために使用されます。
XOR EAX, EAX
テスト命令
TEST命令はAND演算と同じように機能しますが、AND命令とは異なり、第1オペランドを変更しません。 したがって、レジスタ内の数値が偶数か奇数かを確認する必要がある場合は、元の数値を変更せずにTEST命令を使用してこれを行うこともできます。
TEST AL, 01H
JZ EVEN_NUMBER
NOT命令
NOT命令は、ビット単位のNOT演算を実装します。 NOT演算は、オペランドのビットを反転します。 オペランドは、レジスタまたはメモリにあります。
例えば、
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
組み立て-条件
アセンブリ言語での条件付き実行は、複数のループおよび分岐命令によって実現されます。 これらの命令は、プログラムの制御の流れを変えることができます。 条件付き実行は2つのシナリオで観察されます-
Sr.No. | Conditional Instructions |
---|---|
1 |
Unconditional jump これは、JMP命令によって実行されます。 条件付き実行では、現在実行中の命令の後に続かない命令のアドレスへの制御の転送が含まれることがよくあります。 制御の転送は、同じ手順を再実行するために、新しい命令セットを実行するために順方向、または逆方向に行われる場合があります。 |
2 |
Conditional jump これは、条件に応じて一連のジャンプ命令j <condition>によって実行されます。 条件付き命令は、シーケンシャルフローを中断することにより制御を転送し、IPのオフセット値を変更することにより制御を行います。 |
条件付き命令について説明する前に、CMP命令について説明します。
CMP命令
CMP命令は2つのオペランドを比較します。 通常、条件付き実行で使用されます。 この命令は、基本的に、オペランドが等しいかどうかを比較するために、一方のオペランドを他方から減算します。 宛先またはソースのオペランドを乱すことはありません。 意思決定のための条件付きジャンプ命令とともに使用されます。
構文
CMP destination, source
CMPは2つの数値データフィールドを比較します。 デスティネーションオペランドは、レジスタまたはメモリにあります。 ソースオペランドは、定数(イミディエート)データ、レジスタ、またはメモリです。
例
CMP DX, 00 ; Compare the DX value with zero
JE L7 ; If yes, then jump to label L7
.
.
L7: ...
CMPは、ループの実行が必要な回数にカウンター値が達したかどうかを比較するためによく使用されます。 次の典型的な条件を考慮してください-
INC EDX
CMP EDX, 10 ; Compares whether the counter has reached 10
JLE LP1 ; If it is less than or equal to 10, then jump to LP1
無条件ジャンプ
前述のように、これはJMP命令によって実行されます。 条件付き実行では、現在実行中の命令の後に続かない命令のアドレスへの制御の転送が含まれることがよくあります。 制御の転送は、同じ手順を再実行するために、新しい命令セットを実行するために順方向、または逆方向に行われる場合があります。
構文
JMP命令は、制御のフローがすぐに転送されるラベル名を提供します。 JMP命令の構文は-
JMP label
例
次のコードスニペットは、JMP命令を示しています-
MOV AX, 00 ; Initializing AX to 0
MOV BX, 00 ; Initializing BX to 0
MOV CX, 01 ; Initializing CX to 1
L20:
ADD AX, 01 ; Increment AX
ADD BX, AX ; Add AX to BX
SHL CX, 1 ; shift left CX, this in turn doubles the CX value
JMP L20 ; repeats the statements
条件付きジャンプ
条件付きジャンプで指定された条件が満たされると、制御フローはターゲット命令に転送されます。 条件とデータに応じて、多数の条件付きジャンプ命令があります。
以下は、算術演算に使用される符号付きデータで使用される条件付きジャンプ命令です-
Instruction | Description | Flags tested |
---|---|---|
JE/JZ | Jump Equal or Jump Zero | ZF |
JNE/JNZ | Jump not Equal or Jump Not Zero | ZF |
JG/JNLE | Jump Greater or Jump Not Less/Equal | OF, SF, ZF |
JGE/JNL | Jump Greater/Equal or Jump Not Less | OF, SF |
JL/JNGE | Jump Less or Jump Not Greater/Equal | OF, SF |
JLE/JNG | Jump Less/Equal or Jump Not Greater | OF, SF, ZF |
以下は、論理演算に使用される符号なしデータで使用される条件付きジャンプ命令です-
Instruction | Description | Flags tested |
---|---|---|
JE/JZ | Jump Equal or Jump Zero | ZF |
JNE/JNZ | Jump not Equal or Jump Not Zero | ZF |
JA/JNBE | Jump Above or Jump Not Below/Equal | CF, ZF |
JAE/JNB | Jump Above/Equal or Jump Not Below | CF |
JB/JNAE | Jump Below or Jump Not Above/Equal | CF |
JBE/JNA | Jump Below/Equal or Jump Not Above | AF, CF |
次の条件付きジャンプ命令には特別な用途があり、フラグの値を確認します-
Instruction | Description | Flags tested |
---|---|---|
JXCZ | Jump if CX is Zero | none |
JC | Jump If Carry | CF |
JNC | Jump If No Carry | CF |
JO | Jump If Overflow | OF |
JNO | Jump If No Overflow | OF |
JP/JPE | Jump Parity or Jump Parity Even | PF |
JNP/JPO | Jump No Parity or Jump Parity Odd | PF |
JS | Jump Sign (negative value) | SF |
JNS | Jump No Sign (positive value) | SF |
命令のJ <条件>セットの構文-
例、
CMP AL, BL
JE EQUAL
CMP AL, BH
JE EQUAL
CMP AL, CL
JE EQUAL
NON_EQUAL: ...
EQUAL: ...
例
次のプログラムは、3つの変数のうち最大のものを表示します。 変数は2桁の変数です。 3つの変数num1、num2、num3の値はそれぞれ47、22、31です-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx, [num1]
cmp ecx, [num2]
jg check_third_num
mov ecx, [num2]
check_third_num:
cmp ecx, [num3]
jg _exit
mov ecx, [num3]
_exit:
mov [largest], ecx
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,largest
mov edx, 2
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1
int 80h
section .data
msg db "The largest digit is: ", 0xA,0xD
len equ $- msg
num1 dd '47'
num2 dd '22'
num3 dd '31'
segment .bss
largest resb 2
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The largest digit is:
47
アセンブリ-ループ
JMP命令は、ループの実装に使用できます。 たとえば、次のコードスニペットを使用して、ループ本体を10回実行できます。
MOV CL, 10
L1:
<LOOP-BODY>
DEC CL
JNZ L1
ただし、プロセッサ命令セットには、反復を実装するためのループ命令のグループが含まれています。 基本的なLOOP命令には次の構文があります-
LOOP label
ここで、_label_は、ジャンプ命令のようにターゲット命令を識別するターゲットラベルです。 LOOP命令は、* ECXレジスタにループカウント*が含まれていることを前提としています。 ループ命令が実行されると、ECXレジスタの値が減り、ECXレジスタ値、つまりカウンタが値ゼロに達するまで、コントロールがターゲットラベルにジャンプします。
上記のコードスニペットは次のように書くことができます-
mov ECX,10
l1:
<loop body>
loop l1
例
次のプログラムは、画面に1から9までの数字を印刷します-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,10
mov eax, '1'
l1:
mov [num], eax
mov eax, 4
mov ebx, 1
push ecx
mov ecx, num
mov edx, 1
int 0x80
mov eax, [num]
sub eax, '0'
inc eax
add eax, '0'
pop ecx
loop l1
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
num resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
123456789:
アセンブリ-番号
数値データは通常、バイナリシステムで表されます。 算術命令はバイナリデータを操作します。 数字が画面に表示されるか、キーボードから入力されると、それらはASCII形式になります。
これまで、算術計算のためにこの入力データをASCII形式のバイナリに変換し、結果をバイナリに変換しました。 次のコードはこれを示しています-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The sum is:
7
ただし、このような変換にはオーバーヘッドがあり、アセンブリ言語プログラミングにより、バイナリ形式でより効率的な方法で数値を処理できます。 10進数は2つの形式で表すことができます-
- ASCII形式
- BCDまたはバイナリコード10進数形式
ASCII表現
ASCII表現では、10進数はASCII文字の文字列として保存されます。 たとえば、10進数値1234は次のように保存されます-
31 32 33 34H
ここで、31Hは1のASCII値、32Hは2のASCII値などです。 ASCII表現の数字を処理するための4つの命令があります-
- AAA -追加後のASCII調整
- AAS -減算後のASCII調整
- AAM -乗算後のASCII調整
- AAD -除算前のASCII調整
これらの命令はオペランドを使用せず、必要なオペランドがALレジスタにあると想定します。
次の例では、AAS命令を使用して概念を示します-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
sub ah, ah
mov al, '9'
sub al, '3'
aas
or al, 30h
mov [res], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,res ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Result is:',0xa
len equ $ - msg
section .bss
res resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The Result is:
6
BCD表現
BCD表現の2種類があります-
- アンパックBCD表現
- パックされたBCD表現
パックされていないBCD表現では、各バイトに10進数に相当するバイナリが格納されます。 たとえば、番号1234は次のように保存されます-
01 02 03 04H
これらの番号を処理するための2つの指示があります-
- AAM -乗算後のASCII調整
- AAD -除算前のASCII調整
4つのASCII調整命令、AAA、AAS、AAM、およびAADは、アンパックBCD表現でも使用できます。 パックBCD表現では、各桁は4ビットを使用して格納されます。 2つの10進数が1バイトにパックされます。 たとえば、番号1234は次のように保存されます-
12 34H
これらの番号を処理するための2つの指示があります-
- DAA -追加後の10進数調整
- DAS -減算後の小数調整
パックBCD表現では、乗算と除算はサポートされていません。
例
次のプログラムは、2つの5桁の10進数を加算し、合計を表示します。 それは上記の概念を使用します-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov esi, 4 ;pointing to the rightmost digit
mov ecx, 5 ;num of digits
clc
add_loop:
mov al, [num1 + esi]
adc al, [num2 + esi]
aaa
pushf
or al, 30h
popf
mov [sum + esi], al
dec esi
loop add_loop
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,5 ;message length
mov ecx,sum ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Sum is:',0xa
len equ $ - msg
num1 db '12345'
num2 db '23456'
sum db ' '
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The Sum is:
35801
アセンブリ-ストリング
前の例では、すでに可変長文字列を使用しています。 可変長文字列には、必要な数の文字を含めることができます。 一般的に、我々は2つの方法のいずれかによって文字列の長さを指定します-
- 文字列の長さを明示的に保存する
- センチネル文字を使用する
ロケーションカウンタの現在の値を表す$ロケーションカウンタシンボルを使用して、文字列の長さを明示的に保存できます。 次の例では-
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
$は、文字列変数_msg_の最後の文字の後のバイトを指します。 したがって、 _ $-msg_ は文字列の長さを示します。 私たちも書くことができます
msg db 'Hello, world!',0xa ;our dear string
len equ 13 ;length of our dear string
または、文字列の長さを明示的に保存する代わりに、文字列を区切るために末尾のセンチネル文字を使用して文字列を保存できます。 センチネル文字は、文字列内に表示されない特殊文字である必要があります。
たとえば-
message DB 'I am loving it!', 0
文字列命令
各文字列命令には、ソースオペランド、デスティネーションオペランド、またはその両方が必要な場合があります。 32ビットセグメントの場合、文字列命令はESIおよびEDIレジスタを使用して、それぞれソースオペランドとデスティネーションオペランドを指します。
ただし、16ビットセグメントの場合、SIおよびDIレジスタは、それぞれソースとデスティネーションを指すために使用されます。
文字列を処理するための5つの基本的な命令があります。 彼らは-
- MOVS -この命令は、1バイト、ワード、またはダブルワードのデータをメモリロケーションから別の場所に移動します。
- LODS -この命令はメモリからロードされます。 オペランドが1バイトの場合、ALレジスタにロードされ、オペランドが1ワードの場合、AXレジスタにロードされ、ダブルワードがEAXレジスタにロードされます。
- STOS -この命令は、レジスタ(AL、AX、またはEAX)からメモリにデータを保存します。
- CMPS -この命令は、メモリ内の2つのデータ項目を比較します。 データはバイトサイズ、ワードまたはダブルワードの可能性があります。
- SCAS -この命令は、レジスタ(AL、AX、またはEAX)の内容をメモリ内のアイテムの内容と比較します。
上記の各命令にはバイト、ワード、ダブルワードのバージョンがあり、文字列命令は繰り返しプレフィックスを使用して繰り返すことができます。
これらの命令は、ES:DIおよびDS:SIのレジスタのペアを使用します。DIおよびSIレジスタには、メモリに格納されたバイトを参照する有効なオフセットアドレスが含まれます。 通常、SIはDS(データセグメント)に関連付けられ、DIは常にES(追加セグメント)に関連付けられます。
DS:SI(またはESI)およびES:DI(またはEDI)レジスタは、それぞれソースオペランドとデスティネーションオペランドを指します。 ソースオペランドはDS:SI(またはESI)にあり、デスティネーションオペランドはメモリ内のES:DI(またはEDI)にあると想定されます。
16ビットアドレスの場合、SIおよびDIレジスタが使用され、32ビットアドレスの場合、ESIおよびEDIレジスタが使用されます。
次の表は、文字列命令のさまざまなバージョンと、オペランドの想定スペースを示しています。
Basic Instruction | Operands at | Byte Operation | Word Operation | Double word Operation |
---|---|---|---|---|
MOVS | ES:DI, DS:SI | MOVSB | MOVSW | MOVSD |
LODS | AX, DS:SI | LODSB | LODSW | LODSD |
STOS | ES:DI, AX | STOSB | STOSW | STOSD |
CMPS | DS:SI, ES: DI | CMPSB | CMPSW | CMPSD |
SCAS | ES:DI, AX | SCASB | SCASW | SCASD |
繰り返しプレフィックス
REPプレフィックスは、たとえば-REP MOVSBなどの文字列命令の前に設定されると、CXレジスタに配置されたカウンターに基づいて命令を繰り返します。 REPは命令を実行し、CXを1減らし、CXがゼロかどうかをチェックします。 CXがゼロになるまで、命令処理を繰り返します。
方向フラグ(DF)は、操作の方向を決定します。
- CLD(方向フラグをクリア、DF = 0)を使用して、操作を左から右にします。
- 操作を右から左にするには、STD(方向フラグを設定、DF = 1)を使用します。
REPプレフィックスには、次のバリエーションもあります。
- REP:無条件の繰り返しです。 CXがゼロになるまで操作を繰り返します。
- REPEまたはREPZ:条件付き繰り返しです。 ゼロフラグが等しい/ゼロを示す間、操作を繰り返します。 ZFが等しくない/ゼロを示すか、CXがゼロになると停止します。
- REPNEまたはREPNZ:条件付き繰り返しでもあります。 ゼロフラグが等しくない/ゼロを示す間、操作を繰り返します。 ZFが等しい/ゼロを示したとき、またはCXがゼロに減少したときに停止します。
アセンブリ-配列
アセンブラへのデータ定義ディレクティブは、変数のストレージを割り当てるために使用されることをすでに説明しました。 変数は特定の値で初期化することもできます。 初期化された値は、16進、10進、または2進形式で指定できます。
たとえば、次のいずれかの方法で単語変数「月」を定義できます-
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B
データ定義ディレクティブは、1次元配列の定義にも使用できます。 数値の1次元配列を定義しましょう。
NUMBERS DW 34, 45, 56, 67, 75, 89
上記の定義は、それぞれ34、45、56、67、75、89の数字で初期化された6ワードの配列を宣言しています。 これにより、2x6 = 12バイトの連続したメモリ空間が割り当てられます。 最初の番号のシンボリックアドレスはNUMBERSになり、2番目の番号のシンボリックアドレスはNUMBERS + 2などになります。
別の例を取り上げましょう。 サイズ8のinventoryという名前の配列を定義し、すべての値をゼロで初期化できます。
INVENTORY DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
次のように短縮することができます-
INVENTORY DW 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
TIMESディレクティブは、同じ値への複数の初期化にも使用できます。 TIMESを使用すると、INVENTORY配列は次のように定義できます。
INVENTORY TIMES 8 DW 0
例
次の例は、3要素の値2、3、4を格納する3要素配列xを定義することにより、上記の概念を示しています。 それは配列に値を追加し、合計9を表示します-
section .text
global _start ;must be declared for linker (ld)
_start:
mov eax,3 ;number bytes to be summed
mov ebx,0 ;EBX will store the sum
mov ecx, x ;ECX will point to the current element to be summed
top: add ebx, [ecx]
add ecx,1 ;move pointer to next element
dec eax ;decrement counter
jnz top ;if counter not 0, then loop again
done:
add ebx, '0'
mov [sum], ebx ;done, store result in "sum"
display:
mov edx,1 ;message length
mov ecx, sum ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
global x
x:
db 2
db 4
db 3
sum:
db 0
上記のコードをコンパイルして実行すると、次の結果が生成されます-
9
組み立て-手順
アセンブリ言語プログラムはサイズが大きくなる傾向があるため、プロシージャまたはサブルーチンはアセンブリ言語で非常に重要です。 プロシージャは名前で識別されます。 この名前に続いて、明確に定義されたジョブを実行するプロシージャの本体が説明されています。 プロシージャの終了は、returnステートメントによって示されます。
構文
以下は、手順を定義するための構文です-
proc_name:
procedure body
...
ret
プロシージャは、CALL命令を使用して別の関数から呼び出されます。 CALL命令は、以下に示すように引数として呼び出されたプロシージャの名前を持つ必要があります-
CALL proc_name
呼び出されたプロシージャは、RET命令を使用して、呼び出し元のプロシージャに制御を返します。
例
ECXおよびEDXレジスタに保存された変数を追加し、EAXレジスタに合計を返す_sum_という名前の非常に簡単なプロシージャを作成してみましょう-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,'4'
sub ecx, '0'
mov edx, '5'
sub edx, '0'
call sum ;call sum procedure
mov [res], eax
mov ecx, msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx, res
mov edx, 1
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
sum:
mov eax, ecx
add eax, edx
add eax, '0'
ret
section .data
msg db "The sum is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
The sum is:
9
スタックデータ構造
スタックは、メモリ内の配列のようなデータ構造であり、データを格納したり、スタックの「トップ」と呼ばれる場所からデータを削除したりできます。 格納する必要があるデータはスタックに「プッシュ」され、取得されるデータはスタックから「ポップ」されます。 スタックはLIFOデータ構造です。つまり、最初に保存されたデータが最後に取得されます。
アセンブリ言語は、スタック操作のための2つの命令PUSHとPOPを提供します。 これらの指示には次のような構文があります-
PUSH operand
POP address/register
スタックセグメントに予約されているメモリ空間は、スタックの実装に使用されます。 レジスタSSおよびESP(またはSP)は、スタックの実装に使用されます。 スタックの最上部は、スタックに挿入された最後のデータ項目を指し、SS:ESPレジスタによって指し示されます。SSレジスタはスタックセグメントの先頭を指し、SP(またはESP)はスタックセグメント。
スタックの実装には、次の特性があります-
- スタックに保存できるのはバイトではなく、 words または doublewords のみです。
- スタックは逆方向に、つまり下位メモリアドレスに向かって成長します
- スタックの一番上は、スタックに最後に挿入されたアイテムを指します。最後に挿入された単語の下位バイトを指します。
レジスタの値を使用する前にスタックにレジスタの値を保存することについて説明したように、それは次の方法で行うことができます-
; Save the AX and BX registers in the stack
PUSH AX
PUSH BX
; Use the registers for other purpose
MOV AX, VALUE1
MOV BX, VALUE2
...
MOV VALUE1, AX
MOV VALUE2, BX
; Restore the original values
POP BX
POP AX
例
次のプログラムは、ASCII文字セット全体を表示します。 メインプログラムは、ASCII文字セットを表示する_display_という名前のプロシージャを呼び出します。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
call display
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
display:
mov ecx, 256
next:
push ecx
mov eax, 4
mov ebx, 1
mov ecx, achar
mov edx, 1
int 80h
pop ecx
mov dx, [achar]
cmp byte [achar], 0dh
inc byte [achar]
loop next
ret
section .data
achar db '0'
上記のコードをコンパイルして実行すると、次の結果が生成されます-
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
アセンブリ-再帰
再帰プロシージャは、それ自体を呼び出すプロシージャです。 再帰には、直接と間接の2種類があります。 直接再帰では、プロシージャは自分自身を呼び出し、間接再帰では、最初のプロシージャは2番目のプロシージャを呼び出し、2番目のプロシージャは最初のプロシージャを呼び出します。
再帰は、多数の数学的アルゴリズムで観察される可能性があります。 たとえば、数値の階乗を計算する場合を考えます。 数の階乗は式で与えられます-
Fact (n) = n *fact (n-1) for n > 0
例:5の階乗は1 x 2 x 3 x 4 x 5 = 5 x 4の階乗であり、これは再帰的な手順を示す良い例です。 すべての再帰アルゴリズムには終了条件が必要です。つまり、条件が満たされたときにプログラムの再帰呼び出しを停止する必要があります。 階乗アルゴリズムの場合、nが0のときに終了条件に到達します。
次のプログラムは、階乗nがアセンブリ言語でどのように実装されるかを示しています。 プログラムをシンプルに保つために、階乗3を計算します。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov bx, 3 ;for calculating factorial 3
call proc_fact
add ax, 30h
mov [fact], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,fact ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
proc_fact:
cmp bl, 1
jg do_calculation
mov ax, 1
ret
do_calculation:
dec bl
call proc_fact
inc bl
mul bl ;ax = al* bl
ret
section .data
msg db 'Factorial 3 is:',0xa
len equ $ - msg
section .bss
fact resb 1
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Factorial 3 is:
6
アセンブリ-マクロ
マクロを記述することは、アセンブリ言語でモジュール式プログラミングを保証する別の方法です。
- マクロは、名前で割り当てられた一連の命令であり、プログラム内のどこでも使用できます。
- NASMでは、マクロは*%macro および%endmacro *ディレクティブで定義されます。
- マクロは%macroディレクティブで始まり、%endmacroディレクティブで終わります。
マクロ定義の構文-
%macro macro_name number_of_params
<macro body>
%endmacro
ここで、_number_of_params_は数値パラメーターを指定し、_macro_name_はマクロの名前を指定します。
マクロは、必要なパラメーターとともにマクロ名を使用して呼び出されます。 プログラムで命令のシーケンスを何度も使用する必要がある場合は、常に命令を記述する代わりに、それらの命令をマクロに入れて使用できます。
たとえば、プログラムの非常に一般的な必要性は、画面に文字列を書き込むことです。 文字列を表示するには、次の一連の指示が必要です-
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
上記の文字列表示の例では、レジスタEAX、EBX、ECXおよびEDXがINT 80H関数呼び出しによって使用されています。 したがって、画面に表示する必要があるたびに、これらのレジスタをスタックに保存し、INT 80Hを呼び出して、スタックからレジスタの元の値を復元する必要があります。 そのため、データを保存および復元するための2つのマクロを作成すると便利です。
IMUL、IDIV、INTなどの一部の命令では、一部の情報を特定のレジスタに格納し、特定のレジスタに値を返す必要があることを確認しました。 プログラムがすでに重要なデータを保持するためにこれらのレジスタを使用していた場合、これらのレジスタからの既存のデータはスタックに保存され、命令の実行後に復元されます。
例
次の例は、マクロの定義と使用を示しています-
; A macro with two parameters
; Implements the write system call
%macro write_string 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
write_string msg1, len1
write_string msg2, len2
write_string msg3, len3
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $- msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
アセンブリ-ファイル管理
システムは、入力または出力データをバイトストリームと見なします。 3つの標準ファイルストリームがあります-
- 標準入力(stdin)、
- 標準出力(stdout)、および
- 標準エラー(stderr)。
ファイル記述子
- ファイル記述子*は、ファイルにファイルIDとして割り当てられた16ビット整数です。 新しいファイルが作成されるか、既存のファイルが開かれると、ファイルへのアクセスにファイル記述子が使用されます。
標準ファイルストリームのファイル記述子- stdin、stdout および stderr はそれぞれ0、1および2です。
ファイルポインタ
- ファイルポインター*は、ファイル内の後続の読み取り/書き込み操作の場所をバイト単位で指定します。 各ファイルは一連のバイトと見なされます。 開いている各ファイルは、ファイルの先頭を基準にしたバイト単位のオフセットを指定するファイルポインターに関連付けられています。 ファイルが開かれると、ファイルポインターはゼロに設定されます。
ファイル処理システムコール
次の表は、ファイル処理に関連するシステムコールを簡単に説明しています-
%eax | Name | %ebx | %ecx | %edx |
---|---|---|---|---|
2 | sys_fork | struct pt_regs | - | - |
3 | sys_read | unsigned int | char * | size_t |
4 | sys_write | unsigned int | const char* | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | unsigned int | - | - |
8 | sys_creat | const char* | int | - |
19 | sys_lseek | unsigned int | off_t | unsigned int |
システムコールを使用するために必要な手順は、前に説明したものと同じです-
- システムコール番号をEAXレジスタに入れます。
- システムコールへの引数をレジスタEBX、ECXなどに保存します。
- 関連する割り込み(80h)を呼び出します。
- 通常、結果はEAXレジスタに返されます。
ファイルを作成して開く
ファイルを作成して開くには、次のタスクを実行します-
- システムコールsys_creat()番号8をEAXレジスタに入れます。
- EBXレジスタにファイル名を入れます。
- ファイルのアクセス許可をECXレジスタに入れます。
システムコールは、作成されたファイルのファイル記述子をEAXレジスタに返します。エラーの場合、エラーコードはEAXレジスタにあります。
既存のファイルを開く
既存のファイルを開くには、次のタスクを実行します-
- システムコールsys_open()番号5をEAXレジスタに入れます。
- EBXレジスタにファイル名を入れます。
- ファイルアクセスモードをECXレジスタに入れます。
- EDXレジスタにファイルのアクセス許可を入れます。
システムコールは、作成されたファイルのファイル記述子をEAXレジスタに返します。エラーの場合、エラーコードはEAXレジスタにあります。
ファイルアクセスモードの中で、最も一般的に使用されるのは、読み取り専用(0)、書き込み専用(1)、および読み取り/書き込み(2)です。
ファイルからの読み取り
ファイルから読み取るには、次のタスクを実行します-
- システムコールsys_read()番号3をEAXレジスタに入れます。
- ファイル記述子をEBXレジスターに入れます。
- 入力バッファーへのポインターをECXレジスターに入れます。
- バッファーサイズ、つまり読み取るバイト数をEDXレジスタに入れます。
システムコールは、EAXレジスタに読み込まれたバイト数を返します。エラーの場合、エラーコードはEAXレジスタにあります。
ファイルへの書き込み
ファイルに書き込むために、次のタスクを実行します-
- システムコールsys_write()番号4をEAXレジスタに入れます。
- ファイル記述子をEBXレジスターに入れます。
- ECXレジスタに出力バッファへのポインタを置きます。
- バッファーサイズ、つまり書き込むバイト数をEDXレジスタに入れます。
システムコールは、EAXレジスタに書き込まれた実際のバイト数を返します。エラーの場合、エラーコードはEAXレジスタにあります。
ファイルを閉じる
ファイルを閉じるには、次のタスクを実行します-
- システムコールsys_close()番号6をEAXレジスタに入れます。
- ファイル記述子をEBXレジスターに入れます。
システムコールは、エラーの場合、EAXレジスタ内のエラーコードを返します。
ファイルを更新する
ファイルを更新するには、次のタスクを実行します-
- システムコールsys_lseek()番号19をEAXレジスタに入れます。
- ファイル記述子をEBXレジスターに入れます。
- ECXレジスタにオフセット値を入れます。
- EDXレジスタにオフセットの参照位置を配置します。
基準位置は次のとおりです。
- ファイルの始まり-値0
- 現在の位置-値1
- ファイルの終わり-値2
システムコールは、エラーの場合、EAXレジスタ内のエラーコードを返します。
例
次のプログラムは、_myfile.txt_という名前のファイルを作成して開き、このファイルに「Welcome to Tutorials Point」というテキストを書き込みます。 次に、プログラムはファイルから読み取り、_info_という名前のバッファーにデータを保存します。 最後に、_info_に保存されているテキストを表示します。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
;create the file
mov eax, 8
mov ebx, file_name
mov ecx, 0777 ;read, write and execute by all
int 0x80 ;call kernel
mov [fd_out], eax
; write into the file
mov edx,len ;number of bytes
mov ecx, msg ;message to write
mov ebx, [fd_out] ;file descriptor
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
; close the file
mov eax, 6
mov ebx, [fd_out]
; write the message indicating end of file write
mov eax, 4
mov ebx, 1
mov ecx, msg_done
mov edx, len_done
int 0x80
;open the file for reading
mov eax, 5
mov ebx, file_name
mov ecx, 0 ;for read only access
mov edx, 0777 ;read, write and execute by all
int 0x80
mov [fd_in], eax
;read from file
mov eax, 3
mov ebx, [fd_in]
mov ecx, info
mov edx, 26
int 0x80
; close the file
mov eax, 6
mov ebx, [fd_in]
int 0x80
; print the info
mov eax, 4
mov ebx, 1
mov ecx, info
mov edx, 26
int 0x80
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
file_name db 'myfile.txt'
msg db 'Welcome to Tutorials Point'
len equ $-msg
msg_done db 'Written to file', 0xa
len_done equ $-msg_done
section .bss
fd_out resb 1
fd_in resb 1
info resb 26
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Written to file
Welcome to Tutorials Point
アセンブリ-メモリ管理
- sys_brk()*システムコールはカーネルによって提供され、後で移動することなくメモリを割り当てます。 この呼び出しは、メモリ内のアプリケーションイメージのすぐ後ろにメモリを割り当てます。 このシステム機能を使用すると、データセクションで使用可能な最大アドレスを設定できます。
このシステムコールは、設定する必要がある最高のメモリアドレスである1つのパラメータを取ります。 この値はEBXレジスタに保存されます。
エラーが発生した場合、sys_brk()は-1を返すか、負のエラーコード自体を返します。 次の例は、動的メモリ割り当てを示しています。
例
次のプログラムは、sys_brk()システムコールを使用して16kbのメモリを割り当てます-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, 45 ;sys_brk
xor ebx, ebx
int 80h
add eax, 16384 ;number of bytes to be reserved
mov ebx, eax
mov eax, 45 ;sys_brk
int 80h
cmp eax, 0
jl exit ;exit, if error
mov edi, eax ;EDI = highest available address
sub edi, 4 ;pointing to the last DWORD
mov ecx, 4096 ;number of DWORDs allocated
xor eax, eax ;clear eax
std ;backward
rep stosd ;repete for entire allocated area
cld ;put DF flag to normal state
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 80h ;print a message
exit:
mov eax, 1
xor ebx, ebx
int 80h
section .data
msg db "Allocated 16 kb of memory!", 10
len equ $ - msg
上記のコードをコンパイルして実行すると、次の結果が生成されます-
Allocated 16 kb of memory!