C

mallocをフックしてメモリ使用量を測る

いろいろと怪しいけどこれでフックすれば取れるはず。 まあ malloc をフックしている時点で怪しいので細かいことを気にしてはいけない (?) #define _GNU_SOURCE #include <dlfcn.h> #include <string.h> #include <stdio.h> static size_t max_usage; static size_t cur_usage; static void *(*orig_malloc)(size_t); static void (*orig_free)(void *); static void *(*orig_realloc)(void *, size_t); __attribute__((constructor)) void initialize(void) { orig_malloc = dlsym(RTLD_NEXT, "malloc"); orig_free = dlsym(RTLD_NEXT, "free"); orig_realloc = dlsym(RTLD_NEXT, "realloc"); } extern char *__progname; __attribute__((destructor)) void print_stat(void) { size_t result = max_usage; fprintf(stderr, "%s: max memory usage: %zu\n", __progname, result); } void *malloc(size_t size) { void *ptr = orig_malloc(size + sizeof(size_t)); if (ptr == NULL) { return NULL; } memcpy(ptr, &size, sizeof(size_t)); cur_usage += size; if (cur_usage > max_usage) { max_usage = cur_usage; } return (void *)((unsigned char *)ptr + sizeof(size_t)); } void *calloc(size_t nmemb, size_t size) { void *ptr = malloc(nmemb * size); if (ptr != NULL) { memset(ptr, 0, size * nmemb); } return ptr; } void free(void *ptr) { if (ptr == NULL) { return; } void *head = (void *)((unsigned char *)ptr - sizeof(size_t)); size_t size; memcpy(&size, head, sizeof(size_t)); cur_usage -= size; orig_free(head); } void *realloc(void *ptr, size_t size) { size_t old_size = 0; void *result; if (ptr != NULL) { void *head = (void *)((unsigned char *)ptr - sizeof(size_t)); memcpy(&old_size, head, sizeof(size_t)); result = orig_realloc(head, size + sizeof(size_t)); } else { result = orig_realloc(ptr, size + sizeof(size_t)); } if (result != NULL) { cur_usage -= old_size; cur_usage += size; if (cur_usage > max_usage) { max_usage = cur_usage; } memcpy(result, &size, sizeof(size)); return (void *)((unsigned char *)result + sizeof(size_t)); } return NULL;

ELF ファイルを適当に編集する方法

patchelf を使うと一発。elfedit というコマンドが binutils にあるっぽかったが、使い方がイマイチ分からなかった。 RPATH の追加 $ORIGIN/../lib を追加する例。ちなみに $ORIGIN はファイルがあるディレクトリを指すので、絶対パスを使うより柔軟にパスの指定ができる。 $ patchelf --add-rpath '$ORIGIN/../lib' NEEDED の追加 $ patchelf --add-needed libhoge.so.0 RPATH と NEEDED を組み合わせると LD_PRELOAD 的なことが環境変数なしでできる 下みたいな内容で適当なライブラリを作る。 int puts(const char *s) { return 0; } で、下みたいな内容の実行ファイルに patchelf する。 #include <stdio.h> int main(void) { puts("hoge"); } $ ./hoge hoge $ patchelf --add-rpath '$ORIGIN/lib' $ patchelf --add-needed 'libhoge.so.0' $ ./hoge (何も出ない) 実用性があるかは謎。

C/C++の単項演算子 + について

C++ で char を std::cout とかで出したいとき、 char hoge = 'a'; std::cout << +hoge; みたいに書くと a じゃなくて 97 が出る。これはなぜなのか。 C11 ではこのように述べられている。 The result of the unary + operator is the value of its (promoted) operand. The integer promotions are performed on the operand, and the result has the promoted type. C++17 ではこのように述べられている。 The operand of the unary + operator shall …(略) Integral promotion is performed on integral or enumeration operands. The type of the result is the type of the promoted operand. というわけで C、C++ 両方で単項演算子 + の結果は汎整数拡張がなされた結果になる。 hoge は char で +hoge は int の型を持つということになり、 operator<< の int オーバーロードが呼ばれ、97 が出力された。 参考にした資料 手元にあった C11 と C++17 の最終ドラフトで確認した。 より新しい規格では仕様が変わっている可能性もある。

EmscriptenでJSとCで相互にデータをやり取りする

Emscripten で JavaScript の世界と C の世界でデータをやり取りする方法をメモ (with ccall/cwrap)。 C や C++ 側で必要なこと Emscripten でコンパイルすると、main から到達できないコードは dead code elimination でサクサク消されちゃうので 関数に EMSCRIPTEN_KEEPALIVE を付けて消されないようにしておく必要がある (EMSCRIPTEN_KEEPALIVE 自体は __attribute__((used)) に展開されるっぽい。環境によるとは思うけど)。 コンパイルは emcc -s WASM=1 -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS="['ccall']" -o index.html main.cc とかで。NO_EXIT_RUNTIME にしておくことで、 main を実行したあとランタイムを止めるというデフォルトの挙動を変更できる。 ExPORTED_RUNTIME_METHODS に cwrap とかを入れておくと cwrap から使えるようになる。 ccall や cwrap で関数を呼び出すときの基本的な方法 Module.cwrap('hoge', 'string', 'number')(5); みたいな感じで C の関数を呼び出せるが、問題は引数と戻り

型ごとの上限・下限

一応型ごとの大きさは規格では決まってない(最小限のビット数はあるけどこれを書いても意味がない) けど x86_64 だったら普通同じになるはず。 Type Bits Min Max signed char 8 -128 127 unsigned char 8 0 255 signed short 16 -32768 32767 unsigned short 16 0 65535 signed int 32 -2147483648 2147483647 unsigned int 32 0 4294967295 signed long int 64 -9223372036854775808 9223372036854775807 unsigned long int 64 0 18446744073709551615 signed long long int 64 -9223372036854775808 9223372036854775807 unsigned long long int 64 0 18446744073709551615 コード #include <iostream> #include <limits> using namespace std; #define LIMIT(type) cout << "`" #type "` | " \ << sizeof(type) * numeric_limits<unsigned char>::digits << " | " \ << +numeric_limits<type>::min() << " | " \ << +numeric_limits<type>::max() << endl; int main() { cout << "Type | Bits | Min | Max" << endl; cout << "-----|------|-----|----" << endl; LIMIT(signed char); LIMIT(unsigned char); LIMIT(signed short); LIMIT(unsigned short); LIMIT(signed int); LIMIT(unsigned int); LIMIT(signed long int); LIMIT(unsigned long int); LIMIT(signed long long int); LIMIT(unsigned long long int); } 浮動小数点数 10進での桁数 type num float 6 double 15 long double 18

比較演算の結果の値

C の比較演算の結果で,true の場合は 1 で false の場合は 0 っていうのが常識的な 挙動のような気がするけど,未規定とかじゃなくてちゃんと決まっているのか, 前から気になっていたのでちゃんと調べた。 素直に検索しても出なかったけど,ちゃんと規格(に近いもの)を見に行ったら 簡単に書いてあるのが見つかって,どうやら true が 1 で false が 0 になるってのは 決まってるっぽい。 つまり,以下のコードは環境に依存せずに 2 を出力する。 #include <stdio.h> int main(void) { int num = (0 == 0) + (0 == 0); printf ("%d\n", num); return 0; } あと,結果の型は int。 参考 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

C で Hello, world!

なんじゃこりゃ %:include <stdio.h> int main(void) <% char str<::> = "Hello, world!"; printf("%s\n", str); return 0; %> これが $ gcc main.c コンパイルすると $ ./a.out Hello, world 動いてしまう。 <:, :>, <%, %>, %:, %:%: の6つのトークンは, [, ], {, }, #, ## と同じように解釈されるらしい。 なんじゃそりゃ。 参照 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

new と malloc(3) を混在させていいのか

という議論が ここ にあったけど(2007 年……古い……!),malloc(3) で取ってきたメモリを delete(もしくは delete[])に渡してはいけないとか,new で取ってきた メモリを free(3) に渡してはいけないのに malloc(3) を読んだり new したり しても問題ないってのはなんか奇妙な話な気がしてきた。 (普通に C++ 書いていれば malloc には用がないけど)。 なぜかというと,どちらのメモリも malloc(3) が管理していないと, 同じ領域が別の用途で使われてしまうということになるので。 で,new で取ってきたのも malloc が管理しているんだったら, delete しても free(3) に渡されるだけだから, デストラクタとかがなければ問題にならない気がする。 と思って gdb でアタッチして試し

for と while

どうでもいいけど for 文と while 文だったら for 文を優先して使うことが多い気がする。 繰り返し系は最初に for 文の方で書いてみて,変数の初期化の部分と更新の部分が空欄になったら while に変えるみたいな。どうでもいいけど。 while 文は途中で continue とかすると変数の更新を忘れがちなので, 文法に組み込まれている for 文の方が都合が良いという理由はありそうな気がするけど 自分でもよくわからない。

PAM で遊んでみた

PAM で遊んでみた。PAM ってのは Pluggable Authentication Manager の略らしく, Linux のユーザー認証のしくみで,これの設定次第でユーザーが自由に認証の方法を変更できる。 例えばパスワード認証の代わりに指紋認証を使いたいみたいな場合もこの PAM のモジュールで実現できるはず。 当たり前だが,設定次第で認証システムを破綻(?)させることも可能で,常にログインできる PAM モジュールを使ってパスワードなしでログイン可能にしてしまったり,逆に絶対にログインできない PAM モジュールで全ユーザーを締め出したりもできてしまう。てなわけでまあ割とヤバイ部分で遊んだ。 PAM のモジュールを書くのはとても簡単な部類で,常にログインできるやつとかだと下みたいなコードで できたり