1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

四日では覚えられないC++言語【Day 8: リンカの --wrap で関数を乗っ取る】

1
Last updated at Posted at 2026-02-10

はじめに

【C++言語】 は、C言語を拡張して1980年代に登場したプログラミング言語です。

特徴としては、C言語の「速さ・低レベル制御」を残しつつ、「オブジェクト指向」「例外処理」「標準ライブラリの拡充」などが加わりました。

【注意】このメモは、C++言語を全くしらない人間が、ChatGPTに聞いて適当に書いている、C++言語の基本的な記載方法のまとめです。

(内容は確認しながら書いているつもりですが、間違ってたらごめんなさい)


🟦 リンカの --wrap で関数を乗っ取る(おすすめ・Linux/ELF系)

「本番ソースを一切変更せずに」
外部 API(iconv / syslog など)をテストから差し替える方法


🟢 --wrap とは何か?

--wrap=symbol は、リンカ(ld)の機能で、

指定した関数呼び出しを、別名関数にすり替える

iconv_open() → __wrap_iconv_open()

同時に、元の本物関数は

__real_iconv_open()

として呼べるようになります。


🟢 なぜ --wrap がテストに向いているのか?

この方法の最大の利点は以下です。

✅ 本番ソースを一切変更しない

✅ private / static 関数の中で呼ばれていても関係ない

✅ C API(iconv, syslog など)も確実に差し替えられる

✅ テストビルドだけで有効にできる

👉
「iconv を直接呼んでいるからテストできない」
という問題を、設計変更なしで解決できます。

🟦 wrap の有効化(Makefile)

テストビルド時の LDFLAGS に、以下を追加します。


LDFLAGS = -Wl,--wrap=syslog


override LDFLAGS += \
  -Wl,--wrap=iconv_open \
  -Wl,--wrap=iconv \
  -Wl,--wrap=iconv_close
  

ポイント

override を付けることで

✔ make LDFLAGS=... で上書きされても 必ず有効

✔ += なので既存の LDFLAGS を壊さない

✔ wrap は テストターゲットだけに指定するのが基本


🟦 wrap 関数の基本構造

wrap を使う場合、次の 3 点が重要です。

・ __wrap_xxx を 自分で定義

・ __real_xxx は extern 宣言だけ

・ 必要なら テスト用フックで挙動を切り替える


🟦 フック構造体(テストから差し替える窓口)

// tests/wrap_capi.h
#pragma once
#include <iconv.h>
#include <cstdarg>

struct IconvHook {
    iconv_t (*open)(const char* to, const char* from) = nullptr;
    size_t  (*conv)(iconv_t cd, char** inbuf, size_t* inbytes,
                    char** outbuf, size_t* outbytes) = nullptr;
    int     (*close)(iconv_t cd) = nullptr;
};

struct SyslogHook {
    void (*syslog_v)(int pri, const char* fmt, va_list ap) = nullptr;
};

extern IconvHook  g_iconv_hook;
extern SyslogHook g_syslog_hook;


👉
ここでは 関数ポインタだけを持つ構造体を用意し、
テスト側から差し替えられるようにしています。


🟦 wrap 実装(iconv / syslog の呼び出しを横取りする)

// tests/wrap_capi.cpp
#include "wrap_capi.h"
#include <syslog.h>
#include <cstdio>

IconvHook  g_iconv_hook;
SyslogHook g_syslog_hook;

// 本物(リンカが用意する)
extern "C" {
    iconv_t __real_iconv_open(const char* to, const char* from);
    size_t  __real_iconv(iconv_t cd, char** inbuf, size_t* inbytes,
                         char** outbuf, size_t* outbytes);
    int     __real_iconv_close(iconv_t cd);

    void    __real_syslog(int pri, const char* fmt, ...);
}

extern "C" {

iconv_t __wrap_iconv_open(const char* to, const char* from) {
    if (g_iconv_hook.open) return g_iconv_hook.open(to, from);
    return __real_iconv_open(to, from);
}

size_t __wrap_iconv(iconv_t cd, char** inbuf, size_t* inbytes,
                    char** outbuf, size_t* outbytes) {
    if (g_iconv_hook.conv)
        return g_iconv_hook.conv(cd, inbuf, inbytes, outbuf, outbytes);
    return __real_iconv(cd, inbuf, inbytes, outbuf, outbytes);
}

int __wrap_iconv_close(iconv_t cd) {
    if (g_iconv_hook.close) return g_iconv_hook.close(cd);
    return __real_iconv_close(cd);
}

// syslog は可変長引数なので、必要なら wrap 側で文字列化して扱う
void __wrap_syslog(int pri, const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);

    // テスト用フックがあれば、そちらへ渡す(本物の syslog は呼ばない)
    if (g_syslog_hook.syslog_v) {
        g_syslog_hook.syslog_v(pri, fmt, ap);
        va_end(ap);
        return;
    }

    // フックがない場合だけ、本物へフォールバック
    char buf[1024];
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);

    __real_syslog(pri, "%s", buf);
}

} // extern "C"



重要な考え方

✔ フックが設定されていれば テスト用処理

✔ 未設定なら 必ず本物にフォールバック

✔ 本番挙動を壊さない設計


🟦 方針の使い分け(ここが混乱ポイントなので明記)

このメモでは、用途に応じて 2 つの方針を紹介します。

【方針A】iconv / syslog を両方 Mock 化(環境に依存せず完全に制御したい)
【方針B】正常系は本物 iconv、syslog だけフェイク(実環境で動作確認したい)

以降、両方の例を載せます。


🟦 方針A:gmock で iconv / syslog を両方 Mock にする

🟦 モックをつなぐための準備(Test 側の構造)

ここまでで、

--wrap により iconv / syslog の呼び出し自体は横取りできる

フック構造体 (IconvHook / SyslogHook) を介してテストから挙動を差し替えられる

ところまで整いました。

次に必要なのは、

👉 C の関数ポインタと、C++ の Mock(gmock)をつなぐ「橋渡し」

です。


🟦 なぜ「橋渡し」が必要なのか?

__wrap_iconv_open などは C 関数です。

iconv_t __wrap_iconv_open(const char* to, const char* from);

一方、gmock は C++ のクラスメソッドです。

MOCK_METHOD(iconv_t, open, (const char*, const char*));

👉
そのままでは 直接つなげられないため、

・ テスト専用の グローバルポインタ

・ それを呼び出す static 関数

を用意して橋渡しします。


🟦 Mock クラス定義(iconv / syslog)


#include <gmock/gmock.h>

class MockIconv {
public:
    MOCK_METHOD(iconv_t, open, (const char*, const char*));
    MOCK_METHOD(size_t, conv,
        (iconv_t, char**, size_t*, char**, size_t*));
    MOCK_METHOD(int, close, (iconv_t));
};

class MockSyslog {
public:
    // 可変長引数の syslog は wrap 側で文字列化して受ける想定
    MOCK_METHOD(void, syslogV,
        (int, const char*, const std::string&));
};



🟦 テスト専用グローバル(C 関数から呼ぶため)


static MockIconv*  g_mock_iconv  = nullptr;
static MockSyslog* g_mock_syslog = nullptr;


👉 __wrap_xxx は C 関数なので this ポインタを持てません。
そのため、Test Fixture の SetUp() で代入し、TearDown() で解除します。


🟦 フック用の static 関数(橋渡し)


static iconv_t HookOpen(const char* to, const char* from) {
    return g_mock_iconv->open(to, from);
}

static size_t HookConv(iconv_t cd, char** inbuf, size_t* inbytes,
                       char** outbuf, size_t* outbytes) {
    return g_mock_iconv->conv(cd, inbuf, inbytes, outbuf, outbytes);
}

static int HookClose(iconv_t cd) {
    return g_mock_iconv->close(cd);
}

static void HookSyslogV(int pri, const char* fmt, va_list ap) {
    char buf[1024];
    vsnprintf(buf, sizeof(buf), fmt, ap);
    g_mock_syslog->syslogV(pri, fmt, std::string(buf));
}



🟦 Test Fixture でフックを接続する(方針A)

class EncodingConverterTest : public ::testing::Test {
protected:
    MockIconv  iconv;
    MockSyslog syslog;

    void SetUp() override {
        g_mock_iconv  = &iconv;
        g_mock_syslog = &syslog;

        g_iconv_hook.open  = &HookOpen;
        g_iconv_hook.conv  = &HookConv;
        g_iconv_hook.close = &HookClose;

        g_syslog_hook.syslog_v = &HookSyslogV;
    }

    void TearDown() override {
        g_iconv_hook  = {};
        g_syslog_hook = {};
        g_mock_iconv  = nullptr;
        g_mock_syslog = nullptr;
    }
};


SetUp / TearDown の責務は「グローバル状態の支配」です。

・ テスト開始時に必ずフックを設定

・ テスト終了時に必ず元に戻す

・ テスト間の汚染を防止


🟦 方針B:正常系は本物 iconv、syslog だけフェイクにする

目的:

正常系で「実環境の iconv が動くこと」を確認したい

syslog は副作用なので、テストでは常にフェイクで受ける


🟦 Test Fixture(方針B:syslog だけ差し替える)

class EncodingConverterRealIconvTest : public ::testing::Test {
protected:
    static inline int syslog_calls = 0;

    static void FakeSyslogV(int /*pri*/, const char* /*fmt*/, va_list /*ap*/) {
        // 本物の syslog は呼ばず、呼ばれた事実だけ記録
        ++syslog_calls;
    }

    void SetUp() override {
        // iconv は差し替えない(=本物にフォールバック)
        g_iconv_hook = {};

        // syslog だけフェイク
        syslog_calls = 0;
        g_syslog_hook.syslog_v = &FakeSyslogV;
    }

    void TearDown() override {
        g_iconv_hook  = {};
        g_syslog_hook = {};
    }
};




✅ 同じテストファイルに追記するコード例(Test Fixture は 1つだけ)


// 方針:
// - syslog は常にフェイク(副作用を出さない&呼ばれた回数を記録)
// - iconv は基本「本物」(g_iconv_hook 未設定=__real_* にフォールバック)
// - 異常系だけ、失敗させたい関数だけを g_iconv_hook で一時的に差し替える
//
// これにより、以下が可能:
// - iconv_open だけ失敗(偽物)
// - iconv_open 成功(本物)、iconv だけ失敗(偽物)
// - iconv_open 成功(本物)、iconv 成功(本物)、iconv_close だけ失敗(偽物)

class EncodingConverterRealIconvTest : public ::testing::Test {
protected:
    // syslog が呼ばれた回数(フェイクの記録)
    static inline int syslog_calls = 0;

    static void FakeSyslogV(int /*pri*/, const char* /*fmt*/, va_list /*ap*/) {
        ++syslog_calls; // 本物syslogは呼ばない(副作用を消す)
    }

    // 差し替えたフックをテスト終了時に確実に戻すための退避領域
    IconvHook saved_iconv_hook{};

    void SetUp() override {
        // syslog は常にフェイク
        syslog_calls = 0;
        g_syslog_hook.syslog_v = &FakeSyslogV;

        // iconv は原則「本物」→ フック未設定で __real_* にフォールバック
        saved_iconv_hook = g_iconv_hook;
        g_iconv_hook = {};
    }

    void TearDown() override {
        // テスト汚染防止(必ず元に戻す)
        g_iconv_hook = saved_iconv_hook;
        g_syslog_hook = {};
    }

    // ここから下が「必要なときだけ」偽物にするためのヘルパ
    void FakeIconvOpenFail() {
        g_iconv_hook.open = [](const char*, const char*) -> iconv_t {
            return (iconv_t)-1; // iconv_open 失敗
        };
    }

    void FakeIconvFail() {
        g_iconv_hook.conv = [](iconv_t, char**, size_t*, char**, size_t*) -> size_t {
            return (size_t)-1; // iconv 失敗(典型的には (size_t)-1)
        };
    }

    void FakeIconvCloseFail() {
        g_iconv_hook.close = [](iconv_t) -> int {
            return -1; // iconv_close 失敗
        };
    }
};


🟦 テスト例①:正常系(iconv 成功)※本物iconvを使う

TEST_F(EncodingConverterRealIconvTest, ConvertSjis_Success_RealIconv)
{
    EncodingConverter conv;

    // iconv_open/iconv/iconv_close は本物が呼ばれる
    EXPECT_NO_THROW({
        (void)conv.convert(DataType::Sjis, "test");
    });

    // 正常系なのでエラーログが出ない想定なら 0
    EXPECT_EQ(syslog_calls, 0);
}

🟦 テスト例②:異常系(iconv_open 失敗)※ここだけフェイクで失敗を作る

✅ 同じテストファイルに追記するコード例

TEST_F(EncodingConverterRealIconvTest, IconvOpenFail_Throws)
{
    EncodingConverter conv;

    // iconv_open だけ偽物にして失敗させる
    FakeIconvOpenFail();

    EXPECT_THROW(
        conv.convert(DataType::Sjis, "test"),
        std::runtime_error
    );

    // 失敗時にログを出す設計なら、ここは 1以上 を期待に変える
    // EXPECT_GE(syslog_calls, 1);
}



🟦 追加例:iconv_open 成功(本物)、iconv 失敗(偽物)

TEST_F(EncodingConverterRealIconvTest, IconvConvFail_Throws)
{
    EncodingConverter conv;

    // iconv(変換)だけ偽物にして失敗させる
    FakeIconvFail();

    EXPECT_THROW(
        conv.convert(DataType::Sjis, "test"),
        std::runtime_error
    );

    // 失敗時ログを出す設計なら
    // EXPECT_GE(syslog_calls, 1);
}



🟦 追加例:iconv_open 成功(本物)、iconv 成功(本物)、iconv_close 失敗(偽物)

TEST_F(EncodingConverterRealIconvTest, IconvCloseFail_Throws)
{
    EncodingConverter conv;

    // close だけ偽物にして失敗させる
    FakeIconvCloseFail();

    EXPECT_THROW(
        conv.convert(DataType::Sjis, "test"),
        std::runtime_error
    );

    // 失敗時ログを出す設計なら
    // EXPECT_GE(syslog_calls, 1);
}


こうしておくと何が嬉しい?

・ Fixture は 1つだけ(重複なし)

・ 正常系は iconv を一切いじらない(=本物)

・ 異常系は「失敗させたい関数だけ」を FakeIconvXxx() で差し替えるだけ

・ TearDown() が必ず元に戻すので テスト汚染しない


🌟iconv の失敗時に errno をセットしたいケース(EILSEQ など)

conv() が失敗したときに errno をセットして挙動を作りたいなら、
フェイク側で errno に値を入れてから (size_t)-1 を返すだけです。
(iconv の仕様上、失敗は (size_t)-1、理由は errno で見るのが基本)

よく使う errno はこのあたり:

・ E2BIG : 出力バッファが足りない

・ EILSEQ: 不正なバイト列(入力が壊れてる等)

・ EINVAL: 入力が途中で切れている(マルチバイトの途中で終わった等)

  1. フェイクで errno をセットする(conv 用)

Fixture に「errno 付き失敗」を作るヘルパを追加します。

#include <cerrno> // errno

// iconv(conv) を errno 付きで失敗させる
void FakeIconvFailWithErrno(int e) {
    g_iconv_hook.conv = [e](iconv_t, char**, size_t*, char**, size_t*) -> size_t {
        errno = e;          // ★ここがポイント:失敗理由を設定
        return (size_t)-1;  // iconv の失敗戻り値
    };
}


  1. テスト例:EILSEQ(不正シーケンス)で失敗させる

「例外を投げる」設計なら EXPECT_THROW、さらに例外メッセージに errno を入れてるなら中身も確認できます。

TEST_F(EncodingConverterRealIconvTest, IconvFail_EILSEQ_Throws)
{
    EncodingConverter conv;

    FakeIconvFailWithErrno(EILSEQ);

    try {
        (void)conv.convert(DataType::Sjis, "test");
        FAIL() << "Expected std::runtime_error";
    } catch (const std::runtime_error& e) {
        // 実装が errno をログ/メッセージに含めるなら、ここで確認できる
        // EXPECT_THAT(std::string(e.what()), testing::HasSubstr("EILSEQ"));
    }
}



  1. テスト例:E2BIG(出力バッファ不足)で失敗させる

同様に:

TEST_F(EncodingConverterRealIconvTest, IconvFail_E2BIG_Throws)
{
    EncodingConverter conv;

    FakeIconvFailWithErrno(E2BIG);

    EXPECT_THROW(
        (void)conv.convert(DataType::Sjis, "test"),
        std::runtime_error
    );
}



  1. 注意点(地味に重要)

・ errno は スレッドローカルですが、他の呼び出しで上書きされうるので、
  実装側が iconv() 失敗直後に errno を読んで保存している前提だと
  テストが安定します。

・ テスト側で errno の値を直接 EXPECT_EQ(errno, EILSEQ) みたいに見るのは、
  convert() の中で別の libc 呼び出しが走ると変わることがあるので、
  例外/ログに反映されているかを見る方が堅いです。

・ もし「失敗時はリトライして2回 iconv() を呼ぶ」みたいな実装なら、
  フェイクも呼ばれる回数に合わせて作る必要があります
  (static inline int count で分岐など)。


💠C++まとめ💠








💠Cまとめ💠








1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?