リンケイジエディタをカスタマイズする


一抹の不安を抱えつつコンパイル&ゴー!

ここまでの段階で、一応
#include <stdio.h>

int main(int argc, char *argv[])
{
	puts("Hello, world");
	return 0;
}
なるhello.cを
% sh-elf-gcc hello.c
でコンパイルできます。a.outというファイルが新しくできるはずですが、 これがELF形式の実行ファイルになっています。

…あれ? あっけなさすぎてちょっと不安ですね。 ターゲットシステムのメモリマップとか知らせないで大丈夫なんでしょうか。 ちょっと検証してみましょう。

% sh-elf-objcopy -O srec a.out a.mot
というコマンドで、ELFフォーマットで記述してある 実行ファイル(?)a.outを、モトローラS形式でa.motに書き出します。 これを直接読んでもいいんですが、もっと分かりやすい表示がいいなら
% sh-elf-objdump -x a.out
が便利でしょう。

…ありゃりゃ、なんだかメモリの配置が決め打ちですね。 おいおい修正することにします。で、次はこれを実行してみましょう。 んっふっふっ。そうです。実行できるんです。

% sh-elf-gdb a.out
GNU gdb 5.0
[略]
(gdb) target sim
Connected to the simulator.
(gdb) load a.out
Loading section .text, size 0x1dbc vma 0x1000
Loading section .rodata, size 0x32 vma 0x2dbc
Loading section .data, size 0x73c vma 0x2e70
Loading section .stack, size 0x4 vma 0x30000
Start address 0x1000
Transfer rate: 76144 bits in <1 sec.
(gdb) run
Starting program: ......../a.out
Hello, world


Program exited with code 012.
[Switching to process 0]
いやあ、GNUのツールってすごいですねえ…^o^ という個人的な感慨はさておき、ちょっと解説。

まず、target文にてターゲットシステムを指定します。 本物のリモートデバッグ環境があるのならここで指定しても良いのですが、 そんなものないという場合は内蔵シミュレータに「接続」します。

そして、load文でシミュレータのメモリに実行イメージを「ダウンロード」し、 run文にて実行を開始します。

この流れは、ICEを使っている方ならおなじみだと思います。 おのおののgdbのコマンドがわからない場合は、 コマンドラインから「help コマンド」とすれば参照できます。

個人的にはなんでホストコンピュータ環境に 出力ができるのかすんごい不思議なんですが、 おそらくシミュレータ環境がソフトウェア割込みをトラップしてるんでしょう。 GNUの優秀な開発者に感謝しつつ、先に進みましょう。


リンケイジエディタ

さて、次はコンパイル環境をおのおののターゲットシステムにあわせて カスタマイズしていきましょう。 そのためには、システムのメモリマップを、 リンケイジエディタ(リンカ)に教えてやらなければなりません。

そもそも、リンケイジエディタとは、 コンパイルしてできたオブジェクトファイルを、 セクション(というおなじ属性をもったメモリ空間)ごとに集め、 指定されたアドレスに配置するプログラムです。 さきほどgccの動作確認を行いましたが、 そのときのようにsh-elf-gccに-vオプションをつけて実行ファイルを作ってみると 最後にリンクエディット作業が(実際にはcollect2経由で)実行されるのが観察できます。 現在構築中の開発環境の場合、sh-elf-ldコマンドがリンケイジエディタです。 今後は、GNU ld、もしくはgldと呼ぶことにします。

GNU ldはldscriptと呼ばれる設定ファイルに従って オブジェクトの配置を行います。 デフォルトではビルトインのldscriptを使用しますが (GNU ldのgldshelf_get_script関数中にあるものです)、 実際には/usr/local/sh-elf/lib/ldscripts/shelf.xと同じ内容のようです。

メモリへの配置方針は、組み込みシステムの場合以下のように行います。

メモリマップのほうは、以下のように仮定します:


ldscriptを作る1

以上をふまえ、shelf.xをベースにして、 メモリ配置だけ改造してみることにします。

SECTIONSで囲まれたブロックがありますが、 この中の記述がセクションの配置を決定します。この書式は

	出力セクション [start-address]: {
		ファイル名(入力セクション [...])
		[...]
	}
となっています。例として出力セクション".rodata"から見ていきましょう。
  .rodata   : { *(.rodata) *(.rodata.*) *(.gnu.linkonce.r*) }
まず、"*(.rodata)"という部分ですが、これは 「入力ファイルの中にあるすべての.rodataセクション」という意味になります。 入力ファイル中のセクション名と、出力ファイル中のセクション名とは独立で、 入力ファイル中で.rodataセクションであったからといって、 必ずしも出力ファイル中でも.rodataセクションになるとは限りません。 現に、次の"*(.rodata.*)"という指示は、 「入力ファイルの中にあるすべての.rodataほにゃららというセクションを、 出力ファイル中の.rodataに配置する」という意味になります。 また、出力ファイル中の.rodataセクションは、.gnu.linkonce.rほにゃららというセクションも含みます。

ここでは、特にセクションの配置関係自体は変更しないことにします。 また、デバッグ用の情報を含むセクションについてですが、例えば

  .debug_info     0 : { *(.debug_info) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line) }
となって0番地に配置されるように見えますが、 実際にはこのようなセクションは"実行ファイル中には存在するが、ターゲットメモリに配置しない"というフラグが付加されるので特に問題ありません。sh-elf-stripコマンドにて実行ファイルから除去することができます。

ldscriptを作る2

ldscriptの中では、他にシンボルの定義や、 ロケーションカウンタ(オブジェクトの配置をするアドレス)の設定を行います。 ロケーションカウンタを適切に設定することでターゲットシステムのメモリマップに合わせることができます。

変更点1

   /* Read-only sections, merged into text segment: */
   . = 0x1000;
   .interp     : { *(.interp)  }
自由に使えるROM領域は0x2000からなので、これを
   /* Read-only sections, merged into text segment: */
   . = 0x2000;
   .interp     : { *(.interp)  }
に変更します。

変更点2

   .rodata1   : { *(.rodata1) }
   /* Adjust the address for the data segment.  We want to adjust up to
      the same address within the page on the next page up.  */
   . = .;
   .data    :
   {
ここは、read onlyなデータセクションから、writableなデータセクションに切り替わるところです。 以降のセクションがRAM上に存在するように、次のように変更します。
   .rodata1   : { *(.rodata1) }

   .data 0x100000 :

変更点3

   .debug_varnames  0 : { *(.debug_varnames) }
   .stack 0x30000 : { _stack = .; *(.stack) }
   /* These must appear regardless of  .  */
ここは、ヒープ領域が終わり、スタック領域が始まる部分です ("_stack"というシンボルも定義しています)。 これも、個々のターゲットシステムのスタック領域の開始アドレスに変更します。
   .stack 0x280000 : { _stack = .; *(.stack) }

確認

さて、変更したldscriptをdefault.ldとしてカレントディレクトリに置いておきます。 また、次のようなファイルを作ります。(固めたものはここ)

Makefile

include Rules.make

CFLAGS  = -Wl,-Tdefault.ld

run: var_addr
        $(RUN) var_addr
Rules.make
EXEC_PREFIX     =sh-elf-
CC              =$(EXEC_PREFIX)gcc
AS              =$(EXEC_PREFIX)gas
LD              =$(EXEC_PREFIX)ld
AR              =$(EXEC_PREFIX)ar
OBJDUMP         =$(EXEC_PREFIX)objdump
OBJCOPY         =$(EXEC_PREFIX)objcopy
NM              =$(EXEC_PREFIX)nm
RANLIB          =$(EXEC_PREFIX)ranlib
STRIP           =$(EXEC_PREFIX)strip
STRINGS         =$(EXEC_PREFIX)strings

GDB             =$(EXEC_PREFIX)gdb
PROF            =$(EXEC_PREFIX)gprof

RUN             =$(EXEC_PREFIX)run

CC_GENMAP       =-Wl,-M
var_addr.c
#include <stdlib.h>

char *extern_bss;
char *extern_data = "string literal for extern";

static char *filescope_bss;
static char *filescope_data = "string literal for filescope";

void extern_func(void)
{
}

static void private_func(void)
{
}

int main()
{
	int auto_var;
	static char *funcscope_bss;
	static char *funcscope_data = "for funcscope";

	extern_func();
	private_func();

	printf("string literal for extern scope %p\n", extern_data);
	printf("string literal for file scope %p\n", filescope_data);

	printf("extern func addr at %p\n", &extern_func);
	printf("private func addr at %p\n", &private_func);

	printf("initilized extern variable at %p\n", &extern_data);
	printf("initilized filescope variable at %p\n", &filescope_data);
	printf("initilized funcscope variable at %p\n", &funcscope_data);

	printf("uninitilized extern variable at %p\n", &extern_bss);
	printf("uninitilized filescope variable at %p\n", &filescope_bss);
	printf("uninitilized funcscope variable at %p\n", &funcscope_bss);

	printf("auto variable at %p\n", &auto_var);
	printf("malloc returns addr %p\n", malloc(4));

	return 0;
}
これらを、コンパイル&実行します:
% make var_addr
sh-elf-gcc -Wl,-Tdefault.ld    var_addr.c   -o var_addr
% make run
sh-elf-run var_addr
string literal for extern scope 0x8e80
string literal for file scope 0x8e9c
extern func addr at 0x2034
private func addr at 0x2040
initilized extern variable at 0x100000
initilized filescope variable at 0x100004
initilized funcscope variable at 0x100008
uninitilized extern variable at 0x100790
uninitilized filescope variable at 0x100760
uninitilized funcscope variable at 0x10075c
auto variable at 0x27ffec
malloc returns addr 0x100ba8
-Wl,-Tdefault.ldというのが個別のldscriptを使うgccのオプション(*)なのですが、 これを付けないときと比較すると確かにアドレスの配置が変化しているのがわかります。

(*)GNU ldにとっては、-Tdefault.ldというオプションになります。 gccに"-Wl,opt1,opt1,..."というオプションを指定すると、ldに"opt1 opt2 ..."というオプションを渡すことになります。


Home Prev Next(under developped)
card_captor@geocities.co.jp
1