Z80のC言語クロスコンパイル(SDCC)(1)

  • 投稿日:
  • 更新日:2015/03/08
  • by
  • カテゴリ:
第1回 第2回 第3回 第4回 第5回 次へ

UNIXやWindows環境で利用できるフリーのZ80対応クロスC言語コンパイラとして、現在利用可能なのはSDCCくらいしかありません。

このコンパイラに関する日本語のドキュメントはまだほとんどないと思われますので、私が知っている限りで説明しておこうと思います。

コンパイルとリンク

sdccの使い方は、gccなどのコンパイラとそれほど変わりませんが、典型的な使い方を挙げます。

(各プログラムをコンパイル、アセンブル)
% sdcc -mz80 -c myprog1.c -o myprog1.o
% sdcc -mz80 -c myprog2.c -o myprog2.o
% as-z80 -o mycrt0.o mycrt0.S
(リンク)
% sdcc -mz80 --out-fmt-ihx --code-loc 0x840f --data-loc 0 --no-std-crt0 -o myprog.ihx mycrt0.o myprog1.o myprog2.o

mycrt0.Sはこの後説明する独自のスタートアップコードです。リンク時はこのオブジェクトを最初に指定しないとリンクの順番がおかしくなるので注意してください。

なお、このリンク方法でうまくihxが生成されないことがしばしばあります。おそらくバグだと思いますが、その場合、lnkファイルが生成されているので、続けてlink-z80を実行するとうまくいきます。

(リンクはされていなくても、myprog.lnkは作成されている)
% sdcc -mz80 --out-fmt-ihx --code-loc 0x840f --data-loc 0 --no-std-crt0 -o myprog.ihx mycrt0.o myprog1.o myprog2.o
(リンクしてihx作成)
% link-z80 -nf myprog

link-z80にはコマンドモードからの入力オプション(-c)がありますが、これは標準入力からの行単位入力を要求するので注意してください。コマンドライン引数では指定できません。

なお、アセンブラ(as-z80)では、オプションをソースファイルの後ろに指定できませんので注意してください。

sdccのオプション

sdccの基本的なオプションは他のコンパイラと同じですが、特殊なオプションがいろいろあるので、ここではその中でよく使うもののみ挙げます。

コンパイルオプション

  • -c
    コンパイルのみでリンクしない。
  • -o
    出力ファイル指定
  • -I
    インクルードディレクトリ
  • -D
    define定義の指定

コンパイルオプション(sdcc固有)

  • -mz80
    コンパイル対象をz80に指定します。他にも-mhc08などいろいろあるので、Z80プログラムのコンパイルには必須です。
  • --std-c99
    C99標準に準拠します。sdcc 2.8.0では完全ではありません。関連するオプションとして他に、--std-c89, --std-sdcc89, --std-sdcc99があります。sdccがつくオプションは、独自拡張を使うことができます。

リンク時オプション

  • -l
    ライブラリファイルの指定です。ライブラリファイルはsdcclibで作成することもできますし、対象ファイル名を並べただけでもokです。あとで実際の使い方を説明します。
  • -L
    ライブラリファイルのディレクトリ指定です。-lにはディレクトリを指定できませんので、カレントディレクトリ以外にライブラリファイルがある場合は必要です。

リンク時オプション(sdcc固有)

  • --code-loc
    コードセグメント(.area _CODEおよび_GSINIT)の開始番地を指定します。_GSINITは_CODEに続いて配置されます。
  • --data-log
    データセグメント(.area _DATA)の開始番地を指定します。0を指定すると、コードセグメントの直後に配置されます。
  • --out-fmt-ihx
    リンク後のオブジェクトの出力フォーマットをIntel hex形式にします。これを付属のmakebinや拙作のHexameterに与えることにより、バイナリファイルを作成します。
  • --no-std-crt0
    標準のスタートアップコード(crt0.o)を使わない指定です。以降で詳しく説明します。

crt0のリンク

sdcc をインストールしてデフォルトでコンパイルすると、付属しているcrt0.oをリンクします。これが0番地に必ずinitにジャンプするスタートアップを 入れたり、PC-6001や他のプラットフォームで利用するにはそのままでは使いにくいので、変更する必要があります。

しかし、ソースコードのcrt0.sはいろいろ示唆的なので、構造を知っておくと役立ちます。

デフォルトのcrt0.s

デフォルトのcrt0.oのソースコード(crt0.s)を説明します。

	; モジュール名を定義します。
	; スタートアップコードでも、任意の名前が使えます。
	.module crt0
	; C言語のmain関数への参照です。
	; アセンブラでは、C言語の名前の頭に"_"をつけます。
	.globl	_main

	; エリア名を定義します。
	; エリア名は任意ですが、いくつか典型で使われるものがあります。
	; (ABS)は、絶対アドレス指定になります。
	.area	_HEADER (ABS)

	; .orgは次からの命令の格納アドレス指定です。
	; ここからRST 0~RST 0x38までのベクタを定義しています。
	; デフォルトではすべてretiになっています。

	.org	0
	jp	init
	.org	0x08
	reti
	.org	0x10
	reti
	.org	0x18
	reti
	.org	0x20
	reti
	.org	0x28
	reti
	.org	0x30
	reti
	.org	0x38
	reti

	; 初期化ルーチンが0x100からになります。0番地からここに飛んできます。
	.org	0x100
init:
	; スタックポインタの初期化。
	ld	sp,#0xffff
	; グローバル変数初期化ルーチンを呼び出します。
	call	gsinit
	; メインルーチンを実行します。
	call	_main
	; 終了処理へ飛びます。
	jp	_exit

	; 以下はリンカに対してエリアの順序を指示しています。
	; sdccではエリアごとに別のメモリ領域を指定することができます。
	; メモリアドレスは互いに重なってもいいので、64kBを越えるプログラムを扱うことも可能です。
	; ただし、バンク切り替えなどが必要な場合は自分で処理する必要があります。
	.area	_HOME
	.area	_CODE
	.area	_GSINIT
	.area	_GSFINAL
	.area	_DATA
	.area	_BSS
	.area	_HEAP

	; ここからはコード領域です。
	; リンカに対して--code-locで指定したアドレスから配置されます
	.area	_CODE
__clock::
	ld	a,#2
	rst	0x08
	ret
	; 終了処理。エミュレータ用のコードのようです。
_exit::
	;; Exit - special code to the emulator
	ld	a,#0
	rst	0x08
1$:
	halt
	jr	1$
	; ここからはグローバル変数の初期化です。
	; コード内に初期化が必要なグローバル変数があると、初期化コードが自動的にここに配置されます。
	.area	_GSINIT
gsinit::
	; 初期化コードは各ソースから出力されて連続した領域となります。
	; 自動的にリターンしないので、ここで別エリアを定義してretします。
	.area	_GSFINAL
	ret

なお、ラベルはコロンがひとつの場合は.globl定義がない限りローカル、コロンが二つの場合は常にグ ローバルになります。たとえば上記では、initはローカル、gsinitはグローバルです。ただし、アセンブラのas-z80で-aオプションをつける と、すべてのラベルがグローバルになります。

crt0の入れ替え

さて、標準のcrt0.oは使えないので、入れ替えます。

sdccには、「グローバル変数がデフォルトで0に初期化されない」というC言語の仕様に沿っていない部分があるので、そこも修正します。

	; モジュール名とグローバル宣言は一緒です。
	.module	crt0
	.globl	_main

	; リンカへのエリア指定ですが、使うものだけでも構いません。
	; コード(_CODE)、初期化ルーチン(_GSINIT)、データ(_DATA)はsdccで出力するので必須です。
	; _GSFINALは初期化ルーチンから戻るために必須です。_DATAFINALは別に必須ではないのですが、
; 大域変数の初期化時にデータ領域の終了アドレスが必要なので入れています。 .area _CODE .area _GSINIT .area _GSFINAL .area _DATA .area _DATAFINAL ; コードセグメントの最初の部分です。 ; 0番地から飛んできませんが、リンカの--code-loc指定でここから開始するので問題ありません。 .area _CODE init:: ; グローバル変数が入るデータエリア(_datastartから_dataend-1まで)を0で初期化します。 ld hl, #_datastart ld bc, #_dataend _clear_loop: ld a, h sub b jr nz, _clear_next ld a, l sub c jr z, _clear_exit _clear_next: ld (hl), #0 inc hl jr _clear_loop _clear_exit: ; グローバル変数初期化ルーチンを呼び出します。 call gsinit ; メインに飛びます。 ; メインがretするとプログラム自体の呼び出し元に返ります。 jp _main .area _GSINIT gsinit:: .area _GSFINAL ret .area _DATA _datastart:: .area _DATAFINAL _dataend::

上記のcrt0.Sでは、絶対アドレス指定を一切使っていないので、アドレスはすべてリンカで指定することになり、ihxファイルを作るまではリロケータブルです。

プ ログラムによっては、グローバル変数の初期化が不要な場合もあるでしょう。そのような時は、init~_clear_exitの間のコードとcall gsinit、そして_GSFINALのretを削ることができ、メモリ削減になります。C言語から出力された_GSINITはけっこう効率の悪いコード なので、グローバル変数の宣言時に値を代入するより、個別にプログラムしたほうが効率がいいこともよくあります。

このあたりのテクニックはいろいろとあるのですが、少しづつ説明していきたいと思います。

上記のcrt0.sをas-z80でアセンブルしておくことにより、複数のCプログラムをリンクして、PC-6001や他のZ80ベースのパソコンでも使えるようなバイナリを作成することが可能となります。

第1回 第2回 第3回 第4回 第5回 次へ

こちらもよく読まれています