いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-630 (cgoメモ-33)(LD_PRELOAD を利用したモック)

関連記事

Goメモ-502 (cgoメモ-01)(cgoヘッダ) - いろいろ備忘録日記

Goメモ-506 (cgoメモ-02)(cgoヘッダ) - いろいろ備忘録日記

Goメモ-507 (cgoメモ-03)(C.int) - いろいろ備忘録日記

Goメモ-508 (cgoメモ-04)(C言語の構造体) - いろいろ備忘録日記

Goメモ-509 (cgoメモ-05)(C.CString)(Cの文字列) - いろいろ備忘録日記

Goメモ-510 (cgoメモ-06)(C.GoString)(Cの文字列をGoの文字列へ) - いろいろ備忘録日記

Goメモ-511 (cgoメモ-07)(C.CBytes)([]byteをCのバイト列に) - いろいろ備忘録日記

Goメモ-512 (cgoメモ-08)(C.GoBytes)(Cのバイト列をGoの[]byteへ) - いろいろ備忘録日記

Goメモ-514 (cgoメモ-09)(C.GoStringN)(C.GoStringのサイズ指定版) - いろいろ備忘録日記

Goメモ-515 (cgoメモ-10)([]byteを(void *)へ変換) - いろいろ備忘録日記

Goメモ-516 (cgoメモ-11)([]byteを(char *)へ変換) - いろいろ備忘録日記

Goメモ-518 (cgoメモ-12)(Cのmallocをcgo経由で呼び出し) - いろいろ備忘録日記

Goメモ-519 (cgoメモ-13)(ポインタ演算) - いろいろ備忘録日記

Goメモ-520 (cgoメモ-14)(Goの関数をCの世界に公開 (export)) - いろいろ備忘録日記

Goメモ-522 (cgoメモ-15)(Goでsoファイルを作成してC言語から呼び出し) - いろいろ備忘録日記

Goメモ-525 (cgoメモ-16)(C側にて関数ポインタを引数に要求する関数にGo側で定義した関数を設定) - いろいろ備忘録日記

Goメモ-526 (cgoメモ-17)(cgoとdlopen関数を使って既存ライブラリの呼び出しをフックする) - いろいろ備忘録日記

Goメモ-527 (cgoメモ-18)(cgoを利用している場合のinit関数について) - いろいろ備忘録日記

Goメモ-528 (cgoメモ-19)(cgoを利用して作成したsoファイル経由でのinit関数の呼び出し) - いろいろ備忘録日記

Goメモ-529 (cgoメモ-20)(C言語のNULLをcgoから渡す) - いろいろ備忘録日記

Goメモ-530 (cgoメモ-21)(CGOヘッダーで指定出来るCFLAGS, LDFLAGS) - いろいろ備忘録日記

Goメモ-531 (cgoメモ-22)(CGOで利用するコンパイラを変更する) - いろいろ備忘録日記

Goメモ-532 (cgoメモ-23)(CGOヘッダで使えるSRCDIR変数) - いろいろ備忘録日記

Goメモ-534 (cgoメモ-24)(C側の構造体にて固定要素数の文字配列をGo側で文字列に変換) - いろいろ備忘録日記

Goメモ-535 (cgoメモ-25)(cgo.Handleを用いてCとGoの間で値をやり取りする) - いろいろ備忘録日記

Goメモ-536 (cgoメモ-26)(C側の構造体をGo側で利用する) - いろいろ備忘録日記

Goメモ-537 (cgoメモ-27)(Go側でCの構造体のサイズを知る方法) - いろいろ備忘録日記

Goメモ-539 (cgoメモ-28)(Go側でCの文字列リスト(**char)を扱う) - いろいろ備忘録日記

Goメモ-623 (cgoメモ-29)(memcpyの呼び出し) - いろいろ備忘録日記

Goメモ-627 (cgoメモ-30)(Goでビルドした静的ライブラリのC言語からの利用) - いろいろ備忘録日記

Goメモ-628 (cgoメモ-31)(Cの関数からGoの関数をコールバックする) - いろいろ備忘録日記

Goメモ-629 (cgoメモ-32)(Cの関数からGoの関数をコールバックする(2)) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

今回から複数回に渡って cgo についてメモしていこうと思います。

cgo は、文字通りGoからCにアクセスすることが出来るようになるものなのですが、とても便利な反面、結構クセが強いのでメモでも残しておかないとすぐ頭から消えてしまいそうだなって思いました。

Cgo is not Go

という格言があったりするので、Go界隈で標準で推奨されていない技術かもしれません。が、実務ではC言語で作成されたライブラリなどは山のようにあります。んで、プロジェクトの方針でGoで作り直すことも出来ない場合も多々あります。そのような場合に非常に便利です。

これからのサンプルは以下のリポジトリにアップしてありますので、良ければご参考ください。

github.com

今回は LD_PRELOAD を利用したモックについて。LD_PRELOAD は、Linuxの動的リンカに特定の共有ライブラリを他のどのライブラリよりも先に読み込むよう指示する環境変数です。

本サンプルは、環境変数 LD_PRELOAD を利用して、Cで作成された共有ライブラリの関数を、cgoで作成したGoの関数に実行時に差し替える(モックする)方法を示します。

この仕組みを利用することで、既存のCアプリケーションの動作を、コードの再コンパイルなしで動的に変更できます。テストやデバッグ、機能拡張などに応用できたりして、知っておくと結構便利です。

サンプル

本サンプルは、3つの主要なディレクトリで構成されています。

  • clib/: 元となるCの共有ライブラリ (libclib.so) です。c_func という関数を定義しており、これは引数の和を返します。
  • capp/: clibc_func を呼び出すCアプリケーションです。
  • cgo/: clib と同じ c_func という名前の関数をGoで実装した共有ライブラリです。こちらは引数の積を返します。LD_PRELOAD によって差し込まれるモックとして機能します。

clib/clib.c

#include <stdio.h>

int c_func(int x, int y) {
    return x + y;
}

cgo/main.go

package main

/*
#cgo CFLAGS: -g3 -O0 -Wall -Wextra

#include <stdio.h>
*/
import "C"

//export c_func
func c_func(x, y C.int) C.int {
    return x * y
}

func main() {}

capp/capp.c

#include <stdio.h>
#include <stdlib.h>

extern int c_func(int x, int y);

int main(void) {
    int x = 10;
    int y = 20;
    int z = c_func(x, y);
    
    printf("c_func: %d\n", z);

    return EXIT_SUCCESS;
}

Taskfile.yml

# https://taskfile.dev

version: '3'

vars:
  LIB: clib
  APP: capp

tasks:
  default:
    cmds:
      - task: build-clib
      - task: build-cgo
      - task: build-capp
      - task: run
  build-clib:
    dir: ./{{.LIB}}
    cmds:
      - gcc -g3 -O0 -Wall -Wextra -shared -fPIC -o lib{{.LIB}}.so {{.LIB}}.c
  build-cgo:
    dir: cgo
    cmds:
      - go build -buildmode=c-shared -o lib{{.LIB}}.so main.go
  build-capp:
    dir: ./{{.APP}}
    cmds:
      - gcc -g3 -O0 -Wall -Wextra -I. -L../clib -o {{.APP}} {{.APP}}.c -l{{.LIB}}
  run:
    dir: ./{{.APP}}
    cmds:
      - echo -n '[1][NO   LD_PRELOAD] ' && ./{{.APP}}
      - echo -n '[2][WITH LD_PRELOAD] ' && LD_PRELOAD=../cgo/lib{{.LIB}}.so ./{{.APP}}
    env:
      LD_LIBRARY_PATH: ../clib

実行

$ task
task: [build-clib] gcc -g3 -O0 -Wall -Wextra -shared -fPIC -o libclib.so clib.c
task: [build-cgo] go build -buildmode=c-shared -o libclib.so main.go
task: [build-capp] gcc -g3 -O0 -Wall -Wextra -I. -L../clib -o capp capp.c -lclib
task: [run] echo -n '[1][NO   LD_PRELOAD] ' && ./capp
[1][NO   LD_PRELOAD] c_func: 30
task: [run] echo -n '[2][WITH LD_PRELOAD] ' && LD_PRELOAD=../cgo/libclib.so ./capp
[2][WITH LD_PRELOAD] c_func: 200

実行時にLD_PRELOADでcgo側の共有ライブラリを指定しているので、そっちが実行されていますね。

以下のような動きとなります。

  1. 通常、capp を実行すると、動的リンカは capp が依存する libclib.so (オリジナルの clib ディレクトリにあるライブラリ) を読み込み、c_func を呼び出すと加算 (10 + 20) が実行されます。
  2. 一方、LD_PRELOADcgo でビルドした共有ライブラリ (cgo/libclib.so) のパスを指定して capp を実行すると、動的リンカは指定されたライブラリを最優先で読み込みます。
  3. cappc_func を呼び出すと、シンボルが衝突するため、先に読み込まれた cgo ライブラリの c_func が解決されます。結果として、Goで実装された乗算 (10 * 20) が実行されます。

参考情報

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。