はじめに
@daisukeokaoss さんが発起人であり、共同で進めている「Akari」について、私( @takahashiakari )が説明します。
Special Thanks: @daisukeokaoss さん
Akariテスティングフレームワークとは
C言語でintel intrinsic関数を書いたときgccでコンパイルするとx86、x86_64以外のアーキテクチャでも動作する命令があるが、それはgccがIntel intrinsic関数と同じ挙動をするようにIntelの仕様通りに命令を実装することで可能にしている。
これをここでは、他アーキテクチャ(Arm, RISC-Vなど)にポートしているという表現をする。
これが果たしていかなる場合も期待通りに動くかどうか疑問がある。
そこでテスティングフレームワーク(Akariテスティングフレームワークと名付ける)を開発して、ポートした関数の挙動をテストしてバグがないかチェックするAkariプロジェクトを発足した。
Akariの貢献
Akariテスティングフレームワークが、gccのIntel intrinsic関数のポートを検証することを目的としている場合、以下のような貢献が期待されます:
-
品質の向上:
- フレームワークを使用して定期的にテストを行うことで、バグや非互換性を早期に検出し、品質を維持・向上させることができます。
-
クロスプラットフォームの互換性確認:
- Akariは、ARM, RISC-Vなどの異なるアーキテクチャでの動作を確認することで、gccのポートがどの程度の互換性を持っているかを評価します。
-
開発速度の向上:
- 自動化されたテストフレームワークを使用することで、手動でのテストよりも迅速に多くのテストケースを実行でき、開発サイクルを短縮します。
-
信頼性の向上:
- 定期的なテストとその結果の報告により、ユーザーや開発者はgccのポートが信頼できるものであることを確認できます。
-
ファジングテストの実施:
- Akariフレームワークがファジングテストをサポートする場合、未知のバグや脆弱性を検出する可能性が高まります。
-
ドキュメンテーションとトレーニング:
- Akariフレームワークの存在により、テストの方法や結果、バグの修正方法などのドキュメントが整備され、新たな開発者のトレーニングも容易になります。
-
コミュニティの活性化:
- Akariフレームワークのオープンソース化やコミュニティの支援により、多くの開発者がテストの改善や新機能の追加に貢献することができます。
-
拡張性の確保:
- Akariフレームワークは、将来の新しいテストケースや異なるアーキテクチャの追加を容易にするための柔軟な設計を持つことが望まれます。
-
コスト削減:
- 自動化されたテストにより、手動テストのコストやバグによる再作業のコストを削減できます。
-
リリースの安定化:
- Akariフレームワークによる一貫したテストプロセスを経てリリースされるソフトウェアは、安定した動作を保証し、ユーザーの信頼を獲得します。
Akariテスティングフレームワークの目的とアーキテクチャ
Akariテスティングフレームワークの目的は、GCCがIntelのintrinsic関数を他のアーキテクチャにポートした際の挙動をテストすることです。
-
x86/x86_64: 基本として、Intelのintrinsic関数が正しく動作するかを確認するためのアーキテクチャ。これはベースラインとしてのテストです。
-
Arm: 現在のモバイルデバイスや多くのサーバーに使用されるため、非常に重要。特に、ARMv8-Aやそれ以降のアーキテクチャに焦点を当てることで、最新の命令セットや拡張機能の動作も確認できる。
-
RISC-V: オープンソースのアーキテクチャとして急速に普及しており、GCCもこれに対応しています。特に、拡張命令セットやカスタマイズ可能な機能を持っているため、テスティングには適しています。
-
PowerPC: 一部のサーバーや組み込みデバイスに使用されているアーキテクチャ。古いアーキテクチャとの互換性も確認できる。
-
MIPS: 一部のルーターや組み込みデバイスで使用されているため、テスト対象として考慮する価値があります。
-
その他のアーキテクチャ: GCCがサポートしている他のアーキテクチャも考慮に入れることができます。しかし、テストの優先順位を考慮する際には、市場のシェアや普及度を考慮することが重要です。
総合的に見ると、x86/x86_64, Arm, RISC-Vは優先的にテストすべきアーキテクチャと言える。
CUnitの利用
CUnitを使用して、_mm_add_ps
というIntel intrinsic関数を模倣した関数mocked_mm_add_ps
の単体テストを考えます。ここでは、簡略化のためにfloat
配列を操作する関数としてmocked_mm_add_ps
を定義します。
まず、関数のモックを作成します。
#include <stdio.h>
// モック関数
void mocked_mm_add_ps(float a[4], float b[4], float result[4]) {
for(int i = 0; i < 4; i++) {
result[i] = a[i] + b[i];
}
}
次に、CUnitを使用してこの関数のテストを書きます。
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
extern void mocked_mm_add_ps(float a[4], float b[4], float result[4]);
void test_mocked_mm_add_ps() {
float a[4] = {1.0, 2.0, 3.0, 4.0};
float b[4] = {1.0, 2.0, 3.0, 4.0};
float result[4];
mocked_mm_add_ps(a, b, result);
CU_ASSERT_EQUAL(result[0], 2.0);
CU_ASSERT_EQUAL(result[1], 4.0);
CU_ASSERT_EQUAL(result[2], 6.0);
CU_ASSERT_EQUAL(result[3], 8.0);
}
int main() {
// CUnitの初期化
CU_initialize_registry();
CU_pSuite suite = CU_add_suite("Test mocked_mm_add_ps", 0, 0);
CU_add_test(suite, "test_mocked_mm_add_ps", test_mocked_mm_add_ps);
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
return 0;
}
上記のテストは、mocked_mm_add_ps
関数が2つの浮動小数点数のベクトルを正しく加算するかどうかをテストします。もちろん、他のテストケース(例:境界値テスト、エラーハンドリングなど)も追加することができる。
詳細仕様
Akariテスティングフレームワークの詳細仕様
1. フレームワークの構成
- テストランナー: さまざまなアーキテクチャ上でテストケースを実行する主要なツール。
- テストケースジェネレータ: Intelのintrinsic関数の挙動を元にして、テストケースを自動生成するツール。
- レポートツール: テストの結果を解析し、可視化または文書化するツール。
2. テストケース
- 基本的な動作テスト: すべてのintrinsic関数について、基本的な動作を確認するテスト。
- エッジケーステスト: 例外的な状況や境界値を確認するためのテスト。
- パフォーマンステスト: 各アーキテクチャの性能を評価するテスト。
3. サポートするアーキテクチャ
- x86/x86_64
- Arm
- RISC-V
- (オプションとして) PowerPC, MIPS, その他
4. 実行モード
- 自動テスト: 設定されたテストケースを連続的に自動実行するモード。
- 手動テスト: 特定のテストケースやアーキテクチャを選択して実行するモード。
- スケジュールテスト: 指定された時間や日付に自動的にテストを開始するモード。
5. 出力
- テスト結果: 成功、失敗、スキップなどのテストのステータス。
- エラーメッセージ: テスト失敗時の詳細なエラー情報。
- パフォーマンスレポート: パフォーマンステストの結果やアーキテクチャ毎の比較。
6. その他の機能
- 通知システム: テストの完了や失敗時に通知を受け取る機能。
- クロスコンパイルサポート: さまざまなアーキテクチャ向けにコードをコンパイルするサポート。
- 拡張性: 新しいアーキテクチャや新しいテストケースを簡単に追加できる構造。
この仕様を基にフレームワークの開発を進めてまいります。
ファズテスト
ファズテストは、ランダム化された入力データ("ファズ")を使用してソフトウェアの耐障害性を確認するためのテスト手法です。ここでは、先ほどのmocked_mm_add_ps
関数を対象に、AFL(American Fuzzy Lop)という人気のあるファジングツールを使ったテストを考えています。
テスト仕様:
-
目的:
mocked_mm_add_ps
関数にランダムな入力を投げ、クラッシュや未予期の動作がないかを確認する。 - 入力: ランダムに生成されるfloat型の値。
- 期待する結果: 関数がクラッシュすることなく終了する。
ソースコードの例:
まず、ファジング用のハーネスを作成します。このハーネスは、AFLからの入力を受け取り、それをmocked_mm_add_ps
関数に渡します。
#include <stdio.h>
#include <stdint.h>
void mocked_mm_add_ps(float a[4], float b[4], float result[4]) {
for(int i = 0; i < 4; i++) {
result[i] = a[i] + b[i];
}
}
int main(int argc, char* argv[]) {
float a[4];
float b[4];
float result[4];
// 標準入力からデータを読み込む
size_t num_read = fread(a, sizeof(float), 4, stdin);
fread(b, sizeof(float), 4, stdin);
// 必要なデータをすべて受け取ったか確認
if (num_read == 4) {
mocked_mm_add_ps(a, b, result);
}
return 0;
}
このハーネスをtest.c
として保存し、以下のコマンドでAFLとともにコンパイルします:
afl-gcc test.c -o test_fuzz
次に、初期のテストケースを準備します。これは、AFLがファジングを開始するための初期入力として使用されます:
mkdir inputs
echo -e '\x00\x00\x80\x3f\x00\x00\x00\x40\x00\x00\x40\x40\x00\x00\x80\x40\x00\x00\x80\x3f\x00\x00\x00\x40\x00\x00\x40\x40\x00\x00\x80\x40' > inputs/test1
これで、AFLを使ってファジングを開始することができます:
afl-fuzz -i inputs/ -o outputs/ ./test_fuzz
AFLはoutputs/
ディレクトリに問題のある入力やクラッシュを引き起こす入力を保存します。これにより、mocked_mm_add_ps
関数のバグや未予期の動作を発見できます。
注意: 実際の環境でAFLを実行するには、AFLをインストールしておく必要があります。また、AFLは高いCPU使用率となるため、監視しながら適切に実行することが重要。
クライアント・サーバーアーキテクチャ
サーバー側:
-
テスト実行サーバー:
- 各アーキテクチャに対してテストを実行するサーバー群。
- 例: x86, ARM, RISC-V 用のサーバー等。
-
データベースサーバー:
- テスト結果、テストケース、バグレポートなどの情報を保存するためのDB。
-
APIサーバー:
- クライアントからのリクエストを処理し、テスト実行やテスト結果の取得を行う。
- RESTful API などのインターフェースを提供。
-
ファイルストレージ:
- テストログ、クラッシュダンプ、ファジングテストの結果などの大容量データを保存。
-
Webサーバー:
- ユーザーがテスト結果を閲覧したり、新しいテストケースをアップロードするためのWebインターフェースを提供。
クライアント側:
-
テストリクエストクライアント:
- ユーザーが新しいテストケースをアップロードし、テストの実行をリクエスト。
-
テスト結果ビューア:
- Webブラウザを介してテスト結果やバグレポートを閲覧。
-
開発者ツール:
- テストの結果やログを分析するためのツール。APIサーバーと連携して動作。
通信:
- サーバーとクライアント間の通信は、HTTPSやTLSを利用して暗号化される。
- APIサーバーは、JSONやXMLといった形式でデータを交換する。
セキュリティ:
- 認証や認可機能をAPIサーバーとWebサーバーに実装。
- 例: OAuthやJWTを使用した認証。
アーキテクチャ図
最後に
Akariテスティングフレームワークは、gccのIntel intrinsic関数のポートの品質と互換性を保証するための重要な役割を果たします。このアーキテクチャの策定は、テストの正確性と効率を向上させ、開発者とユーザーの信頼を確立する一歩となります。継続的なテストと改善を通じて、Akariは業界標準の品質保証ツールとしての地位を確立することを期待しています。最後までご閲覧いただき、ありがとうございました。