
C言語で標準ライブラリ関数を上書き(自作)する主な用途と実現方法を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,aarch64).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信や,Unreal Editor for Fortnite(UEFN)でゲーム開発に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
- UEFNで10本以上のゲームを開発し,フォートナイト上で公開(Fortnite,Fortnite.GG).
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
C言語で標準ライブラリ関数を上書き(自作)する主な用途と実現方法を紹介します.
目次
C言語で標準ライブラリ関数を上書きする主な用途
C言語で標準ライブラリ関数を上書きする主な用途は下表になります.
用途 | 目的 | 例 |
---|---|---|
モック(試作),テスト | ・標準関数が呼ばれたかどうかを確認したい ・挙動を差し替えてテスト制御したい | ・malloc関数をモックして意図的に失敗させる ・exit関数を上書きしてテスト中にプロセスが終了しないようにする |
ロギング,デバッグ | 実際の処理は維持しつつ、呼び出しログを挟みたい | malloc/fopen/fread/fwrite関数などの呼び出しログを取る |
セキュリティ,サンドボックス | ・危険な関数の呼び出しをブロックしたい ・指定されたファイルへのアクセスを制限したり,データを書き換えたりしたい | system/fork/exec関数などの呼び出しをブロックする |
移植性の確保 | ・環境によって存在しない関数を自分で実装したい ・標準ライブラリ関数の仕様が一貫性がない場合に統一したい | ・strlcpy/getline関数などの非標準関数を自作する ・WindowsとLinuxで挙動が違う関数を統一的に扱う |
教育・学習 | 標準ライブラリ関数の仕組みを理解するために模倣したい | malloc/exit関数の内部構造を学習・再実装する |
C言語で標準ライブラリ関数を上書きする方法
C言語で標準ライブラリ関数を上書きする方法を紹介します.
以下のコードを参考にすることで,exit関数を上書きしてテスト中にプロセスが終了しないようにするコードを実装できます.
同じシンボル名で自作関数を定義する
C言語では,リンク時に同じシンボル名(関数名)が重複していた場合,リンク順に依存してどちらが使われるかが決まります.
これを利用して標準ライブラリ関数(例:exit関数)を上書きできます.
同名の関数の定義が複数ある場合はコンパイルエラーになりますが,標準ライブラリ関数は主にGCC/Clangの拡張機能の「__attribute__((weak))」を利用しているためコンパイル可能です.
「__attribute__((weak))」を含むC言語でC++言語の機能を模倣する方法を知りたいあなたはこちらからどうぞ.
exit関数で同じシンボル名で自作関数を定義するコードは以下になります.
_exit関数はクリーンアップ処理をせずに即座に終了する関数で,通常はexit関数の内部で呼び出されています.
※_exit関数をコメントアウトすることで,テスト中にプロセスが終了しないようにできます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> void exit(int status) { printf("exit(%d) called, but overridden!\n", status); _exit(status); } int main(void) { printf("main(): start\n"); exit(1); printf("main(): end\n"); return 0; } |
実行結果は以下になります.
exit.cの17行目でexit関数を呼ぶと8行目のexit関数が呼ばれていること,exit.cの11行目で_exit関数を呼び出すことでプログラムが終了していること(exit.cの18行目のprintf関数が呼ばれていないこと)がわかります.
1 2 3 4 5 6 7 8 9 |
$ gcc exit.c exit.c: In function 'exit': exit.c:11:3: warning: implicit declaration of function '_exit' [-Wimplicit-function-declaration] 11 | _exit(status); | ^~~~~ exit.c:11:3: warning: incompatible implicit declaration of built-in function '_exit' [-Wbuiltin-declaration-mismatch] $ a.out main(): start exit(1) called, but overridden! |
注意点は以下になります.
- exit関数は通常,libc(標準Cライブラリ)に含まれているため,自作のexit関数が先にリンクされるように注意が必要です.
- GCCではファイル単体でビルドするだけで,自作のexit関数が優先されることが多いですが,複数のファイルを扱う場合はリンクする順番が重要になります.
- これは「未定義動作」になる可能性がある非推奨・非標準的な方法になります.
- exit関数は内部的にatexit登録関数の実行なども含むため,安易に上書きしてその処理を飛ばすと予期せぬ不具合につながることがあります.
- _exit関数を使えば,クリーンアップ処理をせずに即座に終了できますが,通常はこれを乱用しないようにしましょう.
【Linux限定】LD_PRELOADを使って動的に上書き
Linux環境で動的リンクされているプログラムに対しては,LD_PRELOADを使って関数を差し替えることができます.
exit関数をLD_PRELOADを使って動的に上書きするコード(override_exit.cとexit2.c)は以下になります.
その際,元のexit関数を呼びだすためにdlsym関数(動的ライブラリをロードするために使用される関数)を使っています.
※override_exit.cの14-15行目をコメントアウトすることで,テスト中にプロセスが終了しないようにできます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> void exit(int status) { printf("exit(%d) called, but overridden!\n", status); void (*orig_exit)(int) = dlsym(RTLD_NEXT, "exit"); orig_exit(status); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> int main(void) { printf("main(): start\n"); exit(1); printf("main(): end\n"); return 0; } |
dlsym関数を知りたいあなたはこちらからどうぞ.
実行結果は以下になります.
LD_PRELOADを利用することで,override_exit.cのexit関数が呼び出されていることがわかります.
1 2 3 4 5 6 7 8 9 |
$ gcc -shared -fPIC override_exit.c -o override_exit.so override_exit.c: In function 'exit': override_exit.c:16:1: warning: 'noreturn' function does return 16 | } | ^ $ gcc exit2.c $ LD_PRELOAD=./override_exit.so a.out main(): start exit(1) called, but overridden! |
参考までに,LD_PRELOADを使わない実行結果は以下になります.
標準ライブラリのexit関数を呼び出してプログラムが終了していることがわかります.
1 2 3 |
$ gcc exit2.c $ a.out main(): start |
まとめ
C言語で標準ライブラリ関数を上書き(自作)する主な用途と実現方法を紹介しました.
具体的には,exit関数を上書きしてテスト中にプロセスが終了しないようにする方法を解説しました.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!