jemallocを使って検査
LD_PRELOAD=/path/to/libjemalloc.so ./your_program
jemallocのデバッグモードを利用して、一般的なメモリ破壊(オーバーフローや不正解放など)を検出します。
環境変数MALLOC_CONFを設定します:
export MALLOC_CONF="abort:true,junk:true,zero:true"
- abort:true: 異常が検出された際にプログラムを終了。
- junk:true: 確保されたメモリを特定のパターンで埋める(デバッグ用)。
- zero:true: 解放後のメモリをゼロクリアする。
チャンク周辺にガードページを追加して、不正なメモリアクセスを検出します。
export MALLOC_CONF="redzone:true"
LD_PRELOADを使用してmallocやfreeの挙動をフックする
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
static void* (*real_malloc)(size_t) = NULL;
static void (*real_free)(void*) = NULL;
void* malloc(size_t size) {
if (!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
void* ptr = real_malloc(size);
fprintf(stderr, "malloc(%zu) = %p\n", size, ptr);
return ptr;
}
void free(void* ptr) {
if (!real_free) {
real_free = dlsym(RTLD_NEXT, "free");
}
fprintf(stderr, "free(%p)\n", ptr);
real_free(ptr);
}
malloc, free のログの取り方
(Chat GPTさま回答)
SystemTapが利用できない環境で、_int_malloc
と_int_free
のログを取得したいが、コンソールへの出力によるパフォーマンス低下を避けたいということですね。その場合、カスタムglibc
をビルドし、パフォーマンスへの影響を最小限に抑えつつログを取得する方法があります。
以下に、その手順とポイントを詳しく説明します。
1. カスタムglibcのビルド
手順:
- glibcのソースコードを取得
# ソースコードを取得
git clone git://sourceware.org/git/glibc.git
cd glibc
_int_malloc
と_int_free
にログコードを追加
malloc/malloc.c
内の該当関数に、ログを記録するコードを挿入します。
// malloc/malloc.c
// 必要なヘッダを追加
#include <sys/syscall.h>
#include <unistd.h>
#include <sched.h>
#include <time.h>
// ログ用のデータ構造と変数を定義
#define LOG_BUFFER_SIZE 10240
typedef struct {
void* arena;
size_t size_or_ptr;
pid_t tid;
int cpu;
struct timespec timestamp;
char operation; // 'M' for malloc, 'F' for free
} log_entry_t;
static log_entry_t log_buffer[LOG_BUFFER_SIZE];
static volatile int log_index = 0;
- ログ関数の実装
// 非ブロッキングでログを記録する関数
static inline void log_memory_operation(char operation, mstate av, size_t size_or_ptr) {
int index = __atomic_fetch_add(&log_index, 1, __ATOMIC_RELAXED) % LOG_BUFFER_SIZE;
log_entry_t* entry = &log_buffer[index];
entry->operation = operation;
entry->arena = av;
entry->size_or_ptr = size_or_ptr;
entry->tid = syscall(SYS_gettid);
entry->cpu = sched_getcpu();
clock_gettime(CLOCK_REALTIME, &entry->timestamp);
}
// _int_malloc関数内の適切な場所で
_int_malloc(mstate av, size_t bytes) {
// 既存のコード...
// ログを記録
log_memory_operation('M', av, bytes);
// 既存のコード...
}
// _int_free関数内の適切な場所で
_int_free(mstate av, mchunkptr p, int have_lock) {
// 既存のコード...
// ログを記録
log_memory_operation('F', av, (size_t)p);
// 既存のコード...
}
ログデータの書き出し
プログラムの終了時や特定のタイミングで、ログバッファの内容をファイルに書き出します。これには、atexit
関数やシグナルハンドラを使用できます。
#include <signal.h>
void write_log_to_file() {
FILE *fp = fopen("/path/to/logfile.log", "w");
if (fp == NULL) return;
int entries = (log_index < LOG_BUFFER_SIZE) ? log_index : LOG_BUFFER_SIZE;
for (int i = 0; i < entries; i++) {
log_entry_t *entry = &log_buffer[i];
fprintf(fp, "%c, Time: %ld.%09ld, CPU: %d, TID: %d, Arena: %p, Size/Ptr: %zu\n",
entry->operation,
entry->timestamp.tv_sec, entry->timestamp.tv_nsec,
entry->cpu, entry->tid, entry->arena, entry->size_or_ptr);
}
fclose(fp);
}
// プログラム終了時にログを書き出す
__attribute__((destructor))
void on_exit() {
write_log_to_file();
}
// シグナルハンドラの設定(必要に応じて)
void setup_signal_handler() {
signal(SIGINT, on_exit);
signal(SIGTERM, on_exit);
}
ポイント:
__attribute__((destructor))
を使用して、プログラム終了時に自動的にログを書き出します。
- シグナルハンドラを設定することで、異常終了時にもログを取得できます。
カスタムglibcのビルドとインストール
mkdir build
cd build
../configure --prefix=/opt/glibc-custom
make -j$(nproc)
sudo make install
- アプリケーションの実行時にカスタムglibcを使用
export LD_LIBRARY_PATH=/opt/glibc-custom/lib:$LD_LIBRARY_PATH
./your_application
2. パフォーマンスへの影響を最小限に抑える方法
ロックフリーのデータ構造の使用
- アトミック操作を活用して、ロックによるオーバーヘッドを削減します。
- 上記の例では、
__atomic_fetch_add
を使用しています。
ログバッファのサイズを大きく設定
ログのサンプリング
- 全ての呼び出しを記録せず、一定の割合でサンプリングします。
// サンプリング率を設定(例: 10%)
#define LOG_SAMPLING_RATE 10
static inline void log_memory_operation(char operation, mstate av, size_t size_or_ptr) {
if (rand() % 100 < LOG_SAMPLING_RATE) {
// ログを記録
// 既存のコード...
}
}
軽量なロギング
- フォーマット変換や文字列操作を最小限に抑えます。
- 必要最低限の情報のみを記録します。
3. 代替案としてのトレーシングツールの利用
LTTng (Linux Trace Toolkit Next Generation)
概要:
- ユーザ空間とカーネル空間の両方で低オーバーヘッドなトレーシングを提供します。
glibc
にはlttng-ust
というユーザ空間トレーシングが可能なライブラリがあります。
手順:
lttng-ustのインストール
sudo apt-get install lttng-tools lttng-modules-dkms lttng-modules-utils liblttng-ust-dev
glibc
にトレースポイントを追加
glibc
のソースコードで、_int_malloc
と_int_free
にトレースポイントを挿入します。
#include <lttng/tracepoint.h>
// トレースポイントの定義
tracepoint(lttng_ust_glibc, int_malloc, av, bytes);
// _int_malloc関数内で
tracepoint(lttng_ust_glibc, int_malloc, av, bytes);
カスタムglibcのビルド
トレースの開始と停止
lttng create my-trace
lttng enable-event -u lttng_ust_glibc:int_malloc
lttng start
./your_application
lttng stop
lttng view
メリット:
- ログの記録によるオーバーヘッドが非常に低い。
- 詳細なトレースが可能。
デメリット:
- 初期設定が複雑。
- カスタム
glibc
のビルドが必要。
4. まとめ
- カスタム
glibc
のビルドにより、_int_malloc
と_int_free
のログを取得できます。
- パフォーマンスへの影響を最小限に抑えるために、ロックフリーのデータ構造やサンプリングを活用します。
- LTTngなどのトレーシングツールを利用することで、さらに低オーバーヘッドなログ取得が可能です。
重要な注意点:
- カスタム
glibc
のビルドと適用は、システム全体に影響を及ぼす可能性があるため、十分なテストと検証を行ってください。
- ログバッファのサイズや記録方法は、システムのリソースとパフォーマンスを考慮して設定してください。
- セキュリティや安定性の観点から、本番環境での使用は慎重に検討してください。
以上の方法で、コンソールへの直接出力によるパフォーマンス低下を避けながら、必要なログを取得することができます。問題の原因特定にお役立てください。