heap に実行属性つけたのは誰ですか!

今言えない子は、後で先生にこっそり打ち明けなさい。

http://d.hatena.ne.jp/w_o/20060808#p2

を見てて、うわーすばらしーと思いつつも、疑問点が1つ。なんで heap に置いたコードが実行できるんだろうなー、と。まず、トランポリンが入った場合に、スタックに実行属性つけてるのはきっと GCC だろうと思ってました。きっと自動的にスタック全部なり一部にきっと実行属性つけるんだ。

i@u ~/test> cat inner_func.c
#include <stdio.h>

__attribute__((constructor)) void dump_maps() {
    FILE* fp;
    int c;
    fp = fopen("/proc/self/maps", "rb");
    while ((c = fgetc(fp)) != EOF) {
        putchar(c);
    }
    fclose(fp);
}

int main() {
    int i;
    int(*f[10])();
    for (i = 0; i < 10; i++) {
        int a() { return i; }
#ifdef STACK_EXEC
        f[i] = &a;
#endif
    }
    return 0;
}

まぁこんなコード書いて。

i@u ~/test> gcc inner_func.c
i@u ~/test> objdump -x a.out > rw
i@u ~/test> gcc -DSTACK_EXEC inner_func.c
i@u ~/test> objdump -x a.out > rwx

などとして、 objdump の結果を見比べてやると、前者は、

   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rw-

となってる部分が、

   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rwx

となってた。スタック全体に実行属性つけたのは GCC でした。これは予想通り。でも wo さんのコードは malloc したヒープの空間に関数全体コピーしてるよなー、んでそれ実行属性無いから実行できなくね…?と思ったんですが、なんか実際動くわけですね。最初のコードは、 dump_maps 関数で /proc/self/maps の中身を吐き出してるんですが、前者は、

08048000-08049000 r-xp 00000000 03:07 249155     /home/i/test/a.out
08049000-0804a000 rw-p 00000000 03:07 249155     /home/i/test/a.out
0804a000-0806b000 rw-p 0804a000 00:00 0          [heap]
41000000-41019000 r-xp 00000000 03:06 219531     /lib/ld-2.4.so
41019000-4101a000 r--p 00018000 03:06 219531     /lib/ld-2.4.so
4101a000-4101b000 rw-p 00019000 03:06 219531     /lib/ld-2.4.so
4101d000-4114a000 r-xp 00000000 03:06 234583     /lib/libc-2.4.so
4114a000-4114c000 r--p 0012d000 03:06 234583     /lib/libc-2.4.so
4114c000-4114d000 rw-p 0012f000 03:06 234583     /lib/libc-2.4.so
4114d000-41150000 rw-p 4114d000 00:00 0
b7f6f000-b7f70000 rw-p b7f6f000 00:00 0
b7f89000-b7f8b000 rw-p b7f89000 00:00 0
bfe74000-bfe8b000 rw-p bfe74000 00:00 0          [stack]
ffffe000-fffff000 ---p 00000000 00:00 0          [vdso]

で、後者は、

08048000-08049000 r-xp 00000000 03:07 249155     /home/i/test/a.out
08049000-0804a000 rwxp 00000000 03:07 249155     /home/i/test/a.out
0804a000-0806b000 rwxp 0804a000 00:00 0          [heap]
41000000-41019000 r-xp 00000000 03:06 219531     /lib/ld-2.4.so
41019000-4101a000 r-xp 00018000 03:06 219531     /lib/ld-2.4.so
4101a000-4101b000 rwxp 00019000 03:06 219531     /lib/ld-2.4.so
4101d000-4114a000 r-xp 00000000 03:06 234583     /lib/libc-2.4.so
4114a000-4114c000 r-xp 0012d000 03:06 234583     /lib/libc-2.4.so
4114c000-4114d000 rwxp 0012f000 03:06 234583     /lib/libc-2.4.so
4114d000-41150000 rwxp 4114d000 00:00 0
b7f83000-b7f84000 rwxp b7f83000 00:00 0
b7f9d000-b7f9f000 rwxp b7f9d000 00:00 0
bfb88000-bfb9f000 rwxp bfb88000 00:00 0          [stack]
ffffe000-fffff000 ---p 00000000 00:00 0          [vdso]

となってました。後者はなんか、 [stack] といっしょになんか [heap] にも実行属性がついてるわけですね。こりゃ動くはずなんですが、誰が実行属性つけてるねん、と気になりました。

両者を strace してみても、特に不審な mprotect だとかもなく、両方とも同じように brk が呼ばれて heap を確保してました。

んじゃ linux kernel かねぇ、と、 sys_brk を見ると、

https://www.codeblog.org/gonzui/markup/linux-2.6.16.18/mm/mmap.c#l213

この中で do_brk が呼び出されて、まぁざーと見ていくと

https://www.codeblog.org/gonzui/markup/linux-2.6.16.18/mm/mmap.c#l1897

これかなーと。この flags ってのが属性になるみたい。 VM_DATA_DEFAULT_FLAGS と VM_ACCOUNT は見るからに定数なので、 mm->def_flags を眺めると、 mm_struct 内のこのへんで定義されてました。

https://www.codeblog.org/gonzui/markup/linux-2.6.16.18/include/linux/sched.h?q=mm_struct#l327

なんか見るからにスタックの属性に混じってるので、おそらくはここにスタックの属性情報入っててそれがヒープの属性情報に影響してるんだろうなーと思ったんですが、そうなっている理由(なんでスタックとヒープの属性をそろえるんだろう…?)、本当にそうか、の共に自信ありません。まぁカーネル見るとかめんどいのでやめます。

あ、あと、トランポリンの解説については、こういう時こそ gcc -S して自分で見なさい!でいい気もするんですが、まぁこちらで解説されてるような気が。 http://d.hatena.ne.jp/yupo5656/20040602/p1

ついでにレベルは…正の方は Boehm GC 使ってないけど Ruby 使うはクリアしててハッシュ作って飽きたとかもクリアしてるので 6 から 8 程度、負の方は core いじってた時に毎回コード変わってうぜーと思ったので -7 せしょうか。どう考えればいいのかよくわからないですけど、足してみるとレベル 0 くらいなのでちょうど良いのでしょうか。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h