Assembly-programming-quick-guide

提供:Dev Guides
移動先:案内検索

組み立て-はじめに

アセンブリ言語とは何ですか?

各パーソナルコンピューターには、コンピューターの算術、論理、および制御アクティビティを管理するマイクロプロセッサーがあります。

プロセッサの各ファミリには、キーボードからの入力の取得、画面上の情報の表示、その他のさまざまなジョブの実行など、さまざまな操作を処理するための独自の命令セットがあります。 これらの命令セットは「マシン言語命令」と呼ばれます。

プロセッサは、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
  • When two bytes are multiplied −*

被乗数はALレジスタにあり、乗数はメモリまたは別のレジスタのバイトです。 製品はAXにあります。 製品の上位8ビットはAHに格納され、下位8ビットはALに格納されます。

算術1

2

When two one-word values are multiplied −

被乗数はAXレジスタにある必要があり、乗数はメモリ内のワードまたは別のレジスタです。 たとえば、MUL DXのような命令の場合、乗数をDXに、被乗数をAXに保存する必要があります。

結果の積はダブルワードであり、2つのレジスタが必要です。 上位(左端)の部分はDXに格納され、下位(右端)の部分はAXに格納されます。

Arithmetic2

3

When two doubleword values are multiplied −

2つのダブルワード値を乗算する場合、被乗数はEAXである必要があり、乗数はメモリまたは別のレジスタに格納されているダブルワード値です。 生成された製品はEDX:EAXレジスタに保存されます。つまり、上位32ビットがEDXレジスタに保存され、下位32ビットがEAXレジスタに保存されます。

Arithmetic3

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レジスタに行きます。

Arithmetic4

2

When the divisor is 1 word −

配当は32ビット長で、DX:AXレジスタ内にあると想定されます。 上位16ビットはDXにあり、下位16ビットはAXにあります。 除算後、16ビットの商はAXレジスタに送られ、16ビットの余りはDXレジスタに送られます。

算術5

3

When the divisor is doubleword −

被除数は64ビット長で、EDX:EAXレジスタにあると想定されています。 上位32ビットはEDXにあり、下位32ビットはEAXにあります。 除算後、32ビットの商はEAXレジスタに送られ、32ビットの余りはEDXレジスタに送られます。

算術6

次の例では、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!