NetBSD - ブートセレクタとブートローダ

2020/11/06に公開

参考

ブートセレクタとブートローダを読む

概要

ブートセレクタとブートローダの話

NetBSD/i386 のブートローダは、パーティション・ブートコード、 プライマリ・ブートコード、セカンダリ・ブートコードから構成される。

  • ブートセレクタ
    • fdisk でインストール
    • MBR に含まれている
    • 本体 /usr/mdec/mbr
    • ソース src/sys/arch/i386/stand/mbr/mbr
  • ブートローダ
    • パーティション・ブートコード
      • NetBSD パーティションの PBR (セクタ0)に格納される
      • このプログラムはプライマリ・ブートコードをロードして実行する。
      • installboot でインストール src/usr.sbin/installboot
      • 本体 /usr/mdec/bootxx_ffsv2 など
      • ソース src/sys/arch/i386/stand/bootxx/bootxx_ffsv2 など
    • プライマリ・ブートコード
      • NetBSD パーティションのセクタ2〜セクタ14に格納される。
      • このプログラムはセカンダリ・ブートコードをロードして実行する。
      • installboot でインストール src/usr.sbin/installboot
      • 本体 /usr/mdec/bootxx_ffsv2 など
      • ソース src/sys/arch/i386/stand/bootxx/bootxx_ffsv2 など
    • セカンダリ・ブートコード
      • NetBSD のファイルシステム上にファイル(通常は /boot)として格納される。
      • このプログラムはあらかじめ設定されたメニューを画面に表示し、 ユーザの操作に従って NetBSD カーネルをロードして実行する。
      • cp /usr/mdec/boot /boot でインストール
      • 本体 /usr/mdec/boot
      • ソース src/sys/arch/i386/stand/boot

/sbin/fdisk を読む

fdisk -a /dev/rwd0d とか使う

ソース

src/sbin/fdisk/fdisk.c

重要な構造体

src/sys/sys/bootblock.h にある。

/*
 * MBR boot sector.
 * This is used by both the MBR (Master Boot Record) in sector 0 of the disk
 * and the PBR (Partition Boot Record) in sector 0 of an MBR partition.
 */
struct mbr_sector {
  /* Jump instruction to boot code.  */
  /* Usually 0xE9nnnn or 0xEBnn90 */
  uint8_t      mbr_jmpboot[3];
  /* OEM name and version */
  uint8_t      mbr_oemname[8];
  union {        /* BIOS Parameter Block */
    struct mbr_bpbFAT12  bpb12;
    struct mbr_bpbFAT16  bpb16;
    struct mbr_bpbFAT32  bpb32;
  } mbr_bpb;
  /* Boot code */
  uint8_t      mbr_bootcode[310];
  /* Config for /usr/mdec/mbr_bootsel */
  struct mbr_bootsel  mbr_bootsel;
  /* NT Drive Serial Number */
  uint32_t    mbr_dsn;
  /* mbr_bootsel magic */
  uint16_t    mbr_bootsel_magic;
  /* MBR partition table */
  struct mbr_partition  mbr_parts[MBR_PART_COUNT];
  /* MBR magic (0xaa55) */
  uint16_t    mbr_magic;
} __packed;

src/sbin/fdisk/fdisk.c を読む

MBR の変数

これが MBR に書き込まれる

static struct mbr_sector mboot;

これは /usr/mdec/mbr から読み込まれる

static struct mbr_sector bootcode[8192 / sizeof (struct mbr_sector)];

main の流れ

セクタ 1 に何か書かれていたら初期化する

if (read_s0(0, &mboot))
  /* must have been a blank disk */
  init_sector0(1);


    read_boot() でブートコードを読み込む
      ブートコードはデフォルトでは /usr/mdec/mbr である

      #define  DEFAULT_BOOTCODE  "mbr"
      #define  DEFAULT_BOOTDIR    "/usr/mdec"


        if (boot_path != NULL)
          free(boot_path);
        if (strchr(name, '/') == 0)
          asprintf(&boot_path, "%s/%s", boot_dir, name);
        else
          boot_path = strdup(name);
        if (boot_path == NULL)
          err(1, "Malloc failed");

    再度 init_sector0()
    読み込んだブートコードを mboot へコピー
    memcpy(&mboot, bootcode, copy_size);

write_mbr() で実際に書き込む。

   write_mbr
     write_disk

write_mbr() はこう。wfd はグローバル変数。ファイルディスクリプタ

static int
write_mbr(void)
{
  int flag, i;
  daddr_t offset;
  int rval = -1;

  /*
   * write enable label sector before write (if necessary),
   * disable after writing.
   * needed if the disklabel protected area also protects
   * sector 0. (e.g. empty disk)
   */
  flag = 1;
  if (wfd == fd && F_flag == 0 && ioctl(wfd, DIOCWLABEL, &flag) < 0)
    warn("DIOCWLABEL");

  mboot を書き込む

  if (write_disk(0, &mboot) == -1) {
    warn("Can't write fdisk partition table");
    goto protect_label;
  }
  if (boot_installed)


  ケツから bootcode を書き込んでいく
  サイズは 0x200 バイト

    for (i = bootsize; (i -= 0x200) > 0;)
      if (write_disk(i / 0x200, &bootcode[i / 0x200]) == -1) {
        warn("Can't write bootcode");
        goto protect_label;
      }


  拡張ぶんを ディスクパーティションぶん書き込んでいく

  for (offset = 0, i = 0; i < ext.num_ptn; i++) {
    if (write_disk(ext.base + offset, ext.ptn + i) == -1) {
      warn("Can't write %dth extended partition", i);
      goto protect_label;
    }
    offset = le32toh(ext.ptn[i].mbr_parts[1].mbrp_start);
  }
  rval = 0;
    protect_label:
  flag = 0;
  if (wfd == fd && F_flag == 0 && ioctl(wfd, DIOCWLABEL, &flag) < 0)
    warn("DIOCWLABEL");
  return rval;
}

write_disk() はこう。

static int
write_disk(daddr_t sector, void *buf)
{

  wfd で open 済みであるとする
  if (wfd == -1)
    errx(1, "write_disk(); wfd == -1");


  lseek してファイルポインタを sector * 512 まで移動

  if (lseek(wfd, sector * (off_t)512, 0) == -1)
    return (-1);

  buf 512バイトを書き込む
  buf には mboot が渡される

  return (write(wfd, buf, 512));
}

installboot を読む

usr.sbin/installboot/installboot.c

処理の流れ

main() の中でいろいろフラグやらオプションやらを処理し、最終的に構造体メンバの関数ポインタ経由で関数を呼び出す。

オプションにより editboot() clearboot() setboot() のいずれかを呼ぶ。

main
  editboot(params);
  clearboot(params);
  setboot(params)

呼び出す処理はここ。main() の下のほう。

  if (params->flags & IB_EDIT) {
    op = "Edit";
    rv = params->machine->editboot(params);
  } else if (params->flags & IB_CLEAR) {
    op = "Clear";
    rv = params->machine->clearboot(params);
  } else {
    if (argc < 2)
      errx(EXIT_FAILURE, "Please specify the primary "
          "bootstrap file");
    op = "Set";
    rv = params->machine->setboot(params);
  }

params には main() 冒頭で installboot_params が設定されている。

 params = &installboot_params;

params->machine は getmachine() により設定される。

オプションで指定された場合

 if ((p = getenv("MACHINE")) != NULL)
   getmachine(params, p, "$MACHINE");

デフォルトの場合

 /* set missing defaults */
 if (params->machine == NULL) {
   if (uname(&utsname) == -1)
     err(1, "Determine uname");
   getmachine(params, utsname.machine, "uname()");
 }

editboot() clearboot() setboot() の本体は getmachine() によりアーキテクチャごとに設定される。

getmachine() の定義はこう。

static void
getmachine(ib_params *param, const char *mach, const char *provider)
{
  int  i;

  assert(param != NULL);
  assert(mach != NULL);
  assert(provider != NULL);

  for (i = 0; machines[i] != NULL; i++) {
    if (machines[i]->name == NULL)
      continue;
    if (strcmp(machines[i]->name, mach) == 0) {
      param->machine = machines[i];
      return;
    }
  }
  warnx("Invalid machine `%s' from %s", mach, provider);
  machine_usage();
  exit(1);
}

machines[i] は usr.sbin/installboot/machines.c に定義されている。

struct ib_mach * const machines[] = {
    &ib_mach_alpha,
    &ib_mach_amd64,
    &ib_mach_amiga,
    &ib_mach_emips,
    &ib_mach_ews4800mips,
    &ib_mach_hp300,
    &ib_mach_hp700,
    &ib_mach_i386,
    &ib_mach_landisk,
    &ib_mach_macppc,
    &ib_mach_news68k,
    &ib_mach_newsmips,
    &ib_mach_next68k,
    &ib_mach_pmax,
    &ib_mach_sparc,
    &ib_mach_sparc64,
    &ib_mach_sun2,
    &ib_mach_sun3,
    &ib_mach_vax,
    &ib_mach_x68k,
    NULL
};

たとえば i386 の ib_mach_i386 の定義は usr.sbin/installboot/arch/i386.c にある。getmachine() により param->machine に ib_mach_i386 が設定される。

struct ib_mach ib_mach_i386 =
  { "i386", i386_setboot, no_clearboot, i386_editboot,
    IB_RESETVIDEO | IB_CONSOLE | IB_CONSPEED | IB_CONSADDR |
    IB_KEYMAP | IB_PASSWORD | IB_TIMEOUT |
    IB_MODULES | IB_BOOTCONF };

usr.sbin/installboot/arch/i386.c の i386_setboot() を読む。ディスクの先頭 8K バイトを disk_buf へ読む。params->fsfd には installboot に指定したファイルシステム( 例えば /dev/rwd0a ) へのファイルハンドルがある。

  /* Read in the existing disk header and boot code */
  rv = pread(params->fsfd, &disk_buf, sizeof (disk_buf), 0);
  if (rv != sizeof(disk_buf)) {
    if (rv == -1)
      warn("Reading `%s'", params->filesystem);
    else
      warnx("Reading `%s': short read, %ld bytes"
          " (should be %ld)", params->filesystem, (long)rv,
          (long)sizeof(disk_buf));
    return 0;
  }

8K バイトは FFSv1 のデフォルトのブロックサイズぽいような気がするんだけど、なぜ 8K バイトなのかは知らん。

disklabel(8) - NetBSD Manual Pages にも 8KB としか書いていない。

    On systems that expect to have disks with MBR partitions (see fdisk(8))
    disklabel will find, and update if requested, labels in the first 8k of
    type 169 (NetBSD) MBR labels and within the first 8k of the physical
    disk.  On other systems disklabel will only look at the start of the
    disk.  The offset at which the labels are written is also system depen-
    dent.

params->s1fd には installboot に指定したプライマリブートコード( 例えば /usr/mdec/bootxx_ffsv1 )へのファイルハンドルが設定されている。プライマリブートコードを bootstrap へ読み込む。

  /* Read the new bootstrap code. */
  rv = pread(params->s1fd, &bootstrap, params->s1stat.st_size, 0);
  if (rv != params->s1stat.st_size) {
    if (rv == -1)
      warn("Reading `%s'", params->stage1);
    else
      warnx("Reading `%s': short read, %ld bytes"
          " (should be %ld)", params->stage1, (long)rv,
          (long)params->s1stat.st_size);
    return 0;
  }

プライマリブートコードが FAT16 の /usr/mdec/bootxx_fat16 の場合は、/usr/mdec/bootxx_fat16 の先頭 512 - 16 + 4 => 500 バイト目にマジックナンバーが書き込まれている。

  /*
   * The bootstrap code is either 512 bytes for booting FAT16, or best
   * part of 8k (with bytes 512-1023 all zeros).
   */
  if (params->s1stat.st_size == 512) {
    /* Magic number is at end of pbr code */
    magic = (void *)(bootstrap.b + 512 - 16 + 4);
    expected_magic = htole32(X86_BOOT_MAGIC_FAT);

X86_BOOT_MAGIC_FAT は C:\home\public\NetBSD\src\sys\sys\bootblock.h で以下のように定義されている。

#define  X86_BOOT_MAGIC(n)  ('x' << 24 | 0x86b << 12 | 'm' << 4 | (n))
#define  X86_BOOT_MAGIC_1  X86_BOOT_MAGIC(1)  /* pbr.S */
#define  X86_BOOT_MAGIC_2  X86_BOOT_MAGIC(2)  /* bootxx.S */
#define  X86_BOOT_MAGIC_PXE  X86_BOOT_MAGIC(3)  /* start_pxe.S */
#define  X86_BOOT_MAGIC_FAT  X86_BOOT_MAGIC(4)  /* fatboot.S */
#define  X86_MBR_GPT_MAGIC  0xedb88320    /* gpt.S */

'x' は 120(dec) で、'm' は 109(dec) なので、X86_BOOT_MAGIC_FAT の値は具体的には以下のようになる。

(120 << 24 | 0x86B << 12 | 109 << 4 | 4)
=> 2022094548

うむ。分からん。

プライマリブートコードが FAT16 以外の場合は、プライマリブートコード先頭から 512 * 2 + 4 バイト目にマジックナンバー X86_BOOT_MAGIC_1 がある。

  } else {
    /* Magic number is at start of sector following label */
    magic = (void *)(bootstrap.b + 512 * 2 + 4);
    expected_magic = htole32(X86_BOOT_MAGIC_1);

ディスクから読み込んだ値をブートコードへ上書きしている。IMHO は in my humble opinion のことらしい。「私、思うんですが、これバグです」とのこと。

    /*
     * For a variety of reasons we restrict our 'normal' partition
     * boot code to a size which enable it to be used as mbr code.
     * IMHO this is bugus (dsl).
     */
    if (!is_zero(bootstrap.b + 512-2-64, 64)) {
      warnx("Data in mbr partition table of new bootstrap");
      return 0;
    }
    if (!is_zero(bootstrap.b + 512, 512)) {
      warnx("Data in label part of new bootstrap");
      return 0;
    }
    /* Copy mbr table and label from existing disk buffer */
    memcpy(bootstrap.b + 512-2-64, disk_buf.b + 512-2-64, 64);
    memcpy(bootstrap.b + 512, disk_buf.b + 512, 512);
  }

  /* Validate the 'magic number' that marks the parameter block */
  if (*magic != expected_magic) {
    warnx("Invalid magic in stage1 bootstrap %x != %x",
        *magic, expected_magic);
    return 0;
  }

途中すったもんだして、最終的にプライマリブートコードをディスクへ書き込む。

  /* Copy new bootstrap data into disk buffer, ignoring label area */
  memcpy(&disk_buf, &bootstrap, 512);
  if (params->s1stat.st_size > 512 * 2) {
    memcpy(disk_buf.b + 2 * 512, bootstrap.b + 2 * 512,
        params->s1stat.st_size - 2 * 512);
    /* Zero pad to 512 byte sector boundary */
    memset(disk_buf.b + params->s1stat.st_size, 0,
      (8192 - params->s1stat.st_size) & 511);
  }

Discussion