情弱ログ

参考にならないので当てにしないでください

PIE(position-independent executable)なオブジェクトの中身を見てみる

巷ではSpectre/Meltdownが話題になっている.

有効な対策として(K)ASLRが挙げられており,これはプロセス実行時のメモリ空間の配置をランダマイズすることで,例えば攻撃者が配置したコードのアドレスへのJMP等を防ぐことができる.
詳しくはシェルコードでググろう(解説できるほど詳しくない).

共有ライブラリは原則的にPIC(position-independent code)としてビルドされており,これらはASLRの恩恵に与ることができる.

しかし,通常のアプリケーションは絶対アドレスを用いて記述されているため,ASLRによるランダマイズが適用できない.
そこで,セキュリティ向上のために相対アドレスを用いたPIE(position-independent executable)と呼ばれる形式が通常のアプリケーションにおいても使用されている.

昔はセキュリティ上重要なファイルだけがPIEでビルドされていたらしいが,最近ではGentoo Linuxのprofile 17.0からPIEでのビルドがデフォルトオプションになったりしている.(これ全パッケージのリビルドが必要なので,まだ更新できてないです.同時にpython3.4がstableに降ってきたりして酷いことになりました…)
https://www.gentoo.org/support/news-items/2017-11-30-new-17-profiles.html

しかし,PIEでビルドした場合はパフォーマンスの低下が懸念されている.論文ではベンチマークであるSPEC CPU2006を用いて評価実験を行い,幾何平均で9.4%のパフォーマンス低下という結果を得たと述べている.
だが,実際にPIEを用いることで何がボトルネックになっているのか分からないので,今回はPIEなオブジェクトの中身を見てみる.
中身が見たくて見てるので,パフォーマンス低下の考察なら論文でしてると思うよ.


検証には前回と同じファイルを用いる.また,実行環境はGNU/Linux Fedora 25を用い,コンパイラgcc 6.4.1を用いた.

#include <cstdio>

class Base {
public:
    Base() { printf("Base::Base() has called\n"); }
    virtual void hoge() { printf("Base::hoge() has called\n"); }
    virtual void fuga() { printf("Base::fuga() has called\n"); }
};

class Derived : public Base {
public:
    Derived() { printf("Derived::Derived() has called\n"); }
    void fuga() { printf("Derived::fuga() has called\n"); }
};

void func(Base &x)
{
    x.hoge();
    x.fuga();
}

int main()
{
    Derived obj;
    func(obj);

    return 0;
}

PIEとしてビルドする場合,-fPIEもしくは-fpieオプションをつけてコンパイルし,リンク時に-pieオプションをつける.

$ g++ -c -fPIE vtable.cpp
$ g++ -o vtable_pie -pie vtable.o
$ g++ -o vtable_pie -pie -fPIE vtable.cpp # 一発でやる場合
$ ./vtable_pie
Base::Base() has called
Derived::Derived() has called
Base::hoge() has called
Derived::fuga() has called

比較用に普通にビルドしたファイルも用意しておく.

$ g++ -o vtable_normal vtable.cpp
$ ./vtable_normal
Base::Base() has called
Derived::Derived() has called
Base::hoge() has called
Derived::fuga() has called

ELFヘッダを見てみる.まずは普通にビルドした方から.

$ readelf -h vtable_normal
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4005b0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          7288 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

次にPIEな方.

$ readelf -h vtable_pie
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x7f0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          11504 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         32
  Section header string table index: 29

タイプが「EXEC (Executable file)」から「DYN (Shared object file)」になっており,またEntry point addressが小さくなっている.あとセクションヘッダの数が異なっており,そのせいかストリングテーブルのインデックスも異なっている.

セクションヘッダを見てみる.普通の方.

readelf -S vtable_normal
There are 30 section headers, starting at offset 0x1c78:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000028  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002c0  000002c0
       00000000000000d8  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400398  00000398
       0000000000000108  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004004a0  000004a0
       0000000000000012  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004004b8  000004b8
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             00000000004004f8  000004f8
       0000000000000060  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400558  00000558
       0000000000000018  0000000000000018  AI       5    23     8
  [11] .init             PROGBITS         0000000000400570  00000570
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400590  00000590
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         00000000004005b0  000005b0
       0000000000000272  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000400824  00000824
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         0000000000400830  00000830
       0000000000000116  0000000000000000   A       0     0     8
  [16] .eh_frame_hdr     PROGBITS         0000000000400948  00000948
       0000000000000064  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         00000000004009b0  000009b0
       00000000000001b4  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000600dd8  00000dd8
       0000000000000008  0000000000000000  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000600de0  00000de0
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600de8  00000de8
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600df0  00000df0
       0000000000000200  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff0  00000ff0
       0000000000000010  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000020  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000601020  00001020
       0000000000000004  0000000000000000  WA       0     0     1
  [25] .bss              NOBITS           0000000000601028  00001024
       00000000000000b8  0000000000000000  WA       0     0     8
  [26] .comment          PROGBITS         0000000000000000  00001024
       0000000000000058  0000000000000001  MS       0     0     1
  [27] .shstrtab         STRTAB           0000000000000000  00001b6e
       0000000000000108  0000000000000000           0     0     1
  [28] .symtab           SYMTAB           0000000000000000  00001080
       00000000000007b0  0000000000000018          29    46     8
  [29] .strtab           STRTAB           0000000000000000  00001830
       000000000000033e  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

PIEな方.

$ readelf -S vtable_pie
There are 32 section headers, starting at offset 0x2cf0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000000254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000000274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000000298  00000298
       0000000000000030  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000000002c8  000002c8
       0000000000000150  0000000000000018   A       6     2     8
  [ 6] .dynstr           STRTAB           0000000000000418  00000418
       000000000000012f  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000000548  00000548
       000000000000001c  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000000568  00000568
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             00000000000005a8  000005a8
       00000000000001e0  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000000788  00000788
       0000000000000018  0000000000000018  AI       5    25     8
  [11] .init             PROGBITS         00000000000007a0  000007a0
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000000007c0  000007c0
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000000007e0  000007e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000000007f0  000007f0
       00000000000002c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000000ab4  00000ab4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000000ac0  00000ac0
       0000000000000097  0000000000000000   A       0     0     8
  [17] .eh_frame_hdr     PROGBITS         0000000000000b58  00000b58
       0000000000000064  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000000bc0  00000bc0
       00000000000001b4  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000201d38  00001d38
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000201d40  00001d40
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000201d48  00001d48
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .data.rel.ro      PROGBITS         0000000000201d50  00001d50
       0000000000000070  0000000000000000  WA       0     0     8
  [23] .dynamic          DYNAMIC          0000000000201dc0  00001dc0
       0000000000000210  0000000000000010  WA       6     0     8
  [24] .got              PROGBITS         0000000000201fd0  00001fd0
       0000000000000030  0000000000000008  WA       0     0     8
  [25] .got.plt          PROGBITS         0000000000202000  00002000
       0000000000000020  0000000000000008  WA       0     0     8
  [26] .data             PROGBITS         0000000000202020  00002020
       0000000000000004  0000000000000000  WA       0     0     1
  [27] .bss              NOBITS           0000000000202024  00002024
       0000000000000004  0000000000000000  WA       0     0     1
  [28] .comment          PROGBITS         0000000000000000  00002024
       0000000000000058  0000000000000001  MS       0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  00002bd2
       0000000000000119  0000000000000000           0     0     1
  [30] .symtab           SYMTAB           0000000000000000  00002080
       00000000000007f8  0000000000000018          31    48     8
  [31] .strtab           STRTAB           0000000000000000  00002878
       000000000000035a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

.plt.gotと.data.rel.roというセクションが増えているようだ.ググったらRELRO (RELocation Read-Only)という別?の技術が出てきたが何も分からんので宿題にしたい.

アセンブルしてみる.

 objdump -d vtable_pie |c++filt

vtable_pie:     file format elf64-x86-64


Disassembly of section .init:

00000000000007a0 <_init>:
 7a0:	48 83 ec 08          	sub    $0x8,%rsp
 7a4:	48 8b 05 25 18 20 00 	mov    0x201825(%rip),%rax        # 201fd0 <_DYNAMIC+0x210>
 7ab:	48 85 c0             	test   %rax,%rax
 7ae:	74 02                	je     7b2 <_init+0x12>
 7b0:	ff d0                	callq  *%rax
 7b2:	48 83 c4 08          	add    $0x8,%rsp
 7b6:	c3                   	retq

Disassembly of section .plt:

00000000000007c0 <puts@plt-0x10>:
 7c0:	ff 35 42 18 20 00    	pushq  0x201842(%rip)        # 202008 <_GLOBAL_OFFSET_TABLE_+0x8>
 7c6:	ff 25 44 18 20 00    	jmpq   *0x201844(%rip)        # 202010 <_GLOBAL_OFFSET_TABLE_+0x10>
 7cc:	0f 1f 40 00          	nopl   0x0(%rax)

00000000000007d0 <puts@plt>:
 7d0:	ff 25 42 18 20 00    	jmpq   *0x201842(%rip)        # 202018 <_GLOBAL_OFFSET_TABLE_+0x18>
 7d6:	68 00 00 00 00       	pushq  $0x0
 7db:	e9 e0 ff ff ff       	jmpq   7c0 <_init+0x20>

Disassembly of section .plt.got:

00000000000007e0 <.plt.got>:
 7e0:	ff 25 12 18 20 00    	jmpq   *0x201812(%rip)        # 201ff8 <_DYNAMIC+0x238>
 7e6:	66 90                	xchg   %ax,%ax

Disassembly of section .text:

00000000000007f0 <_start>:
 7f0:	31 ed                	xor    %ebp,%ebp
 7f2:	49 89 d1             	mov    %rdx,%r9
 7f5:	5e                   	pop    %rsi
 7f6:	48 89 e2             	mov    %rsp,%rdx
 7f9:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
 7fd:	50                   	push   %rax
 7fe:	54                   	push   %rsp
 7ff:	4c 8d 05 aa 02 00 00 	lea    0x2aa(%rip),%r8        # ab0 <__libc_csu_fini>
 806:	48 8d 0d 33 02 00 00 	lea    0x233(%rip),%rcx        # a40 <__libc_csu_init>
 80d:	48 8d 3d 45 01 00 00 	lea    0x145(%rip),%rdi        # 959 <main>
 814:	ff 15 c6 17 20 00    	callq  *0x2017c6(%rip)        # 201fe0 <_DYNAMIC+0x220>
 81a:	f4                   	hlt
 81b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

0000000000000820 <deregister_tm_clones>:
 820:	48 8d 3d 01 18 20 00 	lea    0x201801(%rip),%rdi        # 202028 <__TMC_END__>
 827:	48 8d 05 01 18 20 00 	lea    0x201801(%rip),%rax        # 20202f <__TMC_END__+0x7>
 82e:	55                   	push   %rbp
 82f:	48 29 f8             	sub    %rdi,%rax
 832:	48 89 e5             	mov    %rsp,%rbp
 835:	48 83 f8 0e          	cmp    $0xe,%rax
 839:	76 15                	jbe    850 <deregister_tm_clones+0x30>
 83b:	48 8b 05 a6 17 20 00 	mov    0x2017a6(%rip),%rax        # 201fe8 <_DYNAMIC+0x228>
 842:	48 85 c0             	test   %rax,%rax
 845:	74 09                	je     850 <deregister_tm_clones+0x30>
 847:	5d                   	pop    %rbp
 848:	ff e0                	jmpq   *%rax
 84a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
 850:	5d                   	pop    %rbp
 851:	c3                   	retq
 852:	0f 1f 40 00          	nopl   0x0(%rax)
 856:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 85d:	00 00 00

0000000000000860 <register_tm_clones>:
 860:	48 8d 3d c1 17 20 00 	lea    0x2017c1(%rip),%rdi        # 202028 <__TMC_END__>
 867:	48 8d 35 ba 17 20 00 	lea    0x2017ba(%rip),%rsi        # 202028 <__TMC_END__>
 86e:	55                   	push   %rbp
 86f:	48 29 fe             	sub    %rdi,%rsi
 872:	48 89 e5             	mov    %rsp,%rbp
 875:	48 c1 fe 03          	sar    $0x3,%rsi
 879:	48 89 f0             	mov    %rsi,%rax
 87c:	48 c1 e8 3f          	shr    $0x3f,%rax
 880:	48 01 c6             	add    %rax,%rsi
 883:	48 d1 fe             	sar    %rsi
 886:	74 18                	je     8a0 <register_tm_clones+0x40>
 888:	48 8b 05 61 17 20 00 	mov    0x201761(%rip),%rax        # 201ff0 <_DYNAMIC+0x230>
 88f:	48 85 c0             	test   %rax,%rax
 892:	74 0c                	je     8a0 <register_tm_clones+0x40>
 894:	5d                   	pop    %rbp
 895:	ff e0                	jmpq   *%rax
 897:	66 0f 1f 84 00 00 00 	nopw   0x0(%rax,%rax,1)
 89e:	00 00
 8a0:	5d                   	pop    %rbp
 8a1:	c3                   	retq
 8a2:	0f 1f 40 00          	nopl   0x0(%rax)
 8a6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 8ad:	00 00 00

00000000000008b0 <__do_global_dtors_aux>:
 8b0:	80 3d 6d 17 20 00 00 	cmpb   $0x0,0x20176d(%rip)        # 202024 <_edata>
 8b7:	75 27                	jne    8e0 <__do_global_dtors_aux+0x30>
 8b9:	48 83 3d 37 17 20 00 	cmpq   $0x0,0x201737(%rip)        # 201ff8 <_DYNAMIC+0x238>
 8c0:	00
 8c1:	55                   	push   %rbp
 8c2:	48 89 e5             	mov    %rsp,%rbp
 8c5:	74 0c                	je     8d3 <__do_global_dtors_aux+0x23>
 8c7:	48 8d 3d 82 14 20 00 	lea    0x201482(%rip),%rdi        # 201d50 <__dso_handle>
 8ce:	e8 0d ff ff ff       	callq  7e0 <puts@plt+0x10>
 8d3:	e8 48 ff ff ff       	callq  820 <deregister_tm_clones>
 8d8:	5d                   	pop    %rbp
 8d9:	c6 05 44 17 20 00 01 	movb   $0x1,0x201744(%rip)        # 202024 <_edata>
 8e0:	f3 c3                	repz retq
 8e2:	0f 1f 40 00          	nopl   0x0(%rax)
 8e6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 8ed:	00 00 00

00000000000008f0 <frame_dummy>:
 8f0:	48 8d 3d 51 14 20 00 	lea    0x201451(%rip),%rdi        # 201d48 <__JCR_END__>
 8f7:	48 83 3f 00          	cmpq   $0x0,(%rdi)
 8fb:	75 0b                	jne    908 <frame_dummy+0x18>
 8fd:	e9 5e ff ff ff       	jmpq   860 <register_tm_clones>
 902:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
 908:	48 8b 05 c9 16 20 00 	mov    0x2016c9(%rip),%rax        # 201fd8 <_DYNAMIC+0x218>
 90f:	48 85 c0             	test   %rax,%rax
 912:	74 e9                	je     8fd <frame_dummy+0xd>
 914:	55                   	push   %rbp
 915:	48 89 e5             	mov    %rsp,%rbp
 918:	ff d0                	callq  *%rax
 91a:	5d                   	pop    %rbp
 91b:	e9 40 ff ff ff       	jmpq   860 <register_tm_clones>

0000000000000920 <func(Base&)>:
 920:	55                   	push   %rbp
 921:	48 89 e5             	mov    %rsp,%rbp
 924:	48 83 ec 10          	sub    $0x10,%rsp
 928:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 92c:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 930:	48 8b 00             	mov    (%rax),%rax
 933:	48 8b 00             	mov    (%rax),%rax
 936:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
 93a:	48 89 d7             	mov    %rdx,%rdi
 93d:	ff d0                	callq  *%rax
 93f:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 943:	48 8b 00             	mov    (%rax),%rax
 946:	48 83 c0 08          	add    $0x8,%rax
 94a:	48 8b 00             	mov    (%rax),%rax
 94d:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
 951:	48 89 d7             	mov    %rdx,%rdi
 954:	ff d0                	callq  *%rax
 956:	90                   	nop
 957:	c9                   	leaveq
 958:	c3                   	retq

0000000000000959 <main>:
 959:	55                   	push   %rbp
 95a:	48 89 e5             	mov    %rsp,%rbp
 95d:	48 83 ec 10          	sub    $0x10,%rsp
 961:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
 965:	48 89 c7             	mov    %rax,%rdi
 968:	e8 75 00 00 00       	callq  9e2 <Derived::Derived()>
 96d:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
 971:	48 89 c7             	mov    %rax,%rdi
 974:	e8 a7 ff ff ff       	callq  920 <func(Base&)>
 979:	b8 00 00 00 00       	mov    $0x0,%eax
 97e:	c9                   	leaveq
 97f:	c3                   	retq

0000000000000980 <Base::Base()>:
 980:	55                   	push   %rbp
 981:	48 89 e5             	mov    %rsp,%rbp
 984:	48 83 ec 10          	sub    $0x10,%rsp
 988:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 98c:	48 8d 15 f5 13 20 00 	lea    0x2013f5(%rip),%rdx        # 201d88 <vtable for Base+0x10>
 993:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 997:	48 89 10             	mov    %rdx,(%rax)
 99a:	48 8d 3d 23 01 00 00 	lea    0x123(%rip),%rdi        # ac4 <_IO_stdin_used+0x4>
 9a1:	e8 2a fe ff ff       	callq  7d0 <puts@plt>
 9a6:	90                   	nop
 9a7:	c9                   	leaveq
 9a8:	c3                   	retq
 9a9:	90                   	nop

00000000000009aa <Base::hoge()>:
 9aa:	55                   	push   %rbp
 9ab:	48 89 e5             	mov    %rsp,%rbp
 9ae:	48 83 ec 10          	sub    $0x10,%rsp
 9b2:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9b6:	48 8d 3d 1f 01 00 00 	lea    0x11f(%rip),%rdi        # adc <_IO_stdin_used+0x1c>
 9bd:	e8 0e fe ff ff       	callq  7d0 <puts@plt>
 9c2:	90                   	nop
 9c3:	c9                   	leaveq
 9c4:	c3                   	retq
 9c5:	90                   	nop

00000000000009c6 <Base::fuga()>:
 9c6:	55                   	push   %rbp
 9c7:	48 89 e5             	mov    %rsp,%rbp
 9ca:	48 83 ec 10          	sub    $0x10,%rsp
 9ce:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9d2:	48 8d 3d 1b 01 00 00 	lea    0x11b(%rip),%rdi        # af4 <_IO_stdin_used+0x34>
 9d9:	e8 f2 fd ff ff       	callq  7d0 <puts@plt>
 9de:	90                   	nop
 9df:	c9                   	leaveq
 9e0:	c3                   	retq
 9e1:	90                   	nop

00000000000009e2 <Derived::Derived()>:
 9e2:	55                   	push   %rbp
 9e3:	48 89 e5             	mov    %rsp,%rbp
 9e6:	48 83 ec 10          	sub    $0x10,%rsp
 9ea:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9ee:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 9f2:	48 89 c7             	mov    %rax,%rdi
 9f5:	e8 86 ff ff ff       	callq  980 <Base::Base()>
 9fa:	48 8d 15 67 13 20 00 	lea    0x201367(%rip),%rdx        # 201d68 <vtable for Derived+0x10>
 a01:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 a05:	48 89 10             	mov    %rdx,(%rax)
 a08:	48 8d 3d fd 00 00 00 	lea    0xfd(%rip),%rdi        # b0c <_IO_stdin_used+0x4c>
 a0f:	e8 bc fd ff ff       	callq  7d0 <puts@plt>
 a14:	90                   	nop
 a15:	c9                   	leaveq
 a16:	c3                   	retq
 a17:	90                   	nop

0000000000000a18 <Derived::fuga()>:
 a18:	55                   	push   %rbp
 a19:	48 89 e5             	mov    %rsp,%rbp
 a1c:	48 83 ec 10          	sub    $0x10,%rsp
 a20:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 a24:	48 8d 3d ff 00 00 00 	lea    0xff(%rip),%rdi        # b2a <_IO_stdin_used+0x6a>
 a2b:	e8 a0 fd ff ff       	callq  7d0 <puts@plt>
 a30:	90                   	nop
 a31:	c9                   	leaveq
 a32:	c3                   	retq
 a33:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 a3a:	00 00 00
 a3d:	0f 1f 00             	nopl   (%rax)

0000000000000a40 <__libc_csu_init>:
 a40:	41 57                	push   %r15
 a42:	41 56                	push   %r14
 a44:	41 89 ff             	mov    %edi,%r15d
 a47:	41 55                	push   %r13
 a49:	41 54                	push   %r12
 a4b:	4c 8d 25 e6 12 20 00 	lea    0x2012e6(%rip),%r12        # 201d38 <__frame_dummy_init_array_entry>
 a52:	55                   	push   %rbp
 a53:	48 8d 2d e6 12 20 00 	lea    0x2012e6(%rip),%rbp        # 201d40 <__init_array_end>
 a5a:	53                   	push   %rbx
 a5b:	49 89 f6             	mov    %rsi,%r14
 a5e:	49 89 d5             	mov    %rdx,%r13
 a61:	4c 29 e5             	sub    %r12,%rbp
 a64:	48 83 ec 08          	sub    $0x8,%rsp
 a68:	48 c1 fd 03          	sar    $0x3,%rbp
 a6c:	e8 2f fd ff ff       	callq  7a0 <_init>
 a71:	48 85 ed             	test   %rbp,%rbp
 a74:	74 20                	je     a96 <__libc_csu_init+0x56>
 a76:	31 db                	xor    %ebx,%ebx
 a78:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)
 a7f:	00
 a80:	4c 89 ea             	mov    %r13,%rdx
 a83:	4c 89 f6             	mov    %r14,%rsi
 a86:	44 89 ff             	mov    %r15d,%edi
 a89:	41 ff 14 dc          	callq  *(%r12,%rbx,8)
 a8d:	48 83 c3 01          	add    $0x1,%rbx
 a91:	48 39 dd             	cmp    %rbx,%rbp
 a94:	75 ea                	jne    a80 <__libc_csu_init+0x40>
 a96:	48 83 c4 08          	add    $0x8,%rsp
 a9a:	5b                   	pop    %rbx
 a9b:	5d                   	pop    %rbp
 a9c:	41 5c                	pop    %r12
 a9e:	41 5d                	pop    %r13
 aa0:	41 5e                	pop    %r14
 aa2:	41 5f                	pop    %r15
 aa4:	c3                   	retq
 aa5:	90                   	nop
 aa6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 aad:	00 00 00

0000000000000ab0 <__libc_csu_fini>:
 ab0:	f3 c3                	repz retq

Disassembly of section .fini:

0000000000000ab4 <_fini>:
 ab4:	48 83 ec 08          	sub    $0x8,%rsp
 ab8:	48 83 c4 08          	add    $0x8,%rsp
 abc:	c3                   	retq

先程宿題にした.plt.gotが逆アセンブルされている.xchg %ax,%axはNOPなので,*(%rip + 0x201812)にJMPするだけのコードのようだ.
以下,普通にビルドしたプログラムの逆アセンブル結果と比較してみる.量が多いのでDerived::Derived()だけ抜き出す.

普通にビルドした方.

0000000000400760 <Derived::Derived()>:
  400760:	55                   	push   %rbp
  400761:	48 89 e5             	mov    %rsp,%rbp
  400764:	48 83 ec 10          	sub    $0x10,%rsp
  400768:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  40076c:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400770:	48 89 c7             	mov    %rax,%rdi
  400773:	e8 8e ff ff ff       	callq  400706 <Base::Base()>
  400778:	ba d8 08 40 00       	mov    $0x4008d8,%edx
  40077d:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400781:	48 89 10             	mov    %rdx,(%rax)
  400784:	bf 88 08 40 00       	mov    $0x400888,%edi
  400789:	e8 12 fe ff ff       	callq  4005a0 <puts@plt>
  40078e:	90                   	nop
  40078f:	c9                   	leaveq
  400790:	c3                   	retq
  400791:	90                   	nop

PIEの方.

00000000000009e2 <Derived::Derived()>:
 9e2:	55                   	push   %rbp
 9e3:	48 89 e5             	mov    %rsp,%rbp
 9e6:	48 83 ec 10          	sub    $0x10,%rsp
 9ea:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9ee:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 9f2:	48 89 c7             	mov    %rax,%rdi
 9f5:	e8 86 ff ff ff       	callq  980 <Base::Base()>
 9fa:	48 8d 15 67 13 20 00 	lea    0x201367(%rip),%rdx        # 201d68 <vtable for Derived+0x10>
 a01:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 a05:	48 89 10             	mov    %rdx,(%rax)
 a08:	48 8d 3d fd 00 00 00 	lea    0xfd(%rip),%rdi        # b0c <_IO_stdin_used+0x4c>
 a0f:	e8 bc fd ff ff       	callq  7d0 <puts@plt>
 a14:	90                   	nop
 a15:	c9                   	leaveq
 a16:	c3                   	retq
 a17:	90                   	nop

見比べてみると,call命令はそのままアドレスが格納されているが,vtableのアドレスやputs()の引数(ここでputsが呼ばれているのはprintfが最適化されているため)は%ripから計算していることが分かる.普通にビルドした方はともかく,PIEの方もcall先のアドレスが固定(e.g. Base::Base() -> 0x980)になっているように見えるが,これはASLRが有効であれば実行時にランダムな値が足し合わさるようだ.
gdbで実行時のアドレスを確認してみる.gdbではASLRが無効化されるので,disable-randomizationオプションをoffにすることで有効化する.

$ gdb -ex 'set print asm-demangle on' -ex 'set disable-randomization off' vtable_pie
(gdb) break Derived::Derived()
Breakpoint 1 at 0x9e6
(gdb) run
Starting program: /home/vagrant/vtable_pie

Breakpoint 1, 0x000055e54d3719e6 in Derived::Derived() ()
(gdb) disassemble
Dump of assembler code for function _ZN7DerivedC2Ev:
   0x000055e54d3719e2 <+0>:	push   %rbp
   0x000055e54d3719e3 <+1>:	mov    %rsp,%rbp
=> 0x000055e54d3719e6 <+4>:	sub    $0x10,%rsp
   0x000055e54d3719ea <+8>:	mov    %rdi,-0x8(%rbp)
   0x000055e54d3719ee <+12>:	mov    -0x8(%rbp),%rax
   0x000055e54d3719f2 <+16>:	mov    %rax,%rdi
   0x000055e54d3719f5 <+19>:	callq  0x55e54d371980 <Base::Base()>
   0x000055e54d3719fa <+24>:	lea    0x201367(%rip),%rdx        # 0x55e54d572d68 <vtable for Derived+16>
   0x000055e54d371a01 <+31>:	mov    -0x8(%rbp),%rax
   0x000055e54d371a05 <+35>:	mov    %rdx,(%rax)
   0x000055e54d371a08 <+38>:	lea    0xfd(%rip),%rdi        # 0x55e54d371b0c
   0x000055e54d371a0f <+45>:	callq  0x55e54d3717d0 <puts@plt>
   0x000055e54d371a14 <+50>:	nop
   0x000055e54d371a15 <+51>:	leaveq
   0x000055e54d371a16 <+52>:	retq
End of assembler dump.
(gdb)

再度起動し,アドレス空間が変わっていることを確認する.

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/vagrant/vtable_pie

Breakpoint 1, 0x000055dda61629e6 in Derived::Derived() ()
(gdb) disassemble
Dump of assembler code for function _ZN7DerivedC2Ev:
   0x000055dda61629e2 <+0>:	push   %rbp
   0x000055dda61629e3 <+1>:	mov    %rsp,%rbp
=> 0x000055dda61629e6 <+4>:	sub    $0x10,%rsp
   0x000055dda61629ea <+8>:	mov    %rdi,-0x8(%rbp)
   0x000055dda61629ee <+12>:	mov    -0x8(%rbp),%rax
   0x000055dda61629f2 <+16>:	mov    %rax,%rdi
   0x000055dda61629f5 <+19>:	callq  0x55dda6162980 <Base::Base()>
   0x000055dda61629fa <+24>:	lea    0x201367(%rip),%rdx        # 0x55dda6363d68 <vtable for Derived+16>
   0x000055dda6162a01 <+31>:	mov    -0x8(%rbp),%rax
   0x000055dda6162a05 <+35>:	mov    %rdx,(%rax)
   0x000055dda6162a08 <+38>:	lea    0xfd(%rip),%rdi        # 0x55dda6162b0c
   0x000055dda6162a0f <+45>:	callq  0x55dda61627d0 <puts@plt>
   0x000055dda6162a14 <+50>:	nop
   0x000055dda6162a15 <+51>:	leaveq
   0x000055dda6162a16 <+52>:	retq
End of assembler dump.
(gdb)

呼び出す関数(Base::Base(),puts@plt)のアドレスが変わっていることが分かる.
実行時にアドレスが足し合わされるといった説明を見たが,call命令のオペランドが実行時に機械語のレベルで書き換えられるのだろうか.それなら確かにパフォーマンス低下もうなずける.というかそれ無茶では…?
実際にこの時の機械語をチェックしてみよう.gdbのdisassemble命令に/rというオプションを付けるとマシン語の対応が分かる.

(gdb) disassemble /r
Dump of assembler code for function _ZN7DerivedC2Ev:
   0x000056083c3fc9e2 <+0>:	55	push   %rbp
   0x000056083c3fc9e3 <+1>:	48 89 e5	mov    %rsp,%rbp
=> 0x000056083c3fc9e6 <+4>:	48 83 ec 10	sub    $0x10,%rsp
   0x000056083c3fc9ea <+8>:	48 89 7d f8	mov    %rdi,-0x8(%rbp)
   0x000056083c3fc9ee <+12>:	48 8b 45 f8	mov    -0x8(%rbp),%rax
   0x000056083c3fc9f2 <+16>:	48 89 c7	mov    %rax,%rdi
   0x000056083c3fc9f5 <+19>:	e8 86 ff ff ff	callq  0x56083c3fc980 <Base::Base()>
   0x000056083c3fc9fa <+24>:	48 8d 15 67 13 20 00	lea    0x201367(%rip),%rdx        # 0x56083c5fdd68 <vtable for Derived+16>
   0x000056083c3fca01 <+31>:	48 8b 45 f8	mov    -0x8(%rbp),%rax
   0x000056083c3fca05 <+35>:	48 89 10	mov    %rdx,(%rax)
   0x000056083c3fca08 <+38>:	48 8d 3d fd 00 00 00	lea    0xfd(%rip),%rdi        # 0x56083c3fcb0c
   0x000056083c3fca0f <+45>:	e8 bc fd ff ff	callq  0x56083c3fc7d0 <puts@plt>
   0x000056083c3fca14 <+50>:	90	nop
   0x000056083c3fca15 <+51>:	c9	leaveq
   0x000056083c3fca16 <+52>:	c3	retq
End of assembler dump.

call命令だけ抜き出して,objdumpの逆アセンブル結果と見比べてみる.
gdb

0x000056083c3fc9f5 <+19>:	e8 86 ff ff ff	callq  0x56083c3fc980 <Base::Base()>

objdump

9f5:	e8 86 ff ff ff       	callq  980 <Base::Base()>

変わってない…?と思ったが,callqのOpcodeを見ると「Call near, relative, displacement relative to next instruction」とのことなので,%ripからの相対位置で呼び出しているようだ.
Liberation: x86 Instruction Set Reference
普通にビルドした方も見てみる.

400773:	e8 8e ff ff ff       	callq  400706 <Base::Base()>

同じくOpcodeがE8なので,おそらくここでパフォーマンス低下は招いていないだろう.

これらの結果より,PIEでない場合は即値で扱っていたアドレスがlea命令に置き換わったことで,ある程度のパフォーマンス低下が起きていると考えられる.また,ローダの処理が追加されていることもパフォーマンス低下を招いていると予想できる.これ以上はちゃんとサーベイしないと分からないので,もっと論文読んで出直します.

今回の内容マサカリビリティに溢れているので怖いっすね

参考資料:
OSセキュリティチュートリアル
PIE (位置独立実行形式) を作成する - bkブログ
Linux の共有ライブラリを作るとき PIC でコンパイルするのはなぜか - bkブログ
革命の日々! PIE(位置独立実行ファイル)で遊んでみる