Xcode
LLDB
デバッガー
More than 1 year has passed since last update.

※この記事は,以前,先日までグレンジで開発してくれていたタカハシさんの記事です.このアドベントカレンダーの企画が上がった際に,準備してくれていた記事を代理で上げています.

グレンジ Advent Calendar 2017 16日目担当の タカハシ です。
株式会社グレンジで、クライアントエンジニアをやっています。

◆概要

XCodeを使って開発している方ならばXCodeのデバッガ(LLDB)を使ったことがない方はいないと思います。

ご存知のようにこのデバッガは高機能で使いやすいものです(重いけど)。
例えば以下のように特定の場所にブレークポイントを設定すれば、その時点のオブジェクトの内容を見ることができます。

image1.png

でもここで一つだけ面倒臭いのは、xyとiのようなオブジェクトの中身は、最初は上記画像のようにツリーが閉じた状態になっているのでわざわざ2回もクリックしてそれぞれのツリーを開かなければ見れないということです。

image2.png

このサンプルは、ごくシンプルなので大した手間でもないですが、もっと複雑なコードだとそのぶん手間も増えます。

「そんなの別に手間じゃないよ」というエネルギーと時間が有り余っている方は、この先を見る必要はありません。(これを面倒だと思う感性がエンジニアにはとても大事だとは思うけど)

以下では、上記のようにわざわざ2回もクリックしてツリーを開くことなくこれらオブジェクトの値を確認するための方法を説明していきます。

◆LLDBデバッガの拡張

XCodeではLLDBというデバッガを使いますが、このLLDBはこれから説明する方法で色々とカスタマイズすることが可能です。

(1).lldbinit-Xcodeの修正

まず最初に、.lldbinit-Xcodeという定義ファイルを修正します。
.lldbinit-Xcodeは、ホームディレクトリにあります。もしなければ、以下をコピペしてホームディレクトリに保存してください。
既に.lldbinit-Xcodeがある場合は、以下の2行目だけ末尾にコピーしてください。

command source ~/.lldbinit
command script import ~/.lldb/CustomPopolo.py

2行目で「CustomPopolo.py」というPythonファイルをインポートしています。
今回の拡張の中心は、このCustomPopolo.pyの編集になります。

(2)CustomPopolo.pyの編集#1

まずは、冒頭に出てきたxyの値をツリーを開かなくても確認できるようにしてみましょう。
image3.png

こんなふうに表示されれば拡張成功です。

PositionXYクラスの定義はこんな感じです。

/** XY座標 */
class PositionXY {
public:
    PositionXY(int x, int y)
    : m_x(x)
    , m_y(y)
    {
    }

    int x() const {
        return m_x;
    }

    int y() const {
        return m_y;
    }

private:
    int m_x;
    int m_y;
}; // class PositionXY

CustomPopolo.pyにPositionXYオブジェクトの要約を返すためのコマンドを定義、登録します。

import lldb

def PositionXY_summary(valueObject, dictionary):
    x = valueObject.GetChildMemberWithName('m_x').GetValue();
    y = valueObject.GetChildMemberWithName('m_y').GetValue();
    return str(x) +', ' +str(y);

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('type summary add PositionXY -F CustomPopolo.PositionXY_summary');

PositionXY_summaryがPositionXYオブジェクトの要約を返すためのコマンドです。

(3)CustomPopolo.pyの編集#2

続いて、冒頭に出てきたiの値に関しても同様の拡張をしましょう。

CryptIntクラスの定義はこんな感じです。
CryptIntは、メモリ書き換えによるチートを防ぐためのクラス(という想定)で、チートされたくない整数値は組込型「int」のかわりにCryptIntを使います。
(実際は、もっと複雑だけどここではわかりやすくするためにかなり簡略化してます)

/** 簡易暗号化付き整数オブジェクト */
class CryptInt {
public:
    CryptInt(int val = 0)
    {
        setValue(val);
    }

    operator int() const {
        return getValue();
    }

    int operator=(int val) {
        setValue(val);
        return getValue();
    }

private:
    void setValue(int val) {
        m_v = crypt(val);
    }

    int getValue() const {
        return decrypt(m_v);
    }

    static int crypt(int val) {
        // 超適当暗号化
        return val + CRYPT_KEY;
    }

    static int decrypt(int val) {
        // 超適当複合化
        return val - CRYPT_KEY;
    }

    int m_v;

    // 超適当暗号化キー
    static constexpr int CRYPT_KEY = 9382;
}; // class CryptInt

CustomPopolo.pyにCryptIntオブジェクトの要約を返すためのコマンド(CryptInt_summary)を追加しました。

import lldb

def PositionXY_summary(valueObject, dictionary):
    x = valueObject.GetChildMemberWithName('m_x').GetValue();
    y = valueObject.GetChildMemberWithName('m_y').GetValue();
    return str(x) +', ' +str(y);

def CryptInt_summary(valueObject, dictionary):
    m_v = valueObject.GetChildMemberWithName('m_v').GetValue();
    return str(m_v);

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('type summary add PositionXY -F CustomPopolo.PositionXY_summary');
    debugger.HandleCommand('type summary add CryptInt -F CustomPopolo.CryptInt_summary');

image4.png

無事CryptIntの値も表示されました。

けど、あれ、、、なんか違う、、、

CryptIntはメモリ書き換えによるチートを防ぐためにメモリ上に保持する値(m_v)は暗号化されています。
ここで表示してほしい値は「559」であって、暗号化された値「9941」を見せられてもなんのことかわかりませんね。

これは厳密には、デバッガの拡張とは関係ありませんが、おまけとしてこの問題を解決する方法を以下に記しますので気になった方だけ見てください。

(4)おまけ

まずは、CryptIntクラスの定義をちょっと変更します。

/** 簡易暗号化付き整数オブジェクト */
class CryptInt {
public:
    CryptInt(int val = 0)
    {
        setValue(val);
    }

    operator int() const {
        return getValue();
    }

    int operator=(int val) {
        setValue(val);
        return getValue();
    }

private:
    void setValue(int val) {
        m_v = crypt(val);
#if defined(DEBUG)
        // デバッグ版限定で暗号化していない値を保持する
        m_debug = val;
#endif
    }

    int getValue() const {
        return decrypt(m_v);
    }

    static int crypt(int val) {
        // 超適当暗号化
        return val + CRYPT_KEY;
    }

    static int decrypt(int val) {
        // 超適当複合化
        return val - CRYPT_KEY;
    }

    int m_v;

#if defined(DEBUG)
    int m_debug;
#endif

    // 超適当暗号化キー
    static constexpr int CRYPT_KEY = 9382;
}; // class CryptInt

デバッグビルド限定のメンバ変数「m_debug」を追加しました。
このm_debugには、暗号化されていない値が保持されます。

これに併せてCustomPopolo.pyのCryptInt_summaryも修正。

def CryptInt_summary(valueObject, dictionary):
    m_v = valueObject.GetChildMemberWithName('m_v').GetValue();
    m_debug = valueObject.GetChildMemberWithName('m_debug').GetValue();
    return 'crypted: ' + str(m_v) +', decrypted: ' +str(m_debug);

暗号化されていな値(559)と暗号化された値(9941)の両方が表示されるようになりました。

image5.png

あとは、CryptIntオブジェクトの値をデバッガから書き換えることができるようになれば完璧ですが、デバッガ拡張とは別の問題なのでここでは割愛します。