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++ で 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);
型ごとの上限・下限
一応型ごとの大きさは規格では決まってない(最小限のビット数はあるけどこれを書いても意味がない)
けど 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
の場合は 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
。
参考
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
できました.
適当に Makefile にしておく
ライブラリが多くて毎回指定するのは面倒なので, Makefile 必須っぽいです.