2016年9月2日金曜日

D言語のコンパイル時に決定する静的な文字列の扱い

ここにOSによって、SEGVするコードがある。
その理由を突き詰めてみる。

なお、元ネタ。


OS(というかバイナリの形式、Windowsの場合はPE、Linuxの場合はELF)によって
配置されるセグメントが違うから、SEGVが起こると考えられる。
実際どこに置かれるか、逆アセンブルして見てみる。

Linux版バイナリははWindows Subsystem for Linuxのbash(x86-64)でビルド
Windows版バイナリはWindows 10 Professional(x86-64)でビルド

Linux
c:\D_Codes\test>bash
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ uname -a
Linux SEGFODESKTOP 3.4.0+ #1 PREEMPT Thu Aug 1 17:06:05 CST 2013 x86_64 x86_64 x86_64 GNU/Linux 

Windows
c:\D_Codes\test>ver
Microsoft Windows [Version 10.0.14393]

dmdのバージョンは以下のとおり

Linux
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ dmd --version
DMD64 D Compiler v2.071.1                                                           
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ exit

Windows
c:\D_Codes\test>dmd --version
DMD32 D Compiler v2.071.1                 
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright


なお、コンパイルは以下のコマンドで行った。(共通)
※WSLでは現状32bitバイナリが実行できないため64bitバイナリを生成する。

dmd -m64 SEGV_OS_Dependent.d

実行結果は以下のとおり。

Linux版バイナリ
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ ./test                                                           
Segmentation fault (コアダンプ)

Windows版バイナリ
c:\D_Codes\test>test                                                                                     
hAgehoge 

Linux版はSEGV
Windows版は最後まで実行できた。

この違いは何か、バイナリレベルで見てみる。
今回は、x64バイナリ、IDA Freeでは見られないので
Linuxはgdb、Windowsはx64Dbgで書き換えられる部分のメモリの属性を見る。

まずはLinux版

RBX: 0x7ffffe14c7f0 --> 0x7ffffe14c810 --> 0x0
RCX: 0x468491 --> 0x65676f6865676f ('ogehoge')   
RDX: 0x1  
[---------------------code------------------------------]
   0x438c12 <_Dmain+50>:        call   0x4390d0 <_D3app7__arrayZ>
   0x438c17 <_Dmain+55>:        inc    rcx
   0x438c1a <_Dmain+58>:        mov    QWORD PTR [rbp-0x10],rcx
=> 0x438c1e <_Dmain+62>:        mov    BYTE PTR [rcx],0x41
[-------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000438c1e in D main () at source/app.d:5                                                                                     
5               ubyte[] sb=cast(ubyte[])s;       

おもむろに、gdbを立ち上げて、runするだけ。

codeペインのこの行は
=> 0x438c1e <_Dmain+62>:        mov    BYTE PTR [rcx],0x41                        

D言語のソースの7行目を指している。
(gdbの画面では5行目がでているが、恐らくバグ)
なお、
Legend: code, data, rodata, value 
で、rodata(読み取り専用セグメント)に代入していることがわかる。

vmmapで確認する

gdb-peda$ vmmap                                              
Start       End        Perm  Name
0x00400000  0x00481000 r-x-  /mnt/c/D_Codes/test/SEGV_OS_Dependent
0x00680000  0x00681000 r---  /mnt/c/D_Codes/test/SEGV_OS_Dependent
0x00681000  0x0068c000 rw--  /mnt/c/D_Codes/test/SEGV_OS_Dependent 

RCXの値は、
RCX: 0x468491 --> 0x65676f6865676f ('ogehoge') 

(cast(ubyte[])"hogehoge")[1] の2文字目の o を指しているポインタであることがわかる。
値は0x468491

vmmapでは、このメモリ領域のパーミッションは、r-x-となっている。
だからSEGVが発生する。

ではWindowsではどうか。

00007FF7229D1039の命令が7行目の命令。
RCXの値は 00007FF722A1EA41

00007FF722A1EA41のメモリ領域は、-RW-- となっており、読み書き自由。


以上のことから、コンパイル先のバイナリやOS?によってDコンパイラはデータを配置するセグメントを変えていると思われる。

Windowsの場合は、明示的にstringをimmutableにしてもキャストしたら実行できてしまった。
このことから、コンパイル時に確定している文字列は暗黙的にimmutable(またはconst)の扱いとなると考えられる。


D言語でも吸収できない闇を垣間見てしまった気がする。
おわり。

2016年8月4日木曜日

Windows subsystem for Linuxを試してみた。

WindowsでUbuntu bash使えるようになってたので
いろいろ動かしてみた。

動いたもの
Pythonライブラリ:pwntools / binjitsu
Python
gdb/gcc
gdb-peda

動かなかったもの
ping(icmpソケットが開けない的な理由)

で、gdb-pedaの動作確認をしていた時の事。
とてもエモい感じだったのでメモ。



a.outの.textのベースアドレスがWindowsっぽく、0x00400000から始まってるのと
各.textセクションのパーミッションが、r-x-のはずがrwx-になっていて非常にエモい。
多分、命令をエミュレート(Windows syscallに動的変換?)する時に書き込んだりしてるのかもね。


それだけ。

2016年7月10日日曜日

D言語でPEファイルヘッダをパースしてみた

今日もD言語やっていこうかと。

と言うわけで、作るもののネタが無いのでPEファイルのヘッダを解析してみる。
アルゴリズム的には、構造体作って、構造体にそのまま取り込んで、表示するだけなので
構造体をコピペすれば完成する代物。

ちょっとだけエイリアスを宣言してやる必要があるけど、やることはそれだけ。
とりあえず、x86バイナリと、x64バイナリに対応できればいいかなって感じ。

ちゃんと解析できてるかどうかの確認はIDA Freeあたりで試す。

D言語の std.File.rawReadは配列しか渡せないので、構造体をC言語みたいにポインタで渡すとか言う直感的な方法でなくて、戸惑ったけど、書き方が微妙に違うだけでほぼ同じだった。
D言語はいいぞ。ランタイムあたりが厄介者だけど、それはどうにかすればいい。

dmd -m32 PEHeaderAnalyzer.d
PEHeaderAnalyzer.exe PEHeaderAnalyzer.exe

実行結果こんな感じ
Machine: i386
applicationBits: 32
Subsystem: Windows CUI
ImageBase: 0x400000
BaseOfCode: 0x2000
SizeOFCode: 183296
AddressOfEntryPoint: 2565c
Absolute entry point: 42565c
SizeOfImage: 274432
IDAで答え合わせ。

※ 画像クリックで拡大可能

Machine: i386 と
ImageBase: 0x400000 はIDAでみると、問題なさそう。

BaseOfCode: 0x2000 も取り出せてる



エントリーポイントのアドレス(Absolute entry point) = ImageBase + AddressOfEntryPoint
ImageBase: 0x400000
AddressOfEntryPoint: 2565c
Absolute entry point: 42565c
合ってるっぽい。



かんたんでよい、きょうはここまで。

2016年7月9日土曜日

D言語ローレイヤ入門

D言語(ローレイヤ)入門していきます。

ぼく「D言語の最小ランタイムほしいよ~」
ぼく「uefi-dとか使ってみたけど構造体宣言できないし色々ダメ」
ぼく「UEFI AppをDでつくりたい、あわよくば・・・OSカーネルも」

というつぶやきをしたらプロから返事が帰ってきた。
Twitterつよい。


というわけで、minimal.zipを落として makeしてみた

minimal-d/object.d:1248: `_deh_beg' に対する定義されていない参照です
minimal-d/object.d:1249: `_deh_end' に対する定義されていない参照です
無いってさ!
ちなみに、object.dの1248行目と1249行目はこんな感じ。

        extern __gshared
        {
            /* Symbols created by the compiler and inserted into the object file
             * that 'bracket' the __deh_eh segment
             */
            void* _deh_beg;
            void* _deh_end;
        }

※object.d:すべてのクラスや構造体のベースになるクラスが入ったファイル。これがないとD言語の機能はもちろん、C言語で普通にできてた構造体の宣言すらできない(RTTI:実行時型情報が必須な言語の宿命(TypeInfo_Struct等が宣言されてないとリンカエラー))ので、大事。


見たとおり外部に宣言してある_deh_begと_deh_endを参照する ってな感じになってるし
もちろんコンパイラはそんなシンボルを生成してくれないしで、きっとD言語の仕様が微妙に変わったのかもしれない。

リンカスクリプトでEHテーブルセクションを定義した時にシンボルも定義すれば良いんだけど、それをやらなくてもできる方法があるので今回はそっちでやる。
ちなみに、EHテーブルセクション(セグメント)については、ここに書いてあった。
http://www.kmonos.net/alang/d/abi.html

例外ハンドラがたくさん書いてあるところ(GOTみたいな関数テーブル)っていう感じ。

先ず、CompilerDSOData構造体と_d_dso_registryって関数をみてみると
CompilerDSOData構造体には、_deh_beg、_deh_endの2つが居るけど
でもその構造体を引数にとってる、_d_dso_registry関数はそれらメンバに対して何も操作してない。
でも、引数をとってるからには、何かが入ってるはずなので、下のソースみたいに
もともと有った外部参照するところをコメントアウトする。
次に、普通にコメントアウトした変数と同名の変数の実体を宣言する。
さらに、宣言した変数に引数からもらった CompilerDSOData構造体の同名の変数の値を代入する。

これでリンカエラーは出なくなる。

じゃあ本当に直した部分は動くの?動きそうなの?
と言う疑問が湧いてきたので、一旦これでmakeコマンド叩いてみて、バイナリを生成して逆アセンブルしてみる。

動きそう、動かなそうの判断は、意図したデータ(EHテーブルセクションの開始位置と終了位置)
が入ってるか入ってないかで判断する。

各メモリアドレスの詳細


一応メモリアドレスがちゃんと有効なセクションを指してるのと、
構造体の順番と同じようにスタックにレイアウトされてるから問題なさそう。

C言語っぽい擬似コード




てわけで、きょうはこんなところで。

2015年11月22日日曜日

AVTokyo2015のバッジを作った。


AVTokyo2015に今年も参加したわけですが、そこでもらったこれ、参加証のバッジ。



これな

もらった時は
なんだこれ????基盤????
パーツねーぞ????んん?????
どうするんだこれ、どうせなら遊びたいぞ
って思いましたが、一緒にいた人達に聞いてみたところ・・・


「あ、このそこでパーツ売ってるよ、買ってきたら?」

ぼく「まじっすか!買ってきますっ!!!!!」
売店のイケメンおにーちゃん「後で呼ぶから、そしたら来て1000円払ってね!」
ぼく「はーい」

1時間経ったぼく「呼ばれないぞ・・・」
2時間経ったぼく「これ無理じゃね」
3時間たったぼく「あ、後30分で終わっちゃう」

ついに、AVTokyo2015が終わってしまった。
最後まで、呼ばれなかった。
・・・ので、売店のおねーさんに問い合わせてみた

ぼく「部品ってここで買えません?」
おねーさん「ちょっと待って下さいね」
隣りにいたおにーさん「あーごめんなさい売り切れちゃいました、
              後でAVTokyoのページに部品全部のせますよ―」
ぼく「まじっすか!!!やったーーーーあざます!!!!」


というわけで、約1週間後の今日、AVTokyo2015のバッジを完成させてみました。
Arduino互換なので、色々と楽でした。

持っててよかったArduino(詳細は後述)


1.パーツを集める
このページに載ってる情報を元にパーツを集める
http://ja.avtokyo.org/avtokyo2015/badge

秋月電子さんで全部買いました。
AVTokyoのページの情報だと、Arduino互換のものが出来上がるらしい。
ちゃんとUARTもついててそれっぽい。

Arduino互換ってことはプログラムは、スケッチじゃろ
あと、Arduino IDEが必要。ライターどうしようかな・・・(後述)


2.ハンダ付けをする
はんだづけして、IC乗っけて終わりーーーーと思った。

でも現実はそう甘くはなく
「そういえばこれプログラムってどうやって載せんの??」
当日に完成させたみなさん、一定のパターンとかで光ってたよね。スイッチとかで切り替えてたよね
プログラム載せる必要あるじゃん

3.つまづく
初心者なので、このあたりのことわからないから、
「ICなんてどこで買っても同じだろwwwww」
と思ってた。そんなことはなかった。

ブートローダーが入ってない奴だったよね。

4.ブートローダーを書く
この辺りを参考にして
https://www.arduino.cc/en/Tutorial/ArduinoISP

こうする。

これでブートローダーが書けるんじゃ・・・便利じゃろ?
高いライターを買わなくてもええんや・・・

んで、問題はスケッチを書くあれだけど、
Arduino互換なら、ブートローダも書いたし、ArduinoのICと付け替えてやれば書けるだろ。

というわけで、Arduinoのもともと有ったICを外して、AVTokyoバッジ用ICを付けてやる。

んで、普通にArduinoに以下のURLのスケッチをブチ込むと書ける。
https://github.com/mikodayo/AVT15/blob/master/AVT15.ino

便利ですなぁ。
持っててよかったArduino。

5. 完成


6.動作確認

無事に動きました。
来年もこういう楽しいのが有ったらいいなー(他力本願)

以上!

2015年10月11日日曜日

C++のインスタンス生成処理

今日はC++です。
正直苦手としていた言語ですが、ひょんなことから始めました。

で、オブジェクトの生成とかどうしてるのか気になりますね。
気になるんです。

具体的には下記1点

  • 配置newしたときにデータと実行コードはどこに置かれるのか
気になりますねー

OS作るときは、データやコードがメモリのどこに配置されるかすごく気を使うので知っておきたいです。

どこに置かれるか、機械語を見てみましょう。
機械語を見ればわかるのです。バイナリです。
とりあえずソースを置いときます。




こんな感じにMain.cppからTestClass.cppをnewするだけです。
じゃあ機械語レベルではどーなってるのって話ですね。

みてみましょ。(画像はすべてクリックで拡大可能)

まずはMain.cppのオブジェクト生成部


TestClass.cppの TestClass::TestClass(char *s);メソッド(コンストラクタ)


見てみた結果

  • 実行コードはメモリ上に1セットしかない。
  • データ領域だけオブジェクトの数分配置される。
ということでした。

すっきり。

2015年6月20日土曜日

UEFIでOSつくるぞー(ブートローダ・カーネルローダー編)

OS作ってますよ


さて、全壊前回の記事からちょっと時間が経過しましたが
今の所飽きずに開発してますよ。面白いですねー

リポジトリはここです

UEFI使って独自OS作ってるって記事がネット上にあんまり転がってなくて、割りと大変。
Linuxとか起動すること出来るよーみたいなの載ってるけどね。

というわけで、はりぼてOSの64ビット版、UEFIデビューを目指して頑張って行きたいと思うですねー
(いつ飽きるかわからんですが・・・w)

開発環境
OS:Ubuntu 14.04
コンパイラ:gcc 4.8.2
リンカ: ld 2.24

作ったもの

  • ブートローダー(解説は、おるみんさんブログの「ブートローダーは4行で実装される」で読んでくだしあ。こっちのほうが詳しいです)
    • 将来的にはブートオプションとかをカーネルローダに渡すことができたら付けられたらいいなー!みたいな。
  • カーネルローダー(この記事解説)
    • OSにブートローダーからもらった情報で初期化したデータを渡すとかしている
    • 現状は、GOP(Graphics Output Protocol)でグラフィックの初期化とフレームバッファ(VRAMの先頭メモリアドレス)の取得。(なんと24ビットカラー!強い!!
  • カーネル(OS、解説は後日)
    • OS本体!
    • メモリマップの取得とUEFIのブートサービスを切る(ExitBootServicesの呼び出し)
    • (ココでやっている理由は、メモリマップを取得した直後じゃないとExitBootServicesが動いてくれない仕様なのでとりあえず、という感じ。)
    • なんかCentOSもそんな感じでやっているっぽい(どこで見たかは忘れた)ので真似してみました。
    • 現状、メモリマップ取得とExitBootServicesを呼ぶソースとカーネル本処理のソースは分けてありますが、モジュール的には1つです
みたいな感じです。

なかなかそれっぽいでしょ?
カタチから入るのもモチベーションだと思うわけですよ。

もちろん、
「俺のOSは3段階じゃなくて2段階でいいよねー 」
「ブートローダーにカーネルロードさせて、カーネルでカーネルローダーが行っている初期化の役割をやってやるぜ!」

とかそういうのも有りじゃねーのかなーと思うので、そこのあたりは思想にお任せします。

現状、UEFIが使えなくなると、画面に変数を表示できなくなるので
newlibを使って、sprintfとOSのグラフィック関数を組み合わせて表示することにします。

で、フォントもUEFIから引き継げない(?)ので、とりあえず今の所は
『30日でできる!OS自作入門』のメモの「C言語で直接フォントを表現」にある素敵ツールを使わせていただきました。(助かってます!!)

(もし、UEFIからフォントデータとかを持ってこれるよ!って情報有ったら教えてくださいなー)


では長くなりましたが、ブートローダーからソースをぺたぺた貼っていきたいと。

このブートローダーは「ブートローダーは4行で実装される」
からパクってきました!(おるみんさんありがとうございます。フル活用させて頂いてます)
後々、テキストファイルにカーネルとカーネルローダーの場所を書いておいて、
そこからロードしてくるように直そうかと。優先度はかなり低め。

ではお次はカーネルローダー。
量的にはボリューム有るように見えたり見えなかったりしますが、やってることは簡単。
GOPを初期化して、ピクセル情報や解像度情報、フレームバッファを取得しているだけです。


大きく分けて、処理は4つ
131行目:ブートオプション(カーネルの起動パス)とイメージハンドルを取得している。
136行目:カーネルの起動情報を格納するメモリを確保。
142行目:GOPの初期化と画面の色々な情報を136で確保したメモリに入れる。
 最大で、1024x768の解像度で初期化してほしい
 という要求をchangeGraphicModeの第2引数・第3引数に渡している。
 最大なので、800x600で起動されても問題ないけど、
 GOPの仕様上1024x768以上の解像度は保証されたハズ

150行目:カーネルを起動する

というような流れになっとります。
「この関数の解説をもっと詳しく!」とか要望があれば、もうチョット書きますが無いですよね。

では!