LD_PRELOADでprintfを後から差し替える

# TL;DR
ビルド済みのバイナリの関数を後から差し替える方法のまとめ

※本記事の方法は共有ライブラリの関数のみ有効

# 差し替え対象コード
差し替え対象のサンプルとしてptintfとfprintfのそれぞれ可変長引数ありとなしを用意する。

```c:print.c
#include <stdio.h>

int main(int argc, char const* argv[])
{
    printf("printf no args\n");
    printf("printf %d\n", 1);

    fprintf(stdout, "fprintf no args\n");
    fprintf(stdout, "fprintf %d\n", 1);

    return 0;
}
```

ビルドと実行結果は以下になる。

```shell-session:ビルドと実行結果
$ gcc print.c 
$ ./a.out 
printf no args
printf 1
fprintf no args
fprintf 1
```

# 差し替えコード
次にLD_PRELOADにて差し替えを行う用のコードを用意する。
可変長引数の有無にかかわらず基本的には差し替えたい関数の定義を持ってきて再定義すれば良い。

```c:override.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
	const char *c = "override fwrite\n";
	write(1, c, strlen(c)); // stdout
	return 0;
}

int fprintf (FILE *__restrict __stream, const char *__restrict __format, ...) {
	const char *c = "override fprintf\n";
	write(1, c, strlen(c)); // stdout
	return 0;
}

int puts(const char *s) {
	const char *c = "override puts\n";
	write(1, c, strlen(c)); // stdout
	return 0;
}

int printf (const char *__restrict __format, ...) {
	const char *c = "override printf\n";
	write(1, c, strlen(c)); // stdout
	return 0;
}
```

これをLD_PRELOADで指定して実行すれば、定義を後から差し替えることができる。

```shell-session:ビルドと実行結果
$ gcc -shared -fPIC override.c -o liboverride.so
$ LD_PRELOAD=./liboverride.so ./a.out
override puts
override printf
override fwrite
override fprintf
```

ただし、GCCでは `printf` と `fprintf` で可変長引数の要素が0の場合、コンパイル時にそれぞれ `puts` と `fwrite` に置き替えられてしまう。(GCCは-O0でも置き替えは有効、clang 3.6では-O0ではprintfのまま置き換えなし、-O1以上で置き換え)

```
$ gcc -S print.c
$ cat print.s
        .file   "print.c"
        .section        .rodata
.LC0:
        .string "printf no args"
.LC1:
        .string "printf %d\n"
.LC2:
        .string "fprintf no args\n"
.LC3:
        .string "fprintf %d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movq    %rsi, -16(%rbp)
        movl    $.LC0, %edi
        call    puts             <- printf が puts になっている
        movl    $1, %esi
        movl    $.LC1, %edi
        movl    $0, %eax
        call    printf
        movq    stdout(%rip), %rax
        movq    %rax, %rcx
        movl    $16, %edx
        movl    $1, %esi
        movl    $.LC2, %edi
        call    fwrite             <- fprintf が fwrite になっている
        movq    stdout(%rip), %rax
        movl    $1, %edx
        movl    $.LC3, %esi
        movq    %rax, %rdi
        movl    $0, %eax
        call    fprintf
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
        .section        .note.GNU-stack,"",@progbits
```

GCCでこの置き替えをもし無効化したい場合は `-fno-builtin-printf` `-fno-builtin-fprintf` をつけてビルドすれば良い

```shell-session:差し替えを無効化したビルドと実行結果
$ gcc -fno-builtin-printf -fno-builtin-fprintf print.c                                                                                                                
$ LD_PRELOAD=./liboverride.so ./a.out                                                                                                                                 
override printf
override printf
override fprintf
override fprintf
```

# 注意点
当然といえば当然だけれども、差し替えた関数内で再度同じ関数を呼ぶと再帰呼び出しになり抜けられなくなる。
例えばprintfを差し替える場合は差し替え処理で使用したライブラリ内で使われていたりもするため、別途気をつける必要がある。

```c:再帰呼び出し
int puts(const char *s) {
        const char *c = "override puts\n";
        write(1, c, strlen(c)); // stdout

        puts("recursive call?"); // ※putsから抜けられなくなる

        return 0;
}
```

# 参考書籍
『BINARY HACKS』 #HACK 60 : LD_PRELOADで共有ライブラリを差し替える

# 関連

[実行時の関数全ての in/out をトレースする - Qiita](http://qiita.com/naohikowatanabe/items/d559858734d8a02f0d8a)

# 参考
[Linux 共有ライブラリ(.so)の動作を変える。LD_PRELOADで楽しく遊ぶ：Garbage In Garbage Out](http://g1g0.com/2012/04/1790/)
[LD_PRELOADで関数フックしてみよう！ - バイナリの歩き方](http://07c00.hatenablog.com/entry/2013/09/02/003629)
[LD_PRELOADで動的ライブラリ関数を上書きする | Siguniang's Blog](https://siguniang.wordpress.com/2015/05/15/override-functions-with-ld_preload/)