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ベースのパソコンでも使えるようなバイナリを作成することが可能となります。
匿名
画面が真っ暗、でもカーソルは出てる状況。
探して、ここにたどり着きました。
パスワード入力で、復活!
修理に出す寸前でした。ホントにありがとう!