第25章 malloc を strace で検証してみる

目次

25.1. ASLR と共有オブジェクト

malloc って裏で何してんでしょうね?

まあ興味もったってプログラマーには影響ないんでしょう… ええ… 

(´・ω・`)

確かに強いプログラマーのブログとか読むと glibc とか dlmalloc のアルゴリズムとか完全理解した高位階の魔術を駆使したウィザードリーをチラ見すると、「はえー、すげえ、草」といった感想しかないでしょう…

わかります… (´・ω・`)

仰られる通り通常のプログラマーには不要な技術ではありますが、表層的にではあっても検証してみると以外に楽しかったりしますんで、やってみましょう。

まずは空のプログラムをビルドしてみましょう。

empty.c. 

  1 int main(){}

ビルドしたバイナリを strace すると以下のようになります。

$ gcc empty.c -g
$ strace ./a.out
execve("./a.out", ["./a.out"], 0x7fff8c9cf800 /* 57 vars */) = 0
brk(NULL)                               = 0x55672d205000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/komatsu/stderred/build/libstderred.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\23\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=23280, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50d732d000
mmap(NULL, 2113992, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f50d6f03000
mprotect(0x7f50d6f07000, 2093056, PROT_NONE) = 0
mmap(0x7f50d7106000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f50d7106000
close(3)                                = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=127214, ...}) = 0
mmap(NULL, 127214, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f50d730d000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f50d6b12000
mprotect(0x7f50d6cf9000, 2097152, PROT_NONE) = 0
mmap(0x7f50d6ef9000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f50d6ef9000
mmap(0x7f50d6eff000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50d6eff000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\16\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=14560, ...}) = 0
mmap(NULL, 2109712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f50d690e000
mprotect(0x7f50d6911000, 2093056, PROT_NONE) = 0
mmap(0x7f50d6b10000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f50d6b10000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50d730a000
arch_prctl(ARCH_SET_FS, 0x7f50d730a740) = 0
mprotect(0x7f50d6ef9000, 16384, PROT_READ) = 0
mprotect(0x7f50d6b10000, 4096, PROT_READ) = 0
mprotect(0x7f50d7106000, 4096, PROT_READ) = 0
mprotect(0x55672c5b9000, 4096, PROT_READ) = 0
mprotect(0x7f50d732f000, 4096, PROT_READ) = 0
munmap(0x7f50d730d000, 127214)          = 0
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

この出力内容では意味不明でしょうが malloc() のコールを追加して見ると良いでしょう。

main.c. 

  1 #include <stdlib.h>
  2 #include <stdio.h>
  3
  4 int main()
  5 {
  6   void *ptr = malloc(sizeof(int));
  7   free(ptr);
  8   return 0;
  9 }

ビルドした実行可能ファイルに strace をした結果を比較すれば malloc() のコールの内部実装がわかろうというものです。

ではやってみましょう。

$ gcc main.c -g
$ strace ./a.out
execve("./a.out", ["./a.out"], 0x7fffa58495b0 /* 57 vars */) = 0
brk(NULL)                               = 0x55620ea32000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/komatsu/stderred/build/libstderred.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\23\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=23280, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23702cc000
mmap(NULL, 2113992, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f236fea2000
mprotect(0x7f236fea6000, 2093056, PROT_NONE) = 0
mmap(0x7f23700a5000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f23700a5000
close(3)                                = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=127214, ...}) = 0
mmap(NULL, 127214, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f23702ac000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f236fab1000
mprotect(0x7f236fc98000, 2097152, PROT_NONE) = 0
mmap(0x7f236fe98000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f236fe98000
mmap(0x7f236fe9e000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f236fe9e000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\16\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=14560, ...}) = 0
mmap(NULL, 2109712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f236f8ad000
mprotect(0x7f236f8b0000, 2093056, PROT_NONE) = 0
mmap(0x7f236faaf000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f236faaf000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f23702a9000
arch_prctl(ARCH_SET_FS, 0x7f23702a9740) = 0
mprotect(0x7f236fe98000, 16384, PROT_READ) = 0
mprotect(0x7f236faaf000, 4096, PROT_READ) = 0
mprotect(0x7f23700a5000, 4096, PROT_READ) = 0
mprotect(0x55620e9ce000, 4096, PROT_READ) = 0
mprotect(0x7f23702ce000, 4096, PROT_READ) = 0
munmap(0x7f23702ac000, 127214)          = 0
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
brk(NULL)                               = 0x55620ea32000
brk(0x55620ea53000)                     = 0x55620ea53000
exit_group(0)                           = ?
+++ exited with 0 +++

2 つを比べると最後のほうに2行だけ追加されています。

brk(NULL)                               = 0x55620ea32000
brk(0x55620ea53000)                     = 0x55620ea53000

後で詳しめに説明しますが brk() はヒープ領域のアドレス取得や、割り当てを行う関数です。

brk(NULL) は現在のヒープ領域の終端アドレス、そして brk(0x55620ea53000) は 「 0x55620ea32000 」 から 「 0x55620ea53000 」にヒープのアドレスを拡大していますね。

Copyright 2018-2019, by Masaki Komatsu