Cプログラムをアセンブリプログラムに変換
Copyright(C) 3Sep2008
Copyright(C)
22Dec2003
coskx
【1】はじめに
この文書は「H8/3048のC言語で書かれたプログラム」を「アセンブリ言語で書かれたプログラム」にコンパイラを使って変換する方法を紹介し,その後でコンパイラが書き出したアセンブリプログラムを考察する。
ここで使用するコンパイラは日立コンパイラVer2である。日立コンパイラVer1にはこのC→ASM変換機能がないためできない。
サンプルのダウンロード
ここのサンプルではリンカオプションなどが考慮されていないため,実際に動かすことはできない。
【2】変換の方法
サンプルを解凍すると,LEDを点滅させるプログラム「led1st.c」が入っている。
このCプログラム「led1st.c」のアイコンを,「c2asm.cmd」にドラッグ&ドロップすると,「led1st.src」と「led1st.lst」が作られる。
「led1st.src」がアセンブリプログラムとなっている。「led1st.lst」はCソースリストである。
どちらも,テキストファイルなので,通常のエディタで中を見ることができる。
【3】サンプルプログラムの変換
「led1st.c」と「led1st.src」を見比べてみよう。
led1st.c (LED点滅プログラム) |
/* msecwait関数で1秒ごとのLEDのON-OFFを行う */ void msecwait(int
msec) main() |
led1st.src (コンパイラによって生成されたもの) |
.CPU
300HA .EXPORT _msecwait .EXPORT _main .SECTION P,CODE,ALIGN=2 ;*** File led1st.c , Line 4 ; block _msecwait: ; function: msecwait ;*** File led1st.c , Line 6 ; block ;*** File led1st.c , Line 8 ; expression statement SUB.W R1,R1 ;*** File led1st.c , Line 8 ; for BRA L11:8 L10: ;*** File led1st.c , Line 8 ; block ;*** File led1st.c , Line 9 ; expression statement MOV.W #3352:16,E0 ;*** File led1st.c , Line 9 ; do L12: ;*** File led1st.c , Line 9 ; expression statement DEC.W #1,E0 BNE L12:8 ;*** File led1st.c , Line 8 ; expression statement INC.W #1,R1 L11: ;*** File led1st.c , Line 8 ; expression statement CMP.W R0,R1 BLT L10:8 ;*** File led1st.c , Line 11 ; block RTS ;*** File led1st.c , Line 13 ; block _main: ; function: main ;*** File led1st.c , Line 14 ; block MOV.W #1000:16,R6 ;*** File led1st.c , Line 18 ; expression statement MOV.B #3:8,R0L MOV.B R0L,@16777160:8 ;*** File led1st.c , Line 19 ; while L13: ;*** File led1st.c , Line 19 ; block ;*** File led1st.c , Line 22 ; expression statement BSET.B #0,@16777162:8 ;*** File led1st.c , Line 24 ; expression statement BCLR.B #1,@16777162:8 ;*** File led1st.c , Line 25 ; expression statement MOV.W R6,R0 BSR _msecwait:8 ;*** File led1st.c , Line 27 ; expression statement BCLR.B #0,@16777162:8 ;*** File led1st.c , Line 29 ; expression statement BSET.B #1,@16777162:8 ;*** File led1st.c , Line 30 ; expression statement MOV.W R6,R0 BSR _msecwait:8 ;*** File led1st.c , Line 19 ; expression statement BRA L13:8 ;*** File led1st.c , Line 32 ; block .END |
いくつかの注釈
「.」で始まると疑似命令(機械語に翻訳されるわけではないので疑似命令と呼ばれる)
.CPU
300HA 対象とするCPUの設定
CPUはH8/300H(H8/3048はその1つ)でアドバンストモード
.EXPORT
_msecwait この名前は外部からの参照を許す(別のソースファイルから参照可能)
.EXPORT
_main 同上
Cプログラムソースのでの名前「main」はアセンブリソースでは
「_main」のように変化している。
.SECTION
P,CODE,ALIGN=2 Pセクションが始まる。
Pセクションがどこに置かれるかはリンカスクリプト(.sub)に書いてあり
リンカによって配置される。
ALIGN=2というのは「メモリ偶数番地からコードを置く」の意味。
(メモリ偶数番地からコードを置くと,メモリから命令を取り出しやすい)
「;」があると,その行の「;」以降は注釈
行の先頭から書かれている「xxxx:」はラベル(ジャンプ先や変数名)
ラベルは命令や値の書かれているアドレスを表わしている。
行の先頭に1つ以上のスペースまたはタブがあると実行命令
「「#」+値」 その値を表す。
値が何バイト(何ビット)で構成されているのかが大事であり,
これは,
「.B」なら「Byte」なので1バイト幅,
「.W」なら「Word」なので2バイト幅,
「.L」なら「Long word」なので4バイト幅
の値を表すことで解決している。
例えば,「MOV.W
#1000:16,R6」は16ビットであらわされている値1000をWord単位(2バイト)で
レジスタR6に格納している。
「「@」+「ラベル」」(「「@」+「変数名」」,「「@」+「アドレス」」も同じ意味である) メモリ上のそのアドレスにある値を表す。
アドレスはある1バイトを指しているので,値が何バイト(何ビット)で構成されているのかが大事である。
(C言語のポインタ変数は,どの型をさすポインタかを明らかにしている。)
これは,
「.B」なら「Byte」なので1バイト幅,
「.W」なら「Word」なので2バイト幅,
「.L」なら「Long word」なので4バイト幅
の値を表すことで解決している。例えば「MOV.B
R0L,@16777160:8」はレジスタR0LをByte単位で,
アドレス16777160へ格納せよの意味になる。「:8」は機械語に変換する際にアドレスを8ビットで表せの意味だが
まともに考えると16777160を8ビットで表せるわけがないが,H8では特別なアドレスになっていて機械語では
修飾語句とともに8ビットであらわされるので問題ない。
不要な行を取り去って,プログラムの実行部分を解釈してみる。
led1st.srcの主要部分 |
_msecwait:
; function:
msecwait 引数はR0に入っている |
【4】和と差のプログラム
グローバル変数で和と差を求めるCプログラムをアセンブリプログラムに変換してみよう。
H8コンパイラはintは16bit扱いである。(int =
short int)
sumdiff.c |
int data1=123; int main() |
sumdiff.src |
.CPU
300HA .EXPORT _data1 .EXPORT _data2 .EXPORT _sum .EXPORT _diff .EXPORT _main .SECTION P,CODE,ALIGN=2 ;*** File sumdiff.c , Line 6 ; block _main: ; function: main ;*** File sumdiff.c , Line 7 ; block ;*** File sumdiff.c , Line 8 ; expression statement MOV.W @_data1:24,R0 MOV.W @_data2:24,R1 ADD.W R1,R0 MOV.W R0,@_sum:24 ;*** File sumdiff.c , Line 9 ; expression statement MOV.W @_data1:24,R0 SUB.W R1,R0 MOV.W R0,@_diff:24 ;*** File sumdiff.c , Line 10 ; block RTS .SECTION D,DATA,ALIGN=2 _data1: ; static: data1 .DATA.W H'007B _data2: ; static: data2 .DATA.W H'01C8 .SECTION B,DATA,ALIGN=2 _sum: ; static: sum .RES.W 1 _diff: ; static: diff .RES.W 1 .END |
プログラムはPセクションに,初期値のある変数はDセクションに,初期値のない変数はBセクションに置かれているのがわかる。
各セクションがメモリ上のどこのアドレスに置かれるかは,リンカスクリプトに書いてあり,リンカが決めてくれる。
_data1:
; static: data1 .DATA.W H'007B |
変数data1をワードサイズ(2バイト)で確保し, 値0x7B(=123)を設定する |
_sum:
; static: sum .RES.W 1 |
変数sumをワードサイズ(2バイト)で確保する。 「RES」は「Reserves data area」の意味 |
sumdiff.srcの主要部分 |
_main:
; function:
main |
演習課題
1.sumdiff.cにおいてlong
int型の変数に変更して,アセンブリプログラムに変換して,主要部分を説明しなさい。
int型変数,long int型変数は,何バイトの値として扱われているか。アセンブリプログラムから読み取りなさい。
sumdiff.srcとここで作成したアセンブリプログラムでは,どこが異なるのか,またそれはなぜか考察しなさい。
(C2asm01.txt)
【5】メモリブロックの転送とフィル
メモリのある領域を別の領域にコピーする作業と,ある領域を特定のデータで埋めるプログラムを考えよう。
ポインタを用いて,srcの10バイトをdstにコピーし,tgtの10バイトをすべて0x55で埋める。
memoryblock.c |
char src[10]={0,1,2,3,4,5,6,7,8,9}; main() |
memoryblock.src |
.CPU
300HA .EXPORT _src .EXPORT _dst .EXPORT _tgt .EXPORT _main .SECTION P,CODE,ALIGN=2 ;*** File memorybloc, Line 5 ; block _main: ; function: main PUSH.L ER4 ;*** File memorybloc, Line 6 ; block ;*** File memorybloc, Line 9 ; expression statement MOV.L #_src:32,ER4 ;*** File memorybloc, Line 10 ; expression statement MOV.L #_dst:32,ER1 ;*** File memorybloc, Line 11 ; expression statement MOV.W #10:16,E0 ;*** File memorybloc, Line 11 ; do L11: ;*** File memorybloc, Line 11 ; block ;*** File memorybloc, Line 12 ; expression statement MOV.B @ER4,R0L MOV.B R0L,@ER1 ;*** File memorybloc, Line 13 ; expression statement INC.L #1,ER1 ;*** File memorybloc, Line 14 ; expression statement INC.L #1,ER4 ;*** File memorybloc, Line 11 ; expression statement DEC.W #1,E0 BNE L11:8 ;*** File memorybloc, Line 16 ; expression statement MOV.L #_tgt:32,ER1 ;*** File memorybloc, Line 17 ; expression statement MOV.W #10:16,E0 ;*** File memorybloc, Line 17 ; do L12: ;*** File memorybloc, Line 17 ; block ;*** File memorybloc, Line 18 ; expression statement MOV.B #85:8,R0L MOV.B R0L,@ER1 ;*** File memorybloc, Line 19 ; expression statement INC.L #1,ER1 ;*** File memorybloc, Line 17 ; expression statement DEC.W #1,E0 BNE L12:8 ;*** File memorybloc, Line 21 ; block POP.L ER4 RTS .SECTION D,DATA,ALIGN=2 _src: ; static: src .DATA.B H'00,H'01,H'02,H'03,H'04,H'05,H'06,H'07,H'08,H'09 .SECTION B,DATA,ALIGN=2 _dst: ; static: dst .RES.B 10 _tgt: ; static: tgt .RES.B 10 .END |
配列であっても,初期化されている変数はセクションDに,初期化されていない変数はセクションBに割り当てられている。
_src:
; static: src .DATA.B H'00,H'01,H'02,H'03,H'04,H'05,H'06,H'07,H'08,H'09 |
変数srcをバイトサイズで10個確保し, 値0,1,2,3,4,5,6,7,8,9を設定する |
memoryblock.srcの主要部分 |
_main:
; function: main |
★注1 例えば,メモリのアドレスff800に1バイトの値123があり,レジスタER4はff800になっていた場合,
「MOV.B
@ER4,R0L」ではER4の保存値をアドレスとみなし,そのアドレスに保存されている値123が,Byte幅で移動し,8ビット幅のレジス
タR0Lの値になる。
メモリのアドレスff800に1バイトの値0x45があり,ff801に値0x78があり,レジスタER4はff800になっていた場合,
「MOV.W
@ER4,R0」ではER4の保存値をアドレスとみなし,そのアドレスのメモリのword幅(連続した2バイト)の内容0x4578が,word幅で移動し,16ビット幅のレジスタR0の値になる。
★mov命令の表記例
mov.w #123:16,r0
16ビット幅の値123をr0に保存
mov.w @123:24,r0 24ビット幅で表わされるアドレス123に保存されている値をr0に保存
正確にはアドレス123から始まるWord幅の値(2バイトの値)
mov.w @abc:24,r0 ラベルabcで示され,24ビット幅で表わされるアドレスに保存されている値をr0に保存
mov.w @er3,r0 レジスタer3(32ビット)に保存されている値をアドレスとみなし,そのアドレスに保存
されている値をr0に保存
【6】関数呼び出し(2引数)と関数の戻り値
int型(short int)の引数を2つ受けて,int型(short
int)の値を返す関数を呼び出すところを見てみよう。
コンパイラが,引数を持つ関数を機械語に変換する時は常に同じルールで変換する。また,関数値を持ち帰る場合についても,常に同じルールで変換する。
このルールがあるから,一度作成した関数を,別なプログラムで再利用してもうまく動作することができる。
多くのライブラリ関数は,機械語の関数となって,提供されているが,このルールが守られてる。
このルールはコンパイラのマニュアルに書いてあるが,自分でテストプログラムを作成してルールを読み取ってみる。
Cからアセンブリで書かれた関数を呼び出す場合や,アセンブリプログラムからCで書かれた関数を呼び出す場合には,ここにあるような簡単な検証で,関数呼び出しのルールを読み取ってから作成することになる。
add.c |
int data1=128; int addfunc(int a, int
b) main() |
add.src |
.CPU
300HA .EXPORT _data1 .EXPORT _data2 .EXPORT _sum .EXPORT _addfunc .EXPORT _main .SECTION P,CODE,ALIGN=2 ;*** File add.c , Line 5 ; block _addfunc: ; function: addfunc ;*** File add.c , Line 6 ; block MOV.W R0,R1 ;*** File add.c , Line 8 ; expression statement ADD.W E0,R1 ;*** File add.c , Line 9 ; return MOV.W R1,R0 ;*** File add.c , Line 10 ; block RTS ;*** File add.c , Line 12 ; block _main: ; function: main ;*** File add.c , Line 13 ; block ;*** File add.c , Line 14 ; expression statement MOV.W @_data2:24,E0 MOV.W @_data1:24,R0 BSR _addfunc:8 MOV.W R0,@_sum:24 ;*** File add.c , Line 15 ; block RTS .SECTION D,DATA,ALIGN=2 _data1: ; static: data1 .DATA.W H'0080 _data2: ; static: data2 .DATA.W H'0100 .SECTION B,DATA,ALIGN=2 _sum: ; static: sum .RES.W 1 .END |
add.srcの主要部分 |
_addfunc:
; function:
addfunc |
_mainの中で,第1引数はR0,第2引数はE0に積んで,関数addfunc()に行く。
関数addfunc()の中では,戻り値をR0に載せて,_mainに戻る。
これらの様子はコンパイラに依存する。(コンパイラマニュアルを参照)
関数addfunc()は無駄があります。第1引数R0,第2引数E0の和をR0に戻せばよいのだから,次のように書けるはずである。
関数addfuncの改善 |
_addfunc:
; function:
addfunc |
このように,Cコンパイラが作成した冗長なアセンブリプログラムを手で最適なプログラムに直すことを「hand optimize」と言う。
演習課題
3.次のCプログラムを,アセンブリプログラムに変換して,主要部分を説明しなさい。また
4つの関数がどのようにアセンブリ言語に変換されているか違いを調べ,気づいた事柄・考察した事柄を述べなさい。1行ずつの説明はなくてもよい。特に第1
引数,第2引数,...はどのようにして関数に運ばれるのか,関数の戻す値はどのように運ばれるのかに着目して説明しなさい。 引数の取り扱い,戻り値の取り扱いのルールを明らかにしておくと,Cプログラムからアセンブリで記述した関数を呼び出す場合や,アセンブリプログラムからCで書いた関数を呼び出す時の作法がわかる。 ヒント1 0x123,0x456,0x789は10進表現では...
|