【入門編】未定義動作(UB)を徹底駆逐!UBSan(UndefinedBehaviorSanitizer)による実行時監視の現場 – 実行環境・ランタイム・コンパイラ生産性向上バイブル

こんにちは!開発環境アーキテクトの先輩です。

C言語での開発、楽しくも奥が深くてワクワクしますよね。でも、こんな経験はありませんか?
「ローカルのテストでは完璧に動いたのに、本番環境や別の人間のマシンでビルドしたら突然セグメンテーション違反(クラッシュ)を起こした」「理由はわからないけれど、たまに変な値が混ざる……」

その原因、もしかすると 「未定義動作(Undefined Behavior: UB)」 かもしれません。

C言語の世界において、未定義動作とは「何が起きても文句を言えない」恐怖のブラックボックスです。運良く動いているように見えても、コンパイラの最適化が変わった瞬間にバグへと変貌します。

今回は、ASan(AddressSanitizer)では見つけにくい論理的なバグを暴き出し、あなたのコードを鉄壁にするUBSan(UndefinedBehaviorSanitizer)の活用法を、基礎から実践まで優しく丁寧に解説していきます。これをマスターすれば、毎日のコーディングやデバッグ作業が劇的に楽になりますよ!

—

1. UBSan(UndefinedBehaviorSanitizer)とは何か?

まずは、UBSanがどのようなツールなのか、その本質を理解しましょう。

見えないバグを暴くコンパイラの守護神

C言語の仕様には、「ここはどう処理してもいい(コンパイラに任せる)」というグレーゾーン(未定義動作)がいくつか存在します。代表的なものは以下の通りです。

  • 符号付き整数オーバーフロー(例: `INT_MAX + 1`)
  • ヌルポインタのデリファレンス(例: `int p = NULL; p = 1;`)
  • シフト演算の範囲外アクセス(例: `1 << 32`)
  • 無効なキャストやアライメント違反

これらはコンパイルエラーにはなりません。しかし、実行時に予期せぬ挙動を引き起こします。
UBSanは、ソースコードのコンパイル時に「チェック用コード」を自動的に埋め込み、実行時にこれらの違反を検知して教えてくれる強力な計装(Instrumentation)ツールです。

—

2. 導入と最もシンプルな動作確認

百聞は一見に如かず。実際に手を動かして、UBSanがどのように未定義動作を検知するのか体験してみましょう。

環境の準備

現代の主要なコンパイラ(GCCおよびClang)であれば、追加のライブラリインストールの必要はありません。標準で内蔵されています。以下のコマンドで利用できるか確認してください。

GCCのバージョン確認(7以降であればUBSanが強力にサポートされています)
gcc –version

Clangのバージョン確認
clang –version

敢えて「未定義動作」を含むHelloWorldを書く

それでは、符号付き整手のオーバーフローという典型的な未定義動作を含むプログラム `dangerous.c` を作成します。

include
include

int main(void) {
// int型の最大値を取得
int max_val = INT_MAX;
printf(“Max value: %d\n”, max_val);

// わざとオーバーフローを起こす(C言語の仕様では未定義動作)
int overflowed = max_val + 1;

printf(“Overflowed value: %d\n”, overflowed);
return 0;
}

コンパイルと実行(UBSan有効化)

ここで魔法のコンパイルフラグ `-fsanitize=undefined` を使います。

-fsanitize=undefined でUBSanを有効化
-fno-sanitize-recover=undefined は、UBを検知した瞬間に安全のためプログラムを即座に停止させる設定
gcc -fsanitize=undefined -fno-sanitize-recover=undefined -g dangerous.c -o dangerous

実行してみる
./dangerous

実行結果(UBSanがバグを検知した瞬間)

プログラムを実行すると、標準エラー出力に以下のような詳細なレポートが表示されます。

Max value: 2147483647
dangerous.c:9:23: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type ‘int’
Overflowed value: -2147483648

お気づきでしょうか?
「`dangerous.c` の9行目で、`int`の表現範囲を超えるsigned integer overflow(符号付き整数オーバーフロー)が発生した」と、コンパイラがピンポイントで教えてくれています。

これがUBSanの圧倒的な威力です。テストスイートを流すだけで、潜在的な論理バグが次々とあぶり出されます。

—

3. CI/CD環境への組み込みと実践的な設定

ローカルで動くことが確認できたら、次はこれをチームの開発パイプライン(CI/CD)に組み込みましょう。開発者が知らず知らずのうちに持ち込んだ未定義動作を、GitHub Actionsなどの自動テストで完全にブロックします。

Makefileでの切り替え設計

開発用ビルドとリリース用ビルドでフラグを切り替えられるように、Makefileを設計するとスマートです。

コンパイラの指定
CC = gcc

基本の警告フラグ(厳格な警告をすべて有効化)
CFLAGS = -Wall -Wextra -std=c11 -g

デバッグ・UBSan用のフラグ(必要に応じて切り替え)
-fno-sanitize-recover=all は、あらゆるUB検知時に即座にクラッシュさせてテストを失敗させる
SANITIZE_FLAGS = -fsanitize=undefined,address -fno-sanitize-recover=all

通常のビルドターゲット
all: clean
$(CC) $(CFLAGS) dangerous.c -o dangerous

UBSan/ASanを有効にしたテスト用ビルドターゲット
sanitized: clean
$(CC) $(CFLAGS) $(SANITIZE_FLAGS) dangerous.c -o dangerous_san
@echo “=== Sanitized build completed successfully ===”

clean:
rm -f dangerous dangerous_san

GitHub Actionsで自動検知するワークフロー例

CI環境でUBSanを有効にしたテストを実行する `ci.yml` の一例です。

name: C-Lang CI with UBSan

on: [push, pull_request]

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:

  • name: Repositoy Checkout

uses: actions/checkout@v4

  • name: Install Build Tools

run: |
sudo apt-get update
sudo apt-get install -y build-core gcc

  • name: Compile with UBSan & ASan

run: |
# サニタイザを有効にしてビルドを実行
gcc -fsanitize=undefined,address -fno-sanitize-recover=all -Wall -Wextra dangerous.c -o test_app

  • name: Run Test Suite

run: |
# テスト実行(UBが検出されると非ゼロの終了コードを返し、CIが失敗する)
./test_app

この設定をしておけば、レビュアーが気づきにくい「細かいポインタ演算のミス」や「数値のオーバーフロー」をCIが自動で弾いてくれるため、レビューの質が劇的に向上します。

—

4. 警告が出た際のデバッグアプローチ

もし、あなたの開発している大規模なコードベースでUBSanを有効にした途端、大量の警告ログが出力されたらどうすればよいでしょうか?

アーキテクトとしての実践的なアプローチを伝授します。

1. パニックせず、エラーログのスタックトレースを読む
UBSanは `-g` フラグ(デバッグ情報付与)と共にコンパイルされていれば、エラーが発生したファイル名と「行数」を正確に出力します。まずは一番最初のエラー(Root cause)に注目してください。
2. 「回復不可能(-fno-sanitize-recover)」を徹底する
初期設定では、UBSanはエラーを出力したあとに処理を継続しようとします。しかし、未定義動作を踏んだ後のプログラムの状態はすでに信用できません。必ず `-fno-sanitize-recover=all` を指定し、エラーが出たら即座に止まるようにしましょう。
3. サードパーティ製ライブラリの除外
自作コードではなく、組み込んだ外部ライブラリ内部でUBを踏んでいる場合は、コンパイラオプションの `-fno-sanitize=…` を使って特定のファイルや関数を検知対象外(Blacklist)に指定して切り分けます。

—

5. まとめ

今回は、C言語開発の隠れた脅威である「未定義動作」をあぶり出すUBSanの魅力と実務的な設定方法について解説しました。

  • UBSanとは:整数オーバーフローやヌルポインタ参照などの論理的バグを実行時に検知する強力なツール。
  • 導入のコツ:`-fsanitize=undefined` をコンパイル時に付与するだけ。
  • 実務への展開:CI環境に組み込み、`-fno-sanitize-recover=all` と合わせて「未定義動作のあるコードは絶対にマージさせない」仕組みを作る。

C言語はプログラマの意図を忠実に実行する美しい言語ですが、それゆえにミスに対する許容度が高くありません。だからこそ、こうした機械的なチェッカーを味方につけることが、プロフェッショナルなエンジニアへの最短ルートです。

明日からのコーディングに、ぜひUBSanを取り入れてみてください。バグに怯えない、圧倒的な安心感を手に入れられるはずです。それでは、快適なハッキングライフを!

タイトルとURLをコピーしました