その理由を突き詰めてみる。
なお、元ネタ。
ふとD言語のimportExpressionが気になって書いてみたコードがあって— α改 (@alpha_kai_NET) 2016年9月2日
なにってそれはバイナリをコンパイル時に読めるかな〜ってことでテストコードを書いたらSEGVしないと思ったところでSEGVしたしバグかなぁ
— α改 (@alpha_kai_NET) 2016年9月2日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]
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版
RBX: 0x7ffffe14c7f0 --> 0x7ffffe14c810 --> 0x0
RCX: 0x468491 --> 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: code, data, rodata, 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: code, data, rodata, 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の値は、
RCX: 0x468491 --> 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 件のコメント:
コメントを投稿