16.05.24 アセンブリ言語の概要


アセンブリ言語で足し算ができるようになるまで

GASで行うことを前提として記述していく。

  • アセンブリ言語で必要な用語
  • アセンブリ言語で行われることの流れ
  • 実際にコードを書いて計算をしてみる

アセンブリ言語で必要な用語

CPU

パソコンの処理を行うためのもの。

メモリ(主記憶装置)

CPUで処理を行うための命令を一時保管しておくところ。

bit

2進数(0か1の数字)で定義されていることが前提で、2bitだったら箱が2つ並んでいる・8bitだったら箱が8つ並んでいる...といったイメージ。

アセンブリ言語

機械語を人間に読みやすくしたプログラミング言語。(機械語と1対1で対応している。)

機械語

パソコンが処理を実行するときに読み込むことができる言葉。数字の列で表現されている。

アセンブリ言語の命令

ニーモック 転送元(ソース・オペランド), 転送先(デスティネーション・オペランド)
といった文型で記述される。

ニーモック

アセンブリ言語では、どのような命令をするかをこのニーモックで決める。
例:movlというニーモック
movl(ニーモック) → mov(オペコード) + l(サフィックス)

オペコード

ニーモックで行う命令を決めるためのもの。引数がついていてこれをオペランドという。

サフィックス

オペランドとして扱うデータサイズを指定する指示語。

サフィックス 詳細
b バイト(8bit)
s ショート(16bit整数)またはシングル(32bit浮動小数点数)
w ワード(16bit)
l ロング(32bit整数または64bit浮動小数点数)
q クワッド(64bit)

レジスタ

CPU内にもメモリがあって、そこに記憶されている変数のようなもの。計算するときには、このレジスタに数字を代入して計算を行う。また、レジスタには「%」をつけて使用する。

スタック


見てわかるように後から入れたものの方が先に出すことができる。この方法をデータを格納するために利用したのがスタック。スタックは基本的に何かのデータを一時的に保管するためにある。一時的にデータをスタックに積んでおいて、レジスタを別の目的に使用した後に、スタックからデータを復元する、といったように使用される。
そのため何かの処理を行うためには、一回スタックにデータを保存しておく必要がある。

セグメント

今回の内容では特には使わない。
レジスタは16bitでのデータの送信を行う。16進数で考えたとしても4桁分までしか一回で記憶できない。そこで複数回に分けて送信することで4桁分までしか記憶できなかったものがより大きな桁数まで記憶できるようになる。その際に送信した後は値の足し算をして元の値と同じ状態に戻してあげる。

もし6桁のデータを送信したかったら(例えば:19f0d8というデータ)
1. 6桁のデータを4桁、2桁にデータを分ける。(19f0 と d8)
2. 分けたデータの少ない方のデータに0を追加して桁数を4桁に揃える。(d8 → 00d8)
3. データを送信後、2つのデータを足し算して元の値と同じ状態にしてあげる。
4. 足し算して同じ値にするために桁数の調整を行う。(イメージとしては、19f0 → 19f000 または 00d8 → 0000d8)
5. データの足し算をしてあげて、元の値と同じにする。(19f000 + 00d8 または 19f0 + 0000d8 後者は先頭の桁数を基準として計算する。)

このときの1の処理で分けたデータの1つ目のものをセグメント、2つ目のものをオフセットという。

アセンブリ言語で行われていることの流れ

まず、足し算を行うのはどういった仕組みになっているのかの概要
これは非常に単純な作業で、理解するのは簡単。
1. レジスタAに2を格納する。int A = 2
2. レジスタAに3を足し合わせる。A + 3
3. その値をレジスタAに格納する。A = A + 3(A + 3は2で行った作業のこと)
今回の関数の中身はこの足し算をするための処理。

そして、関数を呼び出された時に今の処理を行っている位置を保持して、またその場所に返ってこなければならない。そのために、アドレスを格納して覚えておく必要がある。

このことを踏まえて一連の流れを復習すると
1. レジスタに格納されている返りのアドレスをスタックに保存しておく。
2. そのスタックの位置を記憶しておく。
3. 戻り値のあるレジスタに 2 を格納する。
4. 2 を格納したレジスタに +3 をする。
5. 返りのアドレスが格納されているレジスタを取ってくる。
6. 返るための処理をする。

実際にコードを書いて計算してみる

今回は 2+3 をしてその解を返す関数を作る。

calculation.s
function:
 push %ebp   # 呼び出された時点で返るアドレスが格納されているのでそれを忘れないためにスタックに保存しておく
 movl %esp, %ebp   # espレジスタでスタックの一番上の場所を覚えておく

 movl $2, %eax   # eax = 2
 addl $3, %eax   # eax += 3 (eax = 5 になっている)

 # movl %ebp, %esp
 # popl %ebp
 leave   # 関数の復帰処理をまとめたもの(上の二つの処理)
 ret   # 元いたアドレスの場所に戻るための処理

ebp, esp, eaxは全てレジスタ。
ebp:返るアドレスが指定されている
esp:ebpがスタックされたときの位置を記憶しておくもの
eax:戻り値を持つレジスタ

参照ページ

http://capm-network.com/?tag=GAS
http://ings.sakura.ne.jp/prog/asm1.html