Weitere ähnliche Inhalte
Ähnlich wie C・C++用のコードカバレッジツールを自作してみた話 (20)
C・C++用のコードカバレッジツールを自作してみた話
- 5. Why code coverage tool?
・商用のカバレッジツールや開発環境は高い
・Linuxだとgcov使えるけど、マイコンでは使えない
■問題
組込ソフト開発でも気軽にテストを自動化したり、カバレッジ
取りたい
- 11. Automate debug operation ~GDBの自動化~
4607 11.3 7.1 52927752 284968 ? Sl 12:05 1:21 /usr/share/code/code --type=renderer --disable-color-correct-rendering --field-trial-handle
4624 1.6 3.7 38053352 148960 ? Sl 12:05 0:11 /usr/share/code/code --ms-enable-electron-run-as-node --inspect-port=0 /usr/share/code/reso
4625 0.1 1.6 38035456 65348 ? Sl 12:05 0:00 /usr/share/code/code --ms-enable-electron-run-as-node /usr/share/code/resources/app/out/boo
4696 1.0 0.8 1177412 35284 ? Sl 12:05 0:07 /home/simotin13/.vscode/extensions/ms-vscode.cpptools-1.11.4/bin/cpptools
4729 0.5 2.1 37992972 86564 ? Sl 12:05 0:03 /usr/share/code/code --ms-enable-electron-run-as-node /usr/share/code/resource4696 {9A295B6
5316 6.1 1.5 3525864 61696 ? Sl 12:10 0:24 /home/simotin13/.vscode/extensions/ms-vscode.cpptools-1.11.4/debugAdapters/bin/OpenDebugAD7
5331 0.0 0.0 2616 596 pts/1 S+ 12:10 0:00 /bin/sh /tmp/Microsoft-MIEngine-Cmd-qsxfzbaz.lrg
5333 0.0 1.0 60612 43264 pts/1 S 12:10 0:00 /usr/bin/gdb --interpreter=mi --tty=/dev/pts/1
s/app/extensi
5160 0.1 0.3 4801612 13956 ? Sl 12:09 0:00 /home/simotin13/.vscode/extensions/ms-vscode.cpptools-1.11.4/bin/cpptools-srv 5338 0.0 0.0
2364 644 ? ts 12:10 0:00 /home/simotin13/tmp/main
gdbの-i=mi オプションはgdbのコマンド入出力をマシンフレンドリー
にしてくれる。EclipseやVSCodeでも使われている。
■vscodeでデバッグしているときの例
- 12. Automate debug operation ~GDBの自動化~
Breakpoint 1, main (argc=21845, argv=0x0) at main.c:7
7 {
通常起動(-i=mi なし)
~"¥n"
~"Breakpoint 1, main (argc=21845, argv=0x0) at main.c:7¥n"
~"7¥t{¥n"
*stopped,reason="breakpoint-
hit",disp="keep",bkptno="1",frame={addr="0x0000555555555189",func="main",args=[{name="argc",value="21845"},{name
="argv",value="0x0"}],file="main.c",fullname="/home/simotin13/examples/c/function_call/main.c",line="7",arch="i3
86:x86-64"},thread-id="1",stopped-threads="all",core="9"
-i=mi あり
miオプション有効時のレスポンスについて
ブレークポイントで停止したときの例
- 13. Automate debug operation ~GDBの自動化~
~"¥n"
~"Breakpoint 1, main (argc=21845, argv=0x0) at main.c:7¥n"
~"7¥t{¥n"
*stopped,reason="breakpoint-
hit",disp="keep",bkptno="1",frame={addr="0x0000555555555189",func="main",a
rgs=[{name="argc",value="21845"},{name="argv",value="0x0"}],file="main.c",
fullname="/home/simotin13/examples/c/function_call/main.c",line="7",arch="
i386:x86-64"},thread-id="1",stopped-threads="all",core="9"
レスポンスの特徴
・先頭1文字で応答の種類(同期・非同期・通知...etc)を表す
・JSONっぽいデータ構造。でも微妙に違う。
・値は全てダブルクォーテーションで囲まれている。
- 16. How to measure a code coverage?
関数のカバレッジ率 =
関数内の通過した行数
関数全体の行数
1:int func(int a, int b)
2:{
3: return a+b;
4:}
- 17. How to measure a code coverage?
~"¥n"
~"Breakpoint 1, main (argc=21845, argv=0x0) at main.c:7¥n"
~"7¥t{¥n"
*stopped,reason="breakpoint-
hit",disp="keep",bkptno="1",frame={addr="0x0000555555555189",func="main",a
rgs=[{name="argc",value="21845"},{name="argv",value="0x0"}],file="main.c",
fullname="/home/simotin13/examples/c/function_call/main.c",line="7",arch="
i386:x86-64"},thread-id="1",stopped-threads="all",core="9"
gdbのレスポンスから「通過した行やアドレス」は分かる。
関数のカバレッジ率 =
関数内の通過した行数
関数全体の行数
- 18. How to measure a code coverage?
よく考えてみたら「関数が何行あるか?」
を数えるのは簡単ではない
1:int func(int a, int b)
2:{
3:// コメントがあるよ
4:/* ここもコメントだよ */
5:/* #ifもあるよ。どっちを通るか分かるかな? */
6:#if HOGE
7: return a+b;
8:#else
9: return 0;
10:#endif
11:}
- 19. How to measure a code coverage?
1:int func(int a, int b)
2:{
3:// コメントがあるよ
4:/* ここもコメントだよ */
5:/* #ifもあるよ。どっちを通るか分かるかな? */
6:#if HOGE
7: return a+b;
8:#else
9: return 0;
10:#endif
11:}
関数のカバレッジ率 =
関数内の通過した行数
関数内の実行可能な行数
- 20. How to measure a code coverage?
「関数内の実行可能な行数」
はどうやって取得することができるのか?
もしかして、コンパイラとか書かないとだめ?
- 21. How to measure a code coverage?
DWARFというデバッグ情報に
行番号の情報が含まれている
という噂だよ
- 22. Reading DWARF sections
・DWARFってデバッグに関する情報が入っているあれでしょ(知らんけど…)
・DWARFを読みたくなったときどこから読めばいいのか?
・DWARFの仕様書には「Getting started」的な説明がない
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
........................................................................................
........................................................................................
[28] .debug_aranges PROGBITS 0000000000000000 00303a 000030 00 0 0 1
[29] .debug_info PROGBITS 0000000000000000 00306a 000339 00 0 0 1
[30] .debug_abbrev PROGBITS 0000000000000000 0033a3 0000f2 00 0 0 1
[31] .debug_line PROGBITS 0000000000000000 003495 00011b 00 0 0 1
[32] .debug_str PROGBITS 0000000000000000 0035b0 0002a0 01 MS 0 0 1
- 23. Reading DWARF sections
・デバッグ情報の歩き方 @mhiramat
https://qiita.com/mhiramat/items/8df17f5113434e93ff0c
・DWARFファイルフォーマット@koinec
https://ja.osdn.net/projects/drdeamon64/wiki/DWARF%E3%83%95%E3%82%A1%E3%
82%A4%E3%83%AB%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E
3%83%88
■日本語の有力な情報
- 26. Reading DWARF sections
■独断と偏見によるDWARFセクションの概要説明
セクション名 難易度 内容
.debug_arranges .debug_infoで出てくるDIEのサイズに関する情報が入っている。とりあえず
は読まなくても何とかなる。
.debug_str ファイル名や関数名など、デバッグ情報で参照される文字列の情報が
0終端文字列で入っている。読むのは簡単。
.debug_abbrev TagとAttributeからなる略語表(abbreviation table)が入っている。
いわばデバッグ情報の「構造体」のテーブル。
Tag=構造体名, Attribute=メンバー変数。
※実際はTag,Attribute共にID(数値)で表現される。
.debug_line アドレスと行番号に関する情報が入っている。
例). 0x1234→ main.cの5行目
簡易的な状態機械を使って行番号やアドレスを計算するための「命令」が
入っている。計算には意味不明な式が登場する。
.debug_info .debug_abbrevで登場する「構造体」IDが入っており、その「構造体」の定義
に従って読んでいく。読むとプログラムの色んな事が分かる。
可変長のデータ構造なので読み飛ばしができない。
よく分からないDIEが出てくると詰む。
- 27. Reading DWARF sections
■独断と偏見によるDWARFの解説 .debug_lineについて
ファイルのインデックス 行番号 アドレス ブレークポイントをおけ
るか(is_stmt)
0 0 0x00000000 FALSE
インデックス ファイル名
1 main.c
2 calc.c
3 hoge.c
.debug_lineはファイル名・行番号を表現するための簡易的な状態機械に対
する命令が羅列されている。
■状態機械
■ファイル名のテーブル
- 28. Reading DWARF sections
■独断と偏見によるDWARFの解説 .debug_lineについて
ファイルの
インデックス
行番号 アドレス ブレークポイント
をおけるか(is_stmt)
0 0 0x00000000 FALSE
インデックス ファイル名
1 main.c
2 calc.c
3 hoge.c
命令に従って状態機械の各レジスタの値を更新し、行番号の情報を表現す
る
■状態機械
■ファイル名のテーブル
■命令の例
1.ファイルのインデックスに2をセット
2.行番号に1を加算
3.アドレスに0x1000をセット
4.is_stmtをTRUEにセット
5.行番号情報を確定
6.行番号に5を加算
7.アドレスに4を加算
8.行番号を確定
- 29. Reading DWARF sections
■独断と偏見によるDWARFの解説 .debug_lineについて
ファイルの
インデックス
行番号 アドレス ブレークポイント
をおけるか(is_stmt)
2 1 0x00001000 TRUE
インデックス ファイル名
1 main.c
2 calc.c
3 hoge.c
命令に従って状態機械の各レジスタの値を更新し、行番号の情報を表現す
る
■状態機械
■ファイル名のテーブル
■命令の例
1.ファイルのインデックスに2をセット
2.行番号に1を加算
3.アドレスに0x1000をセット
4.is_stmtをTRUEにセット
5.行番号情報を確定 ←いまここ
6.行番号に5を加算
7.アドレスに4を加算
8.行番号を確定
- 30. Reading DWARF sections
.debug_line を読み終わると、
・アドレスに対応するソースファイルのパス
・アドレスに対応するソースファイルの行番号
・ソースファイルの各行が実行可能(is_stmt)かどうか
が分かる
1:int func(int a, int b)
2:{
3:
4: // 足し算を行う関数
5: // aにbを加えた値を返す
6: return a+b;
7:}
filepath: calc.c
1:address 0x1000
2:not stmt
3:not stmt
4:not stmt
5:not stmt
6:address 0x1004
7:address 0x1008
- 31. Reading DWARF sections
1:int func(int a, int b)
2:{
3:
4: // 足し算を行う関数
5: // aにbを加えた値を返す
6: return a+b;
7:}
filepath: src/lib/calc.c
1:address 0x1000
2:not stmt
3:not stmt
4:not stmt
5:not stmt
6:address 0x1004
7:address 0x1008
関数のカバレッジ率 =
関数内の通過した行数
関数内の実行可能な行数
この対応表からカバレッジ率の算出に必要な「関数内の実行可能な
行数」が分かる
- 33. Appply to Embedded software
gdb command/response
JTAG/SWD
Remote
Serial
Protocol
gdb
server
gdb
client
covme target
board
vender
protocol
gdbを使うので組込ソフトの開発とも親和性が高い
→商用開発環境でもデバッガはgdbとDWARFが使われていることが多い。
gdbが使えるならターゲットのCPUやOSを問わずにカバレッジがとれる(はず)
JTAG/ICE
debugger
- 34. Appply to Embedded software
gdb のリモートデバッグ機能を使うことで同じ仕組み
を使ってカバレッジが取れる。
gdbクライアント:gdb-multiarch
gdbサーバ:jlinkのGDBサーバ
ARMマイコン(nucleo F446RE)のターゲットボード上
で動くプログラムのカバレッジを取れるよう修正してみた。
■ARMで試してみた
- 35. Appply to Embedded software
・プログラムによってはDWARFを読むところでバグがいっぱい出た
→想定していなかったDIEが登場する
・カバレッジを取る仕組み自体には問題なさそう
・バグを直さないと ←いまここ
■ARMで試してみた結果
- 37. References ~参考文献~
■GDB
・Rubyist Magazine mruby 用デバッガ 「nomitory」の作り方
https://magazine.rubyist.net/articles/0050/0050-nomitory.html
・GDB/MI インターフェイスについて
https://www.asahi-net.or.jp/~wg5k-ickw/html/online/gdb-5.0/gdb-ja_20.html
■DWARF
・The DWARF Debugging Standard
https://dwarfstd.org/
・デバッグ情報の歩き方 @mhiramat
https://qiita.com/mhiramat/items/8df17f5113434e93ff0c
・DWARFファイルフォーマット@koinec
https://ja.osdn.net/projects/drdeamon64/wiki/DWARF%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83
%83%E3%83%88