#はじめに
BIOS環境やEFI環境(64bit)でBitVisorを起動できるUSBメモリの作り方です。
USBメモリから先に起動する設定にしておくことで、USBメモリを差し込むだけで BitVisor を起動できます。
また、1つのUSBメモリでBIOS環境とEFI環境の両方に対応しており、Linux, Windows, Mac で BitVisor を起動できます。
USBメモリにすることでローカルディスクの変更が不要になるほか、
必要なときだけBitVisorを起動したり不要なときに外したりが簡単にできます。
#準備
###USBメモリ
内容が壊れてもいいUSBメモリを用意します。
###コンパイル済みバイナリ
コンパイル済みの BitVisor のバイナリ(bitvisor.efl)やブートローダ(bootloaderusb, loadvmm.efi)、インストール用スクリプト(install.sh)を用意します。
BitVisor Advent Calendar 2015 の8日目の記事に書いた方法でコンパイルしてください。
説明の都合上、上記のファイルがホームディレクトリにコピーしてあることとします。
$ cd
$ cp bitvisor/bitvisor.elf .
$ cp bitvisor/boot/loader/bootloaderusb .
$ cp bitvisor/boot/loader/install.sh .
$ cp bitvisor/uefi-loader/loadvmm .
コンパイル環境がない場合や面倒な場合は、www.bitvisor.org においてある Nightly Build を使うことも出来ます。
$ wget http://www.bitvisor.org/bitvisor/bitvisor.elf
$ wget http://www.bitvisor.org/bitvisor/boot/loader/bootloaderusb
$ wget http://www.bitvisor.org/bitvisor/boot/loader/install.sh
$ wget http://www.bitvisor.org/bitvisor/boot/uefi-loader/loadvmm.efi
#Linux
##基本編
###USBメモリのデバイス名取得
USBメモリのデバイス名(sd?)を取得します。
dmseg コマンドで確認したり、以下の様なコマンドを入力するなどして、間違えないように十分に確認します。
$ cd /sys/block; for dev in sd?; do echo $dev:; cat $dev/device/vendor $dev/device/model; echo $(($(cat $dev/size)/2048))MiB; done; cd
デバイス名が間違っているとシステムを破壊する危険性がありますので、十分に注意してください。
ここではUSBメモリのデバイス名が sdb であると仮定します。
###パーティションの再作成
BIOS環境で起動する bitvisor.elf とブートローダを格納する領域を確保します。
パーティションを切り直して、先頭パーティションの前に128MiBの空き領域を作成します。
$ sudo dd if=/dev/zero of=/dev/sdb bs=512 count=1
$ sudo parted /dev/sdb mklabel msdos
$ sudo parted /dev/sdb "mkpart p fat32 262144s -0"
$ sudo mkfs.vfat -c /dev/sdb1
###EFI環境のためのインストール
FAT 領域をマウントして、loadvmm.efi と bitvisor.elf を EFI 環境で起動できるように書き込みます。
$ mkdir /tmp/mnt
$ sudo mount /dev/sdb1 /tmp/mnt
$ sudo mkdir -p /tmp/mnt/EFI/BOOT
$ sudo cp ./loadvmm.efi /tmp/mnt/EFI/BOOT/BOOTX64.EFI
$ sudo cp ./bitvisor.elf /tmp/mnt/EFI/BOOT/BITVISOR.ELF
$ sudo umount /tmp/mnt
$ rmdir /tmp/mnt
###BIOS環境のためのインストール
bitvisor.elf とブートローダ(bootloaderusb)をUSBメモリの先頭部分に書き込みます。
$ sudo ./install.sh -f /dev/sdb 0 8 ./bootloaderusb ./bitvisor.elf
2回目以降に書き込むときは、-f オプションは不要です。
###後処理
念のため sync してから eject します。
$ sync
$ eject /dev/sdb
##発展編
基本編のやり方だと、bitvisor.elf がUSBメモリの先頭とFATパーティションとの2箇所に格納されていて無駄な感じがするので、これらを一つにまとめてみます。
###USBメモリの準備
普通のフォーマット済みUSBメモリを準備します。基本編でパーティションを変更した場合は、下記のようにして元に戻しておきます。
$ sudo dd if=/dev/zero of=/dev/sdb bs=512 count=1
$ sudo parted /dev/sdb mklabel msdos
$ sudo parted /dev/sdb "mkpart p fat32 2048s -0"
$ sudo mkfs.vfat -c /dev/sdb1
###コピースクリプトの作成
bitvisor.elf をコピーするためのスクリプトを作成します。
以下のスクリプトを copyelf.sh という名前で保存してください。これは、install.sh から抜き出したものです。
getbsssize(){
bsssize=0
if ! dd if="$elf" bs=1 skip=0 count=4 | od |
grep -q '^0000000 042577 043114'
then
echo "ELF header not found in \`$elf'." >&2
exit 1
fi
set -- $(dd if="$elf" bs=1 skip=28 count=4 | od -i)
phoff=$2
set -- $(dd if="$elf" bs=1 skip=42 count=2 | od -i)
phentsize=$2
set -- $(dd if="$elf" bs=1 skip=44 count=2 | od -i)
phnum=$2
while test $phnum -gt 0
do
phnum=$(($phnum-1))
set -- $(($phoff+$phentsize*$phnum))
set -- $(($1+16)) $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
case "$3" in
1) ;;
*) continue;;
esac
set -- $(($1+4)) $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
set -- "$3" $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
bsssize=$(($bsssize+$3-$1))
done
}
elf=$1
device=$2
getbsssize
{
cat "$1"
shift
if test $bsssize -ge 512
then
dd if=/dev/zero count=$(($bsssize/512))
bsssize=$(($bsssize%512))
fi
if test $bsssize -gt 0
then
dd if=/dev/zero bs=1 count=$bsssize
fi
} | dd of="$device" conv=notrunc
###EFI環境のためのインストール
以下のようにしてFAT領域をマウントしてから、loadvmm.efi と bitvisor.elf をコピーします。
$ mkdir /tmp/mnt
$ sudo mount /dev/sdb1 /tmp/mnt
$ sudo mkdir -p /tmp/mnt/EFI/BOOT
$ sudo cp ./loadvmm.efi /tmp/mnt/EFI/BOOT/BOOTX64.EFI
$ sudo sh copyelf.sh ./bitvisor.elf /tmp/mnt/EFI/BOOT/BITVISOR.ELF
###bitvisor.elf の場所(LBA)の特定
bitvisor.elf がどこに格納されたか LBA を特定します。
$ sudo hdparm --fibmap /tmp/mnt/EFI/BOOT/BITVISOR.ELF
出力結果は、例えば以下のようになります。
/tmp/mnt/EFI/BOOT/BITVISOR.ELF:
filesystem blocksize 512, begins at LBA 2048; assuming 512 byte sectors.
byte_offset begin_LBA end_LBA sectors
0 2728 49135 46408
begin_LBA の項目に書いてある値(2728)がこの場合の LBA の値になります。
複数の行が表示されるときは、フラグメンテーションを起こしていますので、うまく動作しません。デフラグしてください。
###BIOS環境のためのインストール
bitvisor.elf の LBA の値を install.sh の3番めの引数に指定して実行します。
既に bitvisor.elf が書き込まれているところに上書きするので、-f オプションは不要です。
$ sudo ./install.sh /dev/sdb 0 2728 ./bootloaderusb ./bitvisor.elf
###後処理
アンマウントして、USBメモリを抜きます。
$ sudo umount /tmp/mnt
$ rmdir /tmp/mnt
$ sync
$ sudo eject /dev/sdb
Windows
BitVisor Advent Calendar 2015 の8日目の記事に書いた方法で MSYS2 をインストールしてあることが前提です。
Linuxと違い、いきなり発展編でやります。
ここでは USB メモリのドライブ名は D: ドライブであると仮定します。
###コピースクリプトの作成
bitvisor.elf をコピーするためのスクリプトを作成します。
以下のスクリプトを copyelf.sh という名前で保存してください。これは install.sh から抜き出したものです。
getbsssize(){
bsssize=0
if ! dd if="$elf" bs=1 skip=0 count=4 | od |
grep -q '^0000000 042577 043114'
then
echo "ELF header not found in \`$elf'." >&2
exit 1
fi
set -- $(dd if="$elf" bs=1 skip=28 count=4 | od -i)
phoff=$2
set -- $(dd if="$elf" bs=1 skip=42 count=2 | od -i)
phentsize=$2
set -- $(dd if="$elf" bs=1 skip=44 count=2 | od -i)
phnum=$2
while test $phnum -gt 0
do
phnum=$(($phnum-1))
set -- $(($phoff+$phentsize*$phnum))
set -- $(($1+16)) $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
case "$3" in
1) ;;
*) continue;;
esac
set -- $(($1+4)) $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
set -- "$3" $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
bsssize=$(($bsssize+$3-$1))
done
}
elf=$1
device=$2
getbsssize
{
cat "$1"
shift
if test $bsssize -ge 512
then
dd if=/dev/zero count=$(($bsssize/512))
bsssize=$(($bsssize%512))
fi
if test $bsssize -gt 0
then
dd if=/dev/zero bs=1 count=$bsssize
fi
} | dd of="$device" conv=notrunc
###EFI環境のためのインストール
以下のようにしてFAT領域に loadvmm.efi と bitvisor.elf をコピーします。
$ mkdir -p d:/EFI/BOOT
$ cp ./loadvmm.efi d:/EFI/BOOT/BOOTX64.EFI
$ sh ./copyelf.sh ./bitvisor.elf d:/EFI/BOOT/BITVISOR.ELF
###bitvisor.elf の場所(LBA)の特定
以下のプログラムを getlba.c という名前で保存してください。
#include <stdio.h>
#include <string.h>
#include <windows.h>
int main(int argc, char *argv[])
{
BOOL success = TRUE;
DWORD byte, spc, bps;
TCHAR vol[MAX_PATH], drive[] = "\\\\.\\D:", fs[MAX_PATH];
UCHAR bs[512];
success &= GetVolumePathName(argv[1], vol, sizeof(vol));
success &= GetVolumeInformation(vol, NULL, 0, NULL, NULL, NULL,
fs, sizeof(fs));
success &= (strncmp(fs, "FAT", 3) == 0);
drive[4] = vol[0];
HANDLE vhandle = CreateFile(drive, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
success &= ReadFile(vhandle, &bs, sizeof(bs), &byte, NULL);
success &= (byte == 512);
ULONG sz16 = *(USHORT*)&bs[22], sz32 = *(ULONG*)&bs[36];
ULONG sz = sz16 != 0 ? sz16 : sz32;
ULONG start = *(USHORT*)&bs[14] + sz * bs[16]
+ (*(USHORT*)&bs[17] * 32) / 512;
HANDLE handle = CreateFile (argv[1], GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
STARTING_VCN_INPUT_BUFFER vcn = {0};
RETRIEVAL_POINTERS_BUFFER rp;
success &= DeviceIoControl(handle, FSCTL_GET_RETRIEVAL_POINTERS,
&vcn, sizeof(vcn), &rp, sizeof(rp),
NULL, NULL);
success &= GetDiskFreeSpace(vol, &spc, &bps, NULL, NULL);
byte = GetFileSize(handle, NULL);
success &= (byte <= rp.Extents[0].NextVcn.QuadPart * spc * bps);
LONGLONG loff = rp.Extents[0].Lcn.QuadPart * spc * bps;
struct {
ULONG NumberOfPhysicalOffsets;
struct {
ULONG DiskNumber;
LONGLONG Offset;
};
} poff;
success &= DeviceIoControl(vhandle, IOCTL_VOLUME_LOGICAL_TO_PHYSICAL,
&loff, sizeof(loff), &poff, sizeof(poff),
NULL, NULL);
if (success)
printf("LBA: %d\n", start + poff.Offset / bps);
}
gcc でコンパイルして、BITVISOR.ELF のパス名を指定して実行します。このとき、MSYS のパス名("/d/" など)を使わないでください。
$ gcc getlba.c -o getlba
$ ./getlba.exe d:/EFI/BOOT/BITVISOR.ELF
エラーが発生しなければ、LBAの値が表示されます。何も表示されなければ、何らかのエラーが発生していますので、getlba.c を適当に書き換えてエラーの原因を突き止めてください。
LBA: 2728
###デバイス名の特定
まずはUSBメモリのデバイスID(\\.\PhysicalDrive?)を特定します
$ wmic diskdrive list brief
結果は例えば以下の様になります。この場合、USBメモリのデバイスIDは \\.\PHYSICALDRIVE1 です。
Caption DeviceID Model Partitions Size
SAMSUNG MZHPU512HCGL-00004 \\.\PHYSICALDRIVE0 SAMSUNG MZHPU512HCGL-00004 3 512105932800
Ut165 USB2FlashStorage USB Device \\.\PHYSICALDRIVE1 Ut165 USB2FlashStorage USB Device 1 1003484160
/dev の下にあるデバイスIDに対応するデバイスファイルを見つけます。\\.\PHYSICALDRIVE0 なら /dev/sda、\\.\PHYSICALDRIVE1 なら /dev/sdb になるはずです。
ここではUSBメモリのデバイスファイル名が /dev/sdb であると仮定します。
デバイス名が間違っているとシステムを破壊する危険性がありますので、十分に注意してください。
###BIOS環境のためのインストール
特定したデバイスファイルと LBA を指定して、以下のようにインストールします。
既に bitvisor.elf が書き込まれているところに上書きするので、-f オプションは不要です。
ELFヘッダが見つからないので -f オプションをつけろと言われた場合は LBA が間違っている可能性があります。
$ ./install.sh /dev/sdb 0 2728 ./bootloaderusb ./bitvisor.elf
###後処理
Windows の Explorer でUSBメモリの取り外し処理をしてからUSBメモリを取り外します。
#Mac
###USBメモリのデバイス名取得
USBメモリを挿します。Mac ではUSBメモリのボリューム名と同じ名前のディレクトリが /Volumes 以下に作成されて、そこにUSBメモリの内容がマウントされます。
$ ls /Volumes
BITVISOR Macintosh HD
ここでは、ボリューム名は BITVISOR であるとします。
次に、デバイス名を特定します。次のように df コマンドの引数にマウントされたディレクトリを指定して実行してください。
$ df /Volumes/BITVISOR
Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
/dev/disk2s1 62554112 23488 62530624 1% 0 0 100% /Volumes/BITVISOR
"Filesystem" の項目にある /dev/disk2s1 から s1 を取り除いたものがデバイス名です。ここでは disk2 とします。
デバイス名が間違っているとシステムを破壊する危険性がありますので、十分に注意してください。
必要なら以下のようにしてUSBメモリを再フォーマットしておきます。
diskutil eraseDisk FAT32 BITVISOR MBR disk2
###コピースクリプトの作成
bitvisor.elf をコピーするためのスクリプトを作成します。
以下のスクリプトを copyelf.sh という名前で保存してください。これは install.sh から抜き出したものを Mac 向けに少し修正したものです。
getbsssize(){
bsssize=0
if ! dd if="$elf" bs=1 skip=0 count=4 | od |
egrep '^0000000\s+042577\s+043114'
then
echo "ELF header not found in \`$elf'." >&2
exit 1
fi
set -- $(dd if="$elf" bs=1 skip=28 count=4 | od -i)
phoff=$2
set -- $(dd if="$elf" bs=1 skip=42 count=2 | od -i)
phentsize=$2
set -- $(dd if="$elf" bs=1 skip=44 count=2 | od -i)
phnum=$2
while test $phnum -gt 0
do
phnum=$(($phnum-1))
set -- $(($phoff+$phentsize*$phnum))
set -- $(($1+16)) $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
case "$3" in
1) ;;
*) continue;;
esac
set -- $(($1+4)) $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
set -- "$3" $(dd if="$elf" bs=1 skip=$1 count=4 | od -i)
bsssize=$(($bsssize+$3-$1))
done
}
elf=$1
device=$2
getbsssize
{
cat "$1"
shift
if test $bsssize -ge 512
then
dd if=/dev/zero count=$(($bsssize/512))
bsssize=$(($bsssize%512))
fi
if test $bsssize -gt 0
then
dd if=/dev/zero bs=1 count=$bsssize
fi
} | dd of="$device" conv=notrunc
###EFI環境のためのインストール
以下のようにしてFAT領域に loadvmm.efi と bitvisor.elf をコピーします。
$ mkdir -p /Volumes/BITVISOR/EFI/BOOT
$ cp ./loadvmm.efi /Volumes/BITVISOR/EFI/BOOT/BOOTX64.EFI
$ sh ./copyelf.sh ./bitvisor.elf /Volumes/BITVISOR/EFI/BOOT/BITVISOR.ELF
###bitvisor.elf の場所(LBA)の特定
以下のプログラムを getlba.c という名前で保存してください。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/mount.h>
int main(int argc, char *argv[])
{
int fd, status;
off_t lba = 0, filesize, offset;
struct log2phys phys;
struct statfs stat;
char mbr[512];
fd = open(argv[1], O_RDONLY); if (fd < 0) return 1;
filesize = lseek(fd, 0, SEEK_END); if (filesize < 0) return 2;
for (offset = 0; offset < filesize; offset += 4096) {
offset = lseek(fd, offset, SEEK_SET); if (offset < 0) return 3;
status = fcntl(fd, F_LOG2PHYS, &phys); if (status < 0) return 4;
if (lba != 0) {
if (phys.l2p_devoffset - offset != lba)
{ printf("fragmented\n"); return 5; }
} else
lba = phys.l2p_devoffset;
}
status = statfs(argv[1], &stat); if (status < 0) return 6;
stat.f_mntfromname[10] = '\0';
fd = open(stat.f_mntfromname, O_RDONLY); if (fd < 0) return 7;
status = read(fd, mbr, sizeof(mbr)); if (status < 512) return 8;
offset = *(uint32_t *)&mbr[0x1c6];
printf("LBA: %lld\n", offset + lba / 512);
}
gcc でコンパイルして、BITVISOR.ELF のパス名を指定して実行します。
$ gcc getlba.c -o getlba
$ sudo ./getlba /Volumes/BITVISOR/EFI/BOOT/BITVISOR.ELF
エラーが発生しなければ、LBAの値が表示されます。何も表示されなければ、何らかのエラーが発生していますので、getlba.c を適当に書き換えてエラーの原因を突き止めてください。
LBA: 90220
Mac の場合、フラグメンテーションを起こす確率が高いようです。そこでフラグメンテーションが起きた時だけエラーメッセージを表示するようにしてあります。
フラグメンテーションが起きた時は、フラグメンテーションが起きなくなるまで bitvisor.elf を別の名前でコピーを繰り返してください。例えば、以下の様なコマンドを実行します。
$ path=/Volumes/BITVISOR/EFI/BOOT/
$ for i in {0..9}; do f=$path/BITVISOR${i}.ELF; sh ./copyelf.sh ./bitvisor.elf $f >& /dev/null; sudo ./getlba $f && break; done
$ mv $f $path/BITVISOR.ELF
$ rm $path/BITVISOR?.ELF
最終的に表示された LBA を覚えておきます。
###BIOS環境のためのインストール
ボリュームをアンマウントしてから、特定したデバイス名と LBA を指定して、以下のようにインストールします。
既に bitvisor.elf が書き込まれているところに上書きするので、-f オプションは不要です。
ELFヘッダが見つからないので -f オプションをつけろと言われた場合は LBA が間違っている可能性があります。
$ diskutil umountDisk /dev/disk2
$ ./install.sh /dev/disk2 0 90220 ./bootloaderusb ./bitvisor.elf
###後処理
以下のコマンドを実行してから USB メモリを取り外します。
diskutil eject /dev/disk2