RX マイコン用リンカースクリプトとスタートアップ

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」に分けている。