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;
}

void *reallocarray(void *ptr, size_t nmemb, size_t size) {
    return realloc(ptr, nmemb * size);
}

	

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;
}

	

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

C++ で charstd::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++ 両方で単項演算子 + の結果は汎整数拡張がなされた結果になる。 hogechar+hogeint の型を持つということになり、 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_METHODScwrap とかを入れておくと cwrap から使えるようになる。

ccall や cwrap で関数を呼び出すときの基本的な方法

Module.cwrap('hoge', 'string', 'number')(5);

	

型ごとの上限・下限

一応型ごとの大きさは規格では決まってない(最小限のビット数はあるけどこれを書いても意味がない) けど 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

コード

比較演算の結果の値

C の比較演算の結果で,true の場合は 1false の場合は 0 っていうのが常識的な 挙動のような気がするけど,未規定とかじゃなくてちゃんと決まっているのか, 前から気になっていたのでちゃんと調べた。 素直に検索しても出なかったけど,ちゃんと規格(に近いもの)を見に行ったら 簡単に書いてあるのが見つかって,どうやら true1false0 になるってのは 決まってるっぽい。

つまり,以下のコードは環境に依存せずに 2 を出力する。

#include <stdio.h>

int main(void) {
    int num = (0 == 0) + (0 == 0);
    printf ("%d\n", num);
    return 0;
}

あと,結果の型は int

参考

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つのトークンは, [, ], {, }, #, ## と同じように解釈されるらしい。

なんじゃそりゃ。

参照

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

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

PAM で遊んでみた

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

PAM のモジュールを書くのはとても簡単な部類で,常にログインできるやつとかだと下みたいなコードで できたりする。

Ubuntu で freeglut を使う

環境は Ubuntu 18.04.3 LTS x86_64 です.

必要なパッケージを入れる

$ sudo apt install freeglut3-dev

試す

なんか適当に

#include <stdlib.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>

static void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glFlush();
}

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitWindowSize(256,256);
    glutCreateWindow("Hello");
    glClearColor(1, 0, 0, 1);
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

256x256 のウィンドウを赤く塗りつぶすだけです.

で,コンパイル

$ gcc main.c -lglut -lGLU -lGL -lm

できました.

screenshot

適当に Makefile にしておく

ライブラリが多くて毎回指定するのは面倒なので, Makefile 必須っぽいです.