ウォンツテック

そでやまのーと

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