名前参照によらない関数呼び出し
投稿: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に注意)。
管理中に使っていたスクリプトをコピー、セクション.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 | ● | ● | ● | ● |
