RustでのIOストリーム処理
Go言語のio.Reader, io.Writer interfaceを使ったio.Cooyのような処理をRustで行うためには、std::io::Read traitとstd::io::Write traitを使って各ストリームを扱えばいい。
標準入力から標準出力へデータをコピーするサンプルコードを次に書く。
copy関数にstd::io::StdinのRead traitとstd::io::StdoutのWrite traitを渡して、その間でデータを流している。
use std::io::{self, Read, Write}; fn copy(reader: &mut Read, writer: &mut Write) { const BUFFER_SIZE: usize = 32 * 1024; let mut buf = [0u8; BUFFER_SIZE]; while let Ok(n) = reader.read(&mut buf) { if n == 0 { break; } let _ = writer.write(&buf[..n]); } } fn main() { let r = io::stdin(); let mut reader = r.lock(); let w = io::stdout(); let mut writer = w.lock(); copy(&mut reader, &mut writer); }
なお、std::io::copy関数が標準ライブラリに含まれているため、前述のように自前でcopy関数を実装する必要はない。(書いてから気づいた)
use std::io; fn main() { let r = io::stdin(); let mut reader = r.lock(); let w = io::stdout(); let mut writer = w.lock(); let _ = io::copy(&mut reader, &mut writer); }
標準ライブラリのどの型にRead、Write traitが実装されているのかは以下を参照すればいい。
参考
Cortex-MターゲットをOpenOCD + GDBでデバッグする(gdb-dashboard or VSCode)
OpenOCDとGDBを使ったarmのCortex-Mターゲットのデバッグ方法についてまとめておく。
個人的に、CUIベースでのデバッグにはgdb-dashboard、GUIベースでのデバッグにはVSCodeを使うのが好みなため、この2つのUIを介してデバッグする方法についてそれぞれ書くことにする。
なお、動作確認環境はArchLinux。
※OpenOCDでのFWの書き込み方法については、"OpenOCD + ST-LinkでFirmware書き込み"も参照。
共通で必要なもの
gdb-dashboardを使う場合も、VSCodeを使う場合も以下がそれぞれ必要になる。 ([]内は動作確認時のバージョン情報)
- OpenOCD [0.10.0]
- arm-none-eabi-gdb [8.1]
- ターゲットに書き込むelfファイル
- Cortex-Mターゲット(この記事ではSTMicroelectronicsのNUCLEO-F401REを使用)
- ST-Linkなどのデバッガ(この記事ではNUCLEO-F401REにオンボードで載っているのでそれを使う)
gdb-dashboard
cyrus-and/gdb-dashboard: Modular visual interface for GDB in Python
CUIのGDBをグラフィカルに表示してくれるPyhtonスクリプト。 CUIからちょっとデバッグしたい時に便利。
インストール
以下の方法でインストールできる。(gdb-dashboardのREADMEではwget
を使っているが、更新に追従できるようにここではgit clone
を使用する)
$ git clone https://github.com/cyrus-and/gdb-dashboard.git $ cp gdb-dashboard/.gdbinit ~/.gdbinit
デバッグ手順
まず、OpenOCDを起動する。
$ openocd -f board/st_nucleo_f4.cfg
-f board/st_nucleo_f4.cfg
の部分はターゲットに応じて適宜変更する。(ArchLinuxの場合は、/usr/share/openocd/scriptsにOpenOCDの設定ファイルが配置されている)
別の端末からGDBを起動し、以下のコマンドを順次打ち込む。
$ arm-none-eabi-gdb ${target_elf_path} >>> target remote localhost:3333 >>> interrupt >>> monitor reset halt >>> load
これで準備が整った。 ここからブレイクポイントを設定するなり、ステップ実行するなりしてデバッグを行う。
以下は、main.rsというファイルの7行目にブレイクポイントを設定してプログラムを実行する例となる。
>>> tbreak main.rs:7 >>> continue
VSCode + Cortex-Debug Extension
Visual Studio Code - Code Editing. Redefined
- VSCode [1.19.0]
- Cortex-Debug Extension [0.1.14]
VSCodeからOpenOCD + GDBでCortex-Mターゲットをデバッグする場合には、Cortex-Debug extensionをインストールするのがおすすめ。 (後述するが、自前でGDBの設定をするとOpenOCDの手動で起動する必要があるが、このextensionを使うとOpenOCDを自動で起動してくれるようになる)
※gdb-dashboardをインストールして~/.gdbinit
が有効になっているとVSCodeのデバッグがうまく動かなくなるので、~/.gdbinit.bk
などにリネームして無効にする。
インストール
VSCodeのデバッグ構成ファイルを用意する
VSCodeでデバッグするためには launch.json というデバッグ構成を記載するための設定ファイルを書く必要がある。
Cortex-Debug extensionを使う際には、以下のように launch.json を書く(launch.json の修正までの手順は"デバッグ | 非公式 - Visual Studio Code Docs"の記事を参考に)
launch.json
{ "version": "0.2.0", "configurations": [ { "type": "cortex-debug", "servertype": "openocd", "request": "launch", "name": "OpenOCD-Debug", "executable": "${ターゲットELFまでのPATH}", "configFiles": [ "board/st_nucleo_f4.cfg" ], "cwd": "${workspaceRoot}", "gdbpath": "arm-none-eabi-gdb", } ] }
※なお、Cortex-Debug extensionはOpenOCDだけではなく、J-Linkにも対応している模様。
デバッグ手順
VSCode (Cortex-Debug Extension無し)
蛇足になるが、VSCodeでCortex-Debug extensionを使わない場合のやり方も残しておく。
※こちらも同様に、gdb-dashboardをインストールして~/.gdbinit
が有効になっているとVSCodeのデバッグがうまく動かなくなるので、~/.gdbinit.bk
などにリネームして無効にする。
VSCodeのデバッグ構成ファイルを用意する
以下の launch.json を用意する。
launch.json
{ "version": "0.2.0", "configurations": [ { "name": "OpenOCD Debug", "type": "gdb", "request": "attach", "executable": "${ターゲットELFまでのPATH}", "remote": true, "target": ":3333", "cwd": "${workspaceRoot}", "gdbpath": "arm-none-eabi-gdb", "autorun": [ "interrupt", "monitor reset halt", "load", ], } ] }
デバッグ手順
まず、OpenOCDを起動する(-f board/st_nucleo_f4.cfg
の部分はターゲットに応じて適宜変更する)
$ openocd -f board/st_nucleo_f4.cfg
OpenOCDが起動できたら、VSCode上でF5キーを押してデバッグを開始する。
参考
systemd環境でのコアダンプ解析
systemd環境下においては、従来(systemd無しの環境)のコアダンプ解析とは勝手が違ってくる。
コアダンプを有効にする
ulimit
コマンドでcore file size
が0になっていないことを確認する。
$ ulimit -a -t: cpu time (seconds) unlimited -f: file size (blocks) unlimited -d: data seg size (kbytes) unlimited -s: stack size (kbytes) 8192 -c: core file size (blocks) unlimited -m: resident set size (kbytes) unlimited -u: processes 63401 -n: file descriptors 1024 -l: locked-in-memory size (kbytes) unlimited -v: address space (kbytes) unlimited -x: file locks unlimited -i: pending signals 63401 -q: bytes in POSIX msg queues 819200 -e: max nice 0 -r: max rt priority 99 -N 15: unlimited
もしも0になっていたら、次のようにしてコアダンプを有効にする。
$ ulimit -c unlimited
ここまでは従来の方法と同じ。
コアダンプの出力場所
コアダンプの出力場所は /proc/sys/kernel/core_pattern を参照するとわかる。 systemdでは、
$ cat /proc/sys/kernel/core_pattern |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e
となっており、 /var/lib/systemd/coredump/ 以下にLZ4で圧縮されて保存される。
保存されるファイル名は core.user-app.1000.adc138b171b140d2bb0a20e628857f04.22015.1515249501000000.lz4
といったものになる。
コアダンプの確認方法
従来の方法だと、次のコマンドでコアダンプの解析が行える。
$ gdb ./user-app core
systemd環境下で前述の方法と同様に行うと、コアダンプの認識に失敗する。
$ gdb ./user-app /var/lib/systemd/coredump/core.user-app.1000.adc138b171b140d2bb0a20e628857f04.22015.1515249501000000.lz4 GNU gdb (GDB) 8.0.1 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-pc-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./user-app...done. "/var/lib/systemd/coredump/core.user-app.1000.adc138b171b140d2bb0a20e628857f04.22015.1515249501000000.lz4" is not a core dump: File format not recognized
これは /var/lib/systemd/coredump/ 以下のコアダンプファイルがLZ4で圧縮されて保存されているためで、LZ4を展開してやればgdbから認識できるようになる。
$ lz4 -d /var/lib/systemd/coredump/core.user-app.1000.adc138b171b140d2bb0a20e628857f04.22015.1515249501000000.lz4 core $ gdb ./user-app core
しかし、毎回コアダンプを展開するのは手間だ。
そこでsystemdでは coredumpctl
というコマンドが用意されている。
coredumpctl の使い方
保存されているコアダンプ一覧を確認する。
$ coredumpctl list TIME PID UID GID SIG COREFILE EXE Fri 2017-11-24 11:23:30 JST 1242 0 0 11 missing /usr/lib/udisks2/udisksd Sun 2017-11-26 00:12:29 JST 1959 1000 1000 5 missing /usr/bin/ibus-daemon Sun 2017-11-26 00:16:42 JST 10271 1000 1000 5 missing /usr/bin/ibus-daemon Fri 2017-12-15 18:42:18 JST 5242 1000 1000 11 missing /usr/share/franz/franz Tue 2017-12-19 22:49:53 JST 2933 1000 1000 11 missing /opt/dropbox/dropbox Thu 2017-12-21 02:14:13 JST 1723 1000 1000 11 missing /usr/share/franz/franz Fri 2017-12-22 02:04:45 JST 9251 1000 1000 11 missing /usr/lib/electron/electron Sun 2018-01-07 01:25:33 JST 6231 1000 1000 8 present /home/ryo/code/workspace/csfml/user-app
gdbを使ってコアダンプを解析する。例えば、前述のリストから /home/ryo/code/workspace/csfml/user-app の解析をする場合には、PID 6231を指定する。
$ coredumpctl gdb 6231
参考
OpenOCD + ST-LinkでFirmware書き込み
OpenOCDを使ってSTM32F401 Nucleo-64にST-Link経由でFirmwareを書き込む。
OpenOCDとST-Linkドライバのインストール
Arch Linuxなら以下のコマンドでインストールする。
$ pacman -S openocd stlink
Firmwareを書き込む
OpenOCDで指定できるinterface, target, boardの設定ファイルは/usr/share/openocd/scripts
に配置されている。
OpenOCDで接続する
STM32F401 Nucleo-64 boardのUSB接続の場合
$ openocd -f board/st_nucleo_f4.cfg
もしくは、
$ openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg
STM32F401 Chip + 中華ST-Linkの場合
Nucleoボードを使っているのなら中華ST-Linkを使う必要はないが、試した結果を書き残しておく。
$ openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg
なお、Nucleoボード上のSWDコネクタ(CN4)は以下のピンアサインとなっている。
Pin | CN4 |
---|---|
1 | VDD_TARGET |
2 | SWCLK |
3 | GND |
4 | SWDIO |
5 | NRST |
6 | SWO |
※中華ST-Linkでは、一度ST-LinkのFirmwareをアップデートした後に書き込みに成功するようになった。 (書き込みに成功している中華ST-LinkのFW versionはV2J28S7)
telnetで書き込む
上記手順でOpenOCDで接続後、別のterminalからtelnet
を起動する。
$ telnet localhost 4444
telnet
上で以下のコマンドを実行することでFirmwareを書き込む。
> reset halt > flash write_image erase ${elf_file_absolute_path}
telnet無しでFirmwareを書き込む
上記の方法だと、OpenOCDとは別にtelnet
を起動する必要があり、若干煩わしい。
そこで、Firmwareを書き込む手順を記載したOpenOCDのconfigファイルを用意し、OpenOCDだけで書き込みを行えるようにする。
任意の名前で以下の内容のconfigファイルを作成する。 (今回はopenocd.cfgという名前にした)
telnet_port 4444 gdb_port 3333 source [find board/st_nucleo_f4.cfg] init proc flash_elf {elf_file} { reset halt flash write_image erase $elf_file verify_image $elf_file echo "flash write_image ($elf_file) complete" reset exit }
configファイルを作成したら、以下のコマンドで書き込みを実行する。
$ openocd -f openocd.cfg -c "flash_elf ${elf_file_relative_path}"
※OpenOCDのproc
の名前はOpenOCDの既存コマンド名にあるものを使うとエラーになるようだ。
(最初、proc flash {elf_file}
としていたところ、以下のエラーが出て小一時間ほどハマってしまった)
Open On-Chip Debugger 0.10.0 Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD adapter speed: 2000 kHz adapter_nsrst_delay: 100 none separate srst_only separate srst_nogate srst_open_drain connect_deassert_srst Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : clock speed 1800 kHz Info : STLINK v2 JTAG v28 API v2 SWIM v18 VID 0x0483 PID 0x374B Info : using stlink api v2 Info : Target voltage: 3.256353 Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints flash Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : Unable to match requested speed 2000 kHz, using 1800 kHz adapter speed: 1800 kHz target halted due to debug-request, current mode: Thread xPSR: 0x61000000 pc: 0x1fff1048 msp: 0x20002e40 openocd.cfg:13: Error: wrong # args: should be "flash elf_file" in procedure 'flash' at file "openocd.cfg", line 13
ST-LinkのFirmwareアップデート
こちらで公開されているツールを使ってアップデートする。
参考
Rustでクロスコンパイル
x86_64-unknown-linux-gnu
のホスト環境上で、CHIP向けのバイナリをRustでクロスコンパイルすることを目的とする。
概要
rustup target add ${target_triple}
でターゲットのツールチェインを追加- ターゲットのリンカをインストール
- Cargoのconfigファイルにターゲット用のリンカ設定を記述
cargo build --target ${target_triple}
でクロスコンパイル実行
※Rustのツールチェイン管理を行ってくれるツールのrustup
はこちらを参考にインストール済みとする。
環境
ホストのシステム情報。
$ uname -a Linux hostname 4.13.7-1-ARCH #1 SMP PREEMPT Sat Oct 14 20:13:26 CEST 2017 x86_64 GNU/Linux $ rustup show Default host: x86_64-unknown-linux-gnu 1.21.0-x86_64-unknown-linux-gnu (default) rustc 1.21.0 (3b72af97e 2017-10-09)
ターゲットであるCHIPのシステム情報。
chip@chip:~$ uname -a Linux chip 4.4.13-ntc-mlc #1 SMP Thu Nov 3 01:28:54 UTC 2016 armv7l GNU/Linux
ターゲットのツールチェインを追加
rustup target list
で、Rustがサポートするターゲットのリストを確認する。
$ rustup target list | pr -w 90 --columns=2 aarch64-apple-ios mips-unknown-linux-gnu aarch64-linux-android mips-unknown-linux-musl aarch64-unknown-fuchsia mips64-unknown-linux-gnuabi64 aarch64-unknown-linux-gnu mips64el-unknown-linux-gnuabi64 arm-linux-androideabi mipsel-unknown-linux-gnu arm-unknown-linux-gnueabi mipsel-unknown-linux-musl arm-unknown-linux-gnueabihf powerpc-unknown-linux-gnu arm-unknown-linux-musleabi powerpc64-unknown-linux-gnu arm-unknown-linux-musleabihf powerpc64le-unknown-linux-gnu armv7-apple-ios s390x-unknown-linux-gnu armv7-linux-androideabi sparc64-unknown-linux-gnu armv7-unknown-linux-gnueabihf wasm32-unknown-emscripten armv7-unknown-linux-musleabihf x86_64-apple-darwin armv7s-apple-ios x86_64-apple-ios asmjs-unknown-emscripten x86_64-linux-android i386-apple-ios x86_64-pc-windows-gnu i586-pc-windows-msvc x86_64-pc-windows-msvc i586-unknown-linux-gnu x86_64-rumprun-netbsd i686-apple-darwin x86_64-unknown-freebsd i686-linux-android x86_64-unknown-fuchsia i686-pc-windows-gnu x86_64-unknown-linux-gnu (default) i686-pc-windows-msvc x86_64-unknown-linux-musl i686-unknown-freebsd x86_64-unknown-netbsd i686-unknown-linux-gnu x86_64-unknown-redox i686-unknown-linux-musl
armv7-unknown-linux-gnueabihf
が見つかり、RustはCHIPのターゲットをサポートしていることを確認できたため、以下のコマンドでCHIP用のツールチェインを追加する。
$ rustup target add armv7-unknown-linux-gnueabihf
rustup target add
後にrustup show
やrustup target list
を実行してRustのツールチェインの状態を確認すると、armv7-unknown-linux-gnueabihf
が(installed)
になっているのがわかる。
$ rustup show Default host: x86_64-unknown-linux-gnu installed targets for active toolchain -------------------------------------- armv7-unknown-linux-gnueabihf x86_64-unknown-linux-gnu active toolchain ---------------- 1.21.0-x86_64-unknown-linux-gnu (default) rustc 1.21.0 (3b72af97e 2017-10-09) $ rustup target list | pr -w 90 --columns=2 aarch64-apple-ios i686-unknown-linux-musl aarch64-linux-android mips-unknown-linux-gnu aarch64-unknown-fuchsia mips-unknown-linux-musl aarch64-unknown-linux-gnu mips64-unknown-linux-gnuabi64 arm-linux-androideabi mips64el-unknown-linux-gnuabi64 arm-unknown-linux-gnueabi mipsel-unknown-linux-gnu arm-unknown-linux-gnueabihf mipsel-unknown-linux-musl arm-unknown-linux-musleabi powerpc-unknown-linux-gnu arm-unknown-linux-musleabihf powerpc64-unknown-linux-gnu armv7-apple-ios powerpc64le-unknown-linux-gnu armv7-linux-androideabi s390x-unknown-linux-gnu armv7-unknown-linux-gnueabihf (installed) sparc64-unknown-linux-gnu armv7-unknown-linux-musleabihf wasm32-unknown-emscripten armv7s-apple-ios x86_64-apple-darwin asmjs-unknown-emscripten x86_64-apple-ios i386-apple-ios x86_64-linux-android i586-pc-windows-msvc x86_64-pc-windows-gnu i586-unknown-linux-gnu x86_64-pc-windows-msvc i686-apple-darwin x86_64-rumprun-netbsd i686-linux-android x86_64-unknown-freebsd i686-pc-windows-gnu x86_64-unknown-fuchsia i686-pc-windows-msvc x86_64-unknown-linux-gnu (default) i686-unknown-freebsd x86_64-unknown-linux-musl i686-unknown-linux-gnu x86_64-unknown-netbsd
ターゲットのリンカをインストール
Rustのツールチェインではターゲットのリンカはサポートしていないため、別途用意する。
$ apt-get install gcc-arm-linux-gnueabihf
Cargoのconfigにターゲットのリンカ設定を記述
クロスコンパイルする対象とするプロジェクトを生成する。
$ cargo new --bin hello
Cargoが生成したスケルトンコードのままで実行すると、Hello, world!
が標準出力に出力される。
$ cargo run Compiling hello v0.1.0 (file:///host_shared/hello) Finished dev [unoptimized + debuginfo] target(s) in 0.44 secs Running `target/debug/hello` Hello, world!
これをそのままターゲット(CHIP)上で動作するようにするために、プロジェクトのconfigファイルを作成し、ターゲットのリンカの設定を記述する。
$ mkdir .cargo $ cat > .cargo/config << EOF > [target.armv7-unknown-linux-gnueabihf] > linker = "arm-linux-gnueabihf-gcc" > EOF
クロスコンパイル実行
$ cargo build --target armv7-unknown-linux-gnueabihf
ビルドされたバイナリの情報を確認すると、ターゲット(CHIP)向けのバイナリになっていることがわかる。
$ file target/armv7-unknown-linux-gnueabihf/debug/hello target/armv7-unknown-linux-gnueabihf/debug/hello: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=70dfb01aa67d797fc64005ac8cff1dba94a48a34, not stripped $ readelf -A target/armv7-unknown-linux-gnueabihf/debug/hello Attribute Section: aeabi File Attributes Tag_CPU_name: "7-A" Tag_CPU_arch: v7 Tag_CPU_arch_profile: Application Tag_ARM_ISA_use: Yes Tag_THUMB_ISA_use: Thumb-2 Tag_FP_arch: VFPv3-D16 Tag_ABI_PCS_GOT_use: GOT-indirect Tag_ABI_PCS_wchar_t: 4 Tag_ABI_FP_rounding: Needed Tag_ABI_FP_denormal: Needed Tag_ABI_FP_exceptions: Needed Tag_ABI_FP_number_model: IEEE 754 Tag_ABI_align_needed: 8-byte Tag_ABI_enum_size: int Tag_ABI_VFP_args: VFP registers Tag_CPU_unaligned_access: v6 Tag_ABI_FP_16bit_format: IEEE 754
クロスコンパイルしたバイナリをターゲット上で実行
chip@chip:~$ ./hello Hello, world!
ターゲット(CHIP)上で実行できることが確認できた。
参考
Rustでのクロスコンパイルに必要な情報が、以下のページに非常にわかりやすく説明されている。
japaric/rust-cross: Everything you need to know about cross compiling Rust programs!