あなたの天然記念物
ホーム更新雑談Perl鉄ゲタランドナーコースガイド自転車Linuxリンク経歴連絡先

名前参照によらない関数呼び出し (2012.09.19)

後付けの機能を呼出元の修正なしに呼びたい

これまでに挑戦して無理やり(笑)実現しています。 メインのソースに手を入れず処理を追加する方法をごらんください。

これまでの欠点

変数名func_01~func_10のどれかに入れる方式だったので、例えば、試しに作った機能にfunc_04を使い、プロジェクトから除外、次のお試しもfunc_04を使った後、両方とも調子がよくて同時に使いたい場合、func_04が衝突してしまいます。

これからの方法

局所的な変数(cファイルの中だけのスコープ「static int a;」といった宣言)を新規セクション「.func_00」に集積、セクションの先頭アドレス、末尾(厳密には末尾の次)アドレスにラベルを付けて、呼び出し側からセクション内の変数をスキャンして呼び出します。 これからの方法

呼ばれる側の準備

FUNC_00_TYPE型の配列で呼び出して欲しいタイミングと関数を列挙します。 TAG_ENDは終端なのでタイミングに合わせて関数を並べられます。 FUNC_00_SIGNUPはこの配列をシステムに登録します。
FUNC_00_TYPE func_takoluka[] =
{
		{FUNC_MAIN, (uint32_t)takoluka_startup},
		{TAG_END, TAG_NONE}
};

FUNC_00_SIGNUP(func_takoluka);
2つのマクロの内訳です。 FUNC_00_TYPEはcファイルローカルにする、フラッシュROMを参照する仕掛けです。 constがないと起動時にBSS領域(RAM)にコピーされてしまいます。 FUNC_00_SIGNUPは配列名に_addrを加えたローカル変数を確保、配列のアドレスを入れて、セクション.func_00に配置します。
typedef struct {
	uint32_t key;
	uint32_t value;
} TAGITEM_TYPE;

#define FUNC_00_TYPE static const TAGITEM_TYPE
#define FUNC_00_SIGNUP(tagitem) static const TAGITEM_TYPE (* const tagitem##_addr) __attribute__((section(".func_00"), used)) = tagitem

新規セクションをプログラムに残す

プロジェクトの設定でリンカスクリプトを管理外にします。 リンカスクリプトを管理外に 管理中に使っていたスクリプトをコピー、セクション.FUNC_00(/* FUNC_00 SECTION */と書いてある所)を追加します。 DO NOT EDITとか書いてあるけど大丈夫かな? キーワードKEEPはリンカオプションでガベージコレクション(未参照シンボルの削除)をかけても残す指定です。 新規セクションは未参照が前提なのでKEEPがないと破棄されます。 これはDebug版とRelease版の2つ用意します(冒頭のINCLUDEに注意)。
/*
* GENERATED FILE - DO NOT EDIT
* (C) Code Red Technologies Ltd, 2008-10
* Generated linker script file for LPC1768
* Created from nxp_lpc17_c.ld (vRed Suite 3 (NXP Edition) v3.6 (3 [Build 318] [11/04/2011] ))
* By Red Suite 3 (NXP Edition) v3.6.3 [Build 318] [11/04/2011]  on Sun Sep 16 17:39:42 JST 2012
*/


INCLUDE "TestMulti_Debug_lib.ld"
INCLUDE "TestMulti_Debug_mem.ld"

ENTRY(ResetISR)

SECTIONS
{

	/* MAIN TEXT SECTION */	
	.text : ALIGN(4)
	{
		FILL(0xff)
		KEEP(*(.isr_vector))
		
		/* Global Section Table */
		. = ALIGN(4) ;
		__section_table_start = .;
		__data_section_table = .;
		LONG(LOADADDR(.data));
		LONG(    ADDR(.data)) ;
		LONG(  SIZEOF(.data));
		LONG(LOADADDR(.data_RAM2));
		LONG(    ADDR(.data_RAM2)) ;
		LONG(  SIZEOF(.data_RAM2));
		__data_section_table_end = .;
		__bss_section_table = .;
		LONG(    ADDR(.bss));
		LONG(  SIZEOF(.bss));
		LONG(    ADDR(.bss_RAM2));
		LONG(  SIZEOF(.bss_RAM2));
		__bss_section_table_end = .;
		__section_table_end = . ;
		/* End of Global Section Table */
		

		*(.after_vectors*)
		
		*(.text*)
		*(.rodata .rodata.*)
		. = ALIGN(4);
	} > MFlash512

	/* FUNC_00 SECTION */	
	.func_00 : ALIGN(4)
	{
		__section_func_00_start = .;
		KEEP(*(.func_00))
		__section_func_00_end = .;
		. = ALIGN(4);
		
	} > MFlash512

	/*
	 * for exception handling/unwind - some Newlib functions (in common
	 * with C++ and STDC++) use this.
	 */
	.ARM.extab : ALIGN(4)
	{
		*(.ARM.extab* .gnu.linkonce.armextab.*)
	} > MFlash512
	__exidx_start = .;
	
	.ARM.exidx : ALIGN(4)
	{
		*(.ARM.exidx* .gnu.linkonce.armexidx.*)
	} > MFlash512
	__exidx_end = .;
	
	_etext = .;
		
	
	.data_RAM2 : ALIGN(4)
	{
	   FILL(0xff)
		*(.data.$RAM2*)
		*(.data.$RamAHB32*)
	   . = ALIGN(4) ;
	} > RamAHB32 AT>MFlash512
	
	/* MAIN DATA SECTION */

	.uninit_RESERVED : ALIGN(4)
	{
		KEEP(*(.bss.$RESERVED*))
	} > RamLoc32

	.data : ALIGN(4)
	{
		FILL(0xff)
		_data = .;
		*(vtable)
		*(.data*)
		. = ALIGN(4) ;
		_edata = .;
	} > RamLoc32 AT>MFlash512

	
	.bss_RAM2 : ALIGN(4)
	{
		*(.bss.$RAM2*)
		*(.bss.$RamAHB32*)
	   . = ALIGN(4) ;
	} > RamAHB32

	/* MAIN BSS SECTION */
	.bss : ALIGN(4)
	{
		_bss = .;
		*(.bss*)
		*(COMMON)
		. = ALIGN(4) ;
		_ebss = .;
		PROVIDE(end = .);
	} > RamLoc32
	
	PROVIDE(_pvHeapStart = .);
	PROVIDE(_vStackTop = __top_RamLoc32 - 0);
}

呼び出し

修正後のリンカスクリプトでは新規セクション.func_00がシンボル__section_func_00_startと__section_func_00_endに挟まれていますので、スキャンして配列のアドレスを取得、配列から希望のタイミングの呼び出し関数を呼びます。なければ呼びません(そりゃ当然)。
// リンカスクリプトに追加したfunc_00セクションの先頭と末尾(の次)
void __section_func_00_start(void);
void __section_func_00_end(void);

	void *func_00;
	for (func_00 = &__section_func_00_start; func_00 < (void *)&__section_func_00_end; func_00 += 4)
	{
		void (*pFunc)(void) = (void (*)(void))tag_getvalue(*(TAGITEM_TYPE **)func_00, ulTagKey, 0);
		if (pFunc)
		{
			(pFunc)();
		}
	}

呼び出しの現状

新規セクションにはリンカが好きな順序で並べますから「必ずAが先でBを後」にしたい場合、同じ呼び出しタイミング(例:FUNC_MAIN)ではどちらが先に呼ばれるか分かりません。 先に呼ぶ、呼び出しタイミングを新設し(例:FUNC_STARTUP)そこに登録する工夫が必要になります。 今のところの呼び出しタイミングとソース名を表にしました。 表の上から順に呼び出します。 横並びになっている所はどの順に呼ばれてもよいので表の左右を入れ替えもよい訳です。 表を作って気付いたけど用意したFUNC_POWERを誰も使って無かった(苦笑)。
呼び出しタイミング ksrk_clock.c i2c_clock.c ksrk_rtc.c ksrk_startup.c Clock.c Counter.c LedFlash.c TakoLuka.c
FUNC_CLOCK        
FUNC_CLOCK_LAST              
FUNC_POWER                
FUNC_STARTUP              
FUNC_MAIN        

今回の鉄ゲタは…

このページ自体が鉄ゲタですよホントにもう(ぐすん)。 まあ、どうせ話が通じないと思うので、あとはソース見てください(^^;
yrntrlmnmnt20120919.zip (271,749 バイトをVPSから 00:05 で)