■
nchaOSではfirstbootからsecondboot、さらにstartKernelへの移動をljmp命令などで行い、それぞれのバイナリをcatなどでつなげてメモリ上の位置を制御していたが、今回はldのリンカスクリプト等を使い制御していきたいと思う。
そこでリンカスクリプトやその周辺についてメモを適当にまとめてみる。
オブジェクトファイル
1. asの「-a」オプションによりアセンブリリストを出力する 2. objdump -h objfile(objfileはas後のファイル)によりSECTIONのヘッダー情報を出力 3. objdump -t objfile によりシンボル情報を出力
リンカスクリプト
主な機能
1. ファイルの指定方法 ld -T file objfile.o -o outfile 2. 出力ファイルのメモリレイアウト方法 SECTIONS { . = 0x90000; .text : { *(.text) } . = 0x91000; .data : { *(.data) } .bss : { *(.bss) } } 「. = 」によりメモリの位置を指定 2行目の.textは出力ファイルのtextセクションで{}の中は入力セクションを表し、 *(.text)の「*」は任意のファイル名に一致するワイルドカードなので*(.text)は 全入力セクションである。.data .bssも同様。 3. エントリポイントの指定 ファイルがメモリに読み込まれたときに最初に実行されるポイントを指定出来る 以下の順でチェックしていく(指定がなけれ次の項目をチェックしていく) * コマンド行オプションの `-e' entry * リンカスクリプトの ENTRY(symbol) コマンド * 定義されていれば, シンボル start の値 * 存在するなら, .text セクションの先頭のバイトのアドレス * アドレス 0 4. ファイルの読み込み コマンドラインでobjfile.oを読まずにリンカスクリプトで読み込ませる事が可能 INPUT(file, file, ...) またリンカスクリプト自体を読み込ませることが可能 INCLUDE filename 5. "."シンボル 特別なシンボル名 `.' は,位置カウンタを表す。 SECTIONS { . = 0x100 .text: { *(.text) . = 0x200 } . = 0x500 .data: { *(.data) . += 0x600 } } このようなリンカスクリプトでは以下のような構造になっている 0x100 0x200 0x500 0xb00 | *(.text) | 隙間 | *(.data) | 6. セクション間の隙間 SECTIONS { output : { file1(.text) . = . + 1000; file2(.text) . += 1000; file3(.text) } = 0x1234; } . = . + 1000によりfile1とfile2の間には1000byteの隙間が出来る。 この隙間は「= 0x1234」の指定により「0x1234」で埋められる。 7. シンボルへの値の代入 floating_point = 0; SECTIONS { .text : { *(.text) _etext = .; } _bdata = (. + 3) & ~ 4; .data : { *(.data) } } まず、シンボルへ値を代入する事はそのシンボルをグローバルとして定義した事になる(らしい) 上の例ではfloating_pointを0に、_etextを全入力textセクションの直後に、_bdataを出力text セクションの後で4byteのalignを調整(4byte境界にいなければ足りない分を足してる)。
SECTIONSコマンド詳細
1. 入力セクション記述 *(.text)のように全入力セクションをワイルドカードで抽出した場合のメモリ配置はファイルを 読み込んだ順になる。例えば ld -o outfile obj1.o obj2.o のようにコマンド入力した場合obj1.oの.textが先に配置され、次にobj2.oの.textが配置される。 この順序をリンカスクリプトで制御するには以下のようにする SECTIONS { . = 0x0; .text . : { obj2.o(.text) obj1.o(.text) } } このようにするとobj2.oの.textセクションから先に配置される。 また、リンカスクリプトとは違うディレクトリにオブジェクトファイルがある場合などは以下のように 指定する SECTIONS { . = 0x0; .text . : { "../bin/obj2.o"(.text) "../bin/obj1.o"(.text) } }
OSのブートローダとカーネルの配置イメージ
とりあえずものすごい大まかに、ブートローダを二つのオブジェクト (first.o, second.o)とし、カーネルをkernel.oとすると以下のような 配置になると思う。 SECTIONS { . = 0x0; .text . : { first.o(.text) second.o(.text) kernel.o(.text) } .data . : { second.o(.data) kernel.o(.data) } .bss . : { kernel.o(.bss) } } ※.bssとか何に使うかまだよくわかっていません。。 これでkernelはC言語で書き、kernelの最初の関数(startKernel)を secondの最後の方でjmpすれば(もしくはljmp)ldでうまいことリンク 出来ると思う(もちろんやってみないとわからないです。。)
nchaOS
昨日のbootacient.Sからセカンドブートとなるbootmiddle.Sへの繋ぎ部分を少し書く。
#まずはFloppy Driveをリセットしその後に2セクタ目の512Byteを #読み込み fd_reset: /* Reset floppy drive * Set AH = 0, which indicate to reset drive * Set AL = 0, which indicate first floppy drive. * INT 0x13: BIOS Disk services */ movw $0, %ax int $0x13 jnc fd_reset_ok movw $fd_reset_error_mes, %si call printstr jmp halt fd_reset_ok: /* Read next boot section from floppy drive. * Data is read from fd to the memory point of ES:BX. */ movw $0x200, %bx # "es" is already set at 0x9000 movb $0x2, %ah #FDのread命令 movb $0x1, %al #処理するセクタ数(とりあえず1セクタ #512byteだけ) movb $0, %ch #トラック番号 (0-39) movb $0x2, %cl #セクタ番号 (1-9) xorw %dx, %dx #DH:ヘッダ(0-1) ※表か裏か #DL:ドライブ番号(0-3) int $0x13 jc read_error #読み込みに失敗するとcarry flagに #ビットが立つ jmp middle_start #論理セクタ番号 = (トラック番号 * セクタ数/1トラック) + # (ヘッド番号 * セクタ数/1ヘッド) + セクタ番号 #読み込みに失敗したらメッセージを出力してhltする。 read_error: movw $fd_read_error_mes, %si call printstr jmp halt
仮のセカンドブートコード
.code16 .text .global middle_start middle_start: movw %cs, %ax movw %ax, %ds movw %ax, %es movw $sec_boot_mes, %si call printstr halt: jmp halt sec_boot_mes: .ascii "This is middle boot section.\r\n" .byte 0 .org 512
gasの「.global」「.globl」はラベルのグローバル化を指定出来、
他のファイルからそのリンクを参照出来るようにする。
最後に、今日勉強したリンカスクリプトを早速適用
SECTIONS { .text : { "../bin/boota.o"(.text) "../bin/bootm.o"(.text) } }
install.sh
as bootacient.S -o ../bin/boota.o -a > ../list/boota.lst as bootmiddle.S -o ../bin/bootm.o -a > ../list/bootm.lst ld -T boot.ld -o ../bin/boot.bin ../bin/bootm.o ../bin/boota.o -Map ../list/boot.map --oformat binary