Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
C/C++用のコードカバレッジツールを自作してみた話
Search
simotin13
August 13, 2024
0
17
C/C++用のコードカバレッジツールを自作してみた話
Kernel/VM探検隊online part5で発表させて頂いたときの資料です。
simotin13
August 13, 2024
Tweet
Share
More Decks by simotin13
See All by simotin13
マイコン向けのただのリンカを自作してみた話
simotin13
0
29
Pinでコードカバレッジツールを自作してみた話
simotin13
0
1.2k
Featured
See All Featured
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
15k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
600
YesSQL, Process and Tooling at Scale
rocio
173
14k
Code Reviewing Like a Champion
maltzj
525
40k
We Have a Design System, Now What?
morganepeng
53
7.8k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
127
53k
Visualization
eitanlees
148
16k
Rails Girls Zürich Keynote
gr2m
95
14k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
45
2.5k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
358
30k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
34
6.1k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.2k
Transcript
C/C++用のコードカバレッジツールを 自作してみた話 @simotin13
Who am I? @simotin13 Hiroyuki Miyazaki ・関西で主に組込系のコード書いてます。 ・組込,Ruby,低レイヤが好きです。
Abstract C/C++のプログラム用に、カバレッジツールを書いてみま した。 ▪covme https://github.com/simotin13/covme 今のところ、完成度的にはまだまだな点はありますが、X86_64/Linuxで動く実行モ ジュールとサンプルの実行用スクリプトなどを公開しています。 go言語で書いています。
Abstract # カバレッジを取りたいモジュール (-gビルド必須)を指定して実行。 # モジュールが終了するとHTMLで結果を出力します。 $gcc main.c calc.c -g
$./covme a.out
Why code coverage tool? ・商用のカバレッジツールや開発環境は高い ・Linuxだとgcov使えるけど、マイコンでは使えない ▪問題 組込ソフト開発でも気軽にテストを自動化したり、カバレッジ 取りたい
Inspired by PHPUnit PHPのコードを書いていて、PHPUnitというテストフレームワー クを使う機会がありました。
Inspired by PHPUnit ところで、PHPUnitはどうやってカバレッジをとっている のか?
Inspired by PHPUnit PHPUnitを使うときには、php.iniにXDebugの設定を書く必要 がある。 そうか、デバッガを使ってカバレッジ取っているんだろう な... 何となくPHPUnitがやっていることは察しがついた。
Inspired by PHPUnit 伝統的日本企業ではExcelにソースコードを貼り付け、 デバッガでステップ実行した結果を記録 し、ユニットテストを実施します。
Inspired by PHPUnit PHPがXDebug使ってカバ レッジを取れるなら、 C/C++でもデバッガを使っ て同じことができるので は? linux/gdbで試してみよう。
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でデバッグしているときの例
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オプション有効時のレスポンスについて ブレークポイントで停止したときの例
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っぽいデータ構造。でも微妙に違う。 ・値は全てダブルクォーテーションで囲まれている。
Automate debug operation ~GDBの自動化~ ・gdbのプロセスをforkして、コマンドの送信とレスポンスの解析 ができればデバッガの操作の自動化は完了
How to measure a code coverage? そもそもカバレッジってどう測るのか?
How to measure a code coverage? 関数のカバレッジ率 = 関数内の通過した行数 関数全体の行数
1:int func(int a, int b) 2:{ 3: return a+b; 4:}
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のレスポンスから「通過した行やアドレス」は分かる。 関数のカバレッジ率 = 関数内の通過した行数 関数全体の行数
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:}
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:} 関数のカバレッジ率 = 関数内の通過した行数 関数内の実行可能な行数
How to measure a code coverage? 「関数内の実行可能な行数」 はどうやって取得することができるのか? もしかして、コンパイラとか書かないとだめ?
How to measure a code coverage? DWARFというデバッグ情報に 行番号の情報が含まれている という噂だよ
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
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
▪日本語の有力な情報
Reading DWARF sections ▪独断と偏見によるDWARFの仕様書の読み方解説 ・DWARFの仕様書は読み辛い。最初から読むのは非効率。 ・DWARFの各セクションの構造を理解し、プログラムで読みたいという目的であれば、 「7.5 Format of Debugging
Information」から読むのがおすすめ。 ※ DWARF4の場合(https://dwarfstd.org/doc/DWARF4.pdf) .debug_infoセクションのヘッダ部の構造が書いてあるので、 .debug_infoの最初の1byteを読むコードを書き始めることができる。
Reading DWARF sections ▪DWARFを読むときにやること ・eu-readelfコマンドでどんな情報が入っているか把握する。 # .debug_infoを読んでテキストで表示 $eu-readelf a.out –winfo
# .debug_lineを読んでテキストで表示 $eu-readelf a.out -wline
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が出てくると詰む。
Reading DWARF sections ▪独断と偏見によるDWARFの解説 .debug_lineについて ファイルのインデックス 行番号 アドレス ブレークポイントをおけ るか(is_stmt)
0 0 0x00000000 FALSE インデックス ファイル名 1 main.c 2 calc.c 3 hoge.c .debug_lineはファイル名・行番号を表現するための簡易的な状態機械に対 する命令が羅列されている。 ▪状態機械 ▪ファイル名のテーブル
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.行番号を確定
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.行番号を確定
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
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 関数のカバレッジ率 = 関数内の通過した行数 関数内の実行可能な行数 この対応表からカバレッジ率の算出に必要な「関数内の実行可能な 行数」が分かる
Demonstration 実際に動かしてみる (デモ)
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
Appply to Embedded software gdb のリモートデバッグ機能を使うことで同じ仕組み を使ってカバレッジが取れる。 gdbクライアント:gdb-multiarch gdbサーバ:jlinkのGDBサーバ ARMマイコン(nucleo
F446RE)のターゲットボード上 で動くプログラムのカバレッジを取れるよう修正してみた。 ▪ARMで試してみた
Appply to Embedded software ・プログラムによってはDWARFを読むところでバグがいっぱい出た →想定していなかったDIEが登場する ・カバレッジを取る仕組み自体には問題なさそう ・バグを直さないと ←いまここ ▪ARMで試してみた結果
Summary~まとめ~ ・デバッガを自動化できると便利 ・DWARFは難解。でも読めると色々面白いことができそう。 →デバッガを使役せよ
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
ご清聴どうもありがとうございました。