アセンブリ言語入門 その7
今回は、配列と文字列をやっていこうと思います。
配列
配列は、メモリ内の連続した場所に格納されるため簡単にアクセスできます。
スタックは使わないです。
例えば、int型の4つの要素を含む配列だったら、C言語で
int nums[4]={1,2,3,4}
と表せます。同じ処理をアセンブラでしようとすると、4つの要素しかないのにも関わらず、5箇所のメモリアドレスを使用します。これが文字列だったら最後尾にNULL文字が格納されているからとわかりますが、これは配列なのでNULL文字は含みません。
では、なぜ5箇所もメモリアドレスを使用するのかというと、
配列名(1つ)と配列の要素(4つ)を格納するからです。
配列が呼び出されると、まず最初に配列名のメモリアドレスを指します。
配列名のメモリアドレスは、nums[1]のメモリアドレスを指します。
そして、nums[1]のメモリアドレスは数字とnums[2]へのメモリアドレスを指します。
このように、数珠つなぎのように配列は構成されています。
そのため、アセンブリの配列要素は、
・配列のベースアドレス(配列名のアドレスのこと)
・要素のインデックス
・配列内の各要素のサイズ
[配列名 + 0 * <バイト表記による各要素のサイズ>]
に変換されます。
バイト表記による各要素のサイズとは、C言語だと
1バイト char
2バイト short int
4バイト int
long int
float
8バイト double
long double
っていう感じです。実際にアセンブリで先程のCのコードを書いていきます。
nums[0] = [nums+0*4] = [0x4000+0*4] = [0x4000] = 1
nums[1] = [nums+1*4] = [0x4000+1*4] = [0x4004] = 2
nums[2] = [nums+2*4] = [0x4000+2*4] = [0x4008] = 3
nums[3] = [nums+3*4] = [0x4000+3*4] = [0x40012] = 4
➀ ➁ ➂ ➃ ➄
こんな感じになります。たくさん文字が書いてあって、分からなくなるかもしれないですが、 落ち着いて見ていきましょう。
説明が難しいので左から順に➀、➁、➂、➃、➄としますね。
➀は、配列のインデックスを指してます。
➁は先程説明した通りです。*4になっているのは、int型が4バイトだからです。
➂は、nums[0]のメモリアドレスからどれだけ離れているかを示しています。
でも、構造的には➁と同じで配列名がnums[0]のメモリアドレスに変わっただけです。
➃は、➂の[ ]内の計算をしたものでそれぞれの要素のメモリアドレスです。
➄は、配列の要素です。
また、メモリアドレスに 16進数が含まれている場合は、それは配列になります。
難しそうに見えますが、落ち着いてみると意外とわかります。
文字列
配列には文字列も含まれます。
文字列といっても配列が分かっていれば、大丈夫です。
文字列は、文字の配列なので最後尾にNULL文字が入ります。ということと、新しい命令をいくつか覚えていくだけです。
文字列命令の大きな特徴としては、b,w,dのいずれかが、それぞれの命令の後ろに来ることです。
bは、1バイトの文字を操作し、wは2バイトを、dは4バイトの文字データを扱います。
↓1文字あたりのバイトがまとめられていてわかりやすいです。
https://software.fujitsu.com/jp/manual/manualfiles/m150018/b1ws1136/04z200/b1136-i-03-04.html
文字列を扱うレジスタは、EAX,ESI,EDIで、ESI・EDIレジスタは文字列演算を実行シた後、方向フラグ(df)によってインクリメント(i++)か、デクリメント(i--)されます。
CLD命令は、この方向フラグをクリアします。
すると、ESI・EDIレジスタはインクリメントされます。
逆に、STD命令は方向フラグを設定するためESI・EDIレジスタはデクリメントされます。
MOVSX(全般・MOVSB(1バイト・MOVSW(2バイト・MOVSD(4バイト)命令
これらのはESIレジスタで指定されたアドレスからEDIレジスタで指定されたアドレスに決められたバイト数移動する。
REP命令
上の命令たちは、1文字しか移動できないので、マルチバイトの内容をコピーするためには、上の命令たちの前にREP命令を置く。そしたら、NULL文字も一緒にいどうされる。
STOSX(全般・STOSB(1バイト・STOSW(2バイト・STOSD(4バイト)命令
それぞれのレジスタから、EDIで指定されたメモリアドレスへバイトを移動する。
では、最後の総復習としてアセンブリを読んで「アセンブラ言語入門」シリーズを終わりにしましょう。
総復習
push ebp
結構長いですよね。。
mov ebp, esp
sub esp, 14h
mov dword ptr [ebp-14h], 1
mov dword ptr [ebp-11h], 2
mov dword ptr [ebp-1ch], 3
mov dword ptr [ebp-5], 0
loc_401023:
cmp dword ptr [ebp-5], 3
jge loc_40103E
mov eax, [ebp-5]
mov ecx, [ebp+eax*4-14h]
mov [ebp-9], ecx
mov edx, [ebp-5]
add edx, 1
mov [ebp-4], edx
jmp loc_401023
loc_40103E:
xor eax, eax
mov esp, ebp
pop ebp
ret
でも、1行ずつしっかりと落ち着いてみれば読み解けるはずです。
では、さっそく読み解いていきましょー
1,
メモリアドレスをそれぞれ分かりやすく変えます。
loc_401023:は、while文だと分かるのでwhileに書き換えます。
dword ptrは、プログラムの動きに直接は関係ないから消します。
loc_40103Eがなんの処理かわかりませんが、関数なので somethingと書き換えます
14hを10進数に直します。
push ebp
mov ebp, esp
sub esp, 20
mov a, 1
mov b, 2
mov c, 3
mov d, 0
while:
cmp d, 3
jge something
mov eax, d
mov ecx, [ebp+eax*4-20]
mov e, ecx
mov edx, d
add edx, 1
mov d, edx
jmp while
something:
xor eax, eax
mov esp, ebp
pop ebp
ret
大体はこれで読みやすくなりました。
それぞれがどんな動きをしているかざっくり見ると、
1番上のセクションは、something関数を使うための関数インポートとそれぞれのメモリアドレスに値を格納しています。
2番目のwhileセクションは、
jgeは、より大きいか等しいときにsomethingにジャンプするので、
dが3より小さいときだけ処理が続けられるっていうことです。
処理の内容は今は少し大変なので下の2、で読み解いていきたいと思います。
3番目のsomething関数は、
return 0;
関数エピローグですね。特に何もしていません。
2, 高級言語に翻訳していく
実質的に関係のないプログラムを消す
メモリアドレスに16進数のものを配列に変える。
sub esp, 20 ;ESP=20
mov a, 1 ;anon[0]=1
mov b, 2 ;anon[1]=2
mov c, 3 ;anon[2]=3
mov d, 0 ;d=0
while:
cmp d, 3 ;while(d<3){
jge something
mov eax, d ;EAX=d=0
mov ecx, [ebp+eax*4-14h] ;ECX=ebp[eax*4-20]
mov e, ecx ;e=ECX
mov edx, d ;EDX=d
add edx, 1 ;EDX=EDX+1
mov d, edx ;d=EDX
jmp while ;}
something:
xor eax, eax ;return 0
ret
3,より詳しくまとめる
anon[0]=1
anon[1]=2
anon[2]=3
d=0
while(d<3){
EAX=d
ECX=anon[anon+d*4]
e=ECX
EDX=d
d=d+1
}
return 0;
これからも他のシリーズやっていくのでお願いします。
もし、このシリーズに付き合ってくれた人がいたのなら、
本当にありがとうございました。