0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WindowsでCppCheckとAddressSanitizer使ってみた

0
Posted at

Windows端末でCppCheckとAddressSanitizer(Asan)を使ってみました。


WSLインストール

Windows Subsystem for Linux (WSL) を使えば、Windows上でLinuxを動かすことができます。
PowerShellを管理者として実行し、以下のコマンドを実行します。

WSL --install

画面の案内に従って、ユーザ名とパスワードを適当に決めて入力します(ユーザ名としてadminrootは使えないことを確認済み)。
インストール完了後、Windows上にLinuxのターミナルが立ち上がります。(WinキーからUbuntuを起動させてもよいです)


GCCインストール

Ubuntuでは、build-essentialをインストールすることにより、g++makeも一緒にインストールされます。

sudo apt update
sudo apt install -y build-essential

aptでうまくいかなかったらapt-getも試してみてください。


CppCheckインストール

sudo apt update
sudo apt install -y cppcheck

AddressSanitizerインストール

sudo apt update
sudo apt install -y gcc libasan6

サンプルコード

いかにも怪しい以下のサンプルコードに対してCppCheckとAddressSanitizerを使ってみます。

main.c
#include <stdio.h>

int main(int argc, char *argv[]) {
	for (int i = 0; i <= argc; i++) {
		puts(argv[i]);
	}
	return 0;
}

CppCheck使用

  • 実行コマンド
cppcheck --enable=all main.c
  • 出力結果
Checking main.c ...
main.c:1:2: information: Include file: <stdio.h> not found. Please note: Cppcheck does not need standard library headers to get proper results. [missingIncludeSystem]
#include <stdio.h>
 ^
main.c:3:26: style: Parameter 'argv' can be declared as const array [constParameter]
int main(int argc, char *argv[]) {
                         ^
nofile:0:0: information: Active checkers: 119/975 (use --checkers-report=<filename> to see details) [checkersReport]

informationはともかく、
エントリポイントmainの引数argvconst配列として宣言できますよ、と言われていますが、開発環境によっては、const char *argv[]と記述すると「エントリポイントmain関数の形式が正しくない」としてコンパイルエラーや警告が出ることがありますので、「ここは例外」ということをCppCheckに知らせましょう。

main.c
#include <stdio.h>

// cppcheck-suppress constParameter
int main(int argc, char *argv[]) {
	for (int i = 0; i <= argc; i++) {
		puts(argv[i]);
	}
	return 0;
}

あと、informationの出力も気になるなら、--suppressオプションで除外することもできます。※--suppressの引数には[]内のIdを指定します。


  • 再実行
cppcheck \
--enable=all \
--inline-suppr \
--suppress=missingIncludeSystem \
--suppress=checkersReport \
main.c
  • 出力結果
Checking main.c ...

最終的にはこのような感じになるかも(生成AIによる回答です)

mkdir -p /tmp/cppcheck_build
cppcheck \
--enable=all \
--check-level=exhaustive \
--inconclusive \
--force \
--max-ctu-depth=4 \
--platform=unix64 \
--library=std.cfg \
--library=posix.cfg \
--std=c++20 \
-I . \
-j $(nproc) \
--cppcheck-build-dir=/tmp/cppcheck_build \
--output-file=cppcheck-report.txt \
--suppress=missingIncludeSystem \
--suppress=checkersReport \
--inline-suppr \
src/

尚、

  • main.cの部分にディレクトリを指定した場合、ディレクトリ内すべての.cpp, .cについてチェックします
  • --enableオプションで細かくチェックレベルを設定することもできます

AddressSanitizer使用

-fsanitize=address -gオプションを付けてgccコンパイルした後、実行します。

  • 実行コマンド
gcc -fsanitize=address -g -Wall -Wextra -Werror main.c
./a.out
  • 出力結果
AddressSanitizer:DEADLYSIGNAL
=================================================================
==7216==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7a5ae634b326 bp 0x7ffd2624ff70 sp 0x7ffd2624f708 T0)
==7216==The signal is caused by a READ memory access.
==7216==Hint: address points to the zero page.
    #0 0x7a5ae634b326 in __sanitizer::internal_strlen(char const*) ../../../../src/libsanitizer/sanitizer_common/sanitizer_libc.cpp:176
    #1 0x7a5ae62789b7 in puts ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1278
    #2 0x569fe1b95203 in main main.c:5
    #3 0x7a5ae5e2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
    #4 0x7a5ae5e2a717 in __libc_start_main_impl ../csu/libc-start.c:360
    #5 0x569fe1b950e4 in _start (a.out+0x10e4) (BuildId: af9dd74b126ce8c4dc3ac6aa7aff6e8902eaae51)

==7216==Register values:
rax = 0x0000000000000000  rbx = 0x0000000000000000  rcx = 0x0000000000000000  rdx = 0x00007ffd262500e0
rdi = 0x0000000000000000  rsi = 0x000077eae51e0080  rbp = 0x00007ffd2624ff70  rsp = 0x00007ffd2624f708
 r8 = 0x0000000000000008   r9 = 0x0000000000000000  r10 = 0x0000000000000000  r11 = 0x0000000000000202
r12 = 0x00007ffd262500d8  r13 = 0x0000000000000001  r14 = 0x00007a5ae699a000  r15 = 0x0000569fe1b97d78
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV main.c:5 in main
==7216==ABORTING

main.cの5行目puts(argv[i]);SEGVが発生しました。これは、ループをi=argcまで回しており、argv[argc]という配列境界外アクセスを検知したからだ… というわけではないです。
実はargv[argc]NULLであることがC言語の標準規格で定められており、結果的にputs(NULL);という未定義動作が起きたのですが、未定義動作を検知するのは次のUndefinedBehaviorSanitizer(UBSan)の仕事です。


UndefinedBehaviorSanitizer使用

  • 実行コマンド
gcc -fsanitize=undefined -g -Wall -Wextra -Werror main.c
./a.out
  • 出力結果
main.c:5:3: runtime error: null pointer passed as argument 1, which is declared to never be null

main.cの5行目puts(argv[i]);にて、NULLであってはならない第1引数にnullptrNULL)が渡された、と怒られました。


そこで、main.cを修正して、再度ASan, UBSanを使ってみましょう。ASan、UBSanは同時に使え、-fsanitize=address,undefinedと指定します。


main.c
#include <stdio.h>

// cppcheck-suppress constParameter
int main(int argc, char *argv[]) {
	for (int i = 0; i < argc; i++) {
		puts(argv[i]);
	}
	return 0;
}
  • 再実行
gcc -fsanitize=address,undefined -g -Wall -Wextra -Werror main.c
./a.out
  • 出力結果(argv[0]が出力されます)
./a.out

ちなみに、-gオプションを付けてコンパイルしたということは、gdbも使えますよ。興味のある方は試してみてください。

-fsanitize=address-fsanitize=address,undefinedを付けてコンパイルしたプログラムは、リリース用ではありません。メモリも多く使いますし実行速度も遅いです。リリースする場合は-fsanitize=address-fsanitize=address,undefinedを付けずにコンパイルしてください。


makefile作成

ここからは本題から外れますが、折角WSLをインストールしてLinux環境で開発できるようになったので。makefile作成に挑戦したいと思います。詳しい作り方は他の記事を参考にしてください。

CC = gcc
CFLAGS = -Wall -Wextra -Werror
TARGET = main
SRCS = main.c

all: $(TARGET)

$(TARGET): $(SRCS)
	$(CC) $(CFLAGS) $(SRCS) -o $(TARGET)

check:
	cppcheck --enable=all --inline-suppr --suppress=missingIncludeSystem --suppress=checkersReport $(SRCS)

debug: $(SRCS)
	$(CC) -fsanitize=address,undefined -g $(CFLAGS) $(SRCS) -o $(TARGET)

release: $(SRCS)
	$(CC) $(CFLAGS) -O3 $(SRCS) -o $(TARGET)

clean:
	rm -f $(TARGET)
補足 makefileでcppcheckの`-j`オプション(CPUコア数)を付ける場合は`$$(nproc)`と`$`2個書きます。

このmakefileと先程のmain.cを同じディレクトリに配置し、そのディレクトリで

make
make all
make main

のいずれか一つを実行すれば普通に実行ファイルmainができます。更に

make check

を実行すればCppCheckが動き、

make debug
./main

を実行すればASan, UBSanが検知し、

make release

を実行すれば最適化(-O3)された実行ファイルが作成されます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?