6.1  grubの起動シーケンス

 grubが起動するときは普通、stage1に引続きstage2が起動されます。 grubの本体はstage2なのですが、どのようにしてstage2がロードされ動き出す かを調べてみました。

(1) stage1
 stage1はパーティションの先頭セクタにでもインストールできるように、 BPB(BIOS Parameter Block)の部分は使っていません。 (BPBの位置はセクタの先頭からのオフセット: +4から+3D, オフセットは16進で表記します。)
stage1がDISKにインストールされるときは、MBR にインストールされることを考慮して、パーティションテーブルの部分 (+1BE〜+1FD)も使いません。
grub-0.5.95のstage1は1セクタ512バイト中の+3Eから+1B9まで使用してます。

 stage1の機能はstage2をロードすることにつきます。 stage1の中にはstage2の第1セクタの情報がセットされています。 stage1のオフセット+40にドライブ番号(1台目のDISKは80、2台目は81)が、 そして+44からの4バイトにはstage2の第1セクタのLBAがセットされています。 (LBAは0から始まるシーケンシャルなセクタ番号です。 MBRのLBAは0です。/dev/hda1の先頭セクタのLBAは普通63です。)
これらの情報はgrubのインストール時にセットされます。

 stage1をダンプしてみましょう。
   $ dd if=/dev/hda of=mbr count=1
   $ od -x mbr 
   0000000 48eb d090 00bc fb7c 0750 1f50 befc 7c1b
   0000020 1bbf 5006 b957 01e5 a4f3 becb 07be 04b1
   0000040 2c38 097c 1575 c683 e210 cdf5 8b18 8b14
   0000060 83ee 10c6 7449 3816 742c bef6 0710 0103
   0000100 0080 8000 7c91 0014 0800 eafa 7c50 0000
 (16進で40)  ~~      ~~~~~~~~~
 WORD単位で見にくいですが、ドライブ番号=0x80, LBA = 0x147c91 = 1,342,609 (CHS=83/146/17)、これがstage2第1セクタのありかです。

 stage1は出来る限りINT13H拡張BIOSで stage2のセクタを読もうとしますが、だめなら従来のINT13Hで読みます。 stage2の第1セクタが読めたらそこに制御を移します。

(2) stage2
 stage2の第1セクタの機能はstage2の第2セクタ以降を読み込むことです。 第2セクタ以降のありか(位置)は第1セクタの後端に格納されています。 その情報はブロックリストと呼んでいます。 ブロックリストは、4バイトのLBA + 2バイトのセクタ数 + 2バイトの読み込みバッファセグメント、の合わせて8バイトが1エントリで、 1個以上のエントリで情報がセットされています。 (余談ですが、grub-0.5.93.1まではこの情報はstage1にありました。 INT13H拡張BIOSサポートで余裕が無くなったため、stage2の第1セクタに もってきたと思われます。)

 stage2をダンプして第1セクタの後ろあたりを見てみましょう。
   $ cd /boot/grub
   $ od -x stage2 
   0000000 5652 fdbe e880 0116 bf5e 81f8 7d83 0004
   0000020 840f 00cd 7c80 00ff 4574 8b66 661d cb39
   <中略>
   0000460 0000 0000 0000 0000 0000 0000 0000 0000
   *
   0000760 7cf9 0014 002c 1400 7c92 0014 005f 0820
(16進で1F0)~~~~~~~~~   ~~ ~~~~ ~~~~~~~~~   ~~ ~~~~
           LBA         数 Seg. LBA         数 Seg.
           └  第2エントリ  ┘ └  第1エントリ  ┘  (0000460)まで最大26エントリ
 まだ、リアルモードで動いてますから、セグメント値にゼロを 1個追加するとメモリアドレスになります。
 上記の情報から、以下のようにstage2の各セクタをメモリに読み込みます。
  アドレス
   0x8000 +-------------------------------------------------+
          |stage2の第1セクタがすでにロードされている。      |
          |                                                 |
   0x8200 +-------------------------------------------------+
          |LBA=0x147c92 から95(0x5f)セクタをここに読み込む。|
          |(stage2の第2セクタから第96セクタ)                |
          |                                                 |
  0x14000 +-------------------------------------------------+
          |LBA=0x147cf9 から44(0x2c)セクタをここに読み込む。|
          |(stage2の第97セクタから第140セクタ)              |
          |                                                 |
 stage2の全てのセクタを読み込んだらstage2の第2セクタに制御を移します。 stage2では"menu.lst"を読み込みますが、"menu.lst"のありか(位置)は stage2の第2セクタにセットされています。

 stage2をダンプして第2セクタのあたりを見てみましょう。
   $ od -c stage2
   <中略>
(16進で200)
   0001000     p 202  \0  \0  \0 003 001        \0  \0  \0  \0   0   .
   0001020   5   .   9   5  \0   /   b   o   o   t   /   g   r   u   b   /
   0001040   m   e   n   u   .   l   s   t  \0  \0  \0  \0  \0  \0  \0  \0
 ファイルのパスが見えます。この段階ではgrubはすでにファイルシステムを 理解できるようになってます。 インストール時に通常の"menu.lst"以外を指定すると、このあたりが変わります。

(3) ちょっとおもしろい使い方
 普通、menu.lstやカーネルはファイルのパス名で指定しますが、 LBAで指定することもできます。
まず、DISKのパーティションを切るとき、先頭の1シリンダを空けておくのです。 約8Mバイトですので、最近の数10GクラスのHDDにとっては微々たるものでしょう。 そこに、stage2やmenu.lst、果てはカーネルまでぶちこんでしまいます。 そして、grubをインストールします。
(注意:以下のことは第1パーティションの手前に何セクタ空いているか必ず 確認!!!してから実行してください。)
   root# cd /boot/grub

   root# dd if=../vmlinuz-2.2.16 of=/dev/hda seek=1000
   2005+1 records in
   2005+1 records out

   root# cat menu.lst
   title Linux 2.2.16 generic
   kernel (hd0)1000+2006 root=/dev/hda1 ro
   #--- File size = 512 ---#
   #--------------------------------------------------------------
   #--------------------------------------------------------------
   #--------------------------------------------------------------
   #--------------------------------------------------------------
   #--------------------------------------------------------------
   #--------------------------------------------------------------
   #---------------------------------                 

   root# ls -l menu.lst
   -rw-r--r--   1 root     root          512 Jul 28 03:21 menu.lst

   root# dd if=menu.lst of=/dev/hda seek=10
   1+0 records in
   1+0 records out

   root# dd if=stage2 of=/dev/hda seek=100
   139+1 records in
   139+1 records out

   root# grub

   grub> install (hd0,0)/boot/grub/stage1 d (hd0) (hd0)100+140 p (hd0)10+1
 もし試してみる場合は、各数値が何を意味してるかよく理解してからにしてください。 あとは、自分でstage2やmenu.lst、カーネル(initrdも可) などの配置を決めて実行してみてください。 REBOOTしてGRUBのメニューが出て来たら、バンザ〜イ!! 三唱です。
grub 0.97 legacy development, larger than 2 TB disk
2013-03-31
 当サイトの2TiB超HDDサポートgrubですが、stage1.5またはstage2はHDDの2TiB以内に存在しなければいけません。その理由はstage1.5、stage2のLBAを32ビットで保持しているからです。そこで、stage1.5、stage2のLBAを48ビットで保持するstage1を作ってみました。この改造stage1を使えば、stage1.5、stage2を2TiB超HDDのどこにでもセットできます。
 
  ダウンロード:stage1_lba48.tgz (ソースとバイナリ)
 
 ビルドは make 一発でできます(grubがビルドできる環境で)。
ただし、インストールはマニュアルで行う必要があり、バイナリ・エディターや ddコマンドを駆使して行います。
 改造stage1をバイナリ・ダンプしてみます。
  $ hd stage1-lba48load-stage2  (hdはhexdumpコマンドのシンボリックリンク)
  00000000  e9 7f 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
  00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
  *
  00000070  00 00 00 00 00 00 00 00  01 00 00 00 00 00 28 01  |..............(.|
  00000080  ff 00 fa ea 88 7c 00 00  31 c0 8e d8 8e d0 bc bf  |.....|..1.......|
   ・・・
 exFATのBPBを考慮して+78からコードが始まるようにしています。 +78から6バイト(48ビット)がstage2の先頭LBAで、+7Eから2バイト(16ビット)がstage2のサイズ(セクター数)です。サイズはデフォルトで0x128=296と大きめの数になっています。大きいぶんにはかまわないので、これより大きいstage2の場合以外は変更しなくてよいです。 +80はstage2のドライブ番号です。起動ドライブと同じ場合は変更しなくてよいです。 +81はFORCE_LBAオプションです。通常必要ありません。
 
 使用例として、3TBまるごと(whole/entire disk filesystem) ext4にしたHDDにセットアップしてみます。
方針として LBA 0 に改造stage1、HDDの終端少し手前にstage2をセットすることにします。
 
(1) ext4ファイルシステムのブロック数を求めます。
  # mkfs.ext4 -n -b 4096 /dev/sdb 
  mke2fs 1.42 (29-Nov-2011)
  /dev/sdb is entire device, not just one partition!
  Proceed anyway? (y,n) y
  Filesystem label=
  OS type: Linux
  Block size=4096 (log=2)
  Fragment size=4096 (log=2)
  Stride=0 blocks, Stripe width=0 blocks
  183148544 inodes, 732566646 blocks
   ・・・
(2) 732566646ブロックから200kB〜1MB分減じたブロック数を指定してファイルシステムを作成します。
  # mkfs.ext4 -b 4096 /dev/sdb 732566528 
   ・・・
  183148544 inodes, 732566528 blocks
   ・・・
(3) menu.lstを適当に作りgrubディレクトリを作りコピーします。
  # mount /dev/sdb /mnt
  # mkdir -p /mnt/boot/grub
  # cp menu.lst /mnt/boot/grub/ 
(4) stage2をddコマンドでセットします。位置はHDD終端がLBA 5860533167(0x15d50a3af)なのでLBA 5860532736(0x15d50a200)としました。
  # dd if=stage2 of=/dev/sdb seek=5860532736
(5) 改造stage1の+78から5バイトを 00 a2 50 5d 01 に変更します。(バイナリ・エディターで変更後の確認表示)
  $ hd stage1-lba48load-stage2 
   ・・・
  00000070  00 00 00 00 00 00 00 00  00 a2 50 5d 01 00 28 01  |..........P]..(.|
   ・・・
(6) 改造stage1をLBA 0 にセットします。
  # dd if=stage1-lba48load-stage2 of=/dev/sdb
 以上で手動インストールは完了です。リブートし、3TBディスクを1台目のHDDとして起動し、指定したメニューが出ればOKです。
 
2013-04-16
 exFATのPBR(セクター0)にこの改造stage1を設定したところ、Windows XP (KB955704適用)で認識されず“ディスクはフォーマットされていません”が表示されました。どうやればWindowsで認識されるか探求していったところ、セクター0の頭のジャンプコードが eb 76 90 である必要が分かりました。しかもWindowsでフォーマットしたものはVBRチェックサム以外に何らかのチェックがあるようで、どうやっても認識されませんでした。
 従って改造stage1頭のジャンプコードを変更したものを作り、Linuxのmkexfatfs(mkfs.exfat)でフォーマットし、改造stage1をセクター0に設定し、VBRチェックサム(セクター11と23)を再計算しセットすればWindows XPでも認識され使用できるようになりました。
 
  ダウンロード:stage1_lba48-20130416.tgz (ソースとバイナリ)
 
 この改造stage1をバイナリ・ダンプしてみます。
  $ hd stage1-lba48load-stage2 
  00000000  eb 76 90 00 00 00 00 00  00 00 00 00 00 00 00 00  |.v..............|
  00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
  *
  00000070  00 00 00 00 00 00 00 00  fa ea 88 7c 00 00 ff 00  |...........|....|
  00000080  01 00 00 00 00 00 28 01  31 c0 8e d8 8e d0 bc bf  |......(.1.......|
   ・・・
 当初の改造stage1からパラメータの位置が変わっています。 +7Eがstage2のドライブ番号です。 +7FがFORCE_LBAオプションです。 +80から6バイト(48ビット)がstage2の先頭LBAです。 +86から2バイト(16ビット)がstage2のサイズ(セクター数)です。 注意点などは当初のものと変わりません。
2013-04-18
 exFATのPBR(セクター0)への設定方法ですが(パーティションを作らずディスクまるごとexFATにする場合はMBR(LBA 0)になりますが)、まずstage2またはstage1.5をセットする位置を決め、ディスクの先頭からのLBAを10進数と16進数で求めておきます(パーティションの先頭からの位置ではないので念のため)。 exFATの場合、バックアップVBRの後ろのセクター24からFATの開始セクターまで空いていますので、そこにstage1.5なりstage2をセットすることができます。
 設定手順は、まずLinuxのmkfs.exfatでフォーマットします。次に、上記使用例の(4)、(5)の要領でstage2を書き込み、改造stage1の+80から6バイトを上で求めた16進LBAを書き込みます。そして改造stage1の+78から+1b8までセクター0に書き込みます。VBRチェックサムを求めるためセクター0から11セクター読み込み、チェックサム計算プログラムでチェックサムをファイル出力します。そしてセクター0をセクター12にコピーし、VBRチェックサムをセクター11と23に書き込みます。以下のようなコマンド例になります。
  # mkfs.exfat /dev/sdb1
  # dd if=stage2 of=/dev/sdb seek=5860532736
  # dd if=stage1-lba48load-stage2 bs=1 skip=120 seek=120 count=320 of=/dev/sdb1
  # dd if=/dev/sdb1 of=sect0-10 count=11
  # ./vbr_checksum sect0-10 sect11  (exfat-utils等参考にした自作プログラム)
  # dd if=sect11 of=/dev/sdb1 seek=11
  # dd if=sect0-10 of=/dev/sdb1 seek=12
  # dd if=sect11 of=/dev/sdb1 seek=23
2013-04-19
 stage1から直接ロードされたstage2はカレント・パーティションが設定されていません。カレント・パーティションを設定するにはstage2の +20A を書き換えます。第1パーティションが 00 です。バイナリエディターで書き換えてからddコマンドでディスクにセットするのがよいでしょう。
  $ hd stage2 
   ・・・                [変更前]
  000001f0  00 00 00 00 00 00 00 00  02 00 00 00 18 01 20 08  |.............. .|
  00000200  ea 70 82 00 00 00 03 02  ff ff ff 00 00 00 00 00  |.p..............|
  00000210  00 00 30 2e 39 37 00 2f  62 6f 6f 74 2f 67 72 75  |..0.97./boot/gru|
  00000220  62 2f 6d 65 6e 75 2e 6c  73 74 00 00 00 00 00 00  |b/menu.lst......|
   ・・・
   ・・・                [変更後]
  00000200  ea 70 82 00 00 00 03 02  ff ff 00 00 00 00 00 00  |.p..............|
   ・・・
 改造stage1でstage1.5をロードする場合、マニュアルでstage1.5を設定することになりますが、stage2のあるドライブとパーティションをバイナリで設定できます(stage2のパス前に(hd1,3)のように付ける方法以外に)。 ドライブ番号は +21A 、 パーティション番号は +219 です。起動ドライブの場合は ff のままでよいです。
  $ hd exfat_stage1_5  (どのstage1.5でも同じ)
   ・・・              [変更前]
  000001f0  00 00 00 00 00 00 00 00  02 00 00 00 00 00 20 02  |.............. .|
  00000200  ea 70 22 00 00 00 03 02  ff ff ff 00 00 00 00 00  |.p".............|
  00000210  0c 00 30 2e 39 37 00 ff  ff ff ff 2f 62 6f 6f 74  |..0.97...../boot|
  00000220  2f 67 72 75 62 2f 73 74  61 67 65 32 00 00 00 00  |/grub/stage2....|
   ・・・
   ・・・           [変更後] (hd1,3)の場合
  00000210  0c 00 30 2e 39 37 00 ff  ff 03 81 2f 62 6f 6f 74  |..0.97...../boot|
   ・・・
   ・・・           [変更後] (hd0,2)の場合
  00000210  0c 00 30 2e 39 37 00 ff  ff 02 ff 2f 62 6f 6f 74  |..0.97...../boot|
   ・・・
2013-05-12
 パーティション(GPT,FDISKどちらでも)を作ったあとに MBR に改造stage1を設定するときは、先頭から440バイトだけコピーしてください。(例)
  # dd if=stage1-lba48load-stage2 bs=1 count=440 of=/dev/sdb

TOPページにもどる    grubメインページにもどる
更新:12 May. 2013 
28 Jul. 2000