Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。
なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。
今回は第3章。
(https://wccftech.com/review/intel-core-i7-6700k-skylake-k-cpu-review-asus-z170-pro-gaming/3/ より)
Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。
なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。
今回は第3章。
(https://wccftech.com/review/intel-core-i7-6700k-skylake-k-cpu-review-asus-z170-pro-gaming/3/ より)
Rustのライブラリを使えばElfの解析だってできる。
バイナリファイルを読み込んで、HashMapにアドレスとデータの対を格納するプログラムを作成した。 各セクション毎にデータを読み込んで一つのHashMapにバイト単位でデータを書き込む。このモデルを作成すれば、Rustで簡単な命令セットシミュレータが作れるようになるはずだ。
training/program/rust/projects/sim_mem_elf/src/main.rs
fn memory_elf (elf_obj: goblin::elf::Elf, memory: &mut std::collections::HashMap<u64, u8>, buffer: &std::vec::Vec<u8>) -> error::Result<()> { let shdr_strtab = &elf_obj.shdr_strtab; for section in &elf_obj.section_headers { println!("elf_obj.section_headers = {:#?}, file_offset = {:#x}, size = {:#x}", &shdr_strtab[section.sh_name], section.sh_offset, section.sh_size ); if section.sh_size != 0 { for idx in 0..(section.sh_size-1) { let mut offset = idx + section.sh_offset; memory.insert(section.sh_addr + idx, buffer[offset as usize]); } } } Ok(()) }
$ cargo run ~/work/rocket-chip-msyksphinz/riscv-tools/riscv-tests/benchmarks/qsort.riscv
ただしまだRustとかScalaの初心者なので、MutableとかImmutableとか、まだ分かっていないことが多い。コンパイラの結果を見ながら進めている感じ。こんなので大丈夫かな?
Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。
なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。
今回は第2章。
GPUを支える技術 ――超並列ハードウェアの快進撃[技術基礎] (WEB+DB PRESS plus)
Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。
なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。
一応、Jupyter Notebookでまとめを作っていく。画像などはコピーではなく、すべて自分で作成している。
これを最後までできるかな?頑張る。
GPUを支える技術 ――超並列ハードウェアの快進撃[技術基礎] (WEB+DB PRESS plus)
RustのライブラリであるGoblinを使って、バイナリファイルを解析のプログラムを作成している。Rustの構造はなかなか慣れないので、進めるのが大変だ。
Goblinを使った場合、Sectionを一つずつ解析して、バイナリの入っている部分を取り出していく必要がある。
for section in &elf.section_headers { println!("elf.section_headers = {:#?}, file_offset = {:#x}, size = {:#x}", &shdr_strtab[section.sh_name], section.sh_offset, section.sh_size ); for idx in 0..section.sh_size { let mut offset = idx+section.sh_offset; print!("{:02x}", buffer[offset as usize]); if idx % 4 == 3 { print!("\n"); } } }
上記のようにして、elfのバッファ(そのままelfファイルをバイナリとして配列で格納したもの)があるので、各セクションのヘッダ部分をオフセットで計算する。それがsection.sh_offset
だ。
このような実装で、とりあえず各セクションからバイナリの情報を引き出すプログラムを構築した。
cargo run ~/work/rocket-chip-msyksphinz/riscv-tools/riscv-tests/benchmarks/qsort.riscv elf.section_headers = "", file_offset = 0x0, size = 0x0 elf.section_headers = ".text.init", file_offset = 0x1000, size = 0x1c5 81400141 81410142 81420143 81430144 81440145 81450146 ... 4e7fee7f 51617300 20300000 00elf.section_headers = ".tohost", file_offset = 0x2000, size = 0x48 00000000 00000000 00000000 ... 00000000 00000000 00000000 elf.section_headers = ".text", file_offset = 0x2048, size = 0x8c0 aa871737 00001307 e7ae0145 0c4303a8 ... elf.symbol = "" 0x80005b38 elf.symbol = "" 0x80005b80 elf.symbol = "" 0x0 elf.symbol = "/tmp/cc9WJ5Mg.o" 0x0 elf.symbol = "trap_entry" 0x80000120 elf.symbol = "qsort_main.c" 0x0 elf.symbol = "verify.constprop.1" 0x80001048 elf.symbol = "syscalls.c" 0x0 elf.symbol = "vprintfmt" 0x800011ba
Rustを使うなら何を作ってみたいって、並列性をうまく利用してメニーコアのシミュレータとかを作るのが、CPUアーキテクチャの勉強にとっても、Rustの勉強にとっても良さそうだ。
でも、RustでElfファイルを扱う方法ってあるのか?いろいろと調べていると、ライブラリはあるらしい。
今回調査したのは goblin
というライブラリで、 elfファイルをParseしてくれるらしい。
いくつかこれを使ったサンプルプログラムが存在しているが、RustはVerilogやChiselと同じく、ほとんど情報が無い。少し流行っているとはいえ、やはり情報収集は大変だなあ。。。
goblinというライブラリを使うと、バイナリファイルを解析することが出来る。試しにRustでプログラムを書いてみた。
match Object::parse(&buffer)? { Object::Elf(elf) => { // println!("elf: {:#?}", &elf); for header in &elf.program_headers { println!("elf.program_headers = {:#?}", header); } let shdr_strtab = &elf.shdr_strtab; for section in &elf.section_headers { println!("elf.section_headers = {:#?}", &shdr_strtab[section.sh_name]); } let sym_strtab = &elf.strtab; for symbol in &elf.syms { println!("elf.symbol = {:#?} {:#x}", &sym_strtab[symbol.st_name], symbol.st_value); } }
情報が無さ過ぎて大変なので、githubでいくつか探すと、以下のサンプルプログラムが出てきた。
elfの情報からセクションヘッダとシンボルテーブルを読み込んでprintln()
で出力している。 symbol.st_name
や section.sh_name
はシンボルテーブルに対するインデックスに過ぎないので、配列を参照してシンボル情報を出力する。
$ cargo run ~/work/rocket-chip-msyksphinz/riscv-tools/riscv-tests/benchmarks/qsort.riscv ... elf.symbol = "_tls_data" 0x80005b80 elf.symbol = "abort" 0x800015c6 elf.symbol = "_init" 0x80001774 elf.symbol = "setStats" 0x8000154e elf.symbol = "strnlen" 0x80001850 elf.symbol = "sort" 0x80001086 elf.symbol = "_start" 0x80000000 elf.symbol = "memset" 0x8000172e elf.symbol = "main" 0x80001908 elf.symbol = "strcmp" 0x80001872 elf.symbol = "sprintf" 0x800016ac elf.symbol = "printhex" 0x80001634 elf.symbol = "_tdata_end" 0x0 elf.symbol = "_end" 0x80005b80 elf.symbol = "fromhost" 0x80001040 elf.symbol = "_tdata_begin" 0x0 elf.symbol = "tohost" 0x80001000 elf.symbol = "exit" 0x800015be elf.symbol = "tohost_exit" 0x8000159c elf.symbol = "verify_data" 0x80001b38 elf.symbol = "_tbss_end" 0x44 elf.symbol = "strlen" 0x80001838 elf.symbol = "thread_entry" 0x8000162e
MicroPythonをRISC-Vのプラットフォームで動作させたくて、調査している。
MicroPythonはQEMUの環境も用意されていて、ARMやXtensaなどの環境も用意されている。これらを参考にしながら、実装を進めていこう。
RISC-V用に用意しなければならないのは、
nlr_push()
nlr_jump()
の関数だ。これらについて日本語の資料を探したのだが見つからないので、とりあえずARM版、Xtensa版を見ながら見様見真似で進めている。
どうやら、Calling Conventionに基づいてレジスタの情報をバッファに突っ込むような作業が必要になる。
などを保管しておく必要があるが、基本的にCalling ConventionにおけるCallee側を保存して置けばよいという理解だ。
// We only need the functions here if we are on arm/thumb, and we are not // using setjmp/longjmp. // // For reference, arm/thumb callee save regs are: // r4-r11, r13=sp
なるほど、つまりRISC-Vであれば、Callee Save側のレジスタを保存すればよいのかしら。ここらへん実際にアプリがどのように動くのか、さっぱりわからん。
というわけで一生懸命アセンブリを書いた。しかも、あとでよく見てみるとCaller Save側のReturn Addressも保存しなければならないのか。
unsigned int nlr_push(nlr_buf_t *nlr) { __asm volatile ( "sw x2, 8 (x10) \n" // save regs... "sw x8, 12(x10) \n" "sw x9, 16(x10) \n" "sw x18, 20(x10) \n" "sw x19, 24(x10) \n" "sw x20, 28(x10) \n" "sw x21, 32(x10) \n" "sw x22, 36(x10) \n" "sw x23, 40(x10) \n" "sw x24, 44(x10) \n" "sw x25, 48(x10) \n" "sw x26, 52(x10) \n" "sw x27, 56(x10) \n" "sw ra, 60(x10) \n" "j nlr_push_tail \n" // do the rest in C ); return 0; // needed to silence compiler warning } ... NORETURN void nlr_jump(void *val) { nlr_buf_t **top_ptr = &MP_STATE_THREAD(nlr_top); nlr_buf_t *top = *top_ptr; if (top == NULL) { nlr_jump_fail(val); } top->ret_val = val; *top_ptr = top->prev; __asm volatile ( "lw sp, 8 (%0) \n" // restore regs... "lw s0, 12(%0) \n" "lw s1, 16(%0) \n" "lw s2, 20(%0) \n" "lw s3, 24(%0) \n" "lw s4, 28(%0) \n" "lw s5, 32(%0) \n" "lw s6, 36(%0) \n" "lw s7, 40(%0) \n" "lw s8, 44(%0) \n" "lw s9, 48(%0) \n" "lw s10, 52(%0) \n" "lw s11, 56(%0) \n" "lw ra, 60(%0) \n" "li a0, 1 \n" // return 1, non-local return "ret \n" // return : // output operands : "r"(top) // input operands : // clobbered registers ); for (;;); // needed to silence compiler warning }
これで動かしてみる。やってみるのはSpikeシミュレータでの動作だ。とりあえずこれで動作を確認したい。
やった!動作した。とりあえず方針としては合っているようだ。 次は、これをRocket Chipとか、HiFive1で動かしてみたいな。