2016年9月2日金曜日

D言語のコンパイル時に決定する静的な文字列の扱い

ここにOSによって、SEGVするコードがある。
その理由を突き詰めてみる。

なお、元ネタ。


OS(というかバイナリの形式、Windowsの場合はPE、Linuxの場合はELF)によって
配置されるセグメントが違うから、SEGVが起こると考えられる。
実際どこに置かれるか、逆アセンブルして見てみる。

Linux版バイナリははWindows Subsystem for Linuxのbash(x86-64)でビルド
Windows版バイナリはWindows 10 Professional(x86-64)でビルド

Linux
c:\D_Codes\test>bash
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ uname -a
Linux SEGFODESKTOP 3.4.0+ #1 PREEMPT Thu Aug 1 17:06:05 CST 2013 x86_64 x86_64 x86_64 GNU/Linux 

Windows
c:\D_Codes\test>ver
Microsoft Windows [Version 10.0.14393]

dmdのバージョンは以下のとおり

Linux
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ dmd --version
DMD64 D Compiler v2.071.1                                                           
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ exit

Windows
c:\D_Codes\test>dmd --version
DMD32 D Compiler v2.071.1                 
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright


なお、コンパイルは以下のコマンドで行った。(共通)
※WSLでは現状32bitバイナリが実行できないため64bitバイナリを生成する。

dmd -m64 SEGV_OS_Dependent.d

実行結果は以下のとおり。

Linux版バイナリ
segfo@SEGFODESKTOP:/mnt/c/D_Codes/test$ ./test                                                           
Segmentation fault (コアダンプ)

Windows版バイナリ
c:\D_Codes\test>test                                                                                     
hAgehoge 

Linux版はSEGV
Windows版は最後まで実行できた。

この違いは何か、バイナリレベルで見てみる。
今回は、x64バイナリ、IDA Freeでは見られないので
Linuxはgdb、Windowsはx64Dbgで書き換えられる部分のメモリの属性を見る。

まずはLinux版

RBX0x7ffffe14c7f0 --> 0x7ffffe14c810 --> 0x0
RCX0x468491 --> 0x65676f6865676f ('ogehoge')   
RDX: 0x1  
[---------------------code------------------------------]
   0x438c12 <_Dmain+50>:        call   0x4390d0 <_D3app7__arrayZ>
   0x438c17 <_Dmain+55>:        inc    rcx
   0x438c1a <_Dmain+58>:        mov    QWORD PTR [rbp-0x10],rcx
=> 0x438c1e <_Dmain+62>:        mov    BYTE PTR [rcx],0x41
[-------------------------------------------------------]
Legend: codedatarodata, value
Stopped reason: SIGSEGV
0x0000000000438c1e in D main () at source/app.d:5                                                                                     
5               ubyte[] sb=cast(ubyte[])s;       

おもむろに、gdbを立ち上げて、runするだけ。

codeペインのこの行は
=> 0x438c1e <_Dmain+62>:        mov    BYTE PTR [rcx],0x41                        

D言語のソースの7行目を指している。
(gdbの画面では5行目がでているが、恐らくバグ)
なお、
Legend: codedatarodata, value 
で、rodata(読み取り専用セグメント)に代入していることがわかる。

vmmapで確認する

gdb-peda$ vmmap                                              
Start       End        Perm  Name
0x00400000  0x00481000 r-x-  /mnt/c/D_Codes/test/SEGV_OS_Dependent
0x00680000  0x00681000 r---  /mnt/c/D_Codes/test/SEGV_OS_Dependent
0x00681000  0x0068c000 rw--  /mnt/c/D_Codes/test/SEGV_OS_Dependent 

RCXの値は、
RCX0x468491 --> 0x65676f6865676f ('ogehoge') 

(cast(ubyte[])"hogehoge")[1] の2文字目の o を指しているポインタであることがわかる。
値は0x468491

vmmapでは、このメモリ領域のパーミッションは、r-x-となっている。
だからSEGVが発生する。

ではWindowsではどうか。

00007FF7229D1039の命令が7行目の命令。
RCXの値は 00007FF722A1EA41

00007FF722A1EA41のメモリ領域は、-RW-- となっており、読み書き自由。


以上のことから、コンパイル先のバイナリやOS?によってDコンパイラはデータを配置するセグメントを変えていると思われる。

Windowsの場合は、明示的にstringをimmutableにしてもキャストしたら実行できてしまった。
このことから、コンパイル時に確定している文字列は暗黙的にimmutable(またはconst)の扱いとなると考えられる。


D言語でも吸収できない闇を垣間見てしまった気がする。
おわり。

0 件のコメント:

コメントを投稿