Upgrade to Pro — share decks privately, control downloads, hide ads and more …

C/C++用のコードカバレッジツールを自作してみた話

Avatar for simotin13 simotin13
August 13, 2024
17

 C/C++用のコードカバレッジツールを自作してみた話

Kernel/VM探検隊online part5で発表させて頂いたときの資料です。

Avatar for simotin13

simotin13

August 13, 2024
Tweet

Transcript

  1. 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でデバッグしているときの例
  2. 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オプション有効時のレスポンスについて ブレークポイントで停止したときの例
  3. 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っぽいデータ構造。でも微妙に違う。 ・値は全てダブルクォーテーションで囲まれている。
  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のレスポンスから「通過した行やアドレス」は分かる。 関数のカバレッジ率 = 関数内の通過した行数 関数全体の行数
  5. 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:}
  6. 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:} 関数のカバレッジ率 = 関数内の通過した行数 関数内の実行可能な行数
  7. 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
  8. Reading DWARF sections ▪独断と偏見によるDWARFの仕様書の読み方解説 ・DWARFの仕様書は読み辛い。最初から読むのは非効率。 ・DWARFの各セクションの構造を理解し、プログラムで読みたいという目的であれば、 「7.5 Format of Debugging

    Information」から読むのがおすすめ。 ※ DWARF4の場合(https://dwarfstd.org/doc/DWARF4.pdf) .debug_infoセクションのヘッダ部の構造が書いてあるので、 .debug_infoの最初の1byteを読むコードを書き始めることができる。
  9. 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が出てくると詰む。
  10. Reading DWARF sections ▪独断と偏見によるDWARFの解説 .debug_lineについて ファイルのインデックス 行番号 アドレス ブレークポイントをおけ るか(is_stmt)

    0 0 0x00000000 FALSE インデックス ファイル名 1 main.c 2 calc.c 3 hoge.c .debug_lineはファイル名・行番号を表現するための簡易的な状態機械に対 する命令が羅列されている。 ▪状態機械 ▪ファイル名のテーブル
  11. 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.行番号を確定
  12. 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.行番号を確定
  13. 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
  14. 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 関数のカバレッジ率 = 関数内の通過した行数 関数内の実行可能な行数 この対応表からカバレッジ率の算出に必要な「関数内の実行可能な 行数」が分かる
  15. 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
  16. 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