デスクトップ周りのケーブルは全て巻き取り式 or 短いものに買い替えた
絡まった配線を解く作業は時間の無駄だけでなく、精神を蝕むストレスの原因になっているとおもう。 そんな作業はもうやりたくないのでテーブルの上の配線類は全て短いケーブルか巻き取り式のものに変えた。
- USB type-C
これめちゃくちゃ便利です。Type-Cの端子がついたノートPC持ってる人は全員買うべきだと思います。 Macのデフォでついているゴワゴワした長いType-Cケーブルはもう絶対に持ち歩きたくありません
エレコム USB Type C ケーブル [ タイプC ] USB-C & USB-C 巻取 準拠品 0.7m ブラックMPA-CCRL07BK
- 出版社/メーカー: エレコム
- 発売日: 2016/09/28
- メディア: エレクトロニクス
- この商品を含むブログを見る
- Lightningケーブル
- mini usb
短いLightnignケーブルはAmazonのブランドで売られているものの10cmを選択すれば買えます ちなみに無線充電も試しましたが、不要だと感じたので使っていません。
Amazonベーシック Apple認証 ハイクオリティー ライトニングUSB充電ケーブル 小型ヘッド設計 ホワイト 約90cm
- 出版社/メーカー: AmazonBasics
- 発売日: 2015/09/01
- メディア: Wireless Phone Accessory
- この商品を含むブログ (3件) を見る
- オーディオ系
airpodsを持っているので基本的にBTが使えるデバイスではそちらを利用。 どうしてもオーディオジャックしかついておらず使えないもの、例えば写真右のFMラジオなど は右の巻き取り式のイヤホンを使っている
エクセルサウンド 巻き取り式アルミハウジングカナルヘッドホン ブラック EH-R37 BK
- 出版社/メーカー: エクセルサウンド
- メディア: エレクトロニクス
- この商品を含むブログを見る
- HDMIケーブル
これはやりすぎな感じがありますが、徹底してやるつもりだったので例外は設けずやりました。 持ち歩く機会はありませんが、基本的にかなり短い状態で使っているので掃除するとき等、他のケーブルに絡まることが少なくなりました。*1
サンワサプライ HDMI巻取りケーブル 1.2m ブラック KM-HD20-M12
- 出版社/メーカー: サンワサプライ
- メディア: Personal Computers
- この商品を含むブログを見る
これでディスクトップ周りの線で長いものは
- ディスプレイの電源
- タップ
のみになりました。 これで絡まった線を解く作業に精神を疲弊させることは今後ありません。万歳。
先頭で紹介しているType-Cのケーブルはマジでオススメです。
*1:というよりもう他に絡まるケーブルがないけど
C言語ポインタ勉強会に参加
オンラインで開催。
少人数制で東工大の先生(uchan_nos)がポインタについてわかりやすく教えてくれるという贅沢な1.5Hでした
typedefでやると関数ポインタがわかりやすくなる
typedef int add_type(int,int); int add(int a, int b); add_type* fp = add; typedef int* int_ptr_type; int_ptr_type arr[10];
関数ポインタがわかりにくい、という人は、ひとまずこれでポインタ型を説明すると、
関数ポインタの演算子の優先度のために無理やり挿入された()
に煩わされずに済むと思います。*1
実例
#include<stdio.h> typedef int add_type(int,int); int add(int a, int b); int main(){ //typedefバージョン。わかりやすい add_type* fp = add; int num = fp(1,3); printf("%d\n",num);//4が出力される //普通の関数ポインタバージョン int (*fp2)(int,int) = add; int num2 = fp2(2,2); printf("%d\n",num2);//4が出力される //普通の関数呼び出し int num3 = add(3,1); printf("%d\n",num3);//4が出力される } int add(int a,int b){ return a+b; }
メモリレイアウト
メモリには
- .text:実行可能,読み取り可能,変更不可能
- .data:実行不可能,読み取り可能,変更可能
- .rodata:実行不可能,読み取り可能,変更不可能
な3領域がある
質問:char配列とcharポインタを文字列リテラルで初期化した時の違いについて
Char型文字配列を文字リテラルで初期化した時と Char型アドレスを文字リテラルで初期化した時の違いですが、
前者は値の変更ができるが、後者は値の変更ができないように思えますが、 これは前者だとヒープ、後者だと静的領域に確保していて変更できないということでしょうか
char test1[] = "test\0"; char *test2 = "test\0"; test1[1] = 't'; puts(test1); test2[1] = 't';//エラー puts(test2);
質問回答
char配列を文字列リテラルで初期化した場合は、スタックに確保される(基本的に関数内のローカル変数はスタックで初期化される)
しかし、char配列を文字列リテラルで初期化した場合は、どこにデータが置かれるかわからないので*2、環境によっては変更不可能なrodataに置かれたり、.dataに置かれたして、変更可能か不可能かは不安定。だからコンパイルエラーにならず実行時エラーになる。
- ちなみに私の環境だとbus errorというものが出る。
- busエラーとは何か?
- busエラーはCの未定義動作でよくでてくるエラー
- ポイント:関数の中でローカル変数を宣言するとスタック変数になる。
- ヒープは関係ない。mallocした時しか使われない
用語
初めて聞くものがいくつかありました。 一般的ではないアカデミックな世界の用語?と最初は思いましたが、ググったら普通にでてくるので一般的なものだと思います*3
- ダブルポインタ:ポインタのポインタのこと。凡例:
**pp
- 参照外し(dereference):ポインタ変数の参照先の値を取ってくること。凡例:
int* p = &num,int x = *p
ダブルポインタの逆アセンブル結果の解説
講義で使われた擬似コードどほぼ同じコードを書いて逆アセンブルしたところ、講師のuchanさんに1行ずつ解説していただけました!
#include<stdio.h> int main(){ int x = 42; int *p = &x; //ポインタ int **pp = &p; //ダブルポインタ printf("%d\n",**pp); //42が出力される }
逆アセンブルしたニーモニックにコメントを振る形で掲載します。
int main(){ push rbp mov rbp,rsp ;準備。説明略 sub rsp,0x20 ;32バイトを引いて、変数が収まる範囲を準備してあげる ;本来なら8バイト変数2つ(`p,pp`)と4バイト変数一つ(`x`)の合計20バイト分で十分だが、 ;アライメントの関係で16倍の数値で確保している lea rdi,[rip+0x57] # 100000fa6 <_main+0x66> lea rax,[rbp-0x10];raxにはpのアドレスが lea rcx,[rbp-0x4];rcxにはxのアドレスが入ってる ;※leaはメモリから読みだすという操作をしない。のでrbp-0x4のアドレスがレジスタそのまま入る ;lea命令はコンパイラがよく使う ;余談:lea命令を使うと複雑な命令が1命令でできたりする。(掛け算とか) ;int x = 42; mov DWORD PTR [rbp-0x4],0x2a ;rbpはスタックのベースポインタ ;アドレスは低い方に成長していく。 ;スタックの最上位に4バイトのデータ(int型)であるxを入れる。 ;int *p = &x; mov QWORD PTR [rbp-0x10],rcx ;pのアドレス(rbp-0x10)の先に、xのアドレスを代入する ;注:アドレスのサイズは8バイト ;int **pp = &p; mov QWORD PTR [rbp-0x18],rax ;スタックの一番最下層に,`**pp`を確保しそこにrax(pのアドレスを格納する) ;printf("%d\n",**pp); mov rax,QWORD PTR [rbp-0x18] ;pのアドレスがraxに入る, **(pp) mov rax,QWORD PTR [rax] ;xのアドレスがraxが入る *(*pp) mov esi,DWORD PTR [rax] ;4バイト読みだしてesi (**p) ;末尾の()付きの部分が実行されたイメージ。 mov al,0x0 call 100000f84 <_main+0x44> xor esi,esi }
一応逆アセンブル結果の全体を貼っておきます
念のため。僕が作ったデタラメな擬似コードじゃないよ〜という意味で。
$ gobjdump -d -S -M intel ./a.out ./a.out: ファイル形式 mach-o-x86-64 セクション .text の逆アセンブル: 0000000100000f40 <_main>: #include<stdio.h> int main(){ 100000f40: 55 push rbp 100000f41: 48 89 e5 mov rbp,rsp 100000f44: 48 83 ec 20 sub rsp,0x20 100000f48: 48 8d 3d 57 00 00 00 lea rdi,[rip+0x57] # 100000fa6 <_main+0x66> 100000f4f: 48 8d 45 f0 lea rax,[rbp-0x10] 100000f53: 48 8d 4d fc lea rcx,[rbp-0x4] int x = 42; 100000f57: c7 45 fc 2a 00 00 00 mov DWORD PTR [rbp-0x4],0x2a int *p = &x; 100000f5e: 48 89 4d f0 mov QWORD PTR [rbp-0x10],rcx int **pp = &p; 100000f62: 48 89 45 e8 mov QWORD PTR [rbp-0x18],rax printf("%d\n",**pp); 100000f66: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18] 100000f6a: 48 8b 00 mov rax,QWORD PTR [rax] 100000f6d: 8b 30 mov esi,DWORD PTR [rax] 100000f6f: b0 00 mov al,0x0 100000f71: e8 0e 00 00 00 call 100000f84 <_main+0x44> 100000f76: 31 f6 xor esi,esi } 100000f78: 89 45 e4 mov DWORD PTR [rbp-0x1c],eax 100000f7b: 89 f0 mov eax,esi 100000f7d: 48 83 c4 20 add rsp,0x20 100000f81: 5d pop rbp 100000f82: c3 ret セクション __TEXT.__stubs の逆アセンブル: 0000000100000f84 <__TEXT.__stubs>: 100000f84: ff 25 86 00 00 00 jmp QWORD PTR [rip+0x86] # 100001010 <_main+0xd0> セクション __TEXT.__stub_helper の逆アセンブル: 0000000100000f8c <__TEXT.__stub_helper>: 100000f8c: 4c 8d 1d 75 00 00 00 lea r11,[rip+0x75] # 100001008 <_main+0xc8> 100000f93: 41 53 push r11 100000f95: ff 25 65 00 00 00 jmp QWORD PTR [rip+0x65] # 100001000 <_main+0xc0> 100000f9b: 90 nop 100000f9c: 68 00 00 00 00 push 0x0 100000fa1: e9 e6 ff ff ff jmp 100000f8c <_main+0x4c> セクション __TEXT.__unwind_info の逆アセンブル: 0000000100000fac <__TEXT.__unwind_info>: 100000fac: 01 00 add DWORD PTR [rax],eax 100000fae: 00 00 add BYTE PTR [rax],al 100000fb0: 1c 00 sbb al,0x0 100000fb2: 00 00 add BYTE PTR [rax],al 100000fb4: 00 00 add BYTE PTR [rax],al 100000fb6: 00 00 add BYTE PTR [rax],al 100000fb8: 1c 00 sbb al,0x0 100000fba: 00 00 add BYTE PTR [rax],al 100000fbc: 00 00 add BYTE PTR [rax],al 100000fbe: 00 00 add BYTE PTR [rax],al 100000fc0: 1c 00 sbb al,0x0 100000fc2: 00 00 add BYTE PTR [rax],al 100000fc4: 02 00 add al,BYTE PTR [rax] 100000fc6: 00 00 add BYTE PTR [rax],al 100000fc8: 40 0f 00 00 rex sldt WORD PTR [rax] 100000fcc: 34 00 xor al,0x0 100000fce: 00 00 add BYTE PTR [rax],al 100000fd0: 34 00 xor al,0x0 100000fd2: 00 00 add BYTE PTR [rax],al 100000fd4: 84 0f test BYTE PTR [rdi],cl 100000fd6: 00 00 add BYTE PTR [rax],al 100000fd8: 00 00 add BYTE PTR [rax],al 100000fda: 00 00 add BYTE PTR [rax],al 100000fdc: 34 00 xor al,0x0 100000fde: 00 00 add BYTE PTR [rax],al 100000fe0: 03 00 add eax,DWORD PTR [rax] 100000fe2: 00 00 add BYTE PTR [rax],al 100000fe4: 0c 00 or al,0x0 100000fe6: 01 00 add DWORD PTR [rax],eax 100000fe8: 10 00 adc BYTE PTR [rax],al 100000fea: 01 00 add DWORD PTR [rax],eax 100000fec: 00 00 add BYTE PTR [rax],al 100000fee: 00 00 add BYTE PTR [rax],al 100000ff0: 00 00 add BYTE PTR [rax],al 100000ff2: 00 01 add BYTE PTR [rcx],al
C言語で美咲フォントを扱う1
とりあえずfontx2ファイルを読み込んでヘッダだけ表示してみた。
https://github.com/yuyabu/MisakiFont-sandbox
>./font hdader size is 22 filesignature is FONTX fontname is MISAKI width is 8 hight is 8 char code flg is 1 blocks is 5c start_code is 4081 end_code is 7e81
一応MakeFileも作ってる。 警告マックスのオプションつけてるだけだけど。。。
font:font.c font.h gcc -W -Wall -o font font.c
まずはヘッダを定義している構造体。こういうファイルフォーマットのヘッダって多分誰かがすでに作っているけど、自分で書いて見た
typedef struct{ char filesignature[6]; char fontname[8]; char font_w; char font_h; char char_code_flg; char blocks; char start_code[2]; char end_code[2]; } fontx2header;
情報を出力している箇所のみコードで出してみる
//この書き方はできないみたい。filesignatureの開始アドレスからnull文字までが全部出てしまう。 //printf("filesignature is %s",header.filesignature); char temp[100];//ポインタで用意すると色々トラブル発生 strncpy(temp,header.filesignature,5); printf("filesignature is %s\n" ,temp ); strncpy(temp,header.fontname,7); printf("fontname is %s\n" ,temp ); printf("width is %x\n" ,header.font_w ); printf("hight is %x\n" ,header.font_h ); printf("char code flg is %x\n" ,header.char_code_flg); printf("blocks is %x\n" ,header.blocks ); //配列をそのまま16進数で表示する方法がわからないのでかなり頭の悪いやり方でやってる printf("start_code is %x%x\n" ,(unsigned char)header.start_code[0] ,(unsigned char)header.start_code[1]); printf("end_code is %02x%02x\n" ,(unsigned char)header.end_code[0] ,(unsigned char)header.end_code[1] );
char配列をprintfで16進数表示する方法がわからないので かなりアドホックなコードを書いた。
//配列をそのまま16進数で表示する方法がわからないのでかなり頭の悪いやり方でやってる printf("start_code is %x%x\n" ,(unsigned char)header.start_code[0] ,(unsigned char)header.start_code[1]); printf("end_code is %02x%02x\n" ,(unsigned char)header.end_code[0] ,(unsigned char)header.end_code[1] );
文字列を表示のためだけにバッファする変数はchar配列にすると色々面倒が起きずにすむ。
char temp[100];//ポインタで用意すると色々トラブル発生
ポインタにしたり、ポインタにして文字で初期化したものをtempに使って 変更を加えたりするとbus errorがでる。
多分静的変数領域に確保されて 実行時に変更できないという意味だと思う。
配列だとheapにできるので実行時に変更できる、のだと思う。
C言語ちょっと勉強してから空いてたり、色々他にも学んでいるせいで(Goとか) 文法など間違いまくるが、ググり直せばすぐかけるようになるし、 エラー見て大体トラブルシュートできるようになった。 少なくとも総当たりでいじってデバッグみたいなことはなくなった。
やっぱりちゃんと勉強しておいてよかったC言語。
次回はコンソールに美咲フォントを出力して見たい
情報源
fontx2の情報 http://elm-chan.org/docs/dosv/fontx.html https://qiita.com/meketen/items/a66c6953b3cff380d9b8
C言語でファイル読み書き http://wisdom.sakura.ne.jp/programming/c/c43.html http://simd.jugem.jp/?eid=50
第13回osdev-jpもくもく会に参加してLTしました
非常に有意義な1日でした。
LT
https://speakerdeck.com/yuyabu/gong-kai-yong
スパコンのOSを書いてる人、RustでOSを作っている人(2人も!)が登壇したりなどで、 色々と格差を感じるものがありましたがなんとかここ半月ほど調べていたことを20分くらいで話すことが出来ました。
ここ1週間くらいは日課のランニングができなかったり、3ヶ月ほど毎日書いていたブログが一切かけないくらいデバッグ/調査の日々でした。 前日は4時くらいまで起きて発表内容をまとめていたので、当日はちょっと疲労困憊でろれつが回っていませんでした。(解放されてちょっとホッとしている)
初めてのLTで不慣れなこともあり、上級者の人にとってはあまり面白いものではなかったと思うし、初心者の人には何を言っているか意味不明な発表になってしまっていたと思います。当日は会場の入り口で迷ったり、初めての参加で雰囲気に慣れなかったりなどでとにかく余裕がなかった。もうちょっと聞く側のことを考えるべきだと思います。
私は発表の中でデバッグを始めるのは簡単、と言いましたが、何点かハマりポイントがありますので、後日解説するブログを書きたいと思います。
解消された疑問
CPUに超絶詳しそうな人が目の前にいたので日頃の疑問を聞いて見ました。
CRレジスタのPEフラグが立った状態でセグメントレジスタ(CS)が0を指している状態はどういう意味か?
GDTの0番目のエントリはnullセレクタを格納するように決まっています。
Because the first entry of the GDT is not used by the processor, a selector that has an index of zero and a table indicator of zero (i.e., a selector that points to the first entry of the GDT), can be used as a null selector. The processor does not cause an exception when a segment register (other than CS or SS) is loaded with a null selector.
これはhariboteOSもその通りになっています
GDT0: RESB 8 ; ヌルセレクタ ※RESBはオペランドで指定したバイト数分0で埋める命令 DW 0xffff,0x0000,0x9200,0x00cf ; 読み書き可能セグメント32bit DW 0xffff,0x0000,0x9a28,0x0047 ; 実行可能セグメント32bit(bootpack用)
ですが、null selectorをセグメントレジスタで指定した場合は一般保護例外が発生します。
が、以下にhariboteOSの2段目のIPLでプロテクトモードに切り替えている箇所を張りますが、CR0レジスタのPEフラグを立てた後(7行目)、CSレジスタ以外のセグメントレジスタを8にしてCSレジスタが0のまま OS本体をコピーする処理(bootpackの転送のコメント以下)が動作しています。
; プロテクトモード移行 LGDT [GDTR0] ; 暫定GDTを設定 MOV EAX,CR0 AND EAX,0x7fffffff ; bit31を0にする(ページング禁止のため) OR EAX,0x00000001 ; bit0を1にする(プロテクトモード移行のため) MOV CR0,EAX JMP pipelineflush pipelineflush: MOV AX,1*8 ; 読み書き可能セグメント32bit MOV DS,AX MOV ES,AX MOV FS,AX MOV GS,AX MOV SS,AX ; bootpackの転送 MOV ESI,bootpack ; 転送元 MOV EDI,BOTPAK ; 転送先 MOV ECX,512*1024/4 CALL memcpy ; ついでにディスクデータも本来の位置へ転送 ; まずはブートセクタから MOV ESI,0x7c00 ; 転送元 MOV EDI,DSKCAC ; 転送先 MOV ECX,512/4 CALL memcpy ; 残り全部 MOV ESI,DSKCAC0+512 ; 転送元 MOV EDI,DSKCAC+512 ; 転送先 MOV ECX,0 MOV CL,BYTE [CYLS] IMUL ECX,512*18*2/4 ; シリンダ数からバイト数/4に変換 SUB ECX,512/4 ; IPLの分だけ差し引く CALL memcpy ; asmheadでしなければいけないことは全部し終わったので、 ; あとはbootpackに任せる ; bootpackの起動 MOV EBX,BOTPAK MOV ECX,[EBX+16] ADD ECX,3 ; ECX += 3; SHR ECX,2 ; ECX /= 4; JZ skip ; 転送するべきものがない MOV ESI,[EBX+20] ; 転送元 ADD ESI,EBX MOV EDI,[EBX+12] ; 転送先 CALL memcpy skip: MOV ESP,[EBX+12] ; スタック初期値 JMP DWORD 2*8:0x0000001b
この状態の時CSレジスタは0つまりnull selectorを指しているのではないか?なぜ一般保護例外が上がらずに動いていると疑問を持っていましたが、CPUがリアルモードからプロテクトモードに変わるタイミングはPEフラグに1を入れた後ではなく、そのあとにJMP命令(多分far jump)をしたタイミングだったのです。
CPUのドキュメントにもPEフラグを立てた後はJUMP命令を実行せよと書かれています
Immediately after setting the PE flag, the initialization code must flush the processor's instruction prefetch queue by executing a JMP instruction.
出典:INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986/10.3 Switching to Protected Mode*1
だとするとPEビットONからfar JUMPまでの間、変な状態でメモリ書き込みを行っているような気がしますね。
Haribote OSをCやnasmの情報付きでデバッグすることはできるか?
スライドを見ればわかる通り、デバッグはシンボル情報なしで機械語をステップ実行する形で行いました。 一般的にデバッグは普通機械語そのものではなく、コンパイル前のプログラムに対してステップ実行すると思いますが、 デバッグ情報付きでOSをコンパイルする方法がわからなかったので、*2相談してみたところできなさそうです。
ただし、著者オリジナルのツールを一切使わずにhariboteOSを作り上げた場合はその限りではないかもしれません。*3
もくもく会の感想
もくもく会ですが、強い人多すぎ!みたいな感想多いですが、 教育に関わっている人や学生の人、社会人で趣味でやってる人、本業の人など 参加者の層のボリュームが厚いというのが私の印象です。
私もこの会は3回ほど前から参加してみたいな〜と思いつつ 勝手にOS自作本読破が義務教育と思い込んで尻込みしていたのですが、 割と現在進められている方が参加されてかなり有意義なサポートを受けられている様子も見受けられるので、尻込みしている方もどんどん参加すればいいんじゃないかなと思います。
普段はこのレイヤーのトークができる人が周りにいないので雑談するだけでも楽しかったです。
あとサイボウズさんの食堂?的なスペースかっこいいですね。
もくもく
あまり作業出来ませんでしたが、パタヘネの機械語の学習環境を作ろうとしていました。
現在パタヘネを読んでいるのですが、MIPS向けのマシン語を学ぶ章に突入しました。 C言語をMIPSのマシン語にコンパイルしたり、 バイナリを逆アセンブルする課題などあるのですが、 どれも紙面上の話なので実際のコンパイラ、逆アセンブルを動かして見たいと思い、 検証環境を調査しました。
自分のやりたいこととしてはクロスコンパイラ、というものを使って動作しているCPUとは別ターゲットのCPU(MIPS)のアセンブルを作る、とうことが該当しそうです
ほんとは生成したバイトコードをシュミレータか何かで動かせるといいのですが。
gcc -march=mips32 sample.c -o sample
みたいな感じで動かせるかなと思ったけど、うまくいきませんでした。 自分のmacOSのgccは多分Xcodeのコマンドラインツールで入れたものなので、対応していないのかもしれまんせん。
$ gcc --version Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Apple LLVM version 9.1.0 (clang-902.0.39.2) Target: x86_64-apple-darwin17.7.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
この辺試せばいけそう?な気がする
http://www.theairportwiki.com/index.php?title=Building_a_cross_compile_of_GCC_for_MIPS_on_OS_X
https://www.youtube.com/watch?v=AVTRTR589bk&t=305s&frags=pl%2Cwn
vagrantを入れる作戦で進めてみます。 (そういえばxv6の改造の発表をやってた人もvagrant使ってたような?)
Web版のmipsお試しコンパイラ
こういうのもある。ちょっとした検証にはいいかもしれないけど、 リンカとか使ったアドレスを置換する挙動とかも見てみたいし力不足感はある。
http://reliant.colab.duke.edu/c2mips/
教育用の組み込みOSの話
教育用の組み込みOSの話をしている人の発表が刺さりました。
結局昨今の教育はデバイスを叩くだけで終わる、と仰っていましたが、自分の大学時代を思い出すとすごく心当たりがあります。
マイコンをいじる授業でひたすらデータシートを読んでピンの配線を確認したりなど、そういうことに時間を使いすぎていたと思います。そういうものはハードが変われば無駄になる部分が多いので、もっと制御とは何かみたいな本質的な部分を納得するまで学ぶべきだったと思います。*4
私が発表でやったことも結局半分くらいはBIOSというもうすぐ廃止される規格と戯れるものだったので、 痛いほどよくわかります。
もっと視野を広げて勉強しないといけないなと思った1日でした。
486のセグメントデスクリプタが直感的に読めないようになっているワケ
486のセグメントデスクリプタは...
63 55 47 39 31 23 15 7 0 +--------+----+----+--------+--------+--------+--------+--------+--------+ |base_hi |ar |l_hi| ar |base_mid| base_low | limit_low | +--------+----+----+--------+--------+--------+--------+--------+--------+
こんな感じで上位2ビットを分解して各メンバに分解しないと解読できません。 なんでこんな面倒な作りになっているのだろう?と思っていましたが、本日はじめて読む486を読んでいたところ腑に落ちました。
63 55 47 39 31 23 15 7 0 286のセグメントデスクリプタ +--------+--------+--------+--------+--------+--------+ | ar | base | limit | +--------+--------+--------+--------+--------+--------+ この辺が486で追加された <------------------> +--------+----+----+--------+--------+--------+--------+--------+--------+ |base_hi |ar |l_hi| ar |base_mid| base_low | limit_low | +--------+----+----+--------+--------+--------+--------+--------+--------+
こんな感じで下位ビットは286と同じ形式にして互換性を持たせるためだったんですね。
32ビットコンピュータをやさしく語る はじめて読む486 (アスキー書籍)
- 作者: 蒲地輝尚
- 出版社/メーカー: 角川アスキー総合研究所
- 発売日: 2014/10/21
- メディア: Kindle版
- この商品を含むブログを見る
objdumpによる逆アセンブルとgdbの命令形式表示の比較
02_day/helloos5で比較しています。 このOS(というかプログラム)はブートセクタの中でhello worldを画面に出力するだけのものになります。
元のソース(アセンブリ)
; hello-os ; TAB=4 ORG 0x7c00 ; このプログラムがどこに読み込まれるのか ; 以下は標準的なFAT12フォーマットフロッピーディスクのための記述 JMP entry DB 0x90 DB "HELLOIPL" ; ブートセクタの名前を自由に書いてよい(8バイト) DW 512 ; 1セクタの大きさ(512にしなければいけない) DB 1 ; クラスタの大きさ(1セクタにしなければいけない) DW 1 ; FATがどこから始まるか(普通は1セクタ目からにする) DB 2 ; FATの個数(2にしなければいけない) DW 224 ; ルートディレクトリ領域の大きさ(普通は224エントリにする) DW 2880 ; このドライブの大きさ(2880セクタにしなければいけない) DB 0xf0 ; メディアのタイプ(0xf0にしなければいけない) DW 9 ; FAT領域の長さ(9セクタにしなければいけない) DW 18 ; 1トラックにいくつのセクタがあるか(18にしなければいけない) DW 2 ; ヘッドの数(2にしなければいけない) DD 0 ; パーティションを使ってないのでここは必ず0 DD 2880 ; このドライブ大きさをもう一度書く DB 0,0,0x29 ; よくわからないけどこの値にしておくといいらしい DD 0xffffffff ; たぶんボリュームシリアル番号 DB "HELLO-OS " ; ディスクの名前(11バイト) DB "FAT12 " ; フォーマットの名前(8バイト) RESB 18 ; とりあえず18バイトあけておく ; プログラム本体 entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; SIに1を足す CMP AL,0 JE fin MOV AH,0x0e ; 一文字表示ファンクション MOV BX,15 ; カラーコード INT 0x10 ; ビデオBIOS呼び出し JMP putloop fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ msg: DB 0x0a, 0x0a ; 改行を2つ DB "hello, world" DB 0x0a ; 改行 DB 0 RESB 0x7dfe-$ ; 0x7dfeまでを0x00で埋める命令 DB 0x55, 0xaa
gobjdumpによる逆アセンブル
$ gobjdump -b binary -m i386 -M intel -D helloos.img helloos.img: ファイル形式 binary セクション .data の逆アセンブル: 00000000 <.data>: 0: eb 4e jmp 0x50 2: 90 nop 3: 48 dec eax 4: 45 inc ebp 5: 4c dec esp 6: 4c dec esp 7: 4f dec edi 8: 49 dec ecx 9: 50 push eax a: 4c dec esp b: 00 02 add BYTE PTR [edx],al d: 01 01 add DWORD PTR [ecx],eax f: 00 02 add BYTE PTR [edx],al 11: e0 00 loopne 0x13 13: 40 inc eax 14: 0b f0 or esi,eax 16: 09 00 or DWORD PTR [eax],eax 18: 12 00 adc al,BYTE PTR [eax] 1a: 02 00 add al,BYTE PTR [eax] 1c: 00 00 add BYTE PTR [eax],al 1e: 00 00 add BYTE PTR [eax],al 20: 40 inc eax 21: 0b 00 or eax,DWORD PTR [eax] 23: 00 00 add BYTE PTR [eax],al 25: 00 29 add BYTE PTR [ecx],ch 27: ff (bad) 28: ff (bad) 29: ff (bad) 2a: ff 48 45 dec DWORD PTR [eax+0x45] 2d: 4c dec esp 2e: 4c dec esp 2f: 4f dec edi 30: 2d 4f 53 20 20 sub eax,0x2020534f 35: 20 46 41 and BYTE PTR [esi+0x41],al 38: 54 push esp 39: 31 32 xor DWORD PTR [edx],esi 3b: 20 20 and BYTE PTR [eax],ah 3d: 20 00 and BYTE PTR [eax],al ... 4f: 00 b8 00 00 8e d0 add BYTE PTR [eax-0x2f720000],bh 55: bc 00 7c 8e d8 mov esp,0xd88e7c00 5a: 8e c0 mov es,eax 5c: be 74 7c 8a 04 mov esi,0x48a7c74 61: 83 c6 01 add esi,0x1 64: 3c 00 cmp al,0x0 66: 74 09 je 0x71 68: b4 0e mov ah,0xe 6a: bb 0f 00 cd 10 mov ebx,0x10cd000f 6f: eb ee jmp 0x5f 71: f4 hlt 72: eb fd jmp 0x71 74: 0a 0a or cl,BYTE PTR [edx] 76: 68 65 6c 6c 6f push 0x6f6c6c65 7b: 2c 20 sub al,0x20 7d: 77 6f ja 0xee 7f: 72 6c jb 0xed 81: 64 0a 00 or al,BYTE PTR fs:[eax] ... 1fc: 00 00 add BYTE PTR [eax],al 1fe: 55 push ebp 1ff: aa stos BYTE PTR es:[edi],al 200: f0 ff lock (bad) 202: ff 00 inc DWORD PTR [eax] ... 1400: f0 ff lock (bad) 1402: ff 00 inc DWORD PTR [eax] ...
結果としてわかることですが
0~0x3d
はDB命令で書き込んでいるデータ(アセンブリで言う所のとりあえず18バイトあけておく
以上の箇所)
で、コードとして意味がある表示ができている箇所は0x50~0x72
までですね
gdbのxコマンドによる命令表示
(gdb) x/38bi 0x7c00 0x7c00: jmp 0x7c50 0x7c02: nop 0x7c03: dec eax 0x7c04: inc ebp 0x7c05: dec esp 0x7c06: dec esp 0x7c07: dec edi 0x7c08: dec ecx 0x7c09: push eax 0x7c0a: dec esp 0x7c0b: add BYTE PTR [edx],al 0x7c0d: add DWORD PTR [ecx],eax 0x7c0f: add BYTE PTR [edx],al 0x7c11: loopne 0x7c13 0x7c13: inc eax 0x7c14: or esi,eax 0x7c16: or DWORD PTR [eax],eax 0x7c18: adc al,BYTE PTR [eax] 0x7c1a: add al,BYTE PTR [eax] 0x7c1c: add BYTE PTR [eax],al 0x7c1e: add BYTE PTR [eax],al 0x7c20: inc eax 0x7c21: or eax,DWORD PTR [eax] 0x7c23: add BYTE PTR [eax],al 0x7c25: add BYTE PTR [ecx],ch 0x7c27: (bad) 0x7c28: (bad) 0x7c29: (bad) 0x7c2a: dec DWORD PTR [eax+0x45] 0x7c2d: dec esp 0x7c2e: dec esp 0x7c2f: dec edi 0x7c30: sub eax,0x2020534f 0x7c35: and BYTE PTR [esi+0x41],al 0x7c38: push esp 0x7c39: xor DWORD PTR [edx],esi 0x7c3b: and BYTE PTR [eax],ah 0x7c3d: and BYTE PTR [eax],al (gdb) x/18bi 0x7c4f 0x7c4f: add BYTE PTR [eax-0x2f720000],bh 0x7c55: mov esp,0xd88e7c00 0x7c5a: mov es,eax 0x7c5c: mov esi,0x48a7c74 0x7c61: add esi,0x1 0x7c64: cmp al,0x0 0x7c66: je 0x7c71 0x7c68: mov ah,0xe 0x7c6a: mov ebx,0x10cd000f 0x7c6f: jmp 0x7c5f 0x7c71: hlt 0x7c72: jmp 0x7c71 0x7c74: or cl,BYTE PTR [edx] 0x7c76: push 0x6f6c6c65 0x7c7b: sub al,0x20 0x7c7d: ja 0x7cee 0x7c7f: jb 0x7ced 0x7c81: or al,BYTE PTR fs:[eax]
いくつか違う点がありますが、ほぼ逆アセンブルの結果と同じですね
個人的見所
0x76~0x83のオフセットはデータ
そこに書き込まれているのは"hello, world"という文字列です
76: 68 65 6c 6c 6f push 0x6f6c6c65 7b: 2c 20 sub al,0x20 7d: 77 6f ja 0xee 7f: 72 6c jb 0xed 81: 64 0a 00 or al,BYTE PTR fs:[eax]
証拠に"hello, world"という文字のasciiを表示しました
$ echo hello, world|hexdump -C 00000000 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 0a |hello, world.|
アセンブリではDB命令で書き込んでいます
DB "hello, world"
すこし気に入らないのはこれらは固定長のデータとかではなく、 本当にバイナリの並びを命令として解釈しようとしていることですね。
0x7c76: push 0x6f6c6c65 0x7c7b: sub al,0x20 0x7c7d: ja 0x7cee 0x7c7f: jb 0x7ced 0x7c81: or al,BYTE PTR fs:[eax]
DB命令はオペランドを内容をそのままメモリに書き込む内容ですが、 DB命令を多用すると、逆アセンブル時に元の命令と対応させるのが難しい実行ファイルができてしまいそうです。
JMP命令の解釈がgdbと逆アセンブラで違う
一番最初の命令ですね。
- 逆アセ:
jmp 0x50
- GDB:
jmp 0x7c50
gdbで16進数でバイナリダンプしてみましたが、逆アセンブル時と同じく0xed,0x4e
です。
(gdb) x/5bx 0x7c00 0x7c00: 0xeb 0x4e 0x90 0x48 0x45
GDBではセグメントレジスタまで考慮して命令を表示しているのかと思い、qemu monitorで各レジスタを表示してみましたが、そんなことはありませんでした。(cs=0)
(qemu) info registers EAX=0000aa55 EBX=00000000 ECX=00000000 EDX=00000000 ESI=00000000 EDI=00000000 EBP=00000000 ESP=00006ef0 EIP=00007c00 EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 ES =0000 00000000 0000ffff 00009300 CS =0000 00000000 0000ffff 00009b00 SS =0000 00000000 0000ffff 00009300 DS =0000 00000000 0000ffff 00009300 FS =0000 00000000 0000ffff 00009300 GS =0000 00000000 0000ffff 00009300 LDT=0000 00000000 0000ffff 00008200 TR =0000 00000000 0000ffff 00008b00 GDT= 00000000 00000000 IDT= 00000000 000003ff CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=ffff0ff0 DR7=00000400 EFER=0000000000000000 FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80 FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000 XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000 XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000 XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
もちろんこのベースアドレス分の違いはORG命令からきているものだと思いますが、
ORG 0x7c00 ; このプログラムがどこに読み込まれるのか
.imgのバイナリの段階でこの情報が抜け落ちていて、gdbで実行中だけ見えるというのは納得が行きません。
オペランド長の設定が不適切なため、次の命令がオペランドに不正に塗りつぶされている
0x7c6a: mov ebx,0x10cd000f 0x7c6f: jmp 0x7c5f
ここですが、int命令が0x7c6d
から始まるint命令がmove命令のオペランドとして吸収されています
(gdb) x/2bi 0x7c6d 0x7c6d: int 0x10 0x7c6f: jmp 0x7c5f
解決策
16bit命令として解釈させる
$ gobjdump -b binary -m i8086 -M intel -D helloos.img
- m
optionをi386ではなく、i8086を指定すれば良いようだ。
00000000 <.data>: 0: eb 4e jmp 0x50 2: 90 nop 3: 48 dec ax 4: 45 inc bp 5: 4c dec sp 6: 4c dec sp 7: 4f dec di 8: 49 dec cx 9: 50 push ax a: 4c dec sp b: 00 02 add BYTE PTR [bp+si],al d: 01 01 add WORD PTR [bx+di],ax f: 00 02 add BYTE PTR [bp+si],al 11: e0 00 loopne 0x13 13: 40 inc ax 14: 0b f0 or si,ax 16: 09 00 or WORD PTR [bx+si],ax 18: 12 00 adc al,BYTE PTR [bx+si] 1a: 02 00 add al,BYTE PTR [bx+si] 1c: 00 00 add BYTE PTR [bx+si],al 1e: 00 00 add BYTE PTR [bx+si],al 20: 40 inc ax 21: 0b 00 or ax,WORD PTR [bx+si] 23: 00 00 add BYTE PTR [bx+si],al 25: 00 29 add BYTE PTR [bx+di],ch 27: ff (bad) 28: ff (bad) 29: ff (bad) 2a: ff 48 45 dec WORD PTR [bx+si+0x45] 2d: 4c dec sp 2e: 4c dec sp 2f: 4f dec di 30: 2d 4f 53 sub ax,0x534f 33: 20 20 and BYTE PTR [bx+si],ah 35: 20 46 41 and BYTE PTR [bp+0x41],al 38: 54 push sp 39: 31 32 xor WORD PTR [bp+si],si 3b: 20 20 and BYTE PTR [bx+si],ah 3d: 20 00 and BYTE PTR [bx+si],al ... 4f: 00 b8 00 00 add BYTE PTR [bx+si+0x0],bh 53: 8e d0 mov ss,ax 55: bc 00 7c mov sp,0x7c00 58: 8e d8 mov ds,ax 5a: 8e c0 mov es,ax 5c: be 74 7c mov si,0x7c74 5f: 8a 04 mov al,BYTE PTR [si] 61: 83 c6 01 add si,0x1 64: 3c 00 cmp al,0x0 66: 74 09 je 0x71 68: b4 0e mov ah,0xe 6a: bb 0f 00 mov bx,0xf 6d: cd 10 int 0x10 6f: eb ee jmp 0x5f 71: f4 hlt 72: eb fd jmp 0x71 74: 0a 0a or cl,BYTE PTR [bp+si] 76: 68 65 6c push 0x6c65 79: 6c ins BYTE PTR es:[di],dx 7a: 6f outs dx,WORD PTR ds:[si] 7b: 2c 20 sub al,0x20 7d: 77 6f ja 0xee 7f: 72 6c jb 0xed 81: 64 0a 00 or al,BYTE PTR fs:[bx+si] ... 1fc: 00 00 add BYTE PTR [bx+si],al 1fe: 55 push bp 1ff: aa stos BYTE PTR es:[di],al 200: f0 ff lock (bad) 202: ff 00 inc WORD PTR [bx+si] ... 1400: f0 ff lock (bad) 1402: ff 00 inc WORD PTR [bx+si] ...
6dからの命令が正しくint 0x10
と出力されている
gdbで少し探してみたが、set architecture i8086
というコマンドがあるようだ
https://stackoverflow.com/questions/28811811/how-to-use-gdb-in-16-bit-mode
GDBのxコマンドのiオプションは
アセンブラ・シンタックスでの (もしくは、それに近い) マシン・ インストラクションを表示します。指定されたユニットサイズは無視され; 1つのインストラクションを構成するバイト数は、そのマシンで利用されている オペコードとアドレッシングモードの種類に依存します。
なので、かなりこれが解決策っぽい!
http://flex.phys.tohoku.ac.jp/texi/gdb-j/gdb-j_41.html
が、適応してみたところ、結局32bitの機械語として解釈されてしまった。
(gdb) set architecture i8086 The target architecture is assumed to be i8086 (gdb) x/18i 0x7c50 0x7c50: mov eax,0xd08e0000 0x7c55: mov esp,0xd88e7c00 0x7c5a: mov es,eax 0x7c5c: mov esi,0x48a7c74 0x7c61: add esi,0x1 0x7c64: cmp al,0x0 0x7c66: je 0x7c71 0x7c68: mov ah,0xe 0x7c6a: mov ebx,0x10cd000f 0x7c6f: jmp 0x7c5f 0x7c71: hlt 0x7c72: jmp 0x7c71 0x7c74: or cl,BYTE PTR [edx] 0x7c76: push 0x6f6c6c65 0x7c7b: sub al,0x20 0x7c7d: ja 0x7cee 0x7c7f: jb 0x7ced 0x7c81: or al,BYTE PTR fs:[eax]
コード部分(0x50~0x7f)をステップ実行してみる
(gdb) x/18bi 0x7c50 => 0x7c50: mov eax,0xd08e0000 0x7c55: mov esp,0xd88e7c00 0x7c5a: mov es,eax 0x7c5c: mov esi,0x48a7c74 0x7c61: add esi,0x1 0x7c64: cmp al,0x0 0x7c66: je 0x7c71 0x7c68: mov ah,0xe 0x7c6a: mov ebx,0x10cd000f 0x7c6f: jmp 0x7c5f 0x7c71: hlt 0x7c72: jmp 0x7c71 0x7c74: or cl,BYTE PTR [edx] 0x7c76: push 0x6f6c6c65 0x7c7b: sub al,0x20 0x7c7d: ja 0x7cee 0x7c7f: jb 0x7ced
画面の文字表示のAPIが0x7c6dにアロケートされるとがわかったのでそこにブレイクポイントを置いてステップ実行をしています。
(gdb) b *0x7c6d Breakpoint 3 at 0x7c6a (gdb) continue
次回はフロッピー読み込みをデバッグしてみます。
「Linuxの仕組み」を読んだ
推測するな計測せよ!な一冊。
[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識
- 作者: 武内覚
- 出版社/メーカー: 技術評論社
- 発売日: 2018/02/23
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
技術評論社のepubを買った。結構定期的にアップデートされている様子。 売るつもりがないなら電子書籍の方がお得です。
「はじめての486」読了後、読む電子書籍がなくなったので読みました。*1
OS自作本を読んだ後は、 低レイヤーの歩き方を参考にするに、xv6の本かな~と思いきや
『Linuxの仕組み』読みました。 まず全体を通して、まさに"若い頃(中高生)、一番欲しかった書籍"を完璧なまでに体現している内容でした。とりあえずOS学びたい任意の人類に無条件で投げつけて良さそう。 #linux_in_practice
— るくす (@RKX1209) 2018年3月2日
こんな感想が目に留まったのでこちらを先に買いました。
ちなみに私のLinux力をお伝えしておくと、 趣味と大学時代にちょろっと触ったことがある程度で、 特にそこに対して時間をかけて勉強・研究したり 実務で使ったことはありません。
- 速度や負荷計測のコマンドがかなり使われている
- マルチタスクやキャッシュなどの速度検証
ファイルシステムはこちらにまとめました。
仮想ファイルシステムがディバイスアクセスをラップしており、 コンソールやHDDをまるでファイルアクセスのように利用できる点が以外でした。(常識?)
とにかく図がわかりやすい!
あまりOSについて書かれた本は読めていませんが、 ハードウェアの構成図などはもちろん プロセスがタスクスイッチで処理される様子など かなりわかりやすく書かれています。
計測パートについて
- 読書時間が電車の中だったり、
- 手元のMacのVirtual Boxの中のubuntuのキーボードがUS配列に対応していなかったり
- まぁそんなもんぐらいググって設定しろってはなしですが。
- 代わりに使ったKali Linuxにコマンドがあまり入っていない
などの理由によりあまり実測できていません。
また著者はVMWareやVirtual Boxなどの仮想環境より、 実機で検証することを推奨しています。LinuxがブートできるPCを現在保持していません。
あまり手を動かして読めていませんが、ここに書かれている計測方法で utuntu以外のOSもベンチできたら楽しいんじゃないでしょうか。
図などはかなりわかりやすいですが、初心者はプログラムを 自分でコメントを付けながら精読した方がいいと思います。
個人的にディスクIOやページング、ファイルシステムを実装するときに 参考にする1冊になるだろうと考えています。