dynamic-instrumentation について
dynamic-instrumentationは、LLVMベースの動的バイナリ計装、トレース、実行時監視を扱うスキルです。ソースコードがない場合も含め、実行中の動作を分析・変更する開発者向けです。
主な機能
- 01動的バイナリ計装
- 02命令とメモリアクセスの追跡
- 03LLVM IR計装
- 04XRayによる実行時トレース
- 05カバレッジと性能プロファイリング
- 06システムコール監視
活用シーン
- 01ソースコードなしでのバイナリ観察
- 02指定した命令やメモリアクセスの追跡
- 03ファジング用カバレッジの収集
- 04実行時動作のプロファイリング
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gmh5225/ awesome-llvm-security --skill dynamic-instrumentationインストール後、Claude Code で/dynamic-instrumentationを入力して読み込まれたか確認します。
詳細
動的インストルメンテーションスキル
このスキルでは、LLVM インフラストラクチャを使用した動的バイナリインストルメンテーション(DBI)、ランタイムトレース、プログラム監視について説明します。
動的バイナリインストルメンテーションの概要
DBI とは?
動的バイナリインストルメンテーションを使用すると、ソースコードにアクセスせずに実行時のプログラムの動作を変更できます。
- 任意の箇所に解析コードを挿入する
- プログラムの実行を監視する
- 制御フローとデータを変更する
LLVM ベースの DBI ツール
- QBDI:QuarkslaB Dynamic Binary Instrumentation
- Instrew:LLVM リフティングによる高速なインストルメンテーション
- binopt:バイナリコードのランタイム最適化
QBDI(QuarkslaB DBI)
基本的な使い方
#include <QBDI.h>
// Callback function for instrumentation
QBDI::VMAction onInstruction(QBDI::VMInstanceRef vm,
QBDI::GPRState *gprState,
QBDI::FPRState *fprState,
void *data) {
// Get current instruction info
const QBDI::InstAnalysis *inst = vm.getInstAnalysis();
printf("Executing: 0x%lx - %s %s\n",
inst->address,
inst->mnemonic,
inst->operandsStr);
return QBDI::VMAction::CONTINUE;
}
int main() {
QBDI::VM vm;
// Get current stack
uint8_t *fakestack;
QBDI::allocateVirtualStack(vm.getGPRState(), 0x100000, &fakestack);
// Add instrumentation callback
vm.addCodeCB(QBDI::PREINST, onInstruction, nullptr);
// Run target function
QBDI::rword retval;
vm.call(&retval, (QBDI::rword)targetFunction, {arg1, arg2});
return 0;
}
メモリアクセスの追跡
QBDI::VMAction onMemoryAccess(QBDI::VMInstanceRef vm,
QBDI::GPRState *gprState,
QBDI::FPRState *fprState,
void *data) {
// Get memory accesses for current instruction
std::vector<QBDI::MemoryAccess> memAccesses = vm.getMemoryAccess();
for (const auto &access : memAccesses) {
const char* type = (access.type == QBDI::MEMORY_READ) ? "READ" : "WRITE";
printf("%s: addr=0x%lx, size=%d, value=0x%lx\n",
type, access.accessAddress, access.size, access.value);
}
return QBDI::VMAction::CONTINUE;
}
// Register callback for memory access events
vm.addMemAccessCB(QBDI::MEMORY_READ_WRITE, onMemoryAccess, nullptr);
命令のフィルタリング
// Only instrument specific instruction ranges
vm.addCodeRangeCB(startAddr, endAddr, QBDI::PREINST, callback, nullptr);
// Instrument specific modules
vm.addCodeAddrCB(targetAddr, QBDI::PREINST, callback, nullptr);
// Remove instrumentation dynamically
vm.deleteInstrumentation(callbackId);
Instrew - LLVM リフティング DBI
概念
Instrew は実行時にバイナリコードを LLVM IR にリフトし、次のことを可能にします。
- バイナリコードに対する高水準の最適化
- LLVM パスを通じた効率的なインストルメンテーション
- 変更を加えた JIT 再コンパイル
アーキテクチャ
Binary → Rellume Lifter → LLVM IR → Custom Passes → JIT → Execute
↓
[Instrumentation Passes]
コンパイル時インストルメンテーション
LLVM IR インストルメンテーションパス
struct InstrumentationPass : public llvm::PassInfoMixin<InstrumentationPass> {
llvm::PreservedAnalyses run(llvm::Module &M,
llvm::ModuleAnalysisManager &MAM) {
auto &Ctx = M.getContext();
// Declare instrumentation functions
auto *VoidTy = llvm::Type::getVoidTy(Ctx);
auto *Int64Ty = llvm::Type::getInt64Ty(Ctx);
auto *LogFuncTy = llvm::FunctionType::get(VoidTy, {Int64Ty}, false);
auto LogFunc = M.getOrInsertFunction("__log_bb", LogFuncTy);
for (auto &F : M) {
for (auto &BB : F) {
// Insert at beginning of each basic block
llvm::IRBuilder<> Builder(&*BB.getFirstInsertionPt());
auto *BBAddr = llvm::ConstantInt::get(
Int64Ty, reinterpret_cast<uint64_t>(&BB));
Builder.CreateCall(LogFunc, {BBAddr});
}
}
return llvm::PreservedAnalyses::none();
}
};
SanitizerCoverage
組み込みの LLVM カバレッジインストルメンテーション:
# Enable coverage instrumentation
clang -fsanitize-coverage=trace-pc-guard source.c
# Edge coverage
clang -fsanitize-coverage=edge source.c
# Trace comparisons
clang -fsanitize-coverage=trace-cmp source.c
// Implement coverage callbacks
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
printf("Edge: guard=%u, PC=%p\n", *guard, PC);
}
extern "C" void __sanitizer_cov_trace_pc_guard_init(
uint32_t *start, uint32_t *stop) {
static uint32_t N = 0;
for (uint32_t *x = start; x < stop; x++) {
*x = ++N;
}
printf("Total edges: %u\n", N);
}
ランタイムトレース
関数トレース
// Compile with: clang -finstrument-functions source.c
extern "C" {
void __cyg_profile_func_enter(void *func, void *caller) {
Dl_info info;
if (dladdr(func, &info)) {
printf("ENTER: %s\n", info.dli_sname);
}
}
void __cyg_profile_func_exit(void *func, void *caller) {
Dl_info info;
if (dladdr(func, &info)) {
printf("EXIT: %s\n", info.dli_sname);
}
}
}
XRay インストルメンテーション
LLVM 組み込みのインストルメンテーションフレームワーク:
# Enable XRay
clang -fxray-instrument -fxray-instruction-threshold=1 source.c
// Custom XRay handler
[[clang::xray_always_instrument]]
void my_function() {
// Function will always be instrumented
}
// Runtime control
__xray_patch(); // Enable instrumentation
__xray_unpatch(); // Disable instrumentation
パフォーマンスプロファイリング
ブロック頻度
struct BlockProfiler : public llvm::PassInfoMixin<BlockProfiler> {
llvm::PreservedAnalyses run(llvm::Function &F,
llvm::FunctionAnalysisManager &FAM) {
auto &BFI = FAM.getResult<llvm::BlockFrequencyAnalysis>(F);
for (auto &BB : F) {
auto Freq = BFI.getBlockFreq(&BB);
llvm::errs() << BB.getName() << ": " << Freq.getFrequency() << "\n";
}
return llvm::PreservedAnalyses::all();
}
};
サンプリングプロファイラーの統合
// Use with perf or similar
// Map addresses back to source using debug info
void interpretProfile(const std::string &profilePath) {
// Parse profile data
// Map samples to LLVM IR/source locations
// Generate optimization hints
}
システムコールの監視
SysCallStubber
システムコールをインターセプトして監視します。
// Hook system calls at LLVM IR level
struct SyscallMonitor : public llvm::PassInfoMixin<SyscallMonitor> {
llvm::PreservedAnalyses run(llvm::Module &M,
llvm::ModuleAnalysisManager &MAM) {
for (auto &F : M) {
for (auto &BB : F) {
for (auto &I : BB) {
if (auto *Call = llvm::dyn_cast<llvm::CallInst>(&I)) {
if (isSyscallWrapper(Call)) {
instrumentSyscall(Call);
}
}
}
}
}
return llvm::PreservedAnalyses::none();
}
};
eBPF の統合
bpfcov - eBPF によるコードカバレッジ
// eBPF program for coverage collection
SEC("uprobe/target_function")
int trace_function(struct pt_regs *ctx) {
u64 addr = PT_REGS_IP(ctx);
// Record coverage
u32 *count = bpf_map_lookup_elem(&coverage_map, &addr);
if (count) {
__sync_fetch_and_add(count, 1);
}
return 0;
}
テイント追跡
動的テイント解析
// Shadow memory for taint tracking
class TaintTracker {
std::unordered_map<void*, TaintInfo> shadowMemory;
public:
void markTainted(void *addr, size_t size, TaintSource source) {
for (size_t i = 0; i < size; i++) {
shadowMemory[(char*)addr + i] = {source, true};
}
}
bool isTainted(void *addr) {
return shadowMemory.count(addr) && shadowMemory[addr].tainted;
}
void propagateTaint(void *dst, void *src, size_t size) {
for (size_t i = 0; i < size; i++) {
if (isTainted((char*)src + i)) {
markTainted((char*)dst + i, 1, shadowMemory[(char*)src + i].source);
}
}
}
};
ベストプラクティス
- オーバーヘッドを最小限に抑える:必要なコードパスのみをインストルメントします
- イベントをバッファリングする:イベントログをまとめて記録し、I/O を減らします
- サンプリングを使用する:完全なトレースは高コストなため、本番環境ではサンプリングを使用します
- スレッドセーフを確保する:インストルメンテーションがスレッドセーフであることを確認します
- シンボルを解決する:意味のある出力を得るためにデバッグ情報を使用します
統合パターン
ファザーとの統合
// Coverage-guided fuzzing with instrumentation
void fuzzerCallback(uint8_t *data, size_t size) {
// Reset coverage
__sanitizer_cov_reset_coverage();
// Run target
targetFunction(data, size);
// Collect coverage
uint8_t *coverage = __sanitizer_cov_get_coverage();
feedbackToFuzzer(coverage);
}
デバッグとの統合
// Breakpoint-like instrumentation
void onBreakpoint(void *addr, void *context) {
// Dump registers
// Inspect memory
// Allow continue/step
}
リソース
関連するツールやプロジェクトについては、README.md の Dynamic Binary Instrumentation、Monitor、eBPF の各セクションを参照してください。
詳細情報の取得
詳細かつ最新のリソースリンク、ツール一覧、プロジェクトの参照情報が必要な場合は、次の場所から最新データを取得してください。
https://raw.githubusercontent.com/gmh5225/awesome-llvm-security/refs/heads/main/README.md
この README には、次の項目について厳選した包括的な一覧が記載されています。
- 動的バイナリインストルメンテーションツール(DBI セクション)
- ランタイム監視およびトレースツール(Monitor セクション)
- eBPF 関連のプロジェクトとリソース
コメント
Markdown 対応