ここでは、hello出力のC言語ソースのアセンブリソースを調べて、その関数呼び出しを参考に、アセンブリ(MIPS32系)言語でソースを示し、
実験出来る環境を説明しています。(実際にアセンブルできるページは、このページの最後の在るリンクから移動できます)
左下が上記の
RAM内の絶対番地指定で動作するコードのマシンコードです。
次の内容のバッチファイル実行で得られた「a.lst.txt」の一部です。
上記の
0x80005000がtest_mainのスタートアドレスです。また
NOPの位置に注目してください。
左下が上記C言語のhello出力のマシン語とニーモニックです。
右下がこの出力用「
_send_string("hello\r\n");」の記述を削除した場合のコードです。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
Disassembly of section _062726406402b2ad:
80005000 <test_main>:
80005000: 27bdffe8 addiu sp,sp,-24
80005004: afbf0014 sw ra,20(sp)
80005008: afbe0010 sw s8,16(sp)
8000500c: 03a0f021 move s8,sp
80005010: 00000000 nop
80005014: 3c02a000 lui v0,0xa000
80005018: 344241a4 ori v0,v0,0x41a4
8000501c: 8c420000 lw v0,0(v0)
80005020: 3c038000 lui v1,0x8000
80005024: 0040f809 jalr v0
80005028: 24645054 addiu a0,v1,20564
8000502c: 00000000 nop
80005030: 03c0e821 move sp,s8
80005034: 8fbf0014 lw ra,20(sp)
80005038: 8fbe0010 lw s8,16(sp)
8000503c: 03e00008 jr ra
80005040: 27bd0018 addiu sp,sp,24
Disassembly of section .rodata:
80005054 <.LC0>:
80005054: 6c6c6568 0x6c6c6568
80005058: 000a0d6f 0xa0d6f
|
|
Disassembly of section _0624b6406407ceec:
80005000 <test_main>:
80005000: 27bdfff8: addiu sp,sp,-8
80005004: afbe0004 sw s8,4(sp)
80005008: 03a0f021 move s8,sp
8000500c: 00000000 nop
80005010: 00000000 nop
80005014: 03c0e821 move sp,s8
80005018: 8fbe0004 lw s8,4(sp)
8000501c: 03e00008 jr ra
80005020: 27bd0008 addiu sp,sp,8
|
|
04
05
06
07行が、最初の起動関数の初期操作で、関数が呼び出し位置に戻るためのアドレスraレジスタに取得しています。
(このtest_mainの呼び出しは、「UMEHOSHI ITA」の「テスト・ウメ・フラッシュ」からで、
下記赤の部分の
jalr v0で呼び出ししています。)
この命令はv0レジスタ内容のアドレスにジャンプする命令で、raレジスタにその関数から戻る時の番地を記憶する機能でもあります。
MISP命令にはサブルーチン呼び出し命令が存在せずにソフトで実現しています。
それはraレジスタにサブルーチンからの戻り番地を記憶してから
サブルーチンにジャンプして、サブルーチンから戻る時にraレジスタの戻り番地を利用してメイン処理に戻ります。
このtest_main関数から戻るための処理が
16
17
18
19
20の部分です。
さて、上記左右の違いに注目してみます。左上側のtest_mainでは内部で、グローバル配列の[_IDX_API_SEND_STRING]要素の関数を呼び出しています。
対して、右側上側のtest_mainでは関数を呼び出していません。
そのために左側は、関数を呼び出しによるraレジスタの変更前に、スタックへ退避と復元のコードが
多く存在することに注目してください。
なお、上下左右ともs8(別名はfp)のフレームポインタの更新処理も行っています。
フレームポインタは現在の関数のフレームを指します。各関数は必要に応じて新しいフレームを生成し、そこから自動変数または一時変数を割り当てます。
(コンパイラオプションで、フレームポインタの利用の抑制が可能です。「umehoshiEdit」ツールではフレームポインタを利用コードが再生されています)
// 関数実行要求のが合って、送信文字列が無ければ実行
extern void (*go_func)(void);
if(go_func != NULL){
9d005d98: 8f828150 lw v0,-32432(gp)
9d005d9c: 10400006 beqz v0,9d005db8 <.LVL39+0x20>
9d005da0: 8f838134 lw v1,-32460(gp)
if(outBuffCount == 0){
9d005da4: 54600005 bnezl v1,9d005dbc <.LVL39+0x24>
9d005da8: 93828068 lbu v0,-32664(gp)
(*go_func)();// 関数実行要求の実行
9d005dac: 0040f809 jalr v0
9d005db0: 00000000 nop
go_func = NULL;
以上を参考に、同じように動作するように作ったアセンブリコードを下記に示します。
なお、s8のレジスタは、別名のfpを使っています。
(0xa000<<16) + 0x41a4で、0xa00041a4を得てv0に設定しています。
これ値が『_PTR_HANDLERS[_IDX_API_SEND_STRING]』の値で、_send_stringマクロの関数へのポインタです。
_PTR_HANDLERSが0xA0004000で、_IDX_API_SEND_STRINGが105です。それより次のように算出しました。
hex(0xA0004000+105*4)⇒'0xa00041a4'
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#include <xc.h> // mytest.S
.set noreorder #アセンブラに命令の順序を自動変更させない。
.section test_main,address(0x80005000),code
.ent test_main
test_main:
ADDIU sp,sp,-24 # 以下で使う24byteまでの退避サイスをスタックに作る。
SW ra,20(sp) # 現在の関数の戻り番地をスタックに退避
SW fp,16(sp) # 呼び出し前の関数のフレームポインタをスタックに退避
MOVE fp,sp # 現在の関数のフレームポインタをこの関数用に更新
NOP
LUI v0,0xa000 # 以下3行で_PTR_HANDLERS[_IDX_API_SEND_STRING]の内容をv0に設定する
ORI v0,v0,0x41a4
LW v0,0(v0)
LUI v1,%hi(.L_STR_HELLO) # v0の関数の引数を準備
ADDIU a0,v1,%lo(.L_STR_HELLO) # v0の関数の引数をa0レジスタに設定
JALR v0 # Raに戻り値を設置してv0の関数を呼び出す。
NOP
MOVE sp,fp # フレームポインタで退避前でspを退避前に戻す。
LW ra,20(sp) # 現在の関数の戻り番地をスタックから復元
LW fp,16(sp) # この関数の呼び出し前のフレームポインタをスタックから復元
JR ra # この関数の呼び出し後に移動(リターンに相当する)1命令遅れてジャンプするので、次の命令は実行される。
ADDIU sp,sp,24 # この関数の呼び出し前のスタックポインタに戻る
.end test_main
.section .L_STR_HELLO,address(0x80005060),code
.align 2
.L_STR_HELLO:
.ascii "hello\r\n\x00"
|
|
上記のコードを実際にアッセンブルできるページは、
ここにあります。