デュアルスタックを実装する
- なんとなくx86よりかはやりやすそうなのでMIPSで
** エミュレータの改造
-
QEMU
-
tcg_regset_set_reg(s->reserved_regs, TCG_TMP2); /* internal use */ – これを使えばカンタンにレジスタ追加できるのでは
-
MIPS → TCG → x86
- “target” of QEMU: エミュレートされる側
-
“target” of TCG: エミュレートする側(ホスト, コードの変換先アーキ)
-
jal, jr命令の実装 – target/mips/translate.c: 4368L付近 – btgt = btarget オペランドかな? {code} - case OPC_J: - case OPC_JAL: - case OPC_JALX: - /* Jump to immediate */ - btgt = ((ctx->base.pc_next + insn_bytes) & (int32_t)0xF0000000) | - (uint32_t)offset; - break;
- case OPC_JR: - case OPC_JALR: - /* Jump to register */ - if (offset != 0 && offset != 16) { - /* Hint = 0 is JR/JALR, hint 16 is JR.HB/JALR.HB, the - others are reserved. */ - MIPS_INVAL("jump hint"); - generate_exception_end(ctx, EXCP_RI); - goto out; - } - gen_load_gpr(btarget, rs); - break; {/code}
-
遅延スロットの存在を忘れていた
- plan B: 新しいレジスタを追加する ($rp) – jal/jr命令実行時に自動でrastackに積むようにする – target/mips/translate.c: mips_tcg_init() — gen_st() —- gen_base_offset_addr(): cpu_gprbase + offset しているだけ —- tcg_gen_qemu_st_tl(): t1→t0 —- つまりmov base+offset, rt — gen_ld() —- mov rt, base+offset (rtはレジスタ番号) — gen_intermediate_code()→TranslatorOps.translate_insn→mips_tr_translate_insn()→decode_opc()→gen_compute_branch()という流れ — btarget = btgt = jump/branch target – target/mips/cpu.h: struct TCState — gpr33
- GPR = 汎用レジスタ General Purpose Register
-
tcg/tcg.c にもいろいろ書いてある
- syscallの呼び出され方(User mode emulation) – translate.c: EXCP_SYSCALLを発行 {code} - case OPC_SYSCALL: - generate_exception_end(ctx, EXCP_SYSCALL); - break; {/code} – cpu_loop.c: EXCP_SYSCALLを受け取って処理 — GPR2 = $v0 システムコール番号を入れる(4000 ~) — GPR4-7 = $a0-3, それ以降はスタックに引数を入れる
** コンパイラの改造
-
LLVM
- 汎用レジスタのうち一つを使わないようにする → $t9 (= R25) – k0, k1レジスタ(OS用)もあるがこれは割込み(interrupt/trap)時のレジスタ退避に使われるので実行中に変化してしまう – このレジスタの内容を読み出せちゃったりしないか? — とくだんCPUが対策しているわけではない
- 実装方法が全くわからん – レジスタ割当部分の改造 – IRの書き換え — MIR? HIR? LIR?
-
別の方法でやってみることにした→plan B
- 関数プロローグ・エピローグの変更 – (SPが指す)スタックにアドレスは積まなくて良くなる – jal/jr命令をいじるのではなくて直接$rpを変更すればよいのでは→やりづらいかな
** ローダの改造
- Linux
- 2つstackを置かせる
- まあfs/binfmt_elf.cを改造すればいいかな(COFFとかって使われてるのか?w)
- mm_struct構造体を拡張してstart_rastackみたいなのを置けば良さそう
- 特にbprmなどを見てあげる必要もないし、ASLRだけかけて適当に配置する – めんどくさいのでいまのところはstack+0x40000とかいう雑な方法
- brk的な感じでアドレスを持っておかないといけない – start_threadを変更
** 動かしてみる
- Buildrootでクロスコンパイル環境を構築する – http://www.ne.jp/asahi/it/life/it/embedded/buildroot/buildroot_mips.html – http://lists.busybox.net/pipermail/buildroot/2010-December/039828.html
- test.cのコンパイル
{code}
#include
int main(int argc, char **argv) {
- printf(“It works!”);
- return 0; } {/code} – $ ../buildroot/output/host/bin/mipsel-linux-gcc test.c -o test -static – もしくは(?) $ ../buildroot/output/host/bin/mips-linux-gcc test.c -o test -static — $ ../qemu-3.0.0/mips-linux-user/qemu-mips -d op test {code} OPC_JR called still working OPC_JR OP:
- ld_i32 tmp0,env,$0xffffffffffffffe4
- movi_i32 tmp1,$0x0
-
brcond_i32 tmp0,tmp1,lt,$L0
- —- 00401b74 00000000 00000000
- mov_i32 tmp2,rp
- qemu_ld_i32 tmp2,tmp2,beul,2
- mov_i32 k0,tmp2
- movi_i32 tmp2,$0x4
- add_i32 rp,rp,tmp2
- mov_i32 btarget,k0 {/code}
- 逆アセンブルしてみる
{code}
$ ../buildroot/output/host/bin/mipsel-linux-objdump -d test | rg ‘<main>’ -A 30
00400390 <main>:
- 400390: 27bdffe0 addiu sp,sp,-32
- 400394: afbf001c sw ra,28(sp)
- 400398: afbe0018 sw s8,24(sp)
- 40039c: 03a0f025 move s8,sp
- 4003a0: 3c1c0042 lui gp,0x42
- 4003a4: 279c71e0 addiu gp,gp,29152
- 4003a8: afbc0010 sw gp,16(sp)
- 4003ac: afc40020 sw a0,32(s8)
- 4003b0: afc50024 sw a1,36(s8)
- 4003b4: 3c020041 lui v0,0x41
- 4003b8: 2444cbf0 addiu a0,v0,-13328
- 4003bc: 8f828030 lw v0,-32720(gp)
- 4003c0: 0040c825 move t9,v0
- 4003c4: 0411000a bal 4003f0 <__GI_printf>
- 4003c8: 00000000 nop
- 4003cc: 8fdc0010 lw gp,16(s8)
- 4003d0: 00001025 move v0,zero
- 4003d4: 03c0e825 move sp,s8
- 4003d8: 8fbf001c lw ra,28(sp)
- 4003dc: 8fbe0018 lw s8,24(sp)
- 4003e0: 27bd0020 addiu sp,sp,32
- 4003e4: 03e00008 jr ra
- 4003e8: 00000000 nop
- 4003ec: 00000000 nop … {/code} – スタックに積まれた戻りアドレスは実際には利用しない
- Linuxカーネルをビルドしてみる – わりとすぐおわる {code} $ make ARCH=mips config (19: MIPS Malta board を選択) $ make ARCH=mips -j4 {/code}
- BuildrootでビルドしたカーネルをQEMU上で動かしてみる {code} $ make qemu_mips32r2_malta_defconfig $ make $ ../qemu-3.0.0/mips-softmmu/qemu-system-mips -M malta -kernel output/images/vmlinux -serial stdio -drive file=output/images/rootfs.ext2,format=raw -append “root=/dev/hda” -net nic,model=pcnet -net user {/code}
- カスタムしたカーネルをQEMU上で動かしてみる – 案の定バグって、直し方がわからない(ユーザ空間のプロセスは多分大丈夫だけどアセンブラレベルの処理をするときにrpがセットできていないのが原因だろうか)
- User mode emulationを使ってみる – ローダ部分の(擬似)実装を書き換え – もっと早くに知りたかった – linuxload.c: loader_exec() → load_elf_binary(), do_init_thread() – elfload.c: (do_)init_thread()でSP, PCの初期化
- アセンブラで動かしてみる {code} $ cat test.S .data msg: - .ascii “hi system call\n” len = . - msg
.text - .globl main main: - li $v0, 4004 - li $a0, 1 - la $a1, msgaddr - li $a2, len - syscall
- jr $ra
- .align 4 msgaddr:
- .long msg $ ../buildroot/output/host/bin/mips-linux-as test.S -o test.o -march mips32r2 $ ../buildroot/output/host/bin/mips-linux-ld test.o -o test {/code} -- うーん動かない -- cpu_ldl_code()で落ちているみたい これはglueマクロを使ってinclude/exec/cpu_ldst*.hで定義されている --- ptrに0が渡されているのがよくなさそう --- cpu_ld, USUFFIX = l, MEMSUFFIX = _code --- cpu_ldst_useronly_template.h(72行目) --- →ldl_p(g2h(ptr)) = ldl_be_p(p) → bswap32(ldl_he_p(p)) ptrが指すアドレスをいい感じにエンディアン調整して読み出しているみたい --- じゃあやっぱりptr=0がダメなのか --- 1回目のjrで間違った場所に飛ばされたために2回writeされている? ---- →戻り先と勘違いしてジャンプ先のアドレスを保存していた…。 -- そもそもjal = OPC_JALの実装のどこで$raが更新されているかわからない -- まあなんやかんやで解決した - uClibcを動かすにはjalrへの対応も必要だった(即値がレジスタになる以外jalと同じ)
-
これでスタックオーバーフローはできない…はずだったが実際にgetsでオーバーフローするようなコードを書くとSEGVが出た – アセンブラを見てみるとbal命令も使っている – translate.c(19608L):
- rs = register source, rd = register destination ?
- みやすい命令 https://ti.tuwien.ac.at/cps/teaching/courses/cavo/files/MIPS32-IS.pdf
- http://www.howardhuang.us/teaching/cs232/04-Functions-in-MIPS.pdf