glibcを利用しないコンパイル方法
はじめに
今回の検証を行うに至った動機としてリンカやローダといった、一般的にコンパイラとして一括りにされているソフトウェアについて勉強する過程でライブラリを利用せずにコンパイルを行うにはどうすればいいのか、という疑問が生まれました。
そこで、glibcを用いずにコンパイルを行ってみることにしました。
glibcを利用したコンパイル
ライブラリを用いないコンパイルを行う前に、比較を行えるようglibcを用いた一般的なコンパイルを行ったバイナリを生成しました。
そのソースコードは以下になります。
#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
このソースコードをコンパイルし実行すると以下のようになります。
> gcc -o hello hello.c > ./hello Hello World! >
特に問題なく動作しています。
次に、このバイナリのもつセクション情報と逆アセンブル、サイズを調査しました。
> objdump -h hello hello: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000060 00000000004002b8 00000000004002b8 000002b8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 0000003d 0000000000400318 0000000000400318 00000318 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000008 0000000000400356 0000000000400356 00000356 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 0000000000400360 0000000000400360 00000360 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rela.dyn 00000018 0000000000400380 0000000000400380 00000380 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rela.plt 00000030 0000000000400398 0000000000400398 00000398 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 10 .init 0000001a 00000000004003c8 00000000004003c8 000003c8 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .plt 00000030 00000000004003f0 00000000004003f0 000003f0 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 12 .plt.got 00000008 0000000000400420 0000000000400420 00000420 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .text 00000182 0000000000400430 0000000000400430 00000430 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .fini 00000009 00000000004005b4 00000000004005b4 000005b4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 15 .rodata 00000011 00000000004005c0 00000000004005c0 000005c0 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame_hdr 00000034 00000000004005d4 00000000004005d4 000005d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .eh_frame 000000f4 0000000000400608 0000000000400608 00000608 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 18 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3 CONTENTS, ALLOC, LOAD, DATA 19 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3 CONTENTS, ALLOC, LOAD, DATA 20 .jcr 00000008 0000000000600e20 0000000000600e20 00000e20 2**3 CONTENTS, ALLOC, LOAD, DATA 21 .dynamic 000001d0 0000000000600e28 0000000000600e28 00000e28 2**3 CONTENTS, ALLOC, LOAD, DATA 22 .got 00000008 0000000000600ff8 0000000000600ff8 00000ff8 2**3 CONTENTS, ALLOC, LOAD, DATA 23 .got.plt 00000028 0000000000601000 0000000000601000 00001000 2**3 CONTENTS, ALLOC, LOAD, DATA 24 .data 00000010 0000000000601028 0000000000601028 00001028 2**3 CONTENTS, ALLOC, LOAD, DATA 25 .bss 00000008 0000000000601038 0000000000601038 00001038 2**0 ALLOC 26 .comment 00000034 0000000000000000 0000000000000000 00001038 2**0 CONTENTS, READONLY > objdump -d hello hello: file format elf64-x86-64 Disassembly of section .init: 00000000004003c8 <_init>: 4003c8: 48 83 ec 08 sub $0x8,%rsp 4003cc: 48 8b 05 25 0c 20 00 mov 0x200c25(%rip),%rax # 600ff8 <_DYNAMIC+0x1d0> 4003d3: 48 85 c0 test %rax,%rax 4003d6: 74 05 je 4003dd <_init+0x15> 4003d8: e8 43 00 00 00 callq 400420 <__libc_start_main@plt+0x10> 4003dd: 48 83 c4 08 add $0x8,%rsp 4003e1: c3 retq Disassembly of section .plt: 00000000004003f0 <puts@plt-0x10>: 4003f0: ff 35 12 0c 20 00 pushq 0x200c12(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8> 4003f6: ff 25 14 0c 20 00 jmpq *0x200c14(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> 4003fc: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400400 <puts@plt>: 400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18> 400406: 68 00 00 00 00 pushq $0x0 40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28> 0000000000400410 <__libc_start_main@plt>: 400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20> 400416: 68 01 00 00 00 pushq $0x1 40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28> Disassembly of section .plt.got: 0000000000400420 <.plt.got>: 400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <_DYNAMIC+0x1d0> 400426: 66 90 xchg %ax,%ax Disassembly of section .text: 0000000000400430 <_start>: 400430: 31 ed xor %ebp,%ebp 400432: 49 89 d1 mov %rdx,%r9 400435: 5e pop %rsi 400436: 48 89 e2 mov %rsp,%rdx 400439: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40043d: 50 push %rax 40043e: 54 push %rsp 40043f: 49 c7 c0 b0 05 40 00 mov $0x4005b0,%r8 400446: 48 c7 c1 40 05 40 00 mov $0x400540,%rcx 40044d: 48 c7 c7 26 05 40 00 mov $0x400526,%rdi 400454: e8 b7 ff ff ff callq 400410 <__libc_start_main@plt> 400459: f4 hlt 40045a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 0000000000400460 <deregister_tm_clones>: 400460: b8 3f 10 60 00 mov $0x60103f,%eax 400465: 55 push %rbp 400466: 48 2d 38 10 60 00 sub $0x601038,%rax 40046c: 48 83 f8 0e cmp $0xe,%rax 400470: 48 89 e5 mov %rsp,%rbp 400473: 76 1b jbe 400490 <deregister_tm_clones+0x30> 400475: b8 00 00 00 00 mov $0x0,%eax 40047a: 48 85 c0 test %rax,%rax 40047d: 74 11 je 400490 <deregister_tm_clones+0x30> 40047f: 5d pop %rbp 400480: bf 38 10 60 00 mov $0x601038,%edi 400485: ff e0 jmpq *%rax 400487: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 40048e: 00 00 400490: 5d pop %rbp 400491: c3 retq 400492: 0f 1f 40 00 nopl 0x0(%rax) 400496: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40049d: 00 00 00 00000000004004a0 <register_tm_clones>: 4004a0: be 38 10 60 00 mov $0x601038,%esi 4004a5: 55 push %rbp 4004a6: 48 81 ee 38 10 60 00 sub $0x601038,%rsi 4004ad: 48 c1 fe 03 sar $0x3,%rsi 4004b1: 48 89 e5 mov %rsp,%rbp 4004b4: 48 89 f0 mov %rsi,%rax 4004b7: 48 c1 e8 3f shr $0x3f,%rax 4004bb: 48 01 c6 add %rax,%rsi 4004be: 48 d1 fe sar %rsi 4004c1: 74 15 je 4004d8 <register_tm_clones+0x38> 4004c3: b8 00 00 00 00 mov $0x0,%eax 4004c8: 48 85 c0 test %rax,%rax 4004cb: 74 0b je 4004d8 <register_tm_clones+0x38> 4004cd: 5d pop %rbp 4004ce: bf 38 10 60 00 mov $0x601038,%edi 4004d3: ff e0 jmpq *%rax 4004d5: 0f 1f 00 nopl (%rax) 4004d8: 5d pop %rbp 4004d9: c3 retq 4004da: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 00000000004004e0 <__do_global_dtors_aux>: 4004e0: 80 3d 51 0b 20 00 00 cmpb $0x0,0x200b51(%rip) # 601038 <__TMC_END__> 4004e7: 75 11 jne 4004fa <__do_global_dtors_aux+0x1a> 4004e9: 55 push %rbp 4004ea: 48 89 e5 mov %rsp,%rbp 4004ed: e8 6e ff ff ff callq 400460 <deregister_tm_clones> 4004f2: 5d pop %rbp 4004f3: c6 05 3e 0b 20 00 01 movb $0x1,0x200b3e(%rip) # 601038 <__TMC_END__> 4004fa: f3 c3 repz retq 4004fc: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400500 <frame_dummy>: 400500: bf 20 0e 60 00 mov $0x600e20,%edi 400505: 48 83 3f 00 cmpq $0x0,(%rdi) 400509: 75 05 jne 400510 <frame_dummy+0x10> 40050b: eb 93 jmp 4004a0 <register_tm_clones> 40050d: 0f 1f 00 nopl (%rax) 400510: b8 00 00 00 00 mov $0x0,%eax 400515: 48 85 c0 test %rax,%rax 400518: 74 f1 je 40050b <frame_dummy+0xb> 40051a: 55 push %rbp 40051b: 48 89 e5 mov %rsp,%rbp 40051e: ff d0 callq *%rax 400520: 5d pop %rbp 400521: e9 7a ff ff ff jmpq 4004a0 <register_tm_clones> 0000000000400526 <main>: 400526: 55 push %rbp 400527: 48 89 e5 mov %rsp,%rbp 40052a: bf c4 05 40 00 mov $0x4005c4,%edi 40052f: e8 cc fe ff ff callq 400400 <puts@plt> 400534: b8 00 00 00 00 mov $0x0,%eax 400539: 5d pop %rbp 40053a: c3 retq 40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 0000000000400540 <__libc_csu_init>: 400540: 41 57 push %r15 400542: 41 56 push %r14 400544: 41 89 ff mov %edi,%r15d 400547: 41 55 push %r13 400549: 41 54 push %r12 40054b: 4c 8d 25 be 08 20 00 lea 0x2008be(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry> 400552: 55 push %rbp 400553: 48 8d 2d be 08 20 00 lea 0x2008be(%rip),%rbp # 600e18 <__init_array_end> 40055a: 53 push %rbx 40055b: 49 89 f6 mov %rsi,%r14 40055e: 49 89 d5 mov %rdx,%r13 400561: 4c 29 e5 sub %r12,%rbp 400564: 48 83 ec 08 sub $0x8,%rsp 400568: 48 c1 fd 03 sar $0x3,%rbp 40056c: e8 57 fe ff ff callq 4003c8 <_init> 400571: 48 85 ed test %rbp,%rbp 400574: 74 20 je 400596 <__libc_csu_init+0x56> 400576: 31 db xor %ebx,%ebx 400578: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 40057f: 00 400580: 4c 89 ea mov %r13,%rdx 400583: 4c 89 f6 mov %r14,%rsi 400586: 44 89 ff mov %r15d,%edi 400589: 41 ff 14 dc callq *(%r12,%rbx,8) 40058d: 48 83 c3 01 add $0x1,%rbx 400591: 48 39 eb cmp %rbp,%rbx 400594: 75 ea jne 400580 <__libc_csu_init+0x40> 400596: 48 83 c4 08 add $0x8,%rsp 40059a: 5b pop %rbx 40059b: 5d pop %rbp 40059c: 41 5c pop %r12 40059e: 41 5d pop %r13 4005a0: 41 5e pop %r14 4005a2: 41 5f pop %r15 4005a4: c3 retq 4005a5: 90 nop 4005a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4005ad: 00 00 00 00000000004005b0 <__libc_csu_fini>: 4005b0: f3 c3 repz retq Disassembly of section .fini: 00000000004005b4 <_fini>: 4005b4: 48 83 ec 08 sub $0x8,%rsp 4005b8: 48 83 c4 08 add $0x8,%rsp 4005bc: c3 retq > size hello text data bss dec hex filename 1183 552 8 1743 6cf hello > ldd hello linux-vdso.so.1 => (0x00007fffe999f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff8480c0000) /lib64/ld-linux-x86-64.so.2 (0x00007ff848600000) >
この時点ではこのようになっているのだな、程度の調査のみを行い次に行うglibcを利用せずに生成したバイナリを調査する際に比較を行います。
glibcを利用しないコンパイル
次に、glibcを利用せずにコンパイルを行います。まず初めに、コンパイルが正常にできるかを確認するために以下のソースコードを用います。
int main() { char *text = "Hello World!\n"; return 0; }
最初にコンパイルを行ったソースコードではライブラリを利用することができたので「stdio.h」というヘッダファイルをインクルードしていましたが、今回は利用しないので文字列を「text」という変数に初期値設定しています。
また、glibcを利用しない場合は「 -nostdlib 」というオプションを追加します。
ubnt@ubuntu:~$ gcc -nostdlib -o hello_no_libc hello.c /usr/bin/ld: 警告: エントリシンボル _start が見つかりません。デフォルトとして 0000000000400144 を使用します
どうやらそのままではコンパイルができず、エラー内容を読んでみると「_start」というエントリシンボルが存在していないようでした。
このエントリシンボルとはエントリポイントのことであり、このシンボルが存在していないため実行できないということでした。
ということで、エントリポイントを記述します。
.global _start _start: call main mov $60, %eax xor %rdi, %rdi syscall
これでコンパイルを行うために必要な材料はそろったので実際にコンパイルを行ってみます。
ubnt@ubuntu:~$ gcc -nostdlib stubstart.s -o hello_no_libc hello.c ubnt@ubuntu:~$ ./hello_no_libc ubnt@ubuntu:~$
問題なく動作することがわかりました。しかし、これでは比較を行えないのでインクルードファイルからprintf関数の基となるwriteシステムコールラッパを探そうと思ったのですが、システムコールに関する処理はあらかじめコンパイルされてアセンブラとなっているのではないか、という結論に至りインラインアセンブラを用いてwriteシステムコールを呼び出す処理を追加しました。以下はそのソースコードです。
#define WRITE_SYS_NUM 4 #define STDOUT_FILENO 1 int main() { char *str = "Hello World, I am chouett0.\nnice to meet you!!1!\n"; int len=0, err; while (str[len] != '\0') { len++; } asm volatile("mov %0, %%eax" : : "i"(WRITE_SYS_NUM)); asm volatile("mov %0, %%ebx" : : "i"(STDOUT_FILENO)); asm volatile("mov %0, %%ecx" : : "m"(str)); asm volatile("mov %0, %%edx" : : "m"(len)); asm volatile("int $0x80" : "=a"(err)); return 0; }
特に複雑な部分もなく、インラインアセンブラにてwriteシステムコールを呼び出すシステムコール番号とstdoutを意味する「1」,表示する文字列、文字列の長さを各レジスタに格納してint 0x80でシステムコールを呼び出します。その結果は以下のようになり正常に動作することがわかります。
ubnt@ubuntu:~$ gcc -nostdlib stubstart.s -o hello_nolibc hello_nolibc.c ubnt@ubuntu:~$ ./hello_nolibc Hello World, I am chouett0. nice to meet you!!1! ubntubuntu:~$
次に、このバイナリの各セクション情報、逆アセンブラ、サイズを確認します。
ubnt@ubuntu:~$ objdump -h hello_nolibc hello_nolibc: ファイル形式 elf64-x86-64 セクション: 索引名 サイズ VMA LMA File off Algn 0 .note.gnu.build-id 00000024 0000000000400120 0000000000400120 00000120 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 0000005c 0000000000400144 0000000000400144 00000144 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .rodata 00000032 00000000004001a0 00000000004001a0 000001a0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .eh_frame_hdr 00000014 00000000004001d4 00000000004001d4 000001d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .eh_frame 00000038 00000000004001e8 00000000004001e8 000001e8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .comment 00000034 0000000000000000 0000000000000000 00000220 2**0 CONTENTS, READONLY ubnt@ubuntu:~$ objdump -d hello_nolibc hello_nolibc: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: 0000000000400144 <_start>: 400144: e8 0a 00 00 00 callq 400153 <main> 400149: b8 3c 00 00 00 mov $0x3c,%eax 40014e: 48 31 ff xor %rdi,%rdi 400151: 0f 05 syscall 0000000000400153 <main>: 400153: 55 push %rbp 400154: 48 89 e5 mov %rsp,%rbp 400157: 48 c7 45 f0 a0 01 40 movq $0x4001a0,-0x10(%rbp) 40015e: 00 40015f: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%rbp) 400166: eb 09 jmp 400171 <main+0x1e> 400168: 8b 45 ec mov -0x14(%rbp),%eax 40016b: 83 c0 01 add $0x1,%eax 40016e: 89 45 ec mov %eax,-0x14(%rbp) 400171: 48 8b 55 f0 mov -0x10(%rbp),%rdx 400175: 8b 45 ec mov -0x14(%rbp),%eax 400178: 48 98 cltq 40017a: 48 01 d0 add %rdx,%rax 40017d: 0f b6 00 movzbl (%rax),%eax 400180: 84 c0 test %al,%al 400182: 75 e4 jne 400168 <main+0x15> 400184: b8 04 00 00 00 mov $0x4,%eax 400189: bb 01 00 00 00 mov $0x1,%ebx 40018e: 8b 4d f0 mov -0x10(%rbp),%ecx 400191: 8b 55 ec mov -0x14(%rbp),%edx 400194: cd 80 int $0x80 400196: 89 45 fc mov %eax,-0x4(%rbp) 400199: b8 00 00 00 00 mov $0x0,%eax 40019e: 5d pop %rbp 40019f: c3 retq ubnt@ubuntu:~$ size hello_nolibc text data bss dec hex filename 254 0 0 254 fe hello_nolibc an3m0ne@an3m0ne:~$ ldd hello_nolibc 動的実行ファイルではありません ubnt@ubuntu:~$
比較するまでもなく、各項目で明らかに違いがあることがわかります。
まず、セクション情報ではglibcを利用していないので無駄なものが無く変数の値が変化することもないのでtextセクション内で完結することによりbssセクションも必要がなくなります。(文字列をconstで宣言することでさらに無駄が省ける?)また、関数を利用せず直接システムコールを実行しているのでPLT領域も必要なくなります。
次に、逆アセンブラ情報では先ほども述べたPLTに関係する関数やGOTなどが存在せず_startとmainでのみ構成されていることがわかります。その結果、glibcを利用している時には1743byteあったバイナリがglibcを利用していない場合では254byteにまで縮小され明らかにサイズが変化していることがわかります。また、共有ライブラリを一切利用していないこともわかりました。
まとめ
今回の検証でライブラリの便利さや、リンカがどのようにバイナリを生成していくのかを少し理解できたと思います。また、ライブラリの冗長さを改めて理解でき大規模なソフトウェアであれば変わりはないものの今回のような小さなバイナリではその冗長さが速度の低下につながり、そういった部分がパフォーマンスチューニングにつながるのだろうか、と感じました。
参考文献
Chrome使用時にフリーズする問題の簡易的な解決法について
はじめに
Chromeを使用しているときになぜかフリーズしてしまう問題があり、その問題の根本的なものではないにせよ
簡易的に解決したいと思い簡易的なツールを作成したのでその経緯とちょっとした説明の記事です。
以下からDLして任意の場所に保存するだけで使用できます。
動機
先ほども書きましたが、Chromeを使用しているときになぜかフリーズしてしまう問題があり特に私のラップトップではChrome起動状態でスリープ復帰するとほぼ必ずフリーズしてしまいます。 タスクマネージャ立ち上げてプロセスを落とせばよいのですがタスクマネージャを立ち上げてプロセス探して落とす、という動作をいちいち行うのは面倒なので単純動作で行えるよ宇にしたいと考えて作成したのが「Ch0romeKill3er」です。
実装
仕組みというほどではありませんが内部的には
- 特定のプロセス名からプロセスIDを取得
- プロセスのハンドラを取得
- プロセス強制終了
という処理を行っています。
一応拡張性を持たせたいと思いmain関数で呼び出すKillProcess関数に任意のプロセス名を与えるとKillできるようになっています。
まとめ
幼稚園児でも実装できるレベルのツール && n番煎じ
Apple製品のゼロ幅非結合子処理の際に生じるクラッシュについて
初めに
つい先日、最新のものを含むiOSやOS X,Watch OSに特定の文字列を送信することでクラッシュするバグが発見されました。
詳細についてはこちらを参照していただくとして、今回はこの現象を実際に検証してみた結果となります。
本来は画像を張りたかったのですがスクショを取り忘れていたので文字のみでの解説となります。
なお、今回の検証は個人の端末を用いてとなりますので第三者へ向けて行っていません。
またこの記事での内容を実際に行うと最悪の場合端末が利用できなくなる場合がありますのであくまで自己責任でお願いします。
追記: 2/21に公開されたiOS11.2.6へアップデートする事で今回のバグが修正されました
検証環境
今回の検証では
* iPhone5 iOS10.2
* iPhone6 iOS11.1.2
この2台で行い、文字列の送信・生成はPCとAndroid端末から行いました。
文字列を受信、通知させるため方法としてYahooメールアカウントを登録したiOS標準のメールクライアントとSMSにて行いました。
本来はTwitterを用いて行う予定でしたが、すでに対策がなされているようでWeb・モバイルともにバグを引き起こす文字列は送信できなくなっていました。
検証方法
まず初めにYahooメールを用いた検証について記述する前に、検証で用いる文字列について説明したいと思います。
今回問題となっている文字列は「テルグ語」と呼ばれる言語のテルグ文字という文字によるものであり、この文字を表現する際に必要な「ゼロ幅非結合子(以下ZWNJ)」という制御文と特定の組み合わせの際にのみ発火する文字のようです。
詳しい説明は
こちらに掲載されているので重要な部分のみを抜粋すると、今回のバグは「子音1、ヴィラーマ(発音区別符号)、子音2、ZWNJ 、母音」という組み合わせの際に発火するようです。
ということで、初めに検証のために利用したのがおそらくほとんどの記事で扱われている「U+0c1cU+0c4dU+0c1eU+200cU+0c3e」というUnicodeで表現される文字列です。当然といえば当然ですが、クラッシュを引き起こす文字列をニュース記事でそのまま記載することはまずないと思われますので、まずはこのUnicodeを実際の文字列に直していく作業です。
Unicodeから可読文字列に変換する方法はたくさんあると思われますが、私は
https://www.marbacka.net/msearch/tool.php
http://www.eva.hi-ho.ne.jp/cgi-bin/user/zxcv/decodeUTF8.cgi?req=oct&oct=343+222+242+343+202+244
この2つを用いて変換を行いました。
検証
それでは実際に検証を行う手順を記述していきます。
まずメールですが、あらかじめ検証機にメールの設定を入れておきます。
今回は標準メールクライアントを用いたので、まずはメールクライアントを開き「Yahooメール」を選択し、
メールアドレスとPWを入れて完了です。
送信する際はPCからブラウザ上で行いました。
一応件名と本文のどちらが表示されても発火するように両方に文字列を挿入しました。
両方の端末でメールクライアントを起動した状態で実際に送信してみると、正常にアプリケーションがクラッシュして再起動しました。
次に、バナーでの発火の検証をしてみるとバナーが表示される瞬間にSpringBoardがクラッシュして再起動していたことから
今回のバグの危険度がわかります。
メールソフトでの発火は確認できたので、その次にSMSを用いた検証を行いました。
この検証ではSIMを挿入したAndroid端末から同じくSIMを挿入したiPhone6にバグを引き起こす文字列を送信しました。
この検証でもメールのときと同じ結果で、アプリケーションを開いた状態ではアプリケーションがクラッシュし、バナーに表示させるとシステムがクラッシュしてしまいました。
ここまでの検証は記事にある内容の模範に過ぎず、あまり面白みはありませんでした。
そこで、私の先輩からの疑問でほかの組み合わせや少ない文字列では発火しないのかを検証してみました。
あまり長い文章だと読みにくくなってしまうので概要だけ説明すると、まず、今回の検証で用いた文字列から1文字分削った
文字列を送信してみましたが発火することはなく、次に長さは変わらずZWNJを別の場所に移して見たところやはり発火することはありませんでした。
次に、検証方法のところで記述した特定の組み合わせに従い最後の母音を別の文字に置き換えてみたところ正常に発火していました。
これらのことを踏まえると、やはり特定の組み合わせの文字列をZWNJを利用して処理する際にバグが発生していることがわかりました。
まとめ
今回の検証はただバグを引き起こす文字列を送信してクラッシュすることを確認するだけで詳しい原因やほかの組み合わせまでは調査できませんでした。
また、検証で用いた検証機が2台だけだったためそのうちより低いバージョンの端末やMac,Apple Watchでも発火するのかを検証してみたいなと思います。
ここからは完全に余談となりますが、今回メールとSMSを用いたのには理由があり本来であればTwitterを利用したかったのですが、どうやらweb・mobile両方で既に対策が行われているようで投稿はもちろん名前にすら入力できないようにされていました。
しかし、某緑のSNSアプリケーションでは対策がなされておらず、
例のゼロ幅非結合子のバグを普段使っている端末で発火させて無限に後悔してる
— Chouett0 (@chouett0) 2018年2月19日
本来検証する予定ではないプライベート機で発火してしまう事態になってしまいました。
さらに、連携させているApple Watchにも最悪の場合通知が行ってしまいクラッシュする羽目になりかけたり復旧する際に
MacBook Proで無意識にLINEを起動しようとして発火することを思い出したりと散々な思いをしたので早急にiOS11.3がリリースされることを祈ります。
割り込み処理について
初めに
今回の記事も前回同様、技術的な内容ではなく私が理解を深めるためのメモであり
特に面白みのないものとなっています。
また、誤字脱字、間違っている点も多くあると思われますのでご了承ください。
割り込み
- 割り込みとは、ハードウェアから特定の処理を要求されたときに、一時的にソフトウェアの処理を中断させて要求された処理を実行することをいう。
例)キーボードコントローラ->キーが押されたときにCPU割り混みを信号を出し、CPUはどのキーが押されたのかをキーボードコントローラから読み出す処理を行う 割り込みを行うことでCPUがハードウェアを常に監視する必要性がなくなる
割り込みの種類
- 例外(Exception)
- 割り込み要求(IRQ: Interrupt ReQuest)
- ソフトウェア割り込み
例外(Exception)
- CPUが異常を検出した際に発生する。
- ページングで割り当てられたページがない時などに異常を検出して例外処理を行う。
- 主にソフトウェアでの処理で異常を検出する
例) 存在しない配列要素へのアクセス
割り込み要求
- CPUの周辺のハードウェアから要求があった場合に発生する信号
- CPUのピンとハードウェアは電気的にワイヤで接続されており、ハードウェアは割り込み要求を出すときにそのワイヤへ電気信号を出すことでCPUに伝達する
- CPUはワイヤの信号が変化したことを検出すると要求にあった割り込みハンドラを呼び出す。
- この信号はソフトウェアの動作に関係なくいつ信号が送信されるかはわからない
- CPUはPIC(Programmable Interrupt Controller)とわいやでIRQラインが結ばれている。
- PICはフロッピーディスクコントローラやDMAコントローラなどの割り込み要求を一括してCPUに伝える。
ソフトウェア割り込み
- 例外とは異なり、意図的にソフトウェアが発生させることができる割り込み。
- INT命令がソフトウェア割り込みでを発生させるめいれいであり、主にユーザプログラムがシステムコールを呼び出すときに使用する。
割り込みベクタと割り込みハンドラ
- 割り込みが発生したときにどのハンドラを呼び出すのかを記述したものを割り込みハンドラという。
- 割り込みベクタには割り込みハンドラのアドレスが順番に並んでいるようなテーブルのイメージで番号がつけられている。
- 割り込みが発生したときに、その割り込みに対応した番号のハンドラは呼び出されることで、割り込み処理が行われる。
- 割り込みベクタのうち、例外の部分はCPU仕様によって決まっている。
GDT(Global Descriptor Table)
- 64bitを一つのデータとした配列
- 一つの要素である64bitので0他派セグメントディスクリプタといい、セグメントの開始位置、セグメントの崔伊豆アクセス権限などを細かく設定できる。
- このセグメントディスクリプタは最大で8192個まで持つことができる。
IDT(Interrupt Descriptor Table)
- 割り込みと割り込みハンドラを結びつけるテーブルで、8byteのディスクリプタ。
- IDTは256個までの配列でそれぞれ順番に0番から255番までの割り込みに対応している。
GDTと似たディスクリプタを構成していて、3種類のディスクリプタがある。
-> タスクゲートディスクリプタ
-> 割り込みゲートディスクリプタ
-> トラップゲートディスクリプタタスクゲートディスクリプタを使用して割り込みハンドラ処理を行うとタスクスイッチができる。
- 割り込み/トラップディスクリプタは割り込み/例外時に使用するディスクリプタ
- 割り込みゲートディスクリプタとトラップゲートディスクリプタはほとんど同じ動作をするが、割り込み/例外発生時に、割り込みゲートディスクリプタはEFLAGSレジスタのIF(割り込みフラグ)をクリアし、割り込み処理中は他の割り込みが入らないようにする一方、トラップゲートディスクリプタはEFLAGSフラグを変更しないため、他の割り込み/例外を許可する。
IDTR
- CPUが持っている特殊なレジスタを指す。
- LIDT命令を使用してIDTRにIDTのサイズとアドレスを格納する。
- IDTRにIDTのサイズとアドレスが格納されている際にわりこみが発生することでCPUはIDTを参照し、対応する割り込みハンドラの処理を行う。
LDT(Local Descriptor Table)
- タスク毎にGDTの相当するディスクリプタを持たせることでタスク間のセキュリティを高める仕組み
- すべてのタスクはGDTをルートディスクリプタとして共通に使う
- GDT内のあるエントリを、LDTディスクリプタと割り当て、タスクスイッチング毎にそのディスクリプタを更新することで、タスク毎のLDTを実装する。
- セグメントは16bit、下位の3bitは属性、上位はセレクタ値
- セグメントがLDTを示す場合、IDTRレジスタがGDT内のLDTディスクリプタとし、そこからLDTを取得しLDT内のエントリからディスクリプタテーブルを取得する。
- タスク切り替え毎にIDTRレジスタとGDT内のエントリを切り替える必要がある。
参考文献
VyOS
# 宣伝
harekaze.com
# 初めに
Harekaze Advent calendar 14日目の記事となります。
adventar.org
本来は技術的な記事にしたかったのですが、進捗が思ったように出なかったので
今回はVyOSにて家庭内ネットワークを再構築しようとした際に気づいたことのまとめを書いていこうと思います。
# VyOSについて
VyOSとは、Vyattaと呼ばれるLinuxベースのネットワークOSの無償版であるVyatta CoreというものからフォークされたOSです。
UNIXとLinuxに近いと考えるとわかりやすいかもしれません(実際は全然違いますが)
このVyOSですが、実機にインストールできるだけでなくVBox等でVMとしても動作させることができる非常に面白いOSです。
# Edge Router
Edge Routerとは、先ほどのVyOSをベースに作られたOSを搭載した小型の高機能ルータのことで、一般的なルータの機能はもちろんのこと
CiscoやYamahaなどの企業向けルータでできることは大体できるにもかかわらず10k以内で購入できる非常に素晴らしいルータのことです。
実際の我が家の家庭内ネットワークではこのルータを利用していて、小さいながらもなかなか頑張ってくれています。
# 気づいた点
## VyOSではTrackやTrigger shutdownと呼ばれる機能がない
まず、TrackやTrigger Shutdownとは、VRRPという冗長化機能を使用する際に設定されるもので
[ インターネット ]
/ \
/ \
[ Router ] [ Router ]
| |
[ L2SW ]
|
[ PC ]
例としてPC側にVRRPを設定してこのような構成を組んでいるときに、インターネット側が切断されてしまったと想定すると
PC側の回線は接続されている状態なのでVRRPのみでは障害検知ができません。
そこで、先ほどの機能を設定することで対向の回線がDownした際にはPC側もDownするので冗長化構成が効率的に気のすることになります。
そのTrackやTrigger shutdownと呼ばれる機能ですが、私の調べた限りではVyOSに存在しないようです。
とても残念ではありますが、VRRP構成を組んでいるルータ間で渡となる回線を作り、OSPFで自動的にルーティングさせることで回避できます。
# VRRPの仕様
先ほども取り上げたVRRPですが、私はどうしてもこの構成をインターネット側に取り入れたかったのですが、某comのONUの仕様で
MACアドレスの一致した機器以外は接続できないためVRRPは構成できない状態でした。
そこで、VRRPを構成する機器のMACアドレスを同一にすれば問題が解決するのではないかと考えて実際に行ってみました。
結果としては成功したものの、不安定でありなかなかうまいようには行きませんでした。
しかし、私はこの際に両機器がVRRPの切り替わりを正常に行えていることに疑問を抱きました。
なぜかというと、MACアドレスを同一にしているのであればお互いで疎通をとることはできないはずなのにもかかわらず
瞬時に切り替わりを行えていたからです。その原因を探ってみると、VRRPはペアとなる機器に直接パケットを送るのではなく
masterとなるルータがHelloパケットと呼ばれるパケットを定期的にユニキャストしていたことが原因のようです。
この結果、MACアドレスが同一でもbackupルータはHelloを正常に確認できるので、VRRPも正常に切り替えを行えていたようです。
# 〆
今回はあまり技術的な内容を書くことができず、何をしたのかも詳しく書けていないのでそのうち記事にしたいと思います。
UNIXとLinuxとBSDと色々
はじめに
この記事の内容はUNIXとLinuxとBSDとその他色々なOSの違いや成り立ちを忘れないよう、自分なりに理解するためのまとめです。新たな発見も無ければ間違いが多く含まれていると思われます。
UNIX
UNIXとは、言わずと知れた現在にUbuntuやRHELなどのLinuxの誕生に影響を与えたOSです。1969年に米国のAT&Tのベル研究所で開発が行われ、1971年に初めて公開されました。当時はアセンブラ言語での実装が一般的であったため、初期のUNIXはアセンブラ言語で記述されていましたが後にC言語で改めて実装がなされました。オペレーションシステムを高級言語で記述するという試みは、先駆的であり他のプラットフォームへの移植をようにしました。
「UNIXは高級言語で記述された初めての言語」と言われますが、実は最初はアセンブラ言語で実装されていた、と言うのは初耳でした。さらに、当時では高級言語でOSが記述されるという事自体が一般的ではない(と言うよりもCの実装が今より使える言語ではなかった?)と言うことも意外でした。
UNIXの誕生にはさらに深い歴史があり、1960年代中ごろにマサチューセッツ工科大学、ベル研究所、General Electric(GE)がGEのメインフレームコンピュータ用に開発した「Multics」というタイムシェアリングシステム、つまりは複数ユーザで様々な処理を行うことができるシステムを共同開発していました。しかし、革新的かつタイムシェアリングシステムという様々な処理を行うというコンセプトからわかるように、機能が増えていっていくに比例してその複雑さは増す一方となりついにはベル研究所はMultics開発から離脱します。そこで、よりシンプルなシステムを開発しようとして生まれたのがUNIXです。つまりは、UNIXはMulticsの複雑さを複雑さを解消する目的で開発されたということです。
このことから、UNIXはMulticsの影響を受けて開発されたのだろうと解釈しました。また、「UNIX」という名称の由来も初期のUNIX、このころは「Unics」というシステムはMulticsと違いシングルタスクでのみしか動作できなかったのでMultiの逆で単一という意味の「Unit」から生まれたようです。 余談ですが、UNIXはAT&Tの様々な事情によりソースコードが公開され、大学や企業に広まったようです。
GNU
初期のUNIXの公開から約10年後の1983年にリチャード・ストールマンによって、「フリーソフトウェアのみで構成されたUNIXの完全互換ソフトウェア」を開発するという目標の元作り出されたGNUは、ライブラリ、コンパイラ、テキストエディタやUnixシェル、Windowシステムなどを備えていたものの、低水準の要素であるデーモンやカーネル、デバイスドライバといったものは利用できなかったようです。後にLinuxを世に送り出すリーナス・トーバルズは、GNUカーネルが利用できなかったこともLinuxを開発した要因の一つであると述べています。
この記事を書くまでGNUについて詳しく調べてみたことがなかったのですが、GNUはOSでありLinuxとはまた違ったコンセプトで開発されたということがわかりました。さらに、LinuxはUNIXを無料で利用するために誕生したという話を聞いていたので、意外なものが関係しているのだなと思いました。
BSD
1970年代後半から1980年代にかけて、カリフォルニア大学バクレー校のCSRGはUNIXの派生版としてBSDを開発しましたが、UNIXの利用者のライセンスを制限する、つまりは現代でいうところの著作権のようなものの関係がありBSDを公開することができなくなってしまいました。そこで、ライセンスに関わる部分を削り必要な部分を書き換えました。さらに、386で動かすコードを補って1992年にウィリアム・ジョリッツによってリリースされたのが386BSDであり、NetBSDやFreeBSDの祖先となるシステムです。当時、現代的なマルチプロセスやメモリ保護などの機能が実装できる安価な32bitパーソナルコンピュータが普及され始めたこともあり、BSDのようなUNIXの移植が行われるようになりLinuxの開発にも影響しました。 BSDの成り立ちが私の中で一番あいまいで、BSDとUNIXの関係性がイマイチ理解できていませんでしたが 、BSDがLinuxの開発にも影響していたということは知らなかったのでなるほどなと思いました。また、この頃から今のようなPCが徐々に世に出ていくと考えるとなかなか面白くもあり時代が変わっていくのだなと感じます。
Linuxの誕生
上記のBSDでもあるように、1990年代には比較的安価で買えるIntel 80386CPUを搭載した32bitPC/AT互換パーソナルコンピュータが普及し始めていました。その当時ヘルシン大学の学生であったリーナスはオペレーティングシステムについて強い好奇心を抱いていました。そこで彼はその安価なコンピュータを使用してUNIX互換の動作をするOSを動作させたいと考えますが、商業UNIXは高価であり、ここでは登場していませんがMINIXという教育用のOSは制限が多かったため、彼の思うようにはいきませんでした。そこで、自作のターミナルエミュレータやファイルシステムなどのUNIX互換APIを実装して独自のOSカーネルを開発しました。これが後のLinuxカーネルになっていきます。
Linuxの開発はMINIX上で行われていたため、MINIXで動作するソフトウェアはLinuxでも動作可能でしたが後にLinux上でLinuxの開発が行えるようになると、MINIXのコンポーネントはGNUのプロダクトによって置き換わっていきました。
当時のLinuxの実装は単純であり他のUNIX互換のシステムと比べても見劣りするもので、機能面でLinuxは劣っていたものの自由なUNIX互換カーネルを利用でき、かつライセンスなどの問題が発生しないシステムは当時Linuxのみであった。
当時の、PCで動作するフリーのUNIX環境を求めるユーザたちの多くはMINIXを利用していたため、リーナスはMINIXのメーリングリスト上でLinuxを公開しGPLの下で利用しました。ちょうど32bit PC/AT互換パーソナルコンピュータが普及し始めた時期であったためGPLでの改良が可能であったため、多くの開発者たちへ改良を促しました。その結果として開発者たちはLinuxカーネルをより良いものにしていくと同時に、GNUコンポーネントと統合する作業を行い、今のLinuxの形となりました。
今でこそ多くのOSの素となっているLinuxですが、当初は他のOSとは比較できないほど未熟であったのにも関わらずフリーかつLinuxの開発に影響を与えたGNUよりも早い段階で独自のカーネルを形にしていたというところを武器にして負けず劣らずな戦いを繰り広げていたというのはとても面白いですね。また、やはりOSSとしての利点として多くの開発者たちの手によってより良く改良されていったのも要因の一つなのかもしれませんね。さらに、上記のBSDのライセンス問題を解消してリリースされたFreeBSDの公開が1994年11月であるのに対して、Linuxが公開されたのが1990年代前半というタイミングもLinuxの広まった要因の一つのように思えます。ここからは私の想像になってしまいますが、一般的に「Linux」と呼ばれているOSは、ここまで書いてきた「Linuxカーネル」を指すものでありRHELやDebianは「Linuxカーネル」に独自のプログラムを追加したものなのではないかなと思います。
MINIX
Linuxでたびたび出てきたMINIXについても少しだけメモしておきたいと思います。
MINIXは、アンドリュー・タネンバウムによって作られたUNIX系のOSです。コンピュータ科学におけるオペレーティングシステムの教育という目的に重点を置いた設計で、企業ライセンスなどが無いように書かれたものでしたが、コンパイラにライセンスの問題がある、教育目的なので教科書として広く配布するために商業出版を必要とした関係もありライセンスには制限がありました。
まとめ
知っていたようで意外に知らないことも多く、特にUNIXが初めはアセンブラで書かれていたことに驚きました。
BSDの歴史やライセンスの問題、MINIXやMulticsなどのあまり聞きなれないOSなど深く調べれば調べるほど様々な情報が得られ
よりコンピュータについての興味が生まれてくるので現代の技術も大切ですが歴史についても調べてみるのも良いかもしれないなと感じました。
特に、歴史の関連性などがまだ調べられていないのでいつか調べてみたいと思います。
参考文献
ISUCON7で予選落ちした話
はじめに
先週の土日で行われたISUCON7に「インターネットは爆発しました」と言うチームで出場しました。
今回がISUCON初参加だったので参加しての感想とそのWriteupがメインの記事となります。
やったこと
僕の所属していたチームでは、3人中2人がサーバ担当、残りの1人でコードのチューンを行うと言う分担を行いました。
今回の競技ではメインで実行されていたアプリケーションはPythonのようでしたが、PHPに切り替えてチューンを行うことにしました。
補足として、PHPでのオリジナルコードのベンチマークスコアは「6000」でした。
やったこととして、まず始めに
パフォーマンス1000%UP!PHPでMySQLのDB処理を行うと重いときに行うパフォーマンス施策~基礎編~
この記事を参考にコード内のSQLクエリの命令をバッククォートで囲みました。
具体的には
SELECT UserName, Pass, ScreenName FROM UserList WHERE UserName = "hogehoge"
と言うようなクエリがあった場合
SELECT
UserName,
Pass,
ScreenName
FROMUserList
WHEREUserName
= "hogehoge"
と言ったように、テーブル名や要素名をクォートで囲むことで無駄な処理が減り、今回のように数千単位のデータを扱う場合では有効なようです。
さらに、今回のコードではいくつかの機能別に関数を定義しておきそれを別関数内で読んでいる構造をしているためなのか
DBからデータを取得する際に「*」で全フィールドを取ってきていたので、各関数で必要なフィールドを書き出して必要最低限の
データのみを処理するように変更しました。
ひとまずはこれでSQL部分のチューニングはいいだろうと考え、今度はコード全体を眺めていくと、「str_replace」と言う関数を使っている処理を発見しました。この関数でもまったく問題なく動作するのですが、どうやらこの関数はあまり速度的に速く無いようだったため
「strtr」と言う関数へ変更しました。
たったこれだけのことにかなりの時間をかけてしまいましたが、たったこれだけで最終スコアは20000代に跳ね上がり、約4倍の高速化を行うことができました。
それもサーバサイドのチューニングを一切行なっていなかったのでSQLがかなりのボトルネックになっていたのだろうと感じました。
もしこれでサーバサイド、そして記事内で触れていませんでしたが画像呼び出しがSQL内のバイナリを変換して画像化すると言う処理をしている影響で 見て分かるほど処理が重たくなっていたので画像の実ファイル化を行えていればもしかすると今回のボーダーの40000点も超えられたのかなと感じました。
まとめ
今回は予選敗退という結果に終わりましたが、初参戦ながらもそこそこ良い結果を出せたのかなと思いました。また、今までコードチューンを
行う機会がなかったので初めて聞いたことや初めて知ったことなどが多く久しぶりに真剣に取り組んだなぁと感じました。
最近はやたら周りがCTFで盛り上がっていて萎えr...勢いについていけなくなったのもあってか、ISUCON非常に熱いなと感じたので来年も参加したい。