zebian.log

技術系備忘録とか

UEFI Memory Mapで躓いたポイント

概要

UEFIからブートするタイプのOSでは、ブートローダーで取得したUEFI Memory Mapを受け取り、その情報をもとにメモリアロケーションの初期化を行う。私の自作OSもしかり。

Memory Mapの内容は以下の通り(自作OSのコード)。 github.com

pub const UEFI_PAGE_SIZE: usize = 0x1000;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MemoryType {
    Reserved,
    LoaderCode,
    LoaderData,
    BootServicesCode,
    BootServicesData,
    RuntimeServicesCode,
    RuntimeServicesData,
    Conventional,
    Unusable,
    AcpiReclaim,
    AcpiNonVolatile,
    Mmio,
    MmioPortSpace,
    PalCode,
    PersistentMemory,
    Other(u32),
}

#[derive(Debug, Copy, Clone)]
pub struct MemoryDescriptor {
    pub ty: MemoryType,
    pub phys_start: u64,
    pub virt_start: u64,
    pub page_cnt: u64,
    pub attr: u64,
}

ここで言うpage_cntは1ページがUEFI_PAGE_SIZEバイト=つまり4,096バイトのページの個数である。そして以下がOSが受け取ったMemory Mapの出力。

MemoryDescriptor { ty: BootServicesCode, phys_start: 0, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 4096, virt_start: 0, page_cnt: 159, attr: 15 }
MemoryDescriptor { ty: LoaderData, phys_start: 1048576, virt_start: 0, page_cnt: 909, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 4771840, virt_start: 0, page_cnt: 883, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 8388608, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 8421376, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 8433664, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 8437760, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 8454144, virt_start: 0, page_cnt: 240, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 9437184, virt_start: 0, page_cnt: 3328, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 23068672, virt_start: 0, page_cnt: 698677, attr: 15 }
MemoryDescriptor { ty: LoaderData, phys_start: 2884849664, virt_start: 0, page_cnt: 32768, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3019067392, virt_start: 0, page_cnt: 32769, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3153289216, virt_start: 0, page_cnt: 32, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3153420288, virt_start: 0, page_cnt: 9909, attr: 15 }
MemoryDescriptor { ty: LoaderCode, phys_start: 3194007552, virt_start: 0, page_cnt: 56, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3194236928, virt_start: 0, page_cnt: 53, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3194454016, virt_start: 0, page_cnt: 215, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3195334656, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3195346944, virt_start: 0, page_cnt: 6, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3195371520, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3195375616, virt_start: 0, page_cnt: 1501, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3201523712, virt_start: 0, page_cnt: 168, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3202211840, virt_start: 0, page_cnt: 17, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3202281472, virt_start: 0, page_cnt: 6, attr: 15 }
MemoryDescriptor { ty: LoaderData, phys_start: 3202306048, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3202322432, virt_start: 0, page_cnt: 88, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3202682880, virt_start: 0, page_cnt: 25, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3202785280, virt_start: 0, page_cnt: 5, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3202805760, virt_start: 0, page_cnt: 59, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203047424, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203092480, virt_start: 0, page_cnt: 39, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203252224, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203256320, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203268608, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203280896, virt_start: 0, page_cnt: 7, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203309568, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203325952, virt_start: 0, page_cnt: 14, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203383296, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203387392, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203432448, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203440640, virt_start: 0, page_cnt: 13, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203493888, virt_start: 0, page_cnt: 5, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203514368, virt_start: 0, page_cnt: 13, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203567616, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203579904, virt_start: 0, page_cnt: 12, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203629056, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203633152, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203645440, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203653632, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203686400, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203698688, virt_start: 0, page_cnt: 6, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203723264, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203731456, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203776512, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203788800, virt_start: 0, page_cnt: 15, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203850240, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203858432, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203866624, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203874816, virt_start: 0, page_cnt: 20, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203956736, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203960832, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203993600, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204001792, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204005888, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204009984, virt_start: 0, page_cnt: 20, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204091904, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204100096, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204132864, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204165632, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204182016, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204198400, virt_start: 0, page_cnt: 16, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204263936, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204272128, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204276224, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204280320, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204292608, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204304896, virt_start: 0, page_cnt: 35, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204448256, virt_start: 0, page_cnt: 513, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206549504, virt_start: 0, page_cnt: 12, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206598656, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206631424, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206676480, virt_start: 0, page_cnt: 5, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206696960, virt_start: 0, page_cnt: 24, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206795264, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206803456, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206807552, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206815744, virt_start: 0, page_cnt: 9, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206852608, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206860800, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206864896, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206873088, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206877184, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206881280, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206897664, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206905856, virt_start: 0, page_cnt: 23, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3207000064, virt_start: 0, page_cnt: 1045, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211280384, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211313152, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211325440, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211329536, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211333632, virt_start: 0, page_cnt: 10, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211374592, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211382784, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211390976, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211395072, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211407360, virt_start: 0, page_cnt: 586, attr: 15 }
MemoryDescriptor { ty: RuntimeServicesData, phys_start: 3213807616, virt_start: 0, page_cnt: 256, attr: 9223372036854775823 }
MemoryDescriptor { ty: RuntimeServicesCode, phys_start: 3214856192, virt_start: 0, page_cnt: 256, attr: 9223372036854775823 }
MemoryDescriptor { ty: Reserved, phys_start: 3215904768, virt_start: 0, page_cnt: 128, attr: 15 }
MemoryDescriptor { ty: AcpiReclaim, phys_start: 3216429056, virt_start: 0, page_cnt: 18, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 3216502784, virt_start: 0, page_cnt: 128, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3217027072, virt_start: 0, page_cnt: 513, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3219128320, virt_start: 0, page_cnt: 136, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3219685376, virt_start: 0, page_cnt: 32, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3219816448, virt_start: 0, page_cnt: 35, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3219959808, virt_start: 0, page_cnt: 17, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3220029440, virt_start: 0, page_cnt: 24, attr: 15 }
MemoryDescriptor { ty: RuntimeServicesData, phys_start: 3220127744, virt_start: 0, page_cnt: 132, attr: 9223372036854775823 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 3220668416, virt_start: 0, page_cnt: 136, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 4294967296, virt_start: 0, page_cnt: 262144, attr: 15 }
MemoryDescriptor { ty: Reserved, phys_start: 4278173696, virt_start: 0, page_cnt: 4, attr: 0 }

1~3行目に着目してもらいたい。

1: MemoryDescriptor { ty: BootServicesCode, phys_start: 0, virt_start: 0, page_cnt: 1, attr: 15 }
2: MemoryDescriptor { ty: Conventional, phys_start: 4096, virt_start: 0, page_cnt: 159, attr: 15 }
3: MemoryDescriptor { ty: LoaderData, phys_start: 1048576, virt_start: 0, page_cnt: 909, attr: 15 }

2番目の領域、Conventionalのpage_cntは159、つまり領域のサイズとしては159 * 4,096 = 651,264バイトになる。開始アドレスが4,096とすると終了アドレスは4,096 + 651,264 = 655,360になる。

ところが、その次の3番目の領域の開始アドレスを見ると1048,576とあり、2番目の終了アドレスから393,216バイト分差がある。ここが躓きポイントであり、私が1年以上気付かなかった自作OSのメモリ破壊バグの原因である(実はみかん本にはこれについての言及が存在する)。

Memory Mapに存在しない領域をどう扱うか

Memory Mapに存在しない領域、先の例では2番目の領域と3番目の領域の間の393,216バイトのことだが、このような領域は空き領域として自由に読み書きできるように扱ってはいけない。

メモリ破壊バグについて

私の自作OSでは、ヒープアロケータ初期化以前のメモリ管理として、4KiBごとの領域をビットマップで管理するビットマップアロケータを実装している。Memory Mapを見るとOSが自由に扱える領域はConventional、BootServicesCode / Data(中身のデータの退避などをしたあとで)であるが、当初のビットマップアロケータは「全てのビットマップを空き領域としてマップしてから、自由に扱えない領域を使用済みとしてマップする」という実装になっていた。つまり、Memory Mapに存在しない領域は空き領域という扱いになっており、そこに何らかのデータが書き込まれた瞬間、メモリ破壊によってOSがクラッシュした。スタック関連のデータが破壊されている感触があったが、詳細は不明。

謎のクラッシュは大抵はメモリ破壊バグ(戒め)

2023年を振り返る

年末なので、今年1年を振り返ってみようと思います。

1月

だそうです...

今もデュアルでBenQを使ってる。BenQはいいぞ。

2月

確かデータサイエンスの授業の期末課題で取り扱ったネタ。徹夜でやってたのを覚えている。内容はしょうもなかったけど一応高評価をもらえた。 マルチスレッド実行が上手く行かなくてシングルでやったので、データの加工にクソほど時間を要した。

この時期特有の電気使い過ぎ問題。

3月

DTM、楽しいけどやる時間がなさすぎて結局趣味にはならなかった。

この頃ずっと自作OSのUSBドライバの実装に取り組んでた。ちなみにノートPCのキーボードは今でも(仮想?)PS/2制御だということを大分後に知った。

久々にお絵かきをしたり。

人生で最も長時間プレイしたゲームがHearts of Iron IVで、そんな感じのゲームを自作してみたくなった。

こういうのがあるので自作OSのデバッグと同時並行してQEMUソースコードを読む会が行われている。

4月

ぼざろの影響で聴き始めた。個人的にはアフターダークが一番好き。

Cのプリプロセッサで遊んでた。

ELFデバッガーを自作してた。

5月

ELFデバッガーの自作でptraceやforkを学んでたりした。

2度目のセキュリティ・キャンプに参加するために応募課題を書いてた。この悩みは結局杞憂だったが、hsjoihs氏に名言を教えてもらった。

この「作業」というやつが運命の分かれ道だった。

具体的に何をやっていたかというと、応募したゼミがZ2の「Rust製Linux向けアンチウイルス実装ゼミ」で、当時は「アンチウイルスに興味はあるが仕組みが全くわからん」という状態で応募しようとしていたため、せっかくなら少しでも実際に手を動かして勉強してみようと思い、リアルタイムマルウェア検知機をChatGPTに質問しながら作り、応募課題にその進捗状況や感想を書く欄もないのに無理矢理ねじ込んだ。 内容はシンプルで、inotifyによるディレクトリ内の変更検知、変更があったファイルの表層解析、ELFバイナリの静的解析を行っている。静的解析に関してはこの短時間で難しいことはできないので、脆弱性のあるscanfコードをバイナリから検出するみたいなことをしていた。

ちなみにこの古いtogetterまとめを見て大いに勇気づけられたので、みんなも応募課題を書くときはぜひ見てほしい。

6月

初めてSECCON Beginners CTFに参加した。Beginnersであってもやっぱり難しかった。

無事に選考通過。よかった~

ついにUSBキーボードの入力にまでこぎつけることができた。ちなみに入力を続けると止まってしまう現象は未だに直ってない。

アセンブラを自作し始めた。結局全然やらずに放置してしまっているので、いつかは再開したい。

7月

やらかし。メインで使ってたLinuxを破壊した。その後も色々あってメインでLinuxを使うのはやめてWindowsに戻した。今の時代WSL2とか便利なものがあるしね。

ちょうど事前学習期間だった気がする。

8月

ついにセキュリティ・キャンプが始まった!

本番でもかなり波乱万丈なことをやっていたが、とても勉強になった。

去年お世話になった内田さんとhikaliumさんと現地で再開できた。

田舎の工場事務所で社内SEのバイトを始めた。通勤に車で1時間かかるのはアレだけど待遇が良いので結構気に入ってる。

9月

成人した。結局タイミングがなくてほぼ飲酒してない。

バイトの初任給でStarfieldを買った。PCのスペックが低くてしょっちゅうフリーズしたり、メインクエストが進行不可になったりしている。悲しい。

10月

Chromebookをポチった。お絵かきに丁度よくて、こういう持ち運べるのが1台あると結構便利。

課題やらバイトやらの開発で10~12月はかなり忙しかった。セキュリティコンテスト、ドローンの自動操縦システムを作るなどをしていた。

11月

研究開発の課題用でFPGAをやることになったので、Tang Primer 20Kを購入した。思いつきでnand2tetrisを実装している。

友達が通っている千葉工大の文化祭に行った。ちぇりーたくあん氏のNAND CPUも実物で見れて感動。色々おしゃべりできたのでとても楽しかった。

12月

ひたすらFPGAをやっていた。nand2tetris自体は完成していて、MMIOを実装して機械語からLEDやディスプレイを制御できるように増築工事中。

総括

今年は色々なことに挑戦するという目標でやっていたので、本音を言うともうちょっと密度が欲しかったところですが、おおむね達成できたのではと思います。特に去年もそうでしたが、今年の2度目のセキュリティ・キャンプに参加したのをきっかけに視野がかなり広まったので、とても良い機会だったなと思います。

授業などが忙しくてセキュリティの勉強が全然できていなかったので、来年はちゃんとやります。

Tello SDKに書かれていない謎仕様

DJI Telloを使ってVideoとStateを取得するプログラムを書いていたときの出来事。

Tello SDK User Guide

www.ryzerobotics.com

Receive Tello State

Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT: 8890

Remark3: Set up a UDP server on PC, Mac or Mobile device and listen the message from IP 0.0.0.0 via UDP PORT 8890. Do Remark2 to start receiving state data if you haven’t.

事前に192.168.10.1:8889に任意の任意のデータを送信しないと、Stateの受信に失敗する。

Receive Tello Video Stream

Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT:1111

Remark4: Set up a UDP server on PC, Mac or Mobile device and listen the message from IP 0.0.0.0 via UDP PORT 11111.

Remark5: Do Remark2 if you haven’t. Then send “streamon” command to Tello via UDP PORT 8889 to start the streaming.

事前に192.168.10.1:62512に任意のデータを送信しないと、Videoの受信に失敗する。

Wiresharkを覗いてみた

どうやらStateの送信元ポートが8889、Videoの送信元ポートが62512らしい。つまり送信元ポートを叩いていることになる。

謎仕様と書いたが結局のところ...

PythonOpenCVのVideoCaptureでVideoを受信する際にはポートを叩く必要はなかった。VideoとStateを取得するプログラムはRustで書いているので、言語の問題なのかもしれない。

【Linux】Fallout4にModを導入する

LinuxでFallout4にModを導入することができたので、備忘録として残しておこうと思います。

私のPC環境

LinuxのSteamクライアントでFallout4が正常に動いていれば、特に問題はないと思います。

参考にしたサイト

www.nexusmods.com wiki.fallout4.z49.org

英語版Fallout4を日本語化する

Wikiを参考にしながらFallout4Localizerを使って日本語版のバックアップ、英語版の日本語化を行います。私はバッチファイルの内容を確認しながら、手動でファイルのコピーを行いました。

01Fallout4BackupJPFiles.bat

  • Data/Video, Data/Fallout4 - Voices.ba2, Data/Fallout4 - Vocies_rep.ba2Fallout4Localizer内にコピー

02Fallout4InstallJPFiles.bat

英語音声・日本語字幕の場合

  • 以下共通へ

日本語音声・日本語字幕の場合

  • Fallout4Localizer/BackupEnglishVoice内にFallout4 - Voices.ba2をコピー
  • Fallout4Localizer内の*_ja.*に該当するすべてのファイルを*_en.*に書き換え
  • Fallout4Localizer内の*_en.*に該当するすべてのファイルをData内ににコピー
  • バックアップしたFallout4 - Voices.ba2, Fallout4 - Voices_rep.ba2Data内にコピー

以下共通

  • Fallout4Localizer内のInterface, Strings, VideoDataにコピー

iniの編集

Fallout4.iniは、私の環境では/home/[user_name]/.local/share/Steam/steamapps/compatdata/377160/pfx/drive_c/users/steamuser/Documents/My Games/Fallout4/Fallout4.iniに位置しています。Wikiにもあるように、Fallout4.iniは削除してFallout4Custom.iniに以下を追加します。

[Archive]
bInvalidateOlderFiles=1
SResourceArchiveList=Fallout4 - Voices.ba2, Fallout4 - Meshes.ba2, Fallout4 - MeshesExtra.ba2, Fallout4 - Misc.ba2, Fallout4 - Sounds.ba2, Fallout4 - Materials.ba2, Fallout4 - Voices_rep.ba2

Mod Organizer 2をインストールする

次に、Mod Organizer 2をインストールします。Lutrisでも入手することができますが、このリポジトリからのインストールが推奨されているようです。 github.com

READMEに書かれている通り、必要パッケージを事前にインストールし、install.shを実行します。ポップアップが出現するので、それに従ってインストールを進めます。インストールが完了すると、SteamからFallout4を起動しようとするとMod Organizer 2が起動するようになります。私の場合は、インストールする前にProton6.3に変更して一度起動する必要がありました(指示を促すメッセージが表示されます)。

F4SEをインストールする

f4se.silverlock.org

Modを導入する

Nexus ModsからマニュアルダウンロードでModをダウンロードし、Mod Organizer 2からインストールすることができます。

Linuxカーネル(x86_64)をビルドしてQEMUで実行する

カーネルを取得する

$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
$ cd linux
# リポジトリを最新のリリースにチェックアウトする
$ git fetch --tags
$ latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
$ git checkout $latestTag

カーネルをビルドする

# x86_64の設定ファイルをコピー
$ cp arch/x86/configs/x86_64_defconfig ./.config
# Kernel hackingでいろいろ変更できる
$ make menuconfig
# CPUのコア数で並列処理
$ make -j$(nproc)

ユーザーランドを作る

Linuxカーネルは単体だけでは実行することができないため、BusyBoxを使ってinitramfsを作ります。initramfsはcpioアーカイブで、Linuxカーネルは起動時にファイルシステムをマウントする必要があるのですが、起動時はまだ各デバイスを判別することができないため、一時的なファイルシステムとして使われます。

$ cd ../
$ git clone https://git.busybox.net/busybox.git
$ cd busybox
# Setting -> Build static binaryをチェックする
$ make menuconfig
$ make install
$ find . | cpio -o --format=newc > ../../linux/rootfs.img

QEMUで実行する

bzImageLinuxカーネルの実行ファイルで-kernelオプションによってメモリ上に直接展開されるため、ブートローダーを用意する必要はありません。-initrdオプションでは作成したinitramfsを渡し、-appendオプションのrdinitによって、最初に実行されるプログラムをシェルに設定しています。

$ cd ../../linux
$ qemu-system-x86_64 -kernel ./arch/x86_64/boot/bzImage -initrd ./rootfs.img -append "root=/dev/ram rdinit=/bin/sh"

参考サイト

www.rinsymbol.net nullpo-head.hateblo.jp zenn.dev

live-serverでブラウザの自動リロードが効かない問題

www.npmjs.com

live-serverを使ったファイルの変更の監視で、ファイルを更新しても何故かブラウザが自動リロードしてくれなかった。

ビルドスクリプト

www.npmjs.com

esbuild-create-react-appで生成されたbuilder.tsと内容はほぼ同じ。

import { LiveServerParams, start } from "live-server";
import { build, BuildOptions } from "esbuild";
import cssModulesPlugin from "esbuild-css-modules-plugin";
import { removeSync, copySync } from "fs-extra";
import { watch } from "chokidar";

const isWatch = process.argv.includes("--watch");

const serverParams: LiveServerParams =
{
    port: 8181,
    root: "build",
    open: true
};

const buildParams: BuildOptions =
{
    color: true,
    entryPoints: ["src/index.tsx"],
    loader: { ".ts": "tsx", ".json": "json", ".png": "file", ".jpeg": "file", ".jpg": "file", ".svg": "file" },
    assetNames: "assets/[name]-[hash]",
    outdir: "build",
    minify: !isWatch,
    format: "cjs",
    bundle: true,
    sourcemap: isWatch,
    logLevel: "error",
    incremental: isWatch,
    plugins:
  [
        cssModulesPlugin()
    ]
};

// clean build folder
try
{
    removeSync("build");
}
catch (e)
{
    console.error(e);
}

// copy public folder into build folder
try
{
    copySync("public", "build");
}
catch (e)
{
    console.error(e);
}

if (isWatch)
{
    (async () =>
    {
        // build
        const result = await build(buildParams).catch(() => process.exit(1));

        // start live server
        start(serverParams);

        return watch("src/**/*", { ignored: /(^|[/\\])\../, ignoreInitial: true }).on("all", async (event, path) =>
        {
            if (event === "change")
            {
                console.log(`[esbuild] Rebuilding ${path}`);
                console.time("[esbuild] Done");
                if (result.rebuild) await result.rebuild();
                console.timeEnd("[esbuild] Done");
            }
        });
    })();
}
else
{
    console.log(`[esbuild] Building..`);
    build(buildParams).catch(() => process.exit(1));
}
ts-node scripts/build.ts --watch

esbuildがrebuildしたファイルを基にブラウザを自動リロードしたい。

原因

index.htmlのフォーマットに問題があった。 stackoverflow.com

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <!-- <meta
            http-equiv="Content-Security-Policy"
            content="script-src 'self'"
        /> -->
        <title>Title</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="index.js"></script>
    </body>
</html>

electronで使っていたindex.htmlをそのまま流用していたのが良くなかった。上のコメントアウトしたmetaタグがフォーマット的によろしくなかったらしい。

結論

web全然わからん

自作OSでシリアル通信をする

シリアル通信をやる上で参考になったサイトのパクリ個人的なまとめ

なぜシリアル通信をするのか

とりあえず何かしら文字を出力したいという時、例えばC言語だったらprintfC#だったらSystem.Console.WriteLineのように標準出力関数を使って文字を出力する。ところがこれらの機能はOSが提供しているので、そもそもOSが存在しない自作OS環境では使うことができない。もちろん画面に文字を表示する機能を実装すればいいだけの話だが、メモリを直接書き換える分、初心者にとっては割と敷居が高かったり、実装途中で原因不明の例外が発生したり...

QEMUにはシリアルコンソールを表示する機能があり、OS側でシリアル通信を実装すれば、コンソールに文字を出力させることができる。画面に文字を出力するよりかは難易度が低めだし、早めに実装できるのでおすすめ。

QEMUのシリアルコンソール

方法

OSDev Wikiを眺めてみると詳しいやり方が書いてあった。ありがたい。実機ではUARTと呼ばれるハードウェアチップがシリアル通信を制御していて、メモリマップドI/Oを介してOSはUARTを制御できる。QEMUVirtualBoxはこれをエミュレートできるので、我々はI/OレジスタをIN命令とOUT命令で操作するだけでデータの送受信を行うことができる。

シリアル通信をする前に使うポートを指定したり、ボーレートを設定したりなど初期化が必要になってくる。また、データ受信時の割り込み処理をするためにIRQハンドラも設定する必要があるが、今回はそれは使わずにループで受信処理を書こうと思う。(手抜き)ちなみにこのループでいちいち調べに行く方法をポーリングと呼ぶらしい。

ポート

COMポート(シリアルポート)とI/Oポートの対応表。特に何も設定していなければ、QEMUのシリアルコンソールはCOM1を使うようになっている。また、COM1とCOM3はIRQ4、COM2とCOM4はIRQ3に対応しているが、COM3以降はポートが他のハードウェアと競合することがあるため、ほとんどは1と2のみを使う。

COMポート I/Oポート IRQ
COM1 0x3f8 4
COM2 0x2f8 3
COM3 0x3e8 4
COM4 0x2e8 3
... ... ×

I/Oレジスタ

16550 UARTのI/Oレジスタソースコードを眺めると、QEMUでは16550AがUARTのエミュレーションとして実装されていることがわかる。ちなみに16550Aの「A」は16550のバグ修正版という意味らしい。

Interrupt Identification Register、Modem Status Register、Scratch Registerは使ってないので割愛。

I/Oポートのオフセット 命令 説明
+0(DLAB = 0) OUT Transmit Holding Register(THR)
+0(DLAB = 0) IN Receive Buffer Register(RBR)
+0(DLAB = 1) IN/OUT Divisor Latch LSB(DLL)
+1(DLAB = 0) IN/OUT Interrupt Enable Register(IER)
+1(DLAB = 1) IN/OUT Divisor Latch MSB(DLM)
+2 IN Interrupt Identification Register(IIR
+2 OUT FIFO Control Register(FCR)
+3 IN/OUT Line Control Register(LCR)
+4 IN/OUT Modem Control Register(MCR)
+5 IN/OUT Line Status Register(LSR)
+6 IN/OUT Modem Status Register(MSR)
+7 IN/OUT Scratch Register(SCR)

Transmit Holding Register / Receive Buffer Register

THRに書き込んだデータはUARTに送信され、RBRでUARTが受信したデータを確認することができる。

Divisor Latch LSB / MSB

LSBが最下位ビットでMSBが最上位ビット。

ボーレート

Wikipediaから引用。 ja.wikipedia.org

ボー (baud) は、変調レートの単位である。ボーは、搬送波に対する1秒間あたりの変調の回数と定義される。

個人的にはボーレート=転送速度だと思っていたが、実はそうではないらしい。例えば100ボーレート(1秒あたり100回の変調)で1回の変調で2ビット送信できるとすると、速度は100 * 2 = 200bpsになる。UARTは最大で115200bpsで通信することができるが、種類によってはもっと早く通信できるものもあるらしい。

UARTではボーレートではなく除数をレジスタに書き込む。例えば38400ボーレートに設定したいときは115200 / 38400 = 3となる。

Interrupt Enable Register

どの割り込み生成するかを設定する。0が無効で1が有効。

7~4ビット 3ビット 2ビット 1ビット 0ビット
予約済み ステータス変更 Error/Breakシグナルの検知 THRが空 データ受信関連

FIFO Control Register

0ビット目を1にすることでFIFOが有効になり、1ビット目と2ビット目を1にすることでFIFOを初期化できる。

7~6ビット 5~4ビット 3ビット 2ビット 1ビット 0ビット
レシーバトリガ 予約済み DMAモード選択 送信FIFOの破棄 受信FIFOの破棄 FIFO有効

レシーバトリガ

FIFOが有効になっているとき、いくつデータを受信した段階で割り込みを発生させるか。

書き込むビット データの数
00 1個
01 4個
10 8個
11 14個

Line Control Register

7ビット 6ビット 5~3ビット 2ビット 1~0ビット
DLAB ブレーク信号 パリティ ストップビット データビット

DLAB

Divisor Latch Access Bit。0または1のみを設定。これを変更することによってレジスタマッピングを切り替える。

パリティ

誤り検出の手法。データにパリティビット(1ビット)を付与し、データとパリティビットを合わせた全ビットの「1」の数が常に偶数か奇数になるようにパリティビットで調整する。パリティビットの扱いをどうするかをレジスタに書き込む。一般的にはハードウェアではなくプロトコルが誤り検出を行うため、Noneを設定する。

書き込むビット パリティ 動作
000 None パリティなし
001 Odd 奇数
011 Even 偶数
101 Mark パリティビットは常に1
111 Space パリティビットは常に0

ストップビット

各データの終わりに送信されるビット。送信側と受信側がしっかり同期しているかどうかを確認するために使われる。通常は1個のストップビットを使うが、データ長によっては1.5個や2個使う場合がある。

書き込むビット ストップビットの数
0 1個
1 1.5 or 2個

データビット

ビット数は5~8ビットが設定できる。データにはASCIIコードが使われるので、8ビットに設定するのが一般的。(最低7ビット)

書き込むビット ビット数
00 5
01 6
10 7
11 8

Modem Control Register

7~5ビット 4ビット 3ビット 2ビット 1ビット 0ビット
予約済み ループバックモード OUT2ピン OUT1ピン RTSピン DTRピン

ループバックモード

1にするとTHRとRBRが接続され、同じ値を取るようになる。MCRの各ピンもそれぞれUARTの内部信号の値を取るようになる。UARTの診断テストに使う。

OUT2ピン

1にすることでIRQラインを切断し、複数のシリアルポートで1本のIRQラインを共有する。

Line Status Register

各ステータスを示す。

7ビット 6ビット 5ビット 4ビット 3ビット 2ビット 1ビット 0ビット
受信FIFOのエラー TEMT THRE BI FE PE OE DR

TEMT

Transmitter Empty。トランスミッターが空=動いていないときに1になる。

THRE

Transmitter Holding Register Empty。文字通りTHRが空のときに1になる。

BI

Break Interrupt。ブレーク信号を検知したときに1になる。

FE

Framing error。ストップビットの欠落を検知したときに1になる。

PE

Parity error。パリティによるエラー発生時に1になる。

OE

Overrun error。UARTが新しいデータを受信したとき、受信バッファに空きがなかった場合に1になる。

DR

Data ready。受信FIFOにデータが存在するときに1になる。

Rustで実装

OSDev Wikiにあるサンプルコードに沿って実装した。実はResultの使い方がまだイマイチよくわかってなかったりする。

serial.rs

use crate::arch::asm;

pub const IO_PORT_COM1: u16 = 0x3f8;
pub const IO_PORT_COM2: u16 = 0x2f8;
pub const IO_PORT_COM3: u16 = 0x3e8;
pub const IO_PORT_COM4: u16 = 0x2e8;
pub const IO_PORT_COM5: u16 = 0x5f8;
pub const IO_PORT_COM6: u16 = 0x4f8;
pub const IO_PORT_COM7: u16 = 0x5e8;
pub const IO_PORT_COM8: u16 = 0x4e8;

pub struct SerialPort
{
    io_port: u16,
    is_init: bool,
}

impl SerialPort
{
    pub fn new(io_port: u16) -> Self
    {
        return Self { io_port,
                      is_init: false };
    }

    pub fn init(&mut self)
    {
        asm::out8(self.io_port + 1, 0x00); // IER - disable all interrupts
        asm::out8(self.io_port + 3, 0x80); // LCR - enable DLAB
        asm::out8(self.io_port + 0, 0x03); // DLL - set baud late 38400 bps
        asm::out8(self.io_port + 1, 0x00); // DLM
        asm::out8(self.io_port + 3, 0x03); // LCR - disable DLAB, 8bit, no parity, 1 stop bit
        asm::out8(self.io_port + 2, 0xc7); // FCR - enable FIFO, clear TX/RX queues, 14byte threshold
        asm::out8(self.io_port + 4, 0x0b); // MCR - IRQs enabled, RTS/DSR set
        asm::out8(self.io_port + 4, 0x1e); // MCR - set loopback mode, test the serial chip
        asm::out8(self.io_port + 0, 0xae); // RBR - test the serial chip (send 0xae)

        if asm::in8(self.io_port + 0) != 0xae
        {
            return;
        }

        // if serial isn't faulty, set normal mode
        asm::out8(self.io_port + 4, 0x0f);
        self.is_init = true;
    }

    pub fn receive_data(&self) -> Result<u8, &str>
    {
        if !self.is_init
        {
            return Err("Serial port wasn't initialized");
        }

        let res = asm::in8(self.io_port + 5) & 1;

        if res == 0
        {
            return Err("Hasn't received data");
        }

        return Ok(asm::in8(self.io_port));
    }

    pub fn send_data(&self, data: u8) -> Result<(), &str>
    {
        if !self.is_init
        {
            return Err("Serial port wasn't initialized");
        }

        while self.is_transmit_empty() == 0
        {}
        asm::out8(self.io_port, data);
        return Ok(());
    }

    fn is_transmit_empty(&self) -> u8 { return asm::in8(self.io_port + 5) & 0x20; }
}

main.rsカーネルエントリポイントの一部

let mut serial = SerialPort::new(serial::IO_PORT_COM1);
serial.init();
serial.send_data(b'H').unwrap();
serial.send_data(b'e').unwrap();
serial.send_data(b'l').unwrap();
serial.send_data(b'l').unwrap();
serial.send_data(b'o').unwrap();
serial.send_data(b'!').unwrap();
serial.send_data(b'\n').unwrap();

loop
{
    if let Ok(data) = serial.receive_data()
    {
        serial.send_data(data).unwrap();
    }

    //asm::hlt();
}

receive_data()で受け取ったデータをsend_data()に流すことでシリアルコンソールから文字が入力できるようになった。十字キーでカーソルも動いたりするので、コンソール内の自由な場所に文字を上書きできる。まだ割り込みハンドラを作ってないのでHLT命令を飛ばすと動かなくなってしまう。

QEMUのオプションに-serial mon:stdioをつけることでシリアルコンソールの内容をターミナルにリダイレクトさせることが可能。

参考サイト

ja.wikipedia.org wiki.osdev.org www.japansensor.co.jp www2.denshi.numazu-ct.ac.jp archive.linux.or.jp