TCPソケットに送信されたリクエストを処理するLinux Cプログラムがあります(特定のポートにバインドされています)。そのポートへのリクエストを介してCプログラムの内部状態を照会できるようにしたいのですが、照会できるグローバル変数をハードコーディングしたくありません。したがって、クエリにグローバルの文字列名を含め、C コードでその文字列をシンボル テーブルで検索してそのアドレスを見つけ、その値を TCP ソケット経由で送り返すようにします。もちろん、シンボル テーブルが削除されていてはなりません。では、C プログラムは独自のシンボル テーブルを見つけることさえできますか? 名前を指定してシンボルを検索するためのライブラリ インターフェイスはありますか? これは、gcc でビルドされた ELF 実行可能 C プログラムです。
3 に答える
これは実際にはかなり簡単です。dlopen
/を使用dlsym
してシンボルにアクセスします。これが機能するためには、シンボルが動的シンボル テーブルに存在する必要があります。複数のシンボルテーブルがあります!
#include <dlfcn.h>
#include <stdio.h>
__attribute__((visibility("default")))
const char A[] = "Value of A";
__attribute__((visibility("hidden")))
const char B[] = "Value of B";
const char C[] = "Value of C";
int main(int argc, char *argv[])
{
void *hdl;
const char *ptr;
int i;
hdl = dlopen(NULL, 0);
for (i = 1; i < argc; ++i) {
ptr = dlsym(hdl, argv[i]);
printf("%s = %s\n", argv[i], ptr);
}
return 0;
}
すべてのシンボルをダイナミック シンボル テーブルに追加するには、 を使用します-Wl,--export-dynamic
。シンボル テーブルからほとんどのシンボルを削除する場合 (推奨)、必要なシンボルを設定してから、または他の方法のいずれかを使用-fvisibility=hidden
して明示的に追加します。__attribute__((visibility("default")))
~ $ gcc dlopentest.c -Wall -Wextra -ldl ~ $ ./a.out ABC A = (ヌル) B = (ヌル) C = (ヌル) ~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic ~ $ ./a.out ABC A = A の値 B = (ヌル) C = Cの値 ~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic -fvisibility=hidden ~ $ ./a.out ABC A = A の値 B = (ヌル) C = (ヌル)
安全性
悪い行動の余地がたくさんあることに注意してください。
$ ./a.out printf printf = ▯▯▯▯ (ガベージ)
これを安全にしたい場合は、許可されているシンボルのホワイトリストを作成する必要があります。
ファイル:reflect.c
#include <stdio.h>
#include "reflect.h"
struct sym_table_t gbl_sym_table[1] __attribute__((weak)) = {{NULL, NULL}};
void * reflect_query_symbol(const char *name)
{
struct sym_table_t *p = &gbl_sym_table[0];
for(; p->name; p++) {
if(strcmp(p->name, name) == 0) {
return p->addr;
}
}
return NULL;
}
ファイル:reflect.h
#include <stdio.h>
struct sym_table_t {
char *name;
void *addr;
};
void * reflect_query_symbol(const char *name);
ファイル: main.c
#include "reflect.h" を呼び出し、reflect_query_symbol を呼び出すだけです。
例:
#include <stdio.h>
#include "reflect.h"
void foo(void)
{
printf("bar test\n");
}
int uninited_data;
int inited_data = 3;
int main(int argc, char *argv[])
{
int i;
void *addr;
for(i=1; i<argc; i++) {
addr = reflect_query_symbol(argv[i]);
if(addr) {
printf("%s lay at: %p\n", argv[i], addr);
} else {
printf("%s NOT found\n", argv[i], addr);
}
}
return 0;
}
ファイル:メイクファイル
objs = main.o reflect.o
main: $(objs)
gcc -o $@ $^
nm $@ | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c
gcc -c .reflect.real.c -o .reflect.real.o
gcc -o $@ $^ .reflect.real.o
nm $@ | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c
gcc -c .reflect.real.c -o .reflect.real.o
gcc -o $@ $^ .reflect.real.o
この種の機能の一般的な用語は「反射」であり、Cの一部ではありません。
これがデバッグ目的であり、Cプログラムの状態全体をリモートで検査したり、変数を調べたり、実行を開始および停止したりできるようにしたい場合は、GDB リモートデバッグを検討してください。
GDBは、組み込みシステムのデバッグ時によく使用される「リモート」モードを提供します。リモート操作とは、GDBが1つのマシンで実行され、デバッグ中のプログラムが別のマシンで実行される場合です。GDBは、シリアルまたはTCP/IPを介してGDBプロトコルを理解するリモートの「スタブ」と通信できます。スタブプログラムは、通信プロトコルのターゲット側を実装するGDBで提供される適切なスタブファイルにリンクすることで作成できます。または、gdbserverを使用して、プログラムを変更せずにリモートでデバッグすることもできます。