OpenBSDのlibcにはexplicit_bzero(3)という関数があって、変数を使い終わった後にmemset(3)で0クリアできるようにする関数。
これはコンパイラがmemset()後にその変数が使われないならmemset()自体いらないだろうという最適化によってmemset()自体が消されるのを防いでいます。
これの仕組みはコミットログにて解説されているんだけど、内容は至って簡単だけどなるほど〜と思うわけですね。
コードはこれです。
/* $OpenBSD: explicit_bzero.c,v 1.3 2014/06/21 02:34:26 matthew Exp $ */ /* * Public domain. * Written by Matthew Dempsky. */ #include <string.h> __attribute__((weak)) void __explicit_bzero_hook(void *buf, size_t len) { } void explicit_bzero(void *buf, size_t len) { memset(buf, 0, len); __explicit_bzero_hook(buf, len); }
仕組みとしてはmemset()の呼び出し後に何もしない関数を呼んでいるんだけど、これはweakシンボルが付いていてコンパイル時点では何を呼ぶのか決められないようになっているというのがポイントでしょうね。__explicit_bzero_hook()がコンパイル・リンク時に決まらないのでこれについては最適化ができないのでコンパイラはmemset()を消すことができないというところでしょう。
では、ちょっと試してみます。
まずはmemset_optimized.c。
#include <stdio.h> #include <string.h> void clear(void *buf, size_t len) { memset(buf, 0x0, len); } int main(int argc ,char **argv) { char buf[256] = { 0 }; strncpy(buf, argv[0], sizeof(buf) - 1); printf("argv[0]: %s\n", buf); clear(buf, sizeof(buf)); return 0; }
これを最適化のレベルを2にしてアセンブラまで出す。
$ gcc -Wall -O2 -S memset_optimized.c
こんな風になってprintf()後のclear()関数の呼び出しごと消えています。
main: .LFB26: .cfi_startproc subq $264, %rsp .cfi_def_cfa_offset 272 movq (%rsi), %rsi xorl %eax, %eax movq %rsp, %rdi movl $32, %ecx movl $255, %edx rep stosq movq %rsp, %rdi call strncpy movq %rsp, %rsi movl $.LC1, %edi xorl %eax, %eax call printf xorl %eax, %eax addq $264, %rsp .cfi_def_cfa_offset 8 ret .cfi_endproc
つぎにweak属性を付けたダミー関数を呼ぶ場合。
#include <stdio.h> #include <string.h> void __attribute__((weak)) dummy_func(void) { } void clear(void *buf, size_t len) { memset(buf, 0x0, len); dummy_func(); } int main(int argc ,char **argv) { char buf[256] = { 0 }; strncpy(buf, argv[0], sizeof(buf) - 1); printf("argv[0]: %s\n", buf); clear(buf, sizeof(buf)); return 0; }
clear()自体はinline展開されてますがその後にmemset()らしき処理(コンパイラの最適化でmemset()を呼ばずに同様に処理に置き換わっているから)が行われた後にdummy_func()が呼ばれてます。
main: .LFB27: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 pushq %rbx .cfi_def_cfa_offset 24 .cfi_offset 3, -24 xorl %ebp, %ebp movq %rbp, %rax movl $32, %ecx movl $255, %edx subq $264, %rsp .cfi_def_cfa_offset 288 movq (%rsi), %rsi movq %rsp, %rdi rep stosq movq %rsp, %rdi call strncpy movq %rsp, %rsi movl $.LC2, %edi xorl %eax, %eax call printf movq %rbp, %rax movq %rsp, %rdi movl $32, %ecx rep stosq call dummy_func addq $264, %rsp .cfi_def_cfa_offset 24 xorl %eax, %eax popq %rbx .cfi_def_cfa_offset 16 popq %rbp .cfi_def_cfa_offset 8 ret .cfi_endproc
ちなみに、コンパイルオプションで-fno-builtinを使うとmemset()が呼ばれるようになります。
main: .LFB27: .cfi_startproc subq $264, %rsp .cfi_def_cfa_offset 272 movq (%rsi), %rsi xorl %eax, %eax movq %rsp, %rdi movl $32, %ecx movl $255, %edx rep stosq movq %rsp, %rdi call strncpy movq %rsp, %rsi movl $.LC2, %edi xorl %eax, %eax call printf movq %rsp, %rdi movl $256, %edx xorl %esi, %esi call memset call dummy_func xorl %eax, %eax addq $264, %rsp .cfi_def_cfa_offset 8 ret .cfi_endproc
リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY)
- 作者: 坂井弘亮
- 出版社/メーカー: CQ出版
- 発売日: 2010/08
- メディア: 単行本
- 購入: 6人 クリック: 55回
- この商品を含むブログ (16件) を見る