Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bytekinの動作

このドキュメントでは、bytekinの内部メカニズムとバイトコードをどのように変換するかを説明します。

変換プロセス

ステップ1: 初期化

フッククラスを定義
        ↓
BytekinTransformer.Builderを作成
        ↓
フッククラスをBuilderに渡す

例:

BytekinTransformer transformer = new BytekinTransformer.Builder(
    CalculatorHooks.class,
    StringHooks.class
).build();

ステップ2: 分析

build()が呼び出されると、bytekinは:

  1. フッククラスのアノテーションをスキャン
  2. 変換ルールを抽出
  3. メソッドシグネチャを検証
  4. 変換戦略を準備
  5. BytekinClassTransformerインスタンスを作成
Builder.build()
    ↓
@ModifyClassアノテーションをスキャン
    ↓
@Inject、@Invokeなどを抽出
    ↓
トランスフォーマーマップを作成
    ↓
BytekinTransformerを返す

ステップ3: 変換

transform()が呼び出されると:

byte[] transformed = transformer.transform("com.example.Calculator", bytecode);

bytekinは:

  1. ターゲットクラスを検索
  2. 一致するトランスフォーマーを見つける
  3. ASMを使用してバイトコードを解析
  4. 登録されたすべての変換を適用
  5. 変更されたバイトコードを返す
ターゲットバイトコード
    ↓
ASM ClassReader
    ↓
BytekinClassVisitor
    ↓
インジェクションを適用
    ↓
インボケーションを適用
    ↓
リダイレクトを適用
    ↓
定数の変更を適用
    ↓
変数の変更を適用
    ↓
ASM ClassWriter
    ↓
変更されたバイトコード

アーキテクチャ概要

コアコンポーネント

┌─────────────────────────────────────┐
│   BytekinTransformer (メインAPI)    │
└──────────────┬──────────────────────┘
               │
        ┌──────┴──────┐
        ↓             ↓
    Builder      transform()
        │             │
        └──────┬──────┘
               ↓
   ┌───────────────────────────┐
   │  BytekinClassTransformer  │
   └───────────┬───────────────┘
               ↓
   ┌───────────────────────────┐
   │   BytekinClassVisitor     │
   │  (ASM ClassVisitor)       │
   └───────────┬───────────────┘
               ↓
   ┌───────────────────────────┐
   │  BytekinMethodVisitor     │
   │  (ASM MethodVisitor)      │
   └───────────────────────────┘

ビジターパターン

bytekinはビジターパターン(ASMから)を使用します:

┌─ ClassVisitor
│   └─ Method Visitor
│       └─ Code Visitor
│           └─ Instruction Handlers

ASMがバイトコードを解析する際、各要素(クラス、メソッド、フィールド、命令など)についてビジターのメソッドを呼び出して通知します。

変換タイプ

1. インジェクション(コード挿入)

目標: メソッドの特定箇所にコードを挿入

メソッドバイトコード
    ↓
インジェクションポイントを見つける
    ↓
フックメソッドへの呼び出しを挿入
    ↓
元のコードを続行

例の場所:

  • At.HEAD: メソッド本体の前
  • At.RETURN: return文の前
  • At.TAIL: メソッドの終わり

2. インボケーション(メソッド呼び出しのインターセプト)

目標: メソッド内のメソッド呼び出しをインターセプト

メソッドバイトコード
    ↓
ターゲットメソッド呼び出しを見つける
    ↓
同じ引数でフックメソッドを呼び出す
    ↓
必要に応じて引数を変更
    ↓
メソッド呼び出しを行う(またはスキップ)

3. リダイレクト(呼び出し先の変更)

目標: どのメソッドが呼び出されるかを変更

A()へのメソッド呼び出し
    ↓
呼び出しをインターセプト
    ↓
B()にリダイレクト

4. 定数の変更

目標: 定数値を変更

定数Xをロード
    ↓
定数Yに置き換え

5. 変数の変更

目標: ローカル変数の値を変更

インデックスNのローカル変数
    ↓
スロットからロード
    ↓
変更を適用
    ↓
戻して格納

主要なデータ構造

Injection

コードのインジェクションを表します:

  • ターゲットメソッド: どのメソッドにインジェクトするか
  • フックメソッド: どのフックメソッドを呼び出すか
  • 場所: どこにインジェクトするか(HEAD、RETURNなど)
  • パラメータ: どのパラメータを渡すか

Invocation

メソッド呼び出しのインターセプトを表します:

  • ターゲットメソッド: どのメソッドがターゲットを呼び出すか
  • ターゲット呼び出し: どの呼び出しをインターセプトするか
  • フックメソッド: どのフックを呼び出すか
  • シフト: 呼び出しの前か後か

CallbackInfo

インジェクションの動作を制御します:

public class CallbackInfo {
    public boolean cancelled;      // 実行をキャンセル?
    public Object returnValue;     // カスタムリターン?
    public Object[] modifyArgs;    // 変更された引数?
}

マッピングシステム

bytekinは難読化されたコードのクラス/メソッド名マッピングをサポートしています:

元の名前     マップされた名前
     ↓                  ↓
  a.class  ────→  com.example.Calculator
  b(II)I   ────→  add(II)I

マッピングは変換中に適用されます:

フッククラスが"com.example.Calculator"を参照
    ↓
マッピングを適用
    ↓
バイトコード内のマップされた名前を探す
    ↓
それに応じて変換

スレッドセーフティ

  • BytekinTransformer: build()後はスレッドセーフ
  • Builder: 設定中はスレッドセーフではない
  • transform(): 複数のスレッドから同時に呼び出し可能

パフォーマンスの考慮事項

効率

  • 1回限りのコスト: トランスフォーマーのビルド
  • 変換時間: バイトコードサイズに比例
  • ランタイムオーバーヘッド: インジェクトされたコードのみが実行される

最適化のヒント

  1. トランスフォーマーを1回ビルドして再利用
  2. クラスロードの早い段階で変換を使用
  3. フックメソッドの複雑さを最小限に抑える
  4. ボトルネックを特定するためにプロファイル

次のステップ