07:10:36 # Life MacBook Air を grub-efi で起動する。 TestingのデイリービルドのDebian installer で MacBook Air 6,1 にDebianをインストールして、rEFIndをインストールしたらrEFIndがLinux Kernel を 起動してくれるようになりました。それでよいきもしていたんだけ どなんかinitrdが間違ったファイルをみているっぽい。 あと、インストール時にgrub-efiが設定されているっぽいのにgrub-efiがEFIから起動できない。 grub-installをするとstage1がインストールされるらしいので作業してみました。 MacBook Air でのデフォルトのGPTのパーティション設定では /dev/sda1 がEFIパーティションなので、それを適当なところ(/boot/efi)にマウントしてgrub-installで指定すると /boot/efi/EFI/debian/grubx64.efi ができて、 これでgrubから起動できるようになりました。 しかし、grub メニューからMac OS X を選択しても起動しない気がする。 option を押しながら起動して rEFInd を選択して起動したらそこからはMac OS Xは起動するのでよかったことに。 efibootmgr / efivars を利用してファームウェアの起動順をDebian から操作できるようになっているようでOS X のblessに依存する必要がないのかな。 昔とちがってEFI起動で結構安定してうごいているので素晴らしい。
# mkdir /boot/efi # mount -t vfat /dev/sda1 /boot/efi # grub-install --efi-directory=/boot/efi/ BootCurrent: 0080 Timeout: 5 seconds BootOrder: 0000,0080 Boot0080* Mac OS X Boot0082* BootFFFF* Boot0000* debian Installation finished. No error reported.
10:01:54 # Life 公開鍵認証をやりすぎてssh でパスワードログインできない。 新しく設定しているマシンにパスワードでログインしようとしたらログインできず。なんか ssh -v のエラーメッセージみてみたら ssh-agentに登録されている鍵を全部試してみて試行回数の上限(5回?)を超えてコネクションがきられているよう。 僕の手元では管理している対象ごとに鍵をたくさん生成しているのでssh-agentは大量の鍵をかかえているようでした。 .ssh/config に PubkeyAuthentication no にして、公開鍵認証を試さないようにして解決。
Host xxx.yyy.zzz PasswordAuthentication yes PubkeyAuthentication no
公開鍵認証でログインできるように設定したら以下のように特定の鍵だけを試すように変更。これで解決したかな。また鍵が増えた。
Host xxx.yyy.zzz PasswordAuthentication no PubkeyAuthentication yes IdentitiesOnly yes IdentityFile ~/.ssh/hogehoge
07:26:08 # Life Debian testing netinst イメージでMacBook Airを起動。 デイリービルドのCDイメージをダウンロードしてきた。netinst というのはネットワークインストール前提のミニマルイメージで200MB程度。 そのままCDイメージをミラーから取得するより効率的な方法として、jigdoを使った。 jigdoファイルを取得したあと、jigdo-lite ./debian-testing-amd64-netinst.jigdoで本体をダウンロード。 結果取得できる./debian-testing-amd64-netinst.isoはCD用の起動可能el-torito 形式のISO9660 イメージであると同時にGPTパーティションとFATパーティションとしても見えるようでEFI起動可能なFATパーティションがついている。 USBメモリが手元にないのでUSB SDカードリーダーと使ってない512MBのSDカードを活用。 一般ユーザ権限(root権限じゃなくてもUSBメモリにはアクセスできる)でsd card にdd でコピーdd if=debian-testing-amd64-netinst.iso of=/dev/sdb bs=1Mすると起動したいMacBook Airにさしこんで起動。 事前にrEFItをインストールしていたからか普通に sd card から起動した。 起動の試験として一応手元でqemuでBIOSブートとEFIブートの両方が可能なことを確認。 インストールは普通にやっていったら素直に進んでいった。はまりどころはgrub-efiでext4 の / 以下にある /boot にインストールしてくれてるっぽいところか。 多分EFIのFATファイルシステムに書き込んでくれるとEFIが標準で読めると思うんだけど、EFIは標準でext3/4は読み込めないのでドライバを用意する必要がある。 refitはext3やreiserfs, refindはext4やbtrfsのドライバを同梱しているのでそれを使うか、FATファイルシステムにgrub-efi が書き込むようにするかの二択のようなきがする。
$ kvm -boot d -cdrom /dev/sdb # tianocore bios.bin があるディレクトリでEFIブート $ qemu-system-x86_64 -L . -cdrom /dev/sdb
09:21:45 # Life GNU EFIとEDK2の違い。 EFI は windows の ABI でうごいているっぽくて、GNU EFI は uefi_call_wrapperでABIのコールスタックの調整をして呼び出しているっぽい。 まぁシステムコールだと思えばそこまで変でもないかも。 一方EFI Development Kit 2 だとmingwターゲットのgccをつかっているのかな? eliloとかgrubとかrefindとかrefit とかのブートローダーの開発をしている人たちはみんなGNU EFIを使っていてオープンソースプロジェクトは ELFからPE32+に変換するというハックを多分過去15年くらい使っているわけなんだけどEDK2のアプローチのほうがまっとうなきもするなぁ。 ただEDK2のどのファイルになにがあるのかまだ把握していないので試せてない。
12:23:45 # Life crypto API. AF_ALGというアドレスファミリーでソケットを開いたらwriteしたデータをreadしたら暗号化されたデータがもらえる。おもしろいAPIだなぁ。
14:32:18 # Life memfd, kbusのsealing について。 ゼロコピーでも別のプロセスのTLBいじるコストがあるので、 512k以下だとメモリコピーのほうがましだった、というのがおもしろい。 Androidのashmemと同じ、らしい。 binder/ashmem とどうやってマージするのかなぁ。
17:22:31 # Life x86-64 EFI で brainfuck. MacBook Air を新しく買ってきたのでまたインストーラーでもいじろうかなと思ってたので EFIの開発環境を整備してみた。 gnu-efi で配布されているリンカスクリプトでELFの.soを作成してそこからPE32+ 実行ファイルをobjcopyで作成するという手順がだいぶ変態的でそのためのMakefileが複雑すぎ。 EDK2ではリンカが用意されているという噂だけどどこに実体があるのか発見できず。 Tianocore プロジェクトにEFIファームウェアがあって、qemuのBIOSとして利用できるので手元で使ってみた。 qemu の fat:コマンドラインを使うとディレクトリを読み込み専用の仮想FATファイルシステムとして見せてくれるのでEFIから見える。ファイルシステムイメージを毎回作成する必要がないのですごい便利。シリアルコンソールでも画面サイズが80x24だと仮定していて、 そこはどうしたらよいのかな。
$ file brainfuckefi.efi brainfuckefi.efi: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows $ qemu-system-x86_64 -L . -hda fat:. -nographic Shell> fs0: fs0:\> brainfuckefi.efi InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 5C4BE40 Loading driver at 0x00005C21000 EntryPoint=0x00005C24000 InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 5C64418 InstallProtocolInterface: 47C7B223-C42A-11D2-8E57-00A0C969723B 7A798E0 Hello World!
08:50:02 # Life openat システムコール。 一連の*at というやつがあって、最近はopenのかわりにopenatをつかうのがかっこいいみたい。たとえばarm64ではopen がなくてopenat しかない。 openatはopenと違いディレクトリのファイルデスクリプタを引数としてとってそこから相対的に名前を解決してくれる。 フルパスの解決はアトミックじゃない操作なのでこれはこれで一つよくなったのかもしれず。 man みたらスレッドごとに cwd を管理するのにも便利と書いてあってたしかにそうかもしれない。getcwd で得られる情報ってプロセス単位だからマルチスレッドプログラムで chdir すると相対パスで指定したファイルアクセスのデバッグがめんどくさいんだよね。
06:58:22 # Life brainfuck compiler in x86_64 C++ を書いた。 C++ で gas 用のコードを生成して、コンパイルして実行する形式。 ループ処理をCPU側でやってもらうことによりインタプリタより圧倒的にシンプルになった。 gas のマニュアルの symbol names を読んで相対シンボル指定の方法を確認した。 ELF の場合は .Lで始まるシンボルはローカルシンボルとなり、 数字だけのシンボル名はローカルシンボルを自動生成する、というところまで把握。 同じシンボルが複数あってもよくて、 'f' 'b' のポストフィックスをつけると 直前または直後のシンボルを指定する。数字でシンボル指定するのがべんりかどうかはよくわからんけどまぁ今必要な用途では便利。
1: // hogehoge cmp $0, %rax jne 1b
19:02:56 # Life x86 の hello world。 そういえば、x86ってどうだったかなとおもって、libcを使う版と使わない版のhello worldを書いてみた。関数呼び出しのABIはスタックベースなのでスタックに積んであとから掃除する必要がある一方でシステムコールはレジスタABI。 関数呼び出しから戻ってきたあとに掃除するコードが必ずはいるので無駄だなぁと感じる。cdeclじゃなくてpascal にしたくなったり fastcallにしたくなったりする気持ちがわかる。 手元でコンパイルするのにgcc -m32したらファイルがたりなくて、libc6-dev-i386をインストールした。multiarchの時代においてはもうちょっと違うパッケージになるのかな?
push $msg call puts add $4, %esp
21:58:54 # Life x86_64 の hello world。 アセンブリで書いたHello World、ELFを手書きしてみて素直に書いて176バイトになった。 久しぶりにリロケーションを手でやってる。 アセンブリで書いた -nostdlibで 1115B, strip して 688Bだったので大幅に削減。 ELFヘッダをオーバラップさせればもっと短くできると思われる。58バイトとかに到達できるんだろうか。
23:10:37 # Life hello world. なんとなく Hello World ってどうだっけなとおもったので gcc -nostdlib でコンパイルしてうごく x86_64 Hello Worldかいてみた。 システムコールの番号ってどこで定義されているのだとおもったら linux/arch/x86/syscalls/syscall_64.tbl にあった。__NR_writeとかどっかでinclude できるはず。 この段階だとまだstrip して 688Bあるので56バイトとかまでやるのはすごいなぁ。 x86だとシステムコールの1番がexitだったりするんだけど、hello worldするのに必須なwriteが1番になっていてexitはおいやられているx86_64はえらいなぁという感想。
.data msg: .ascii "Hello World!\n" msgend: .text .globl _start _start: movq $1, %rdi movq $msg, %rsi movq $msgend-msg, %rdx movq $1, %rax syscall movl $60, %eax syscall
07:20:33 # Life qemu の proc. /proc/cpuinfo とかはホストのままだよなぁと思ってたけど、/proc/self/maps は syscall.c の open_self_maps によって書き換えられている。
/proc/[pid]/maps $ cat /proc/27355/maps 60000000-601cd000 r-xp 00000000 08:05 100482 /sid-chroot-arm/usr/bin/qemu-arm-static 602cd000-602fd000 rw-p 002cd000 08:05 100482 /sid-chroot-arm/usr/bin/qemu-arm-static 602fd000-622fe000 rwxp 00000000 00:00 0 622fe000-6230b000 rw-p 00000000 00:00 0 6230b000-6230d000 rwxp 00000000 00:00 0 6230d000-62363000 rw-p 00000000 00:00 0 63e49000-63e6c000 rw-p 00000000 00:00 0 [heap] 7fd1efa83000-7fd1efad0000 rw-p 00000000 00:00 0 7fd1efad0000-7fd1efad8000 ---p 00000000 00:00 0 7fd1efad8000-7fd1efb9b000 r-xp 00000000 08:05 11591958 /sid-chroot-arm/bin/bash [中略] 7fff2a5a0000-7fff2a5c1000 rw-p 00000000 00:00 0 [stack] 7fff2a5ff000-7fff2a600000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] /proc/self/maps 00000000-00008000 ---p 00000000 00:00 0 00008000-00012000 r-xp 00000000 08:05 11592151 /bin/cat 00012000-00019000 ---p 00000000 00:00 0 00019000-0001a000 r--p 00009000 08:05 11592151 /bin/cat 0001a000-0001b000 rw-p 0000a000 08:05 11592151 /bin/cat 0001b000-0003c000 rw-p 00000000 00:00 0 0003c000-f669b000 ---p 00000000 00:00 0 f669b000-f669c000 rw-p 00000000 00:00 0 f669c000-f67c6000 r-xp 00000000 08:05 11591816 /lib/arm-linux-gnueabi/libc-2.13.so f67c6000-f67ce000 ---p 0012a000 08:05 11591816 /lib/arm-linux-gnueabi/libc-2.13.so f67ce000-f67d0000 r--p 0012a000 08:05 11591816 /lib/arm-linux-gnueabi/libc-2.13.so f67d0000-f67d1000 rw-p 0012c000 08:05 11591816 /lib/arm-linux-gnueabi/libc-2.13.so f67d1000-f67d5000 rw-p 00000000 00:00 0 f67d5000-f67d8000 ---p 00000000 00:00 0 f67d8000-f67d9000 rw-p 00000000 00:00 0 f67d9000-f67f6000 r-xp 00000000 08:05 11593229 /lib/arm-linux-gnueabi/ld-2.13.so f67f6000-f67fd000 ---p 00000000 00:00 0 f67fd000-f67fe000 r--p 0001c000 08:05 11593229 /lib/arm-linux-gnueabi/ld-2.13.so f67fe000-f67ff000 rw-p 0001d000 08:05 11593229 /lib/arm-linux-gnueabi/ld-2.13.so f67ff000-f6800000 ---p 00000000 00:00 0 f6800000-f7000000 rw-p 00000000 00:00 0 [stack]
18:25:25 # Life qemu-user 環境でのデバッグ。 通常のstraceやgdbが使えないので代替手段が用意されていて、 環境変数 QEMU_GDBでgdb stubの指定、 QEMU_STRACEでstrace相当の表示がされる。 linux-user/main.c を読むとよいかも。man qemu-xxx は Debian用に作成された古いドキュメント。 straceの場合、QEMU_STRACE=1 commandと環境変数を定義して実行するとstrace的な出力が出る。 gdbの場合はもうちょっと複雑で、gdb-multiarch をホスト環境にインストールしておきQEMU_GDB=portで起動したバイナリーに対して gdbをアタッチする。
target$ QEMU_GDB=8099 ./binary # 起動したら、gdb アタッチを待ってとまっている host$ gdb-multiarch ./binary (gdb) set architecture armv5te (gdb) b main # 適当にmainあたりにブレークポイントを設定してみる。 (gdb) c
09:09:11 # Life thumb と arm assembly のプロファイルを raspberry pi でとった。 brainfuck interpreter に100000回くらいループしてもらってperf record でトレースをとって、perf report で確認。 UIで選択していったらどのアセンブリ命令にいるときに割り込みが起きたのかがわかっておもしろい。 アセンブリソースで記述したラベル(hoge:)が関数名として見えているのでどっかのレベルでデバッグ情報がはかれているのかな。まだ把握してない。 thumb と arm でナイーブに実装しただけでは実行時パフォーマンスに差がない。単純にarm のほうがコードサイズが倍になるのでこの場合はthumbのほうがよいということになるのかも。 あとプロファイル見てる限りだとループの境界チェックで定数のロードでメモリアクセスにいくところが遅いのでそこをレジスタに割りつけたら20%高速化した。 命令がどうとかよりメモリアクセスを減らすのが重要っぽい。 たとえば、cmpだと引き算しているのでもったいないなぁとteqでxorにしてみても特に高速化とかしてない気配。 aarch64は手元に実機がないのでプロファイルをとるたのしみがない。
17:37:44 # Life qemu-debootstrap というスクリプト. Debian の qemu-user-static パッケージには qemu-debootstrapと いうスクリプトがあって、debootstrapしてqemuをコピーして debootstrap --second-stage を実行してという手続きをやってくれ る模様。あとはDebianのアーキテクチャ名からqemu のアーキテクチャ 名のマッピングもやってくれる。 qemu-debootstrap --arch=[アーキテクチャ] sid [ディレクトリ名] http://ftp.jp.debian.org/debian
$ for A in mips mipsel powerpc armel; do sudo qemu-debootstrap --arch=$A sid $A http://ftp.jp.debian.org/debian ; done $ for A in *; do sudo chroot $A echo "hello world $A"; done hello world armel hello world mips hello world mipsel hello world powerpc
debian-portsからブートストラップできるアーキテクチャもいくつかあって、sh4とsparc64は十分うごくみたい。
for A in sh4 sparc64; do sudo qemu-debootstrap --foreign --no-check-gpg --arch=$A sid $A http://ftp.debian-ports.org/debian ; done
19:18:18 # Life brainfuck interpreter in A64/aarch64/arm64を書いた。 手元にマシンがないのでqemu-user-staticパッケージに2.0からはいったaarch64 エミュレータを利用。 chroot の作成方法についてはDebian wiki の Arm64Portに詳しい。 まだdebian-portsに必要なパッケージが全部入ってはいないので試行錯誤が必要かも。apt だけで依存関係が解決できない場合があるのでときどきdpkgを使ってみた。 32bit 命令セットなんだけど conditionalを指定出来なくしたため、thumb 命令 + レジスタを32種類用意するようになった、という雰囲気。 .arch armv8-a。 レジスタ名がドキュメント的にはr0とかなんだけど実際にはそれではダメで、x0かw0なのか指定しないとだめっぽい。 push/pop命令がなくなっていて、2つづつレジスタを書き込んでspの増減をしてくれる stp/ldp 命令が追加されている。 bx命令がなくなっていてthumb/arm の切り替えがない、どうも32-bit と 64-bit はスーパバイザ経由でないと切り替えられないよう。 レジスタセットが違うのでまぁ妥当な感じかも。普通のアーキテクチャね。 スタックは常に16バイトバウンダリにないといけなくて2つづつレジスタをスタックにかかないといけないのがちょっと不条理。 ABI は IHI0055B_aapcs64.pdf が公開されているのでそれを読んで把握。 ARM ARMは登録しないと読めないのとaarch64 の命令セットのよいドキュメントがない・・・。 通信用語が異様に詳しい。なぜ?
18:08:53 # Life brainfuck interpreter in ARM Thumb を書いた。 昨日書いたARM assembly で書いたnmemonic に .thumb と .thumb_func ディレクティブを追加したら16bit の命令セットになって そのまま実行できた。 レジスタ指定に使えるビットが1ビットへって3ビットになってたり、 レジスタ複数push/pop命令がレジスタを9bitのbit mapで指定するようになってたり、 ジャンプ命令で条件分岐できるやつは8 bitの相対アドレスになって条件なしブランチも11bit だったり、大きなプログラムを手書きで書いていたら発狂しそうな制約がいくつか。 手元のARM7-TDMI マニュアルにはFPUのアクセス方法が全く記述され ていないのだけどこれどうなっているのだろう。 なんかドキュメントをあさっていたら thumb-2 vfpv3 だとサポートされているらしく、 raspberry pi は vfpv2 なのでつかえなさそう。
18:05:44 # Life brainfuck interpreter in ARM assembly を書いた。 C, C++, x86_64 とかいたので次は ARM かなと思って。 ARMのドキュメントがどこにあるのかよくわからずまたてこずったの と gas の記法がまた ARM 用で違うのがつらい。 必要だったドキュメントは ARM の命令セットのドキュメント。 ARM v7-A Architecture Reference Manualが一番よいマニュアルっぽいのだけどARMのサイトではダウンロードするのに登録が必要という。 登録とか必要ないドキュメントもけっこうたくさんあって、 ARM7500FE のデータシートの ARM Processor Instruction Set -- Instruction Set Summary がわかりやすくてよかった。ARM 命令の直交性がよいとかいう人がいるけどまぁわからんでもないという。 一番面食らうのはサブルーチンを呼び出すCALL命令に相当するものがなくて、B 命令のバリエーションでLR(リンクレジスタ)にPCの値を保持するバージョンとLRにジャンプするバージョンがあって、bl と bx lr を使う。スタック使わなくても良い設計になっているのかな。 あとx86と違うのは、演算はレジスタ間でしかできなくてロードストアはldr/str命令を使うところ。 32bit固定長の命令を使っているため即値命令が16bit代入のmov と movt とPC相対の代入命令とかを使うハメになるので .text の近所に read only data がおいてあって .ltorg ディレクティブでそこにはかせるとか理解を超える挙動が。 あと、あらゆる命令が条件実行できるのだけどそんな機能使える人いるのかな。 関数の呼び出しとかの仕方についてはOABI, EABI とかいろいろあるんだけど今はarmel とよばれるEABIでよいとおもわれる、 ARM ABIにくわしくて、 AAPCSでどのレジスタがどういう意味なのかが解説してある。
ABIでレジスタの意味で、ざっくりメモしたのだと。
r15 == PC (program counter) r14 == LR (link register) r13 == SP (stack pointer) r12 == IP (intra-procedure-call scratch register) r11 == v8 r10 == v7 r9 == v6, SB, TR, platform register. TR=Thread Register. r8 == v5 r7 == v4 r6 == v3 r5 == v2 r4 == v1 variable registers r3 == a4 r2 == a3 argument / scratch register. r1 == a2 r0 == a1 argument / result/ scratch register v == callee-saved variable register. a == caller-saved.
二時間くらいドキュメントを読みながらx86のコードをarmの対応す るコードに置き換えてみたら brainfuck コンパイラーがあっけなく 動いてしまった。満足。
17:14:28 # Life dl_iterate_phdrと戯れる. 現在ロードされている共有ライブラリの一覧を取得できる。 コールバックはdl_phdr_info 経由でELFのプログラムヘッダにアクセスできるというELFがちがちなインタフェース。 手元で試してみたら一番目がメインのバイナリで二番目がVDSOっぽくて、 メインのバイナリのdlpi_name は空文字列がかえってくる。 二番目はVDSOなんだけど、なんかglibc 2.13では名前がうまくとれない。 glibc 2.18 では linux-vdso.so.1 という名前を取得してくれる。 dlpi_addr が 7fff0a4ff000 で dlpi_phdr[].p_vaddr が ffffffffff700548 なのでアドレスを見るとなにかがおかしいのがわかるんだけど、アドレスが不正なのでPT_DYNAMICなヘッダを読み込もうとすると segv することになる、のかな。 このアドレスを読むと死ぬのでそれまでのところで発見する必要があるっぽいんだけど、VDSOのアーキテクチャだと二番目に来るという以外になんか特徴あるのかね。 手元のkernel は stable なので 3.2.0、結構古い。 VDSOはカーネル内部で適当にでっち上げたヘッダなのでまぁこわれているということもあるだろうなという印象なんだけど、こわれているのは困るなぁ。 vdso って x86_64 限定のしくみなのかなと思っていたらいろんなアーキテクチャで使われていた。vdso(7) はけっこういい入門かも。 あとそこから参照されている linux/Documentation/vDSO/あたりも。 /proc/X/maps をみると 7fff0a4ff000 には vdso, ffffffffff700548 には vsyscall がマップされていることになってる。
17:44:11 # Life gasでx86_64 用のコードを書くのに必要なドキュメントとは。 多分見る必要があるドキュメントとして、まず命令セットの理解が必要だと思うので、 Intel(R) 64 and IA-32 Architectures Developer's Manual。 あとは System V Application Binary Interface AMD64 Architecture Processor Supplementで関数呼び出し規約を確認。 x86 の呼び出し規約はスタック使いまくりなんだけどx86_64はレジスタ呼び出しが基本なのでそこらへんをじっくりと味わう感じで。 で、gasの記法は独特なところがあるので info をよむのがよいでしょう。 Debian だと binutils-doc パッケージ info as タイトルがすごくあっさりしていて"Using as"、 Machine Dependent Features を中心にながめればよいきがする。 入門するときに基本的な記法がまったくわからないので、g++ -g -S でC++をコンパイルしてその出力をながめるというのがよいかも。 あと gdb で stepi/nexti してアセンブリ命令一命令ごとに実行しながら GUD で M-x gdb-display-registers-bufferとM-x gdb-display-assembly-buffer しているのがよいきがする。
16:26:58 # Life ia32 のドキュメント。 intel CPU向けにコード書くときに必携の325462 というドキュメントがあるんですが、なんか今見たら3355ページもあって検索効率が悪い。どうするのがよいんでしょうね。目次すらページ数が結構あるので目次をさがすのもなんか大変。
$Id: 201405.html.ja,v 1.31 2014/06/01 23:09:06 dancer Exp $