UMEHOSHI ITA TOP PAGE

ここでは、hello出力のC言語ソースのアセンブリソースを調べて、その関数呼び出しを参考に、アセンブリ(MIPS32系)言語でソースを示し、 実験出来る環境を説明しています。(実際にアセンブルできるページは、このページの最後の在るリンクから移動できます)

hello表示の関数呼び出し(XC32アセンブリ)

UMEHOSHI ITA基板で使っている制御チップのPIC32MX270F256B用(MIPS32系)アセンブリに必要な情報の概要です。

Cコンパイラが生成するアセンブラリストを調べる

下記のhelloを表示させるプログラムを逆アセンブルして動作を検証して、そのアセンブリソースを示しています。
#include <xc.h>	// test.c
#include "common.h"

// RAMで動かす場合の絶対アドレス範囲は、(0x80005000〜0x80008000)です。
__attribute__((address( 0x80005000 ))) void test_main (void);
void test_main(){
	__asm__ ("NOP"); // 始まりを分かりやすくするためのダミーコード
	_send_string("hello\r\n");// USBへ文字列送出マクロ「(((void (**)(char *) )_PTR_HANDLERS)[_IDX_API_SEND_STRING](s))」
	__asm__ ("NOP"); // 終わりを分かりやすくするためのダミーコード
}

左下が上記の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"
上記のコードを実際にアッセンブルできるページは、ここにあります。