RX マイコン用 gcc を更新したので、リンカースクリプトも更新してみた。
gcc のリンカースクリプトの記述仕様は「不明」の部分が多く、それでも、C 言語
なら、そう問題にならないが、C++ の場合、事前準備が必要なので、そう簡単では
無いと思える。
また、gcc の新しい機能として、新規のセクションが必要な場合もあるかもしれず、
gcc のバージョンを変更したら、その gcc がインストールされたディレクトリーに
ある基準となるリンカースクリプトをベースにしておく必要がある。
組み込みマイコン用に、自前で gcc をビルドして使っている人の中には、かなり
昔に作られた、乖離したリンカースクリプトを使っている場合もあるようだけど、
これは、少し問題がある、何か新しい機能が新設された場合、ライブラリーが正しく
動作する保障は無いと言える。
※ C 言語のみの場合は、あまり問題にならない場合が多いかもしれないけど・・
自分の環境では:(/usr/local/rx-elf)
gcc 付属のリンカースクリプトは、「/usr/local/rx-elf/rx-elf/lib/rx.ld」にあり、
それを、基準にしている。
・「rx.ld」を観ていこう~
(1) これは、RAM、ROM領域、及びスタックを定義している、自分が使うマイコンの
メモリーマップに従って、領域を設定する。
※スタックは、下位アドレスに向かって消費するので、「LENGTH」に大きな意味は無いものと
思う。
/* This memory layout corresponds to the smallest predicted RX600 chip. */
MEMORY {
RAM (w) : ORIGIN = 0x00000000, LENGTH = 0x00020000 /* 128k */
STACK (w) : ORIGIN = 0x00020000, LENGTH = 16 /* top of RAM */
ROM (w) : ORIGIN = 0xfff40000, LENGTH = 0x000bffd0 /* 768k */
/* This is the largest RX6000: */
/* ROM (w) : ORIGIN = 0xffe00000, LENGTH = 0x001fffd0 */ /* 2Mb */
}
(2) このセクションは、プログラムコードが収められている。
.text :
{
PROVIDE (_start = .);
*(.text P .stub .text.* .gnu.linkonce.t.*)
KEEP (*(.text.*personality*))
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.interp .hash .dynsym .dynstr .gnu.version*)
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
. = ALIGN(4);
KEEP (*(.init))
KEEP (*(.fini))
} > ROM
(3) このセクションは、読み出し専用データが収められている。
.rodata : {
. = ALIGN(4);
*(.plt)
*(.rodata C C_2 C_1 W W_2 W_1 .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
*(.eh_frame_hdr)
KEEP (*(.eh_frame))
KEEP (*(.gcc_except_table)) *(.gcc_except_table.*)
. = ALIGN(4);
PROVIDE(__romdatastart = .);
} > ROM
(4) このセクションは、起動時、RAM にコピーしておく必要のあるデータなどで、ROM 領域
に配置する。
※「__romdatacopysize」が定義されている。
この中で、「.ctors」、「.dtors」と二つの重要な部分がある、これは、アプリケーション
を起動する前に準備しておく必要のあるコンストラクター、及び、アプリケーション終了時
に呼び出す、デストラクター関係である。
※通常組み込みでは、アプリケーションは終了しないので、デストラクターは呼び出す必要
が無い、もちろん、複数のアプリケーションを動的に呼び出すような機構を備えたシステム
では、デストラクターを呼び出す事は、必須であるけど。
.data : {
. = ALIGN(4);
PROVIDE (__datastart = .); /* IF_ROROM */
PROVIDE (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE (__init_array_end = .);
PROVIDE (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE (__fini_array_end = .);
LONG(0); /* Sentinel. */
/* gcc uses crtbegin.o to find the start of the constructors, so
we make sure it is first. Because this is a wildcard, it
doesn't matter if the user does not actually link against
crtbegin.o; the linker won't look for a file to match a
wildcard. The wildcard also means that it doesn't matter which
directory crtbegin.o is in. */
KEEP (*crtbegin*.o(.ctors))
/* We don't want to include the .ctor section from from the
crtend.o file until after the sorted ctors. The .ctor section
from the crtend file contains the end of ctors marker and it
must be last */
KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
KEEP (*crtbegin*.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
KEEP (*(.jcr))
*(.data.rel.ro.local) *(.data.rel.ro*)
*(.dynamic)
*(.data D .data.* .gnu.linkonce.d.*)
KEEP (*(.gnu.linkonce.d.*personality*))
SORT(CONSTRUCTORS)
*(.data1)
*(.got.plt) *(.got)
/* We want the small data sections together, so single-instruction offsets
can access them all, and initialized data all before uninitialized, so
we can shorten the on-disk segment size. */
. = ALIGN(4);
*(.sdata .sdata.* .gnu.linkonce.s.* D_2 D_1)
. = ALIGN(4);
_edata = .;
PROVIDE (edata = .);
PROVIDE (__dataend = .);
} > RAM AT> ROM
/* Note that __romdatacopysize may be ZERO for the simulator, which
knows how to intialize RAM directly. It should ONLY be used for
copying data from ROM to RAM; if you need to know the size of the
data section, subtract the end symbol from the start symbol. */
/* Note that crt0 assumes this is a multiple of four; all the
start/stop symbols are also assumed long-aligned. */
PROVIDE (__romdatacopysize = SIZEOF(.data));
(5) このセクションは、変数などの領域、通常、起動時に「0」クリアしておく。
.bss : {
. = ALIGN(4);
PROVIDE (__bssstart = .);
*(.dynbss)
*(.sbss .sbss.*)
*(.bss B B_2 B_1 .bss.* .gnu.linkonce.b.*)
. = ALIGN(4);
*(COMMON)
. = ALIGN(4);
PROVIDE (__bssend = .);
_end = .;
PROVIDE (end = .);
} > RAM
PROVIDE (__bsssize = SIZEOF(.bss) / 4);
(6) このセクションは、スタック領域
RX マイコンの場合、スタックは2つあり、ハードウェアー依存のスタック(主に、割り込み
発生の場合などで、ステータスやレジスターを退避する)と、ユーザー領域依存のスタックが
ある。
.stack (ORIGIN (STACK)) :
{
PROVIDE (__stack = .);
*(.stack)
}
(7) このセクションは、ハードウェアー依存のベクターテーブル専用領域。
※RX マイコンでは、「リセット」信号がアサートされると、「_start」から起動する。
※他に、ハードウェアー依存の例外が発生した場合に起動するベクターを記述する。
/* Providing one of these symbols in your code is sufficient to have
it linked in to the fixed vector table. */
PROVIDE (__rx_priviledged_exception_handler = 0x00000000);
PROVIDE (__rx_access_exception_handler = 0x00000000);
PROVIDE (__rx_undefined_exception_handler = 0x00000000);
PROVIDE (__rx_floating_exception_handler = 0x00000000);
PROVIDE (__rx_nonmaskable_exception_handler = 0x00000000);
.vectors (0xFFFFFFD0) :
{
PROVIDE (__vectors = .);
LONG (__rx_priviledged_exception_handler);
LONG (__rx_access_exception_handler);
LONG (0);
LONG (__rx_undefined_exception_handler);
LONG (0);
LONG (__rx_floating_exception_handler);
LONG (0);
LONG (0);
LONG (0);
LONG (0);
LONG (__rx_nonmaskable_exception_handler);
LONG (_start);
}
・それでは、「_start」関数から、「main」までにどのような手順を踏んだら良いのか?
同じディレクトリーに、「crt0.o」がありこのコードが基準となる。
このオブジェクトは、既にバイナリーになっているので、objdump などで、逆にアセン
ブリコードに戻す事で、内容を確認出来る。
rx-elf-objdump -S crt0.o
crt0.o: file format elf32-rx-le
Disassembly of section P:
00000000 <_start>:
.text
.global _start
_start:
.LFB2:
mvtc #0, psw
0: fd 77 00 00 mvtc #0, psw
/* Enable the DN bit - this should have been done for us by
the CPU reset, but it is best to make sure for ourselves. */
mvtc #0x100, fpsw
4: fd 7b 03 00 01 mvtc #256, fpsw
9: fb 02 00 00 00 00 mov.l #0, r0
mov #__stack, r0
f: fd 73 0c 00 00 00 00 mvtc #0, intb
mvtc #__vectors, intb
16: fb 12 00 00 00 00 mov.l #0, r1
mov #__datastart, r1
1c: fb 22 00 00 00 00 mov.l #0, r2
mov #__romdatastart, r2
22: fb 32 00 00 00 00 mov.l #0, r3
mov #__romdatacopysize, r3
smovf
28: 7f 8f smovf
2a: fb 12 00 00 00 00 mov.l #0, r1
mov #__bssstart, r1
mov #0, r2
30: 66 02 mov.l #0, r2
32: fb 32 00 00 00 00 mov.l #0, r3
mov #__bsssize, r3
sstr.l
38: 7f 8a sstr.l
3a: fb d2 00 00 00 00 mov.l #0, r13
/* Initialise the constant data pointer and small data pointers. */
mov #__pid_base, r13
mov #__gp, r12
#else
/* Initialise the small data area pointer. */
mov #__gp, r13
40: 05 00 00 00 bsr.a 40 <_start+0x40>
mov # _start, r1
mov # _etext, r2
bsr.a __monstartup
#endif
mov #0, r1 /* argc */
44: 66 01 mov.l #0, r1
mov #0, r2 /* argv */
46: 66 02 mov.l #0, r2
mov #0, r3 /* envv */
48: 66 03 mov.l #0, r3
4a: 05 00 00 00 bsr.a 4a <_start+0x4a>
bsr.a _main
4e: 05 00 00 00 bsr.a 4e <_start+0x4e>
00000052 <_rx_run_preinit_array>:
mov r1, r13 ; Save return code.
bsr.a __mcleanup
mov r13, r1
#endif
bsr.a _exit
52: fb 12 00 00 00 00 mov.l #0, r1
.global _rx_run_preinit_array
.type _rx_run_preinit_array,@function
_rx_run_preinit_array:
mov #__preinit_array_start,r1
58: fb 22 00 00 00 00 mov.l #0, r2
mov #__preinit_array_end,r2
5e: 04 25 00 00 bra.a 83 <_rx_run_inilist>
00000062 <_rx_run_init_array>:
bra.a _rx_run_inilist
62: fb 12 00 00 00 00 mov.l #0, r1
.global _rx_run_init_array
.type _rx_run_init_array,@function
_rx_run_init_array:
mov #__init_array_start,r1
68: fb 22 00 00 00 00 mov.l #0, r2
mov #__init_array_end,r2
mov #4, r3
6e: 66 43 mov.l #4, r3
70: 04 13 00 00 bra.a 83 <_rx_run_inilist>
00000074 <_rx_run_fini_array>:
bra.a _rx_run_inilist
74: fb 22 00 00 00 00 mov.l #0, r2
.global _rx_run_fini_array
.type _rx_run_fini_array,@function
_rx_run_fini_array:
mov #__fini_array_start,r2
7a: fb 12 00 00 00 00 mov.l #0, r1
mov #__fini_array_end,r1
mov #-4, r3
80: fb 36 fc mov.l #-4, r3
00000083 <_rx_run_inilist>:
/* fall through */
_rx_run_inilist:
next_inilist:
cmp r1,r2
83: 47 12 cmp r1, r2
85: 20 17 beq.b 9c
beq.b done_inilist
mov.l [r1],r4
87: ec 14 mov.l [r1], r4
cmp #-1, r4
89: 75 04 ff cmp #-1, r4
8c: 20 0c beq.b 98
beq.b skip_inilist
cmp #0, r4
8e: 61 04 cmp #0, r4
90: 20 08 beq.b 98
beq.b skip_inilist
pushm r1-r3
92: 6e 13 pushm r1-r3
jsr r4
94: 7f 14 jsr r4
popm r1-r3
96: 6f 13 popm r1-r3
00000098 :
skip_inilist:
add r3,r1
98: 4b 31 add r3, r1
9a: 2e e9 bra.b 83 <_rx_run_inilist>
0000009c :
bra.b next_inilist
done_inilist:
rts
9c: 02 rts
Disassembly of section .fini:
00000000 <__rx_fini>:
.text
.global _start
_start:
.LFB2:
mvtc #0, psw
0: 05 00 00 00 bsr.a 0 <__rx_fini>
00000003 :
...
ここから、必要な部分をコピーして、また、自分のシステムに必要な部分を追加して、スタートアップ
ルーチンを構築するのが良いと思う。
で、RX63T(RAM: 8KB, ROM: 64KB) 用のリンカースクリプトと、スタートアップルーチンを作成してみた。
RX63T 用リンカースクリプト
スタートアップルーチン「start.s」
初期化関数「init.c」
※自分の実装では、「スタートアップ」は、「start.s」と「init.c」に分けている。