2016年12月30日 星期五

c++ exception handling 的實作 (2) - 使用 g++ 5.4.0

binary hacks 繁體中文版 item 38, 39, 40, 41 是用 gcc 3.4.4 講解, 用目前的 gcc 5.4.0 (20161231) 編譯的執行檔會直接 segmentfault, 無法在目前的系統上執行有點可惜, 我希望這段程式碼可以使用 gcc 5.4.0 編譯測試, 來試試看吧!

你可能有興趣:
c++ exception handling (1) - 原理篇

挑戰自己, 先來編譯 gcc 5.4.0, glibc 2.23。

env:
x64 debian 64 bit

系統上的 glibc 是使用 dwarf 的方式處理 exception handle 編譯出來的, 要以 static link 編譯 a.cpp 就出了問題, 得自己編譯以 sjlj 處理 exception handle 的 glibc。

dwarf 呼叫的是 _Unwind_RaiseException, sjlj 的版本呼叫的則是 _Unwind_SjLj_RaiseException, symbol 不一樣。

編譯 gcc 5.4.0
../gcc-5.4.0/configure --enable-languages=c,c++ --enable-sjlj-exceptions  --disable-nls
make
make install

編譯 glibc 2.23
../glibc-2.23/configure --disable-nls --disable-sanity-checks
make
make install

編譯 glibc 時會遇到 ldconfig, sln 無法使用 static 編譯的問題, 我用 touch 建立這 2 個檔案, 還有 libgcc.so 找不到的問題, 我修改了路徑以及加入 /usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/libgcc.a, 成功編譯出呼叫 sjlj 系列函式的 glibc。

修改 libgcc 的路徑, 補上紅色那段, 移出原本的 -lgcc
 1 
 3 gcc -nostdlib -nostartfiles /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/debug/pcprofiledump    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/debug/pcprofiledump.o  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed  `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o
 4 
 5 gcc -nostdlib -nostartfiles /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/login/utmpdump    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/login/utmpdump.o  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o
 6 
 7 gcc -nostdlib -nostartfiles /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/elf/sprof    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/elf/sprof.o /media/descent/usbhd/glibc-build/dlfcn/libdl.so.2  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o
 8 
 9 
10 gcc -nostdlib -nostartfiles /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/elf/pldd    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/elf/pldd.o /media/descent/usbhd/glibc-build/elf/xmalloc.o  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o
11 #!/bin/sh
12 gcc   -shared -static-libgcc -Wl,-O1  -Wl,-z,defs -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2  -B/media/descent/usbhd/glibc-build/csu/  -Wl,--version-script=/media/descent/usbhd/glibc-build/libc.map -Wl,-soname=libc.so.6 -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both -nostdlib -nostartfiles -e __libc_main -L/media/descent/usbhd/glibc-build -L/media/descent/usbhd/glibc-build/math -L/media/descent/usbhd/glibc-build/elf -L/media/descent/usbhd/glibc-build/dlfcn -L/media/descent/usbhd/glibc-build/nss -L/media/descent/usbhd/glibc-build/nis -L/media/descent/usbhd/glibc-build/rt -L/media/descent/usbhd/glibc-build/resolv -L/media/descent/usbhd/glibc-build/crypt -L/media/descent/usbhd/glibc-build/mathvec -L/media/descent/usbhd/glibc-build/nptl -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/libc.so -T /media/descent/usbhd/glibc-build/shlib.lds /media/descent/usbhd/glibc-build/csu/abi-note.o /media/descent/usbhd/glibc-build/elf/soinit.os /media/descent/usbhd/glibc-build/libc_pic.os /media/descent/usbhd/glibc-build/elf/sofini.os /media/descent/usbhd/glibc-build/elf/interp.os /media/descent/usbhd/glibc-build/elf/ld.so 
13 
14 #!/bin/sh
15 gcc   -shared -static-libgcc -Wl,-O1  -Wl,-z,defs -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2  -B/media/descent/usbhd/glibc-build/csu/  -Wl,--version-script=/media/descent/usbhd/glibc-build/libc.map -Wl,-soname=libc.so.6 -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both -nostdlib -nostartfiles -e __libc_main -L/media/descent/usbhd/glibc-build -L/media/descent/usbhd/glibc-build/math -L/media/descent/usbhd/glibc-build/elf -L/media/descent/usbhd/glibc-build/dlfcn -L/media/descent/usbhd/glibc-build/nss -L/media/descent/usbhd/glibc-build/nis -L/media/descent/usbhd/glibc-build/rt -L/media/descent/usbhd/glibc-build/resolv -L/media/descent/usbhd/glibc-build/crypt -L/media/descent/usbhd/glibc-build/mathvec -L/media/descent/usbhd/glibc-build/nptl -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/linkobj/libc.so -T /media/descent/usbhd/glibc-build/shlib.lds /media/descent/usbhd/glibc-build/csu/abi-note.o /media/descent/usbhd/glibc-build/elf/soinit.os -Wl,--whole-archive /media/descent/usbhd/glibc-build/linkobj/libc_pic.a -Wl,--no-whole-archive /media/descent/usbhd/glibc-build/elf/sofini.os /media/descent/usbhd/glibc-build/elf/interp.os /media/descent/usbhd/glibc-build/elf/ld.so 
16 
17 gcc -nostdlib -nostartfiles /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/iconv/iconvconfig    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/iconv/iconvconfig.o /media/descent/usbhd/glibc-build/iconv/strtab.o /media/descent/usbhd/glibc-build/iconv/xmalloc.o /media/descent/usbhd/glibc-build/iconv/hash-string.o  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed  `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o
18 
19 gcc -nostdlib -nostartfiles /usr/local/lib64/libgcc_s.so -o /media/descent/usbhd/glibc-build/iconv/iconv_prog    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/iconv/iconv_prog.o /media/descent/usbhd/glibc-build/iconv/iconv_charmap.o /media/descent/usbhd/glibc-build/iconv/charmap.o /media/descent/usbhd/glibc-build/iconv/charmap-dir.o /media/descent/usbhd/glibc-build/iconv/linereader.o /media/descent/usbhd/glibc-build/iconv/dummy-repertoire.o /media/descent/usbhd/glibc-build/iconv/simple-hash.o /media/descent/usbhd/glibc-build/iconv/xstrdup.o /media/descent/usbhd/glibc-build/iconv/xmalloc.o  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o
20 
21 gcc -nostdlib -nostartfiles -o /media/descent/usbhd/glibc-build/locale/localedef    -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both /media/descent/usbhd/glibc-build/csu/crt1.o /media/descent/usbhd/glibc-build/csu/crti.o `gcc  --print-file-name=crtbegin.o` /media/descent/usbhd/glibc-build/locale/localedef.o /media/descent/usbhd/glibc-build/locale/ld-ctype.o /media/descent/usbhd/glibc-build/locale/ld-messages.o /media/descent/usbhd/glibc-build/locale/ld-monetary.o /media/descent/usbhd/glibc-build/locale/ld-numeric.o /media/descent/usbhd/glibc-build/locale/ld-time.o /media/descent/usbhd/glibc-build/locale/ld-paper.o /media/descent/usbhd/glibc-build/locale/ld-name.o /media/descent/usbhd/glibc-build/locale/ld-address.o /media/descent/usbhd/glibc-build/locale/ld-telephone.o /media/descent/usbhd/glibc-build/locale/ld-measurement.o /media/descent/usbhd/glibc-build/locale/ld-identification.o /media/descent/usbhd/glibc-build/locale/ld-collate.o /media/descent/usbhd/glibc-build/locale/charmap.o /media/descent/usbhd/glibc-build/locale/linereader.o /media/descent/usbhd/glibc-build/locale/locfile.o /media/descent/usbhd/glibc-build/locale/repertoire.o /media/descent/usbhd/glibc-build/locale/locarchive.o /media/descent/usbhd/glibc-build/locale/md5.o /media/descent/usbhd/glibc-build/locale/charmap-dir.o /media/descent/usbhd/glibc-build/locale/simple-hash.o /media/descent/usbhd/glibc-build/locale/xmalloc.o /media/descent/usbhd/glibc-build/locale/xstrdup.o  -Wl,-dynamic-linker=/usr/local/lib/ld-linux-x86-64.so.2 -Wl,-rpath-link=/media/descent/usbhd/glibc-build:/media/descent/usbhd/glibc-build/math:/media/descent/usbhd/glibc-build/elf:/media/descent/usbhd/glibc-build/dlfcn:/media/descent/usbhd/glibc-build/nss:/media/descent/usbhd/glibc-build/nis:/media/descent/usbhd/glibc-build/rt:/media/descent/usbhd/glibc-build/resolv:/media/descent/usbhd/glibc-build/crypt:/media/descent/usbhd/glibc-build/mathvec:/media/descent/usbhd/glibc-build/nptl /media/descent/usbhd/glibc-build/libc.so.6 /media/descent/usbhd/glibc-build/libc_nonshared.a -Wl,--as-needed /media/descent/usbhd/glibc-build/elf/ld.so -Wl,--no-as-needed -lgcc  `gcc  --print-file-name=crtend.o` /media/descent/usbhd/glibc-build/csu/crtn.o

某些檔案編不起來, 直接 touch 建立這些檔案
touch /media/descent/usbhd/glibc-build/locale/localedef
touch /media/descent/usbhd/glibc-build/locale/locale
touch /media/descent/usbhd/glibc-build/catgets/gencat
touch /media/descent/usbhd/glibc-build/timezone/zic
touch /media/descent/usbhd/glibc-build/timezone/zdump
touch /media/descent/usbhd/glibc-build/posix/getconf
touch /media/descent/usbhd/glibc-build/io/pwd

touch /media/descent/usbhd/glibc-build/nss/getent
touch /media/descent/usbhd/glibc-build/nss/makedb
touch /media/descent/usbhd/glibc-build/sunrpc/rpcgen
touch /media/descent/usbhd/glibc-build/nscd/nscd
touch /media/descent/usbhd/glibc-build/elf/sln
touch /media/descent/usbhd/glibc-build/elf/ldconfig

sln, ldconfig 在 make install 時
sln, ldconfig 需要修改執行權限
chmod 755 sln
chmod 755 ldconfig

其內容改成
cat ldconfig
#!/bin/sh
exit 0

cat sln
#!/bin/sh
exit 0

以 dynamic link 編譯 a.cpp 時, 該注意的地方
dynamic link libgcc 時
使用 ldd 查看 so
descent@deb:eh_impl$ /usr/bin/ldd a
linux-vdso.so.1 (0x00007ffe1b261000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f549386f000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f549356b000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5493354000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5492fb6000)
/lib64/ld-linux-x86-64.so.2 (0x00005636522ad000)

/lib/x86_64-linux-gnu/libgcc_s.so.1 沒有執行我們編譯的那個,

export LD_LIBRARY_PATH=/usr/local/lib64/ # x64 64 bit environment
export LD_LIBRARY_PATH=/usr/local/lib/ # x86 32 bit environment

再一次 ldd
descent@deb:eh_impl$ /usr/bin/ldd a
linux-vdso.so.1 (0x00007fff905b2000)
libstdc++.so.6 => /usr/local/lib64/libstdc++.so.6 (0x00007f463eb5e000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f463e843000)
libgcc_s.so.1 => /usr/local/lib64/libgcc_s.so.1 (0x00007f463e631000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f463e293000)
/lib64/ld-linux-x86-64.so.2 (0x000055bb0e8f5000)

注意 libgcc_s.so.1 path。

static link a.cpp 需要重新編譯 glibc, 所以才編譯了 glibc-2.23, 為什麼需要使用 static link 呢? 因為使用 gdb 時, libgcc.so 的行號對應似乎有問題, 所以才想要使用 static link, 不過由於有 2 套 glibc, 我不知道怎麼讓 gcc 使用我自己編的那個, 就有了很蠢 d.sh 那個方法, -lunwind 也可以拿掉。

而和 gcc 3.4.4 搭配的 glibc 是 2.3.5, 2.23 無法使用 gcc 3.4.4 編譯, 在目前的系統上因為有些工具太新而不能編譯, 例如: sed, awk, make, binutility ...

d.sh for static link
1 #!/bin/sh
2 /usr/local/libexec/gcc/x86_64-unknown-linux-gnu/5.4.0/collect2 -plugin /usr/local/libexec/gcc/x86_64-unknown-linux-gnu/5.4.0/liblto_plugin.so -plugin-opt=/usr/local/libexec/gcc/x86_64-unknown-linux-gnu/5.4.0/lto-wrapper -plugin-opt=-fresolution=/tmp/ccnKcDbO.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lc -m elf_x86_64 -static -o a /usr/local/lib/crt1.o /usr/local/lib/crti.o /usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/crtbeginT.o -L/usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.4.0 -L/usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/../../../../lib64 -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/local/lib -L/usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/../../.. a.o -lstdc++ -lm --start-group -lunwind -lgcc -lgcc_eh -lc --end-group /usr/local/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/crtend.o /usr/local/lib/crtn.o

一開始沒有修改 lsda, 執行下去馬上就 segmentfault, 所以該啟動反組譯工程了。

有了自行編譯的 glibc 之後, 就可以使用 gdb debug 這個 static link 的檔案, 方便理解整個來龍去脈, 遺憾的就是 lsda 裡頭的格式我還是不了解, 但我還是可以把 lsda 的格式填對, 怎麼做呢?

list 1 是 .gcc_except_table, 就是那個 lsda, 可是 L7 不是一個值, 而是一個需要計算才知道的值, 我要怎麼填呢?

list 1. g++ -S a.cpp
 1 .LFE1042:
 2         .section        .gcc_except_table
 3         .align 4
 4 .LLSDA1042:
 5         .byte   0xff
 6         .byte   0x3
 7         .uleb128 .LLSDATT1042-.LLSDATTD1042
 8 .LLSDATTD1042:
 9         .byte   0x1
10         .uleb128 .LLSDACSE1042-.LLSDACSB1042
11 .LLSDACSB1042:
12         .uleb128 0
13         .uleb128 0x1
14         .uleb128 0x1
15         .uleb128 0
16 .LLSDACSE1042:
17         .byte   0x1
18         .byte   0
19         .align 4
20         .long   _ZTIi
21 .LLSDATT1042:
22         .text

靠 objdump -D a, list 2 L1329 開始的 ff, 03, 0x0d, 0x01, 4, 0, 1, 1, 0, 1, 0 不就是答案了嗎?

list 2. objdump -D a
1322 Disassembly of section .gcc_except_table:
1323
1324 00000000004010e4 <.gcc_except_table>:
1325   4010e4:   ff                      (bad)
1326   4010e5:   ff 01                   incl   (%rcx)
1327   4010e7:   02 00                   add    (%rax),%al
1328   4010e9:   00 00                   add    %al,(%rax)
1329   4010eb:   00 ff                   add    %bh,%bh
1330   4010ed:   03 0d 01 04 00 01       add    0x1000401(%rip),%ecx        # 14014f4 <_end+0xdff30c>
1331   4010f3:   01 00                   add    %eax,(%rax)
1332   4010f5:   01 00                   add    %eax,(%rax)
1333   4010f7:   00 d0                   add    %dl,%al
1334   4010f9:   21 60 00                and    %esp,0x0(%rax)
1335
1336 Disassembly of section .init_array:

把這串神秘的數字換掉 binary hack 的範例程式, 就可以使用 g++ 5.4.0 來編譯這個程式, 並且成功的執行 exception handle, 爽阿!

完整 source code 請參考 https://github.com/descent/eh_impl, 支援 gcc 3.4.4 以及 gcc 5.4.0。

2016年12月25日 星期日

c++ exception handling 的實作 (1) - 原理篇


20161227 補充
原本的文章寫的實在是太爛了, 先寫出來是為了讓自己記得改善, 只是沒想到這麼快就可以改善了。我竟然忘了參考 binary hacks 繁體中文版 item 38, 39, 40, 41, 以前看過好幾次都看不懂, 這次竟然看懂了, 趕緊補上像樣點的心得, 若你能看到我的 git log, 一定可以理解我花在這篇文章的心血。

一直以來都會以一個小程式來說明某個概念, 而不是形而上的理論/觀念的敘述, 可以用 gdb 來追蹤, 觀察執行後的結果, 反組譯執行檔, 這是我認為在程式學習上很重要的步驟, 只有口頭敘述, 沒有將它具象化, 感覺好像明白, 但又有什麼沒搞懂的模糊感。

你一定有興趣:
c++ exception handling (2) - 使用 g++ 5.4.0

fig 0. 金字塔知識門檻

c++ exception handling 還真不是普通的複雜, 我目前僅僅知道其實作原理, 但實作細節太複雜, 沒能搞懂。面試 c++ 時常會看到 virtaul function 如何實作的考題, 但卻沒看過問 c++ exception handling 怎麼實作, 沒有別的原因, 就是因為它難到只有很少人才知道怎麼實作, 不知道怎麼實做 exception handling 一點都不丟臉, 因為連 cfront 也搞不定

挑戰這麼難的東西, 又沒有什麼經濟效益, 我一定是阿達了。廢話不多說, 來看看 gcc 怎麼實作 c++ exception handling。

vc 和 gcc 有不同的作法, 我研究的是 gcc 的作法。

看了不少參考資料, 本篇文章以 binary hacks 繁體中文版 item 38, 39, 40, 41 為主, 因為有個小程式可以用來實驗以及說明 exception handle。

下面這 3 個函式是最主要的關鍵:
1 __cxa_throw
2 _Unwind_RaiseException
3 __gxx_personality_v0 (int version, _Unwind_Action actions, _Unwind_Exception_Class exception_class, struct _Unwind_Exception *ue_header, struct _Unwind_Context *context)

這些函式的 source code 在 gcc libgcc 目錄下, libgcc 是一個很神秘的 library, 裡頭幾乎是 gcc 特異功能的實做。unwind, 軟體浮點數 ... 都是在這裡。

gcc-3.4.4/gcc
gcc-5.4.0/libgcc

_Unwind_SjLj_RaiseException
_Unwind_RaiseException
gcc-5.4.0/libgcc/unwind-sjlj.c
gcc-5.4.0/libgcc/unwind.inc
#define PERSONALITY_FUNCTION __gxx_personality_v0

PERSONALITY_FUNCTION (int version,
_Unwind_Action actions,
_Unwind_Exception_Class exception_class,
struct _Unwind_Exception *ue_header,
struct _Unwind_Context *context)

/gcc-5.4.0/libstdc++-v3/libsupc++/eh_personality.cc
__cxa_throw
extern "C" void __cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo, void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
gcc-5.4.0/libstdc++-v3/libsupc++/eh_throw.cc

a.cpp L116 throw 100;
會轉成呼叫 (ref a.cpp L118 ~ 120)
__cxa_allocate_exception()
__cxa_throw()

__cxa_throw() 發動時的流程:
__cxxabiv1::__cxa_throw
->
執行的是 _Unwind_SjLj_RaiseException

#ifdef _GLIBCXX_SJLJ_EXCEPTIONS
_Unwind_SjLj_RaiseException (&header->exc.unwindHeader);
#else
_Unwind_RaiseException (&header->exc.unwindHeader);
#endif
|
|
|->   __gxx_personality_sj0
|
|
|-> uw_install_context

uw_install_context 會呼叫 longjmp 回到上一個函式, 以 a.cpp 來說, 就是 func1()。

__gxx_personality_sj0 是幹麻用的? 搜尋是不是有對應的 catch statement, 或是有那個物件需要解構, 得去執行解構函式, 要跳去的那個位址有個很厲害的術語叫做 landing_pad, source code 會看到 landing_pad = info.LPStart + cs_lp;, 就是用來找到要去執行解構函式或是 catch statement 的位址, 一旦 uw_install_context 執行之後, 就會跳去那個位址。

像 func1() 有個物件需要解構, __gxx_personality_sj0 知道這件事情, 所以才要讓 _Unwind_RaiseException 往 func1 跳, 很神奇是吧! 一但 func1() 拿掉 a.cpp L128 那個 Obj obj, 就不會跳回 func1()。

那 __gxx_personality_sj0 怎麼知道這些事情的, 這個就很複雜, 得靠 g++ 在編譯的時候塞入 dwarf 裡頭的資訊, 而要怎麼取出這些資訊也很神秘, 和 CIE 及 FDE 有關, 不過我不知道這 2 個是什麼東西, 也不知道怎麼取出來, 就算讀了 source code, 也還是看不懂。

另外 __gxx_personality_sj0 會比對丟出的例外物件和 catch 的例外物件, 如果一樣, landing_pad 才會往那個 catch 指定, 這就是為什麼 exception handle 需要 rtti 的支援, rtti 的 type_info 物件, 就是拿來比對這 2 個例外物件有沒有一致。

bt.cpp 只有模擬一半的功能, 使用 setjmp/longjmp, back_to_func 可以回到前一個 function, sjlj 就是用類似的方法串起這些 jmp_buf; 不過我不知道怎麼使用 .eh_frame, .gcc_except_table section 裡頭的資料來得知是不是有那個解構函式需要執行, 是不是有符合的 catch statement。

bt.cpp
 1 #include <setjmp.h>
 2 #include <string>
 3 #include <map>
 4 
 5 using namespace std;
 6 
 7 map<string, jmp_buf> stack_frame;
 8 
 9 void back_to_func(const string &fn)
10 {
11   //jmp_buf frame =  stack_frame[fn];
12   //stack_frame[fn];
13   longjmp(stack_frame[fn], 5);
14 }
15 
16 void f3()
17 {
18   printf("in f3\n");
19   back_to_func("f2");
20 }
21 
22 void f2()
23 {
24   jmp_buf frame; 
25   int ret = setjmp(frame);
26   if (ret == 0)
27   {
28     stack_frame.insert({"f2", frame});
29     f3();
30   }
31   else
32   {
33     printf("back to f2\n");
34     back_to_func("f1");
35   }
36 }
37 
38 void f1()
39 {
40   jmp_buf frame; 
41   int ret = setjmp(frame);
42   if (ret == 0)
43   {
44     stack_frame.insert({"f1", frame});
45     f2();
46   }
47   else
48   {
49     printf("back to f1\n");
50     back_to_func("main");
51   }
52 }
53 
54 int main(int argc, char *argv[])
55 {
56   jmp_buf frame; 
57   int ret = setjmp(frame);
58   if (ret == 0)
59   {
60     stack_frame.insert({"main", frame});
61     f1(); 
62   }
63   else
64   {
65     printf("back to main\n");
66   }
67   printf("end main\n");
68   return 0;
69 }

binary hacks 繁體中文版 item 38, 39, 40, 41 是用 gcc 3.4.4 講解, 雖然過時了, 但基本原理是一樣的, 就先從 gcc 3.4.4 的建構開始吧。

g++ 使用 setjmp/longjmp, dwarf 這兩種來支援 c++ exception handle, 目前的 gcc 5 似乎不使用 --enable-sjlj-exceptions, 我比較熟悉 setjmp/longjmp 的作法, dwarf2 太苦了, 我不想走這條路, 先以 --enable-sjlj-exceptions 來建構 gcc 3.4.4。

我以熟悉的 setjmp/long 來學習, 編譯 gcc 3.4.4 加上 --enable-sjlj-exceptions, 即使用以 setjmp/longjmp 實做的 exception handle。

setjmp/longjmp, dwarf 是用來處理 unwind, 就是從目前的函式回到上一個函式, 類似 bt.cpp 做的事情, dwarf 的作法需要去理解 dwarf 格式, 聽說是不得了的複雜, 我不想花時間在上頭, 而 setjmp/longjmp 我已經知道其實作原理, 不需要在花額外的功夫。

另外一個需要的能力就是知道要回到那一個 function, 這就是靠神秘的 LSDA 的內容來得知, g++ 會在 .gcc_except_table section 插入某些資訊, 讓 __gxx_personality_sj0 可以用來判斷要回到那個函式。

env:
32 bit debian

編譯 gcc-3.4.4
tar xvf gcc-3.4.4.tar.bz2
mkdir gcc-build
cd gcc-build
../gcc-3.4.4/configure --enable-languages=c,c++ --enable-sjlj-exceptions
make
make install

編譯時可能會遇到一些 header 的問題, 我把 /usr/include/i386-linux-gnu/* link 到 /usr/include

root@debian32:/usr/include# ls -l sys
lrwxrwxrwx 1 root root 32 Dec 26 15:42 sys -> /usr/include/i386-linux-gnu/sys

沒支援 --enable-sjlj-exceptions g++ 的編譯錯誤
descent@debian64:eh_impl$ g++ -g -o a a.cpp
/tmp/ccRq4BBp.o: In function `main':
/home/descent/git/eh_impl/a.cpp:126: undefined reference to `__gxx_personality_sj0'
/home/descent/git/eh_impl/a.cpp:138: undefined reference to `_Unwind_SjLj_Register'
/home/descent/git/eh_impl/a.cpp:142: undefined reference to `_Unwind_SjLj_Unregister'
collect2: error: ld returned 1 exit status

支援 --enable-sjlj-exceptions 的 g++
descent@debian32:eh_impl$ g++ -v
Reading specs from /usr/local/lib/gcc/i686-pc-linux-gnu/3.4.4/specs
Configured with: ../gcc-3.4.4/configure --enable-languages=c,c++ --enable-sjlj-exceptions
Thread model: posix
gcc version 3.4.4

a.cpp 是 binary hack 書上提供的範例, 提供了對照, try/catch/throw 是怎麼轉成一般的 c++ 程式碼, 看上去就清楚了, 最麻煩的就是那個 lsda 到底是怎麼樣的資料結構, 可惜書上也沒寫得很清楚, 看來只能看第 0 手資料了。

list 1. a.cpp 執行結果
/usr/local/bin/g++ -g -o a a.cpp 

使用 ldd 查看 so

descent@debian32:eh_impl$ ldd a
linux-gate.so.1 (0xb77bf000)
libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb763a000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb75e5000)
libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb75c8000) # 沒有 dynamic link 到我們編譯的那個 libgcc_s.so.1
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7411000)
/lib/ld-linux.so.2 (0x8007d000)

debian32:eh_impl$ export LD_LIBRARY_PATH=/usr/local/lib # x86 32 bit environment

再一次 ldd
descent@debian32:eh_impl$ ldd a
linux-gate.so.1 (0xb779e000)
libstdc++.so.6 => /usr/local/lib/libstdc++.so.6 (0xb76b6000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb764f000)
libgcc_s.so.1 => /usr/local/lib/libgcc_s.so.1 (0xb7647000) # link 到我們自己編譯的 gcc 3.4.4 的 libgcc.so
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7490000)
/lib/ld-linux.so.2 (0x800e2000)

這樣就對了。

descent@debian32:eh_impl$ ./a 
func1 begin
obj ctor
func2 begin
obj dtor
thrown_obj: 100

list 1 的結果可以成功呼叫解構函式, 以及跑到正確的 catch 程式碼。可以用 gdb 跑跑看, exception handle 的神秘感解除了一半, 另外一半還在 libunwind, libgcc 裡頭的函式。

a.cpp L166 就是 L118 ~ 120 那 3 行; a.cpp L137 ~ 145 就是 L149 ~ 167 那麼多行。

a.cpp
  1 // test c++ exception handle by g++ 3.4.4
  2 // example code from binary hacks chinese version, page 145
  3 
  4 #include <cstdio>
  5 #include <iostream>
  6 #include <typeinfo>
  7 using namespace std;
  8 
  9 #include <unwind.h>
 10 
 11 extern "C" 
 12 {
 13   // libsupc++/eh_alloc.cc
 14   void * __cxa_allocate_exception(std::size_t thrown_size);
 15 
 16   // libsupc++/eh_throw.cc
 17   //void __cxa_throw (void *obj, std::type_info *tinfo, void (*dest) (void *));
 18   void __cxa_throw (void *obj, void *tinfo, void (*dest) (void *));
 19 
 20   // libsupc++/eh_catch.cc
 21   void * __cxa_begin_catch (void *exc_obj_in);
 22   void __cxa_end_catch ();
 23 
 24 
 25   #define PERSONALITY_FUNCTION    __gxx_personality_sj0
 26 
 27   // libsupc++/eh_personality.cc
 28   _Unwind_Reason_Code PERSONALITY_FUNCTION (int version,
 29                       _Unwind_Action actions,
 30                       _Unwind_Exception_Class exception_class,
 31                       struct _Unwind_Exception *ue_header,
 32                       struct _Unwind_Context *context);
 33 
 34 
 35 }
 36 
 37 struct Lsda
 38 {
 39   unsigned char start_format;
 40   unsigned char type_format;
 41   unsigned char type_length;
 42   unsigned char call_site_format;
 43   unsigned char call_site_length;
 44   unsigned char call_site_table[2];
 45   signed char action_table[2];
 46   const std::type_info *catch_type[1];
 47 }__attribute__((packed));
 48 
 49 Lsda my_lsda=
 50 {
 51   0xff,
 52   0x00,
 53   10,
 54   0x01,
 55   2,
 56   {0,1},
 57   {1,0},
 58   &typeid(int),
 59 };
 60 
 61 
 62 // unwind-sjlj.c
 63 /* This structure is allocated on the stack of the target function.
 64    This must match the definition created in except.c:init_eh.  */
 65 struct SjLj_Function_Context
 66 {
 67   /* This is the chain through all registered contexts.  It is
 68      filled in by _Unwind_SjLj_Register.  */
 69   struct SjLj_Function_Context *prev;
 70   
 71   /* This is assigned in by the target function before every call
 72      to the index of the call site in the lsda.  It is assigned by
 73      the personality routine to the landing pad index.  */
 74   int call_site;
 75   
 76   /* This is how data is returned from the personality routine to
 77      the target function's handler.  */
 78   _Unwind_Word data[4];
 79   
 80   /* These are filled in once by the target function before any
 81      exceptions are expected to be handled.  */
 82   _Unwind_Personality_Fn personality;
 83   void *lsda;
 84 
 85 #ifdef DONT_USE_BUILTIN_SETJMP
 86   /* We don't know what sort of alignment requirements the system
 87      jmp_buf has.  We over estimated in except.c, and now we have
 88      to match that here just in case the system *didn't* have more
 89      restrictive requirements.  */
 90   jmp_buf jbuf __attribute__((aligned));
 91 #else
 92   void *jbuf[];
 93 #endif 
 94 };
 95 
 96 //#define CXX_EH
 97 
 98 class Obj
 99 {
100   public:
101     Obj()
102     {
103       cout << "obj ctor" << endl;
104     }
105     ~Obj()
106     {
107       cout << "obj dtor" << endl;
108     }
109 
110 };
111 
112 void func2()
113 {
114   cout << "func2 begin" << endl;
115 #ifdef CXX_EH
116   throw 100;
117 #else
118   void *throw_obj = __cxa_allocate_exception(sizeof(int));
119   *(int*)throw_obj = 100; // 這就是那個 throw 100, 的那個 100
120   __cxa_throw(throw_obj, (std::type_info*)&typeid(int), NULL);
121 #endif
122   cout << "func2 end" << endl;
123 }
124 
125 void func1()
126 {
127   cout << "func1 begin" << endl;
128   Obj obj;
129 
130   func2();
131   cout << "func1 end" << endl;
132 }
133 
134 int main(int argc, char *argv[])
135 {
136 #ifdef CXX_EH
137   try
138   {
139     cout << "hello" << endl; 
140     func1();
141   }
142   catch (int eh)
143   {
144     cout << "catch int: " << eh << endl; 
145   }
146 
147 #else
148   
149   SjLj_Function_Context sjlj;
150 
151   sjlj.personality = __gxx_personality_sj0;
152   sjlj.lsda = (void*)&my_lsda;
153   sjlj.call_site = 1;
154 
155   if (__builtin_setjmp(sjlj.jbuf) == 1)
156   {
157     void *thrown_obj = __cxa_begin_catch((void*)sjlj.data[0]);
158     printf("thrown_obj: %d\n", *(int*)thrown_obj);
159     __cxa_end_catch();
160   }
161   else
162   {
163     _Unwind_SjLj_Register(&sjlj);
164     //throw 100;
165     func1();
166   }
167   _Unwind_SjLj_Unregister(&sjlj);
168 #endif
169   return 0;
170 }

objdump -d a 看不到詳細的反組譯程式碼, 我使用 gdb 來反組譯, 這是意外的收穫。

list 2. dis.gdb
 1   >0x8048b16 <func1()+96>  lea    -0x28(%ebp),%eax
 2    0x8048b19 <func1()+99>  mov    %eax,(%esp)       
 3    0x8048b1c <func1()+102> call   0x8048d2e <Obj::Obj()>
 4    0x8048b21 <func1()+107> movl   $0x1,-0x58(%ebp)           
 5    0x8048b28 <func1()+114> call   0x8048a32 <func2()> 
 6    0x8048b2d <func1()+119> movl   $0x8048e5a,0x4(%esp)     
 7    0x8048b35 <func1()+127> movl   $0x804b080,(%esp)       
 8    0x8048b3c <func1()+134> call   0x80487d0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 
 9    0x8048b41 <func1()+139> movl   $0x8048780,0x4(%esp)
10    0x8048b49 <func1()+147> mov    %eax,(%esp)        
11    0x8048b4c <func1()+150> call   0x8048770 <_ZNSolsEPFRSoS_E@plt> 
12    0x8048b51 <func1()+155> jmp    0x8048b8c <func1()+214>    
13    0x8048b53 <func1()+157> lea    0x18(%ebp),%ebp    
14    0x8048b56 <func1()+160> mov    -0x54(%ebp),%eax  
15    0x8048b59 <func1()+163> mov    %eax,-0x64(%ebp) 
16    0x8048b5c <func1()+166> mov    -0x64(%ebp),%edx 
17    0x8048b5f <func1()+169> mov    %edx,-0x60(%ebp)   
18    0x8048b62 <func1()+172> lea    -0x28(%ebp),%eax  
19    0x8048b65 <func1()+175> mov    %eax,(%esp)      
20    0x8048b68 <func1()+178> movl   $0x0,-0x58(%ebp) 
21    0x8048b6f <func1()+185> call   0x8048d02 <Obj::~Obj()>  the 1st dtor 
22    0x8048b74 <func1()+190> mov    -0x60(%ebp),%eax  
23    0x8048b77 <func1()+193> mov    %eax,-0x64(%ebp) 
24    0x8048b7a <func1()+196> mov    -0x64(%ebp),%edx
25    0x8048b7d <func1()+199> mov    %edx,(%esp)    
26    0x8048b80 <func1()+202> movl   $0xffffffff,-0x58(%ebp) 
27    0x8048b87 <func1()+209> call   0x80487e0 <_Unwind_SjLj_Resume@plt> 
28    0x8048b8c <func1()+214> lea    -0x28(%ebp),%eax      
29    0x8048b8f <func1()+217> mov    %eax,(%esp)          
30    0x8048b92 <func1()+220> movl   $0xffffffff,-0x58(%ebp)
31    0x8048b99 <func1()+227> call   0x8048d02 <Obj::~Obj()>          the 2nd dtor  
32    0x8048b9e <func1()+232> lea    -0x5c(%ebp),%eax 
33    0x8048ba1 <func1()+235> mov    %eax,(%esp)     
34    0x8048ba4 <func1()+238> call   0x8048810 <_Unwind_SjLj_Unregister@plt>
35    0x8048ba9 <func1()+243> add    $0x6c,%esp     
36    0x8048bac <func1()+246> pop    %ebx          
37    0x8048bad <func1()+247> pop    %esi         
38    0x8048bae <func1()+248> pop    %edi        
39    0x8048baf <func1()+249> pop    %ebp       
40    0x8048bb0 <func1()+250> ret   

list 2 L21, L31 有 2 個 dtor, 很奇怪吧, L21 是給 exception handle 用的, 當從 throw 回到 func1 時, 會莫名的抵達這裡, 事實上是回到 L13 0x8048b53 這裡, 然後在執行 L27 回到上一個 stack frame (本例來說就是 main); L31 則是給正常執行流程呼叫的 dtor, L12 有個狡猾的 jmp, 真是機關算盡。

list 3 是 g++ 3.4.4 的反組譯版本, 更清楚了, 我應該早點想到的, 它不只為我解除了 2 個 dtor 的疑惑, 還把莫名會抵達 func1() 的原因也找了出來, 甚至連那個 Lsda 也幫我釐清了, 也因為知道 Lsda 的內容, 我連帶改出 g++ 5.4.0 的版本了。

list 3 是使用 try/catch/throw 的版本, list 3 L302, 303, 是不是和自己填入 a.cpp L151 ~ 153 一樣呢?

list 3 L303, L387 就是那個該死的 lsda, 從 list 3 L387 ~ L402, 在 .gcc_except_table section (就是 LSDA - Language Specific Data Area), 又是另外一個狡猾的地方。

至於 g++ 5.4.0 我怎麼改出來的呢? 就是用 g++ 5.4.0 去反組譯 try/catch/throw 的版本, 把 .gcc_except_table section, 填到那個 lsda 就好了, 果然還真的不同。

再來是那個莫名回到 func1 的動作是怎麼作到的呢? 這個困擾我好久, 用 gdb 追也找不出所以然, 照理說應該要有一個 setjmp 在這裡, 才能透過 longjmp 回到這, 但我就一直找不到哪裡有 call setjmp, 直到我用 g++ -S 之後才看到, 原來 g++ 在 func1 安插了類似 setjmp 的程式碼, 這才讓 _Unwind_RaiseException 有能力回到 func1。

list 3 L182 _Unwind_SjLj_Register 的動作類似 bt.cpp 那個 map<string, jmp_buf>, 把每一個 fuction 要回來的位置記起來, 它的參數 SjLj_Function_Context 裡頭有 jmp_buf, 得先把 jmp_buf 填好才行, 讓 uw_install_context 的 longjmp 回到這裡。

由於是 g++ 插入的 code, 得從組合語言去看出來才行, 還真是難。L177 的 .L18 就是 setjmp 紀錄起來的值, 這裡就是在填上面說的 jmp_buf 的部份, 但並不是產生呼叫 setjmp 的程式碼, 而是填入那個 jmp_buf 所需要的值就可以了, 所以 _Unwind_RaiseException 發動 uw_install_context, 就會回到 L202, 和 gdb 的顯示是一樣的。

把 func1() Obj obj; 拿掉, 再看 g++ 產生的 a.s, 就會發現那個 func1 和 c 的長相一樣, 不會被偷偷插入那麼多程式碼了。

#define uw_install_context(CURRENT, TARGET)       \
  do                                              \
  {                                               \
    _Unwind_SjLj_SetContext ((TARGET)->fc);    \
    longjmp ((TARGET)->fc->jbuf, 1);        \
  }                                               \
  while (0)

list 3 L172 ~ 173 是不是有類似的行為, 塞入 __gxx_personality_sj0, lsda 這些資料, lsda 是我目前還無法突破的部份。

list 3. g++-3.4.4 -S a.cpp a-3.3.4.s
  1  .file "a.cpp"
  2  .text
  3  .align 2
  4  .type _ZSt17__verify_groupingPKcjRKSs, @function
116 .LC0:
117  .string "func2 begin"
118 .LC1:
119  .string "func2 end"
120  .text
121  .align 2
122 .globl _Z5func2v
123  .type _Z5func2v, @function
124 _Z5func2v:
125  pushl %ebp
126  movl %esp, %ebp
127  subl $24, %esp
128  movl $.LC0, 4(%esp)
129  movl $_ZSt4cout, (%esp)
130  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
131  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
132  movl %eax, (%esp)
133  call _ZNSolsEPFRSoS_E
134  movl $4, (%esp)
135  call __cxa_allocate_exception
136  movl $100, (%eax)
137 .L11:
138  movl $0, 8(%esp)
139  movl $_ZTIi, 4(%esp)
140  movl %eax, (%esp)
141  call __cxa_throw
142  movl $.LC1, 4(%esp)
143  movl $_ZSt4cout, (%esp)
144  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
145  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
146  movl %eax, (%esp)
147  call _ZNSolsEPFRSoS_E
148  leave
149  ret
150 .L10:
151  .size _Z5func2v, .-_Z5func2v
152 .globl _Unwind_SjLj_Resume
153 .globl __gxx_personality_sj0
154 .globl _Unwind_SjLj_Register
155 .globl _Unwind_SjLj_Unregister
156  .section .rodata
157 .LC2:
158  .string "func1 begin"
159 .LC3:
160  .string "func1 end"
161  .text
162  .align 2
163 .globl _Z5func1v
164  .type _Z5func1v, @function
165 _Z5func1v:
166  pushl %ebp
167  movl %esp, %ebp
168  pushl %edi
169  pushl %esi
170  pushl %ebx
171  subl $108, %esp
172  movl $__gxx_personality_sj0, -68(%ebp)
173  movl $.LLSDA1420, -64(%ebp)
174  leal -60(%ebp), %eax
175  leal -24(%ebp), %edx
176  movl %edx, (%eax)
177  movl $.L18, %edx
178  movl %edx, 4(%eax)
179  movl %esp, 8(%eax)
180  leal -92(%ebp), %eax
181  movl %eax, (%esp)
182  call _Unwind_SjLj_Register
183  movl $.LC2, 4(%esp)
184  movl $_ZSt4cout, (%esp)
185  movl $-1, -88(%ebp)
186  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
187  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
188  movl %eax, (%esp)
189  call _ZNSolsEPFRSoS_E
190  leal -40(%ebp), %eax
191  movl %eax, (%esp)
192  call _ZN3ObjC1Ev
193  movl $1, -88(%ebp)
194  call _Z5func2v
195  movl $.LC3, 4(%esp)
196  movl $_ZSt4cout, (%esp)
197  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
198  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
199  movl %eax, (%esp)
200  call _ZNSolsEPFRSoS_E
201  jmp .L15
202 .L18:
203  leal 24(%ebp), %ebp
204  movl -84(%ebp), %eax
205  movl %eax, -100(%ebp)
206 .L14:
207  movl -100(%ebp), %edx
208  movl %edx, -96(%ebp)
209  leal -40(%ebp), %eax
210  movl %eax, (%esp)
211  movl $0, -88(%ebp)
212  call _ZN3ObjD1Ev
213  movl -96(%ebp), %eax
214  movl %eax, -100(%ebp)
215 .L16:
216  movl -100(%ebp), %edx
217  movl %edx, (%esp)
218  movl $-1, -88(%ebp)
219  call _Unwind_SjLj_Resume
220 .L15:
221  leal -40(%ebp), %eax
222  movl %eax, (%esp)
223  movl $-1, -88(%ebp)
224  call _ZN3ObjD1Ev
225 .L13:
226  leal -92(%ebp), %eax
227  movl %eax, (%esp)
228  call _Unwind_SjLj_Unregister
229  addl $108, %esp
230  popl %ebx
231  popl %esi
232  popl %edi
233  popl %ebp
234  ret
235  .size _Z5func1v, .-_Z5func1v
236  .section .gcc_except_table,"a",@progbits
237 .LLSDA1420:
238  .byte 0xff
239  .byte 0xff
240  .byte 0x1
241  .uleb128 .LLSDACSE1420-.LLSDACSB1420
242 .LLSDACSB1420:
243  .uleb128 0x0
244  .uleb128 0x0
245 .LLSDACSE1420:
246  .text
247  .section .rodata
248 .LC4:
249  .string "obj dtor\n"
250  .section .gnu.linkonce.t._ZN3ObjD1Ev,"ax",@progbits
251  .align 2
252  .weak _ZN3ObjD1Ev
253  .type _ZN3ObjD1Ev, @function
254 _ZN3ObjD1Ev:
255  pushl %ebp
256  movl %esp, %ebp
257  subl $8, %esp
258  movl $.LC4, (%esp)
259  call printf
260  leave
261  ret
262  .size _ZN3ObjD1Ev, .-_ZN3ObjD1Ev
263  .section .rodata
264 .LC5:
265  .string "obj ctor\n"
266  .section .gnu.linkonce.t._ZN3ObjC1Ev,"ax",@progbits
267  .align 2
268  .weak _ZN3ObjC1Ev
269  .type _ZN3ObjC1Ev, @function
270 _ZN3ObjC1Ev:
271  pushl %ebp
272  movl %esp, %ebp
273  subl $8, %esp
274  movl $.LC5, (%esp)
275  call printf
276  leave
277  ret
278  .size _ZN3ObjC1Ev, .-_ZN3ObjC1Ev
279  .section .rodata
280 .LC6:
281  .string "hello"
282 .LC7:
283  .string "catch int: "
284  .text
285  .align 2
286 .globl main
287  .type main, @function
288 main:
289  pushl %ebp
290  movl %esp, %ebp
291  pushl %edi
292  pushl %esi
293  pushl %ebx
294  subl $92, %esp
295  andl $-16, %esp
296  movl $0, %eax
297  addl $15, %eax
298  addl $15, %eax
299  shrl $4, %eax
300  sall $4, %eax
301  subl %eax, %esp
302  movl $__gxx_personality_sj0, -44(%ebp)
303  movl $.LLSDA1421, -40(%ebp)
304  leal -36(%ebp), %eax
305  leal -12(%ebp), %edx
306  movl %edx, (%eax)
307  movl $.L31, %edx
308  movl %edx, 4(%eax)
309  movl %esp, 8(%eax)
310  leal -68(%ebp), %eax
311  movl %eax, (%esp)
312  call _Unwind_SjLj_Register
313  movl $.LC6, 4(%esp)
314  movl $_ZSt4cout, (%esp)
315  movl $2, -64(%ebp)
316  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
317  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
318  movl %eax, (%esp)
319  call _ZNSolsEPFRSoS_E
320  call _Z5func1v
321  jmp .L24
322 .L30:
323  cmpl $1, -84(%ebp)
324  je .L25
325  movl -76(%ebp), %eax
326  movl %eax, (%esp)
327  movl $-1, -64(%ebp)
328  call _Unwind_SjLj_Resume
329 .L25:
330  movl -76(%ebp), %edx
331  movl %edx, (%esp)
332  movl $-1, -64(%ebp)
333  call __cxa_begin_catch
334  movl (%eax), %eax
335  movl %eax, -16(%ebp)
336  movl $.LC7, 4(%esp)
337  movl $_ZSt4cout, (%esp)
338  movl $1, -64(%ebp)
339  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
340  movl %eax, %edx
341  movl -16(%ebp), %eax
342  movl %eax, 4(%esp)
343  movl %edx, (%esp)
344  call _ZNSolsEi
345  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
346  movl %eax, (%esp)
347  call _ZNSolsEPFRSoS_E
348  jmp .L27
349 .L31:
350  leal 12(%ebp), %ebp
351  movl -64(%ebp), %eax
352  movl -60(%ebp), %edx
353  movl %edx, -76(%ebp)
354  movl -56(%ebp), %edx
355  movl %edx, -84(%ebp)
356  cmpl $1, %eax
357  je .L30
358 .L26:
359  movl -76(%ebp), %eax
360  movl %eax, -80(%ebp)
361  call __cxa_end_catch
362  movl -80(%ebp), %edx
363  movl %edx, -76(%ebp)
364 .L28:
365  movl -76(%ebp), %eax
366  movl %eax, (%esp)
367  movl $-1, -64(%ebp)
368  call _Unwind_SjLj_Resume
369 .L27:
370  call __cxa_end_catch
371 .L24:
372  movl $0, -72(%ebp)
373 .L23:
374  leal -68(%ebp), %eax
375  movl %eax, (%esp)
376  call _Unwind_SjLj_Unregister
377  movl -72(%ebp), %eax
378  leal -12(%ebp), %esp
379  popl %ebx
380  popl %esi
381  popl %edi
382  popl %ebp
383  ret
384  .size main, .-main
385  .section .gcc_except_table
386  .align 4
387 .LLSDA1421:
388  .byte 0xff
389  .byte 0x0
390  .uleb128 .LLSDATT1421-.LLSDATTD1421
391 .LLSDATTD1421:
392  .byte 0x1
393  .uleb128 .LLSDACSE1421-.LLSDACSB1421
394 .LLSDACSB1421:
395  .uleb128 0x0
396  .uleb128 0x0
397  .uleb128 0x1
398  .uleb128 0x1
399 .LLSDACSE1421:
400  .byte 0x1
401  .byte 0x0
402  .align 4
403  .long _ZTIi
404 .LLSDATT1421:
405  .text
406  .section .gnu.linkonce.t._ZSt3minIjERKT_S2_S2_,"ax",@progbits
407  .align 2
408  .weak _ZSt3minIjERKT_S2_S2_
409  .type _ZSt3minIjERKT_S2_S2_, @function
430  .text
431  .align 2
454  .align 2
455  .type _GLOBAL__I_my_lsda, @function
456 _GLOBAL__I_my_lsda:
457  pushl %ebp
458  movl %esp, %ebp
459  subl $8, %esp
460  movl $65535, 4(%esp)
461  movl $1, (%esp)
462  call _Z41__static_initialization_and_destruction_0ii
463  leave
464  ret
465  .size _GLOBAL__I_my_lsda, .-_GLOBAL__I_my_lsda
466  .section .ctors,"aw",@progbits
467  .align 4
468  .long _GLOBAL__I_my_lsda
469  .text
470  .align 2
471  .type _GLOBAL__D_my_lsda, @function
472 _GLOBAL__D_my_lsda:
473  pushl %ebp
474  movl %esp, %ebp
475  subl $8, %esp
476  movl $65535, 4(%esp)
477  movl $0, (%esp)
478  call _Z41__static_initialization_and_destruction_0ii
479  leave
480  ret
481  .size _GLOBAL__D_my_lsda, .-_GLOBAL__D_my_lsda
482  .section .dtors,"aw",@progbits
483  .align 4
484  .long _GLOBAL__D_my_lsda
494  .section .note.GNU-stack,"",@progbits
495  .ident "GCC: (GNU) 3.4.4"

由於用到 typeinfo 來判斷型別, 這是為什麼 exception handle 需要有 rtti 支援的原因。

從 global object, static object, virtaul function, rtti 到 exception handle, 現在你知道 c++ 有那麼多的黑魔法, c++ 真是不簡單, 這也是為人所詬病的一個特性, 太黑箱了。

在 c++ 這麼多的特性, 我最有興趣的是 virtual function 和 exception handle 的實作, 我已經找了多年的資料, 有點收穫真是開心。

typeid ref:
  • typeid详解
  • 執行時期型態資訊(RTTI)
  • A General-Purpose Run-Time Type Information System for C++
  • http://www.cs.rug.nl/~alext/SOFTWARE/RTTI/rtti_doc.html
  • https://pdfs.semanticscholar.org/ca44/58d8cb126fe6eae8f19cab6efb9b9fe47c88.pdf
ref:
Visual C++ 的 exception handle:

dwarf:


2016年12月17日 星期六

[books] doom 啟示錄

2016年8月13日 訂購於中國亞馬遜
43.10 rmb + 35 nt = 250.5 nt 20160821 收到貨品
這本書的原文版本我很早就從美國亞馬遜購入 (200x 左右), 但在書架上擺了很久, 因為英文實在沒好到可以順暢的看懂。這次的簡體中文版本是再版的, 把握這次機會趕緊購入。

我也曾有過這樣的夢想, 寫出自己的遊戲。但是我從來沒寫過 pc game, 我只有在玩這些 pc game, 這是我和兩位 John 不同的地方, 雖然我因為 pc game 而進入軟體開發這行業, 但我從來沒寫過遊戲, 而如果不是看了 doom 啟示錄, 我幾乎快要忘記這個夢想了。

我想為自己辯護一下, 不是我不用功, 只是我有了別的目標, 把時間花到別的地方了。勉強和遊戲沾上邊的只有《[game] - 文字大富翁》, 這是我移植別人寫好的版本。

書中人物:
  • John Romero (羅梅洛)
  • John Carmack (卡馬克)
這本書有討人厭的推薦序, 我是一個習慣從第一頁看到最後一頁的讀者, 但我從來不對這些推薦序感興趣, 這本書有 6 篇推薦序, 這就算了, 每篇的長度還不少。

我對 id software 的印象是從「德軍總部3D」開始, 原來之前還有個遊戲是「德軍總部」, 3D 的版本就是從這個遊戲的構想而來。

書上先從羅梅洛和卡馬克的生平開始介紹起, 然後怎麼認識, 怎麼在同一間公司一起開發遊戲。

卡馬克在當時的 pc 上開發了 2d 捲軸技術, 這是可以讓遊戲平滑移動的效果, 當然現在的玩家可能已經無法體驗這個技術的強大了, 現在的遊戲如果還不能平順的移動, 應該連免費下載都沒人要玩。總之, 靠著這個技術, 他們實做了超級瑪莉的第一關, 是的, 就是任天堂的那個超級瑪莉一代, 羅梅洛寄了這個展示給任天堂, 看看能不能有合作的方式, 可以開發 pc 版本的超級瑪莉, 不過任天堂當時並不想在 pc 開發遊戲 (現在好像也不想), 所以我們就沒有緣份看到 pc 版本的超級瑪莉了。

再來就是德軍總部3D -> 毀滅戰士 -> 毀滅戰士 2 -> 雷神之鎚的故事, 每當卡馬克改善了目前遊戲引擎之後, 羅梅洛就會為這個引擎設計一個新的遊戲, 這系列的遊戲讓他們賺了大錢, 大家都買了法拉利跑車, 而卡馬克在一場毀滅戰士的比賽中, 捐出他的第一台跑車當作冠軍獎品, 再給冠軍 5000 美金把這台法拉利運回家。而很久之後, 羅梅洛在大徹大悟之後, 也做了一樣的舉動, 羅梅洛不再迷失金錢、物質享受上, 反璞歸真, 找回製作遊戲的本質。

和蘋果的兩位創辦人類似, 蘋果是由兩位 Steve 創辦, Steve jobs, Steve Wozniak 而 id software 則是兩個 John, 兩位 John 一開始也是在 apple II 開發遊戲, 後來才轉到 IBM PC 上。也和蘋果一樣, 讓我尊敬的人是軟體工程人員, Steve Wozniak, John Carmack, 程式人尊敬程式人, 這是很理所當然的, 理性, 勿戰。

在那個年代, 有很多類似的遊戲紛紛出籠, 被稱作是 doom like game, 原來是 id software 把遊戲引擎授權給其他遊戲商使用, 這還真是一個聰明的點子。

卡馬克遵循著駭客精神, 把遊戲引擎 open source, 這可真是不簡單, 商業公司有那個神經燒壞了的傢伙會這麼做。而他們的德軍總部3D 被玩家修改成不同的版本時, 卡馬克和羅梅洛都很高興, 但其他沒這駭客精神的人就不開心了, 認為他們的東西被侵犯了。而在開發毀滅戰士時, 為了方便玩家修改, 卡馬克還特別設計, 讓玩家可以更容易的修改毀滅戰士的關卡, 有個版本是把毀滅戰士修改成星際大戰的人物, 這一定很酷。一般商業至上的公司一定不會讓玩家這麼做的吧!

在那個年代, 很多程式員都被一本叫《駭客》的書給影響了, 卡馬克和羅梅洛都有這樣的駭客精神, 他們認為分享是一件很重要的事情。

再完成雷神之鎚後, 卡馬克逼著所有股份成員開除了羅梅洛, 羅梅洛挾著其高知名度, 開了新的公司, 企圖製作一個更好的遊戲, 可惜不是很成功, 有時候金錢、權力真的會讓人迷失。

雷神之鎚的引擎讓卡馬克商透了腦筋, 從微軟挖來了邁克爾·亞伯拉什 (Michael Abrash), 有在開發遊戲圖形的人一定看過他的書, 厚厚一本, 卡馬克也是看他的書學習遊戲畫面怎麼開發的。

doom 的暴力也帶來許多社會議題, 相關人事把學生殺人事件怪罪到 doom 頭上, 企圖為這樣的行為找個代罪羔羊, 當然也間接帶給 id 不少麻煩。

很佩服卡馬克的專注, 集中精神在同一件事情上並不容易, 能持續的堅持更是困難, 我雖然也在電腦程式上耕耘, 但還有沒到這樣的境界, 也知道這樣的努力是要付出多大的代價。

這算是本歷史書, 介紹了第一人稱視角遊戲的歷史, 如何在遊戲界引領風騷, 2 位創辦人從合作到分道揚鑣, 若你也是遊戲開發人員, 應該讀讀看本書。

ref:
A Visit To id Software Circa 1993
第一人称射击游戏教父,首款3D游戏的开发者,传奇程序员——约翰·卡马克

這是另外一個有關卡馬克的傳奇演算法:

2016年12月14日 星期三

bss section (1)

the 1st edition: 20120229 (3)
the 2nd edition: 20161214 (3)

之前在《bss section (0)》留下來的問題要在這篇解答。

先來看看 b.c, 這程式很簡單, b.c L6 int i; 會放在 bss, p() 會把 c=i+1 用 itoa() 轉成 string 印出。預期得到 1, 因為在 C 中, 教科書都告訴我們 int i; 雖然沒有初值, 但 c 會初始為 0, 這不是憑空得來的, 裡頭有魔法, 讓我們來惡搞一下 bss 吧!

這是 bare-metal 程式, 和一般程式的編譯以及執行方式有些不同。

b.c
 1 asm(".code16gcc\n");
 2 
 3 typedef unsigned char u8;
 4 typedef unsigned int u32;
 5 
 6 int i;
 7 char* itoa(int n, char* str);
 8 
 9 void print(const char   *s)
10 {
11   while(*s)
12   {
13     __asm__ __volatile__ ("int  $0x10" : : "a"(0x0E00 | *s), "b"(7)); 
14     s++;
15   }
16 }
17 
18 int p()
19 {
20   int c=i+1;
21   char arr[6]="a";
22   char *s = arr;
23   
24   //const char *str="test";
25   //volatile u8 *video_addr = (u8*)0xB8000;
26   //asm("movw $0xb000 %gs");
27   //*video_addr = 'a';
28   s = itoa(c, s);
29   print(s);
30   return c;
31 }
32 
33 #if 1
34 char* itoa(int n, char* str)
35 {
36   char digit[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
37   char* p=str;
38   char* head=str;
39   int radix = 10;
40 
41 //  if(!p || radix < 2 || radix > 36)
42 //    return p;
43   if (n==0)
44   {
45     *p++='0';
46     *p=0;
47     return str;
48   }
50   if (radix == 10 && n < 0)
51   {
52     *p++='-';
53     n= -n;
54   }
56   while(n)
57   {
58     *p++=digit[n%radix];
59     n/=radix;
60   }
61   *p=0;
63   for (--p; head < p ; ++head, --p)
64   {
65     char temp=*head;
66     *head=*p;
67     *p=temp;
68   }
70   return str;
71 }
72 #endif
73 

c_init.s
 1 .code16
 2 .extern __bss_start__
 3 .extern __bss_end__
 4 
 5 .text
 6 .global _start
 7 _start:
 8   mov %cs, %ax
 9   mov %ax, %ds
10   mov %ax, %es
11 
12 # setup stack
13   mov %ax, %ss
14   mov $0xffff, %sp
15 
16 #  call disp_str
17   call init_bss_asm
18   call p
19 #  call disp_str2
20   jmp .
41 
42 
43 # init bss
44 init_bss_asm:
45   movw $__bss_end__, %di    /* Destination */
46   movw $__bss_start__, %si   /* Source */
47   movw $0x0, %ax
48   movw %ax, %gs
49   jmp 2f
50 1:
51   movw %si, %ax
52   movb $0x1, %gs:(%eax)
53   add $1, %si
54   
55 2:
56   cmpw %di, %si
57   jne 1b
58   ret
59 

編譯/執行方式
as --32 -o c_init.o c_init.s
gcc -std=c99 -fno-stack-protector -m32 -ffreestanding -fno-builtin -g -Iinclude -I../include -fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables -Os -c b.c
ld -m elf_i386 -nostdlib -g -o c_init.elf -Tbss.lds c_init.o b.o 
objcopy -R .pdr -R .comment -R.note -S -O binary c_init.elf c_init.bin

c_init.bin 為要執行的 bin 檔

dd if=c_init.bin of=boot.img bs=512 count=1
dd if=/dev/zero of=boot.img skip=1 seek=1 bs=512 count=2879

透過 qemu 執行
qemu-system-i386 -fda boot.img 

由 c_init.s _start 開始執行, 執行 init_bss_asm 呼叫 p(), p() 把 i+1 印出來。i 位於 bss, 所以應該看到 1 被印出來 (fig 1), 不過執行結果是 16843010, 怎麼不是 1, 我又搞錯了嗎? 非也, 這是刻意營造的結果, 我把 bss 初始為 0x01010101 , 加上 0x1 後為 0x01010102 = 十進位 16843010。 因為受限於開機磁區的 512 byte 限制, 所以編譯這段程式碼需要加上 -Os, 讓它可以小於 512 byte。

我加入以下選項, 讓 gcc 不要產生 .eh_frame section, .eh_frame section 對 bare-metal 用處不大, 還佔了 0x8c 的空間。

-fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables
.eh_frame, .eh_frame_hdr -

ref: how to remove that trash

之前用 gcc 4.x 好像不會產生 .eh_frame section, 這次用 gcc 5.4 就需要這些選項來避免產生 .eh_frame section。

fig 1

初始化 bss, c_init.s L17 call init_bss_asm 把 0x1 copy 到這個 bss 區域 (c_init.s L52), bss 開頭和結束可由 Linker Scripts 得知, bss.lds L18, L23 可以看到 __bss_start__, __bss_end__, 這就是 c_init.s 用到的那兩個變數, 神奇吧!「還可以這樣用的哦!」gnu toolchain 實在太 ... 厲害了。當然在 Linker Scripts 直接指定一個位址也可以, 不過這樣用比較帥 (帥阿! 老皮), 也比較方便。

bss 區的變數不會存放在執行檔裡, 需要靠 c runtime library 來初始化, 我們能痛快的使用這些變數, 便是這些初始化程式碼的功勞。

沒有初始值或是初始值為 0 的 static, global 變數, 就是放在 bss。

bss.lds
 1 ENTRY(_start)
 2 
 3 SECTIONS
 4 {
 5   . = 0x7c00;
 6   .text :
 7   {
 8     *(.text)
 9   }
10   .= ALIGN(32);
11 
12   .data :
13   {
14     *(.data)
15   }
16 
17   .= ALIGN(32);
18   __bss_start__ =.;
19   .bss :
20   {
21     *(.bss)
22   }
23   __bss_end__ = .;
24 
25   .sig : AT(0x7DFE)
26   {
27     SHORT(0xaa55);
28   }
29 /*
30   .asig : AT(0x7e50)
31   {
32     SHORT(0xefab);
33   }
34   .bsig : AT(0x7f50)
35   {
36     SHORT(0xefab);
37   }
38 */
39     /DISCARD/ :
40     {
41         *(.note*);
42         *(.iplt*);
43         *(.igot*);
44         *(.rel*);
45         *(.comment);
46 /* add any unwanted sections spewed out by your version of gcc and flags here */
47     }
48 
49 }

list 5 L88 i 為於 0x7d60, 而 list 5 L27 指出的 .bss 從 0x7d60 開始, 總共 4 byte, 剛好就是 i 的位址。

0x7d60 位於 .bss 區, 長度4 byte, 自然就是 int i 佔據的大小。若你不相信, 把 bochs run 一次, 反組譯後就可得証。

list 5. readelf -a c_init.elf
 1 ELF Header:
 2   Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
 3   Class:                             ELF32
 4   Data:                              2's complement, little endian
 5   Version:                           1 (current)
 6   OS/ABI:                            UNIX - System V
 7   ABI Version:                       0
 8   Type:                              EXEC (Executable file)
 9   Machine:                           Intel 80386
10   Version:                           0x1
11   Entry point address:               0x7c00
12   Start of program headers:          52 (bytes into file)
13   Start of section headers:          5692 (bytes into file)
14   Flags:                             0x0
15   Size of this header:               52 (bytes)
16   Size of program headers:           32 (bytes)
17   Number of program headers:         3
18   Size of section headers:           40 (bytes)
19   Number of section headers:         16
20   Section header string table index: 13
21 
22 Section Headers:
23   [Nr] Name           Type      Addr     Off    Size   ES Flg Lk Inf Al
24   [ 0]                NULL      00000000 000000 000000 00      0   0  0
25   [ 1] .text          PROGBITS  00007c00 000c00 000138 00  AX  0   0  1
26   [ 2] .rodata.str1.1 PROGBITS  00007d38 000d38 000025 01 AMS  0   0  1
27   [ 3] .bss           NOBITS    00007d60 000d5d 000004 00  WA  0   0  4
28   [ 4] .sig           PROGBITS  00007d64 000d64 000002 00  WA  0   0  1
29   [ 5] .debug_info    PROGBITS  00000000 000d66 00017e 00      0   0  1
30   [ 6] .debug_abbrev  PROGBITS  00000000 000ee4 000128 00      0   0  1
31   [ 7] .debug_loc     PROGBITS  00000000 00100c 000162 00      0   0  1
32   [ 8] .debug_aranges PROGBITS  00000000 00116e 000020 00      0   0  1
33   [ 9] .debug_ranges  PROGBITS  00000000 00118e 000018 00      0   0  1
34   [10] .debug_line    PROGBITS  00000000 0011a6 00007c 00      0   0  1
35   [11] .debug_str     PROGBITS  00000000 001222 000131 01  MS  0   0  1
36   [12] .debug_frame   PROGBITS  00000000 001354 00008c 00      0   0  4
37   [13] .shstrtab      STRTAB    00000000 00159b 0000a0 00      0   0  1
38   [14] .symtab        SYMTAB    00000000 0013e0 000170 10     15  16  4
39   [15] .strtab        STRTAB    00000000 001550 00004b 00      0   0  1
40 Key to Flags:
41   W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
42   L (link order), O (extra OS processing required), G (group), T (TLS),
43   C (compressed), x (unknown), o (OS specific), E (exclude),
44   p (processor specific)
45 
46 There are no section groups in this file.
47 
48 Program Headers:
49   Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
50   LOAD           0x000000 0x00007000 0x00007000 0x00d5d 0x00d64 RWE 0x1000
51   LOAD           0x000d64 0x00007d64 0x00007dfe 0x00002 0x00002 RW  0x1000
52   GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
53 
54  Section to Segment mapping:
55   Segment Sections...
56    00     .text .rodata.str1.1 .bss 
57    01     .sig 
58    02     
59 
60 There is no dynamic section in this file.
61 
62 There are no relocations in this file.
63 
64 The decoding of unwind sections for machine type Intel 80386 is not currently supported.
65 
66 Symbol table '.symtab' contains 23 entries:
67    Num:    Value  Size Type    Bind   Vis      Ndx Name
68      0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
69      1: 00007c00     0 SECTION LOCAL  DEFAULT    1 
70      2: 00007d38     0 SECTION LOCAL  DEFAULT    2 
71      3: 00007d60     0 SECTION LOCAL  DEFAULT    3 
72      4: 00007d64     0 SECTION LOCAL  DEFAULT    4 
73      5: 00000000     0 SECTION LOCAL  DEFAULT    5 
74      6: 00000000     0 SECTION LOCAL  DEFAULT    6 
75      7: 00000000     0 SECTION LOCAL  DEFAULT    7 
76      8: 00000000     0 SECTION LOCAL  DEFAULT    8 
77      9: 00000000     0 SECTION LOCAL  DEFAULT    9 
78     10: 00000000     0 SECTION LOCAL  DEFAULT   10 
79     11: 00000000     0 SECTION LOCAL  DEFAULT   11 
80     12: 00000000     0 SECTION LOCAL  DEFAULT   12 
81     13: 00000000     0 FILE    LOCAL  DEFAULT  ABS c_init.o
82     14: 00007c13     0 NOTYPE  LOCAL  DEFAULT    1 init_bss_asm
83     15: 00000000     0 FILE    LOCAL  DEFAULT  ABS b.c
84     16: 00007c2f    36 FUNC    GLOBAL DEFAULT    1 print
85     17: 00007cf5    67 FUNC    GLOBAL DEFAULT    1 p
86     18: 00007d60     0 NOTYPE  GLOBAL DEFAULT    3 __bss_start__
87     19: 00007c53   162 FUNC    GLOBAL DEFAULT    1 itoa
88     20: 00007d60     4 OBJECT  GLOBAL DEFAULT    3 i
89     21: 00007d64     0 NOTYPE  GLOBAL DEFAULT    3 __bss_end__
90     22: 00007c00     0 NOTYPE  GLOBAL DEFAULT    1 _start
91 
92 No version information found in this file.

以上為 x86 真實模式下的 bss 環境。那麼在 x86 保護模式下呢? 原理是一樣的, 就算是 arm 也是一樣, 只是切入保護模式後 segment register 要改用 selector, 確認使用的 selector:offset 是 bss 區域即可。這說來簡單, 我可花了不少時間才搞清楚:
  1. selector:offset
  2. 程式整個載入位址
  3. 整個程式的定址空間
用組合語言來初始化 bss 不太好, 我後來寫了 c 語言的版本, 可攜性較高。在 OS 環境的庇護下寫程式, 真是很幸福, 不需要知道很多事情; 也很不幸, 因為有很多事情被遮蔽起來, 讓我們無法了解細節。

ref:

2016年12月11日 星期日

20161029 ~ 20161030 mopcon

mopcon 是不亞於 coscup 的開發者大會, 而且是南部少見的大型研討會。搭著捷運從橘色線的鹽埕埔 2 號出口出站, 在沿著大勇路穿越過大仁路, 中正路後到達國際會議中心, 步行大概 10 分鐘內。



高捷少女現在可是高雄捷運的當紅炸子雞, 撈了不少錢吧! 美麗島站還有間專門賣相關週邊商品的店面。

fig 1 kkbox 紋身貼紙與贈品
kkbox 不愧是最支持 opensource 研討會的贊助商, coscup 看得到她的身影, mopcon 也看得到, 南北 2 地用力的支持類似的研討會。

紋身貼紙是在是個厲害的點子, 該行銷人員應該記個大功。

mopcon 的參與者似乎有很多女性開發人員, 這是我少數參加的言討中有看到那麼多的女性同胞。

R1, R2, R3 的議程主持人都很棒, 感覺很專業, 聲音也很甜美, 讓會眾聽起來很舒服, mopcon 很用心在主持人的挑選上。

20161029 議程心得

第一場是翟博士的演講, 一樣很能吸引大眾目光, 分析了物連網與雲端以及手持裝置的未來商業方式。

對於 2 場 vr 的介紹讓我大開眼界, 原來 vr/ar 已經進步到了不可思議的地步, 離小說中的想像不在那麼遙遠。早上的謝小姐以市場方式切入, 介紹了許多的 vr/ar 應用, 讓我這個土包子大開眼界, 原來 vr 這麼有搞頭, 難怪 htc 要轉型玩這個了, 不過看來很燒錢, htc 應該沒問題吧! 而最後一場的講者王 sir 更具有 10 年以上 vr 開發經驗, 仔細述說了在 vr 上遇到的技術難題, 我聽的津津有味, 原來 vr 的學問這麼大, 還牽扯了人體的平衡、眼睛系統等器官。

HackMd 的共筆系統相當不簡單, 也成功吸引不少使用者, 是國內少數可以登上國際舞台的網站服務。作者吳 sir 的努力, 以現有的函式庫, 將之修改後打造出這樣的系統, 和我的以打造輪子的方式完全不同。我並不是要開發什麼系統, 只是單純想學習, 所以使用造輪子的方式, 但開發一個系統若要從輪子打造起, 那就太費工夫, 站在巨人的肩膀是正確的策略。HackMd  比較困難的部份是同步不同人寫的段落, 以及 diff 等級的版本控制。

fig 2 community hero t-shirt
fig 2 似乎是 microsoft 的 community hero 服務, 只要註冊並登入就可以拿 t-shirt, 不知道為什麼人不多, 也沒打出 microsoft 的 logo, 難道 microsoft 的贊助都這麼低調嗎?


20161030 議程心得

jessechen yapi.js 的介紹, 如何使用 html 5 來打造影音播放, yapi.js 是台語「影片」的發音, 介紹了一些在 stream 播放上的難題, 如何降低延遲, 高畫質, 這是我比較沒接觸的領域。

Bobby Tung 的《幾個中文排版訣竅,有效改善閱讀體驗》介紹了書籍、網頁排版的相關技巧。對於和書籍排矲相關的議題我都很有興趣, 怎麼排版會擁有好的閱讀體驗是很重要的, 畢竟我很喜歡閱讀, 在這議程上我得到了董大哥的鑰匙圈小禮物 (fig 3, 列印的文件是我找空檔研讀 c++ exception handle 實作, 有夠難), 是 W3C 的鑰匙圈, 份量很重, 質感很好, 我很喜歡。會後纏著董大哥聊了不少排版、電子書相關議題, 可惜要吃飯了, 沒能盡興聊完, 不過好像也打擾太久了。《中文EPUB 3製作規格書》這是董大哥的另外一份文件。

下野 健二 Hacking with Linux on Android Devices, 我是衝著日本人來的, 本來以為會用英文主講, 我想聽聽日本人的英文, 結果大會竟然派出翻譯人員, 著實讓我嚇了一跳, 這也太貼心了。

鄭鈞隆(Chris Jeng)Mobile 跨平台開發從測試到架構, 這是談如何跨平台開發 android, ios, web 程式, 使用了 MPV 分層技巧, 讓這 3 個平台公用商業邏輯的程式碼 (java), 在透過 java2objc 轉到 ios 上使用, 儘量重覆利用可以跨平台的程式碼。

15: 15 我去參加了 unconference 的 webduino, 利用 javascript 大幅度降低開發門檻, 吸引不少非專業人員, 這是很不簡單的事情。

unconference 是一個不錯的創舉, 可以讓講者自行發表議程, 會眾若對於目前的大會議程沒興趣就可以來這裡看看, 說不定會有自己喜歡的題目。這個和 lighten talk 一樣搶手, 當我發現時, 2 天的 unconference 時間都已經被排滿了。

fig 3 份量很重, 質感很好 的 W3C 鑰匙圈
回到 webduino, 有人會說這不是很專業, 不過對於想自己打造簡單的應用, 就非常實用, 例如想完成一個溫度、溼度計, 很容易就實作出來, 完全不需要理解非常專業的知識, 對於 DIY 一族是很有吸引力的, 不過我認為對於大量的商品化, 可能就不是 webduino 的市場目標了, 比較適合 DIY/maker 族群。

這次大會送出不少 webduino 開發版, 不過我一個也沒拿到, 太搶手了, 而 2 天的下午茶點心也都沒吃到。

這次遇到 bjd 同好, 在會場看到 DD size 的 3 分 t-shirt, 可惜是 VIP 專屬品, 會後我試著徵求, 可惜沒有任何回音, 很想讓自己的 DD 娃也穿上軟體開發的相關服飾, 不過買不到就沒辦法了。

mopcon 真是用心, 也很努力堅持在南部繼續辦這樣的大型研討會, 很感謝他們, 辛苦了。

2016年12月3日 星期六

[books] 致命Bug:软件缺陷的灾难与启示

致命Bug:软件缺陷的灾难与启示 30.6/39
20160516 在中國亞馬遜訂購
20160530 到集運商結算貨品
20160531 集運商發送到台灣
20160603 收到貨品
這是一本在講軟體 bug 小故事的書, 和我平常在讀的電腦技術書籍比起來, 非常容易讀, 也很有趣, 讓我知道了不少事情, 有些事應該是需要被報導出來的, 可是我完全沒印象有聽過這些報導, 我對自己的孤陋寡聞感到哀傷。可惜這本書沒有電子版, 這是本很適合用電子閱讀器讀的書。

本書寫的淺顯、易懂、好讀, 就算不是相關的軟體專業人員, 也可以當作是一篇篇的故事來讀, 精彩度絲毫不遜於小說。

本書的原文是韓文, 這是翻譯重要的一種表現, 我相信有人英文一定很好, 看原文書沒問題, 但在這些人當中韓文也好的那可能就很少了。在我購買的書籍中, 有日文、韓文、英文這些翻譯本, 但事實上還會有德文、法文、俄文等的翻譯本, 不太可能把這些語言都學過一遍的。

翻譯工業應該要由政府來當領頭羊, 將整個人民的眼界帶到全世界才是, 人們眼界開了, 國家就會更進步才是, 因為語言隔閡而不能大開眼界, 真是太冤枉了, 要求每個人外文學的好的難度大於將翻譯工業好好的建構起來。

軟體在一般人的印象是不是網頁, 手機上的 app, 還有 windows 上的那些軟體、遊戲呢? 這本書提到的都是讓人很難想到的領域軟體, 書上有幾個很深刻的軟體 bug 想和大家聊聊。

chapter 4 在講停電的故事, 因為軟體的原因, 導致電力負荷不過來時, 接應的系統沒來幫忙, 就這樣, 像水管承受不了水壓而爆開, 電力系統最終癱瘓。

chapter 5 的約克城號是艘戰艦, 使用的是 ms windows 4.0, 它有一天突然不動了, 就這樣靜靜地停在海上, 任由海洋帶領, 漂浮了 2 小時 45 分鐘, 軟體的錯誤是 divided by zero。

chapter 9 的美國的文森號戰艦, 它擊落了伊朗航空 655 民航機, 因為文森號戰艦把他當成了伊朗的 F-14 戰鬥機, 至於為什麼伊朗會有美國的 F-14 那是另外一個值得探究的問題。

美軍當然不是傻蛋, 怎麼會搞不清楚是戰鬥機還是民航機呢? 但是可遇而不可求的巧合還是讓這場悲劇發生了, 總共要符合以下條件:
  • 文森號戰艦使用軍用無線電頻率警告對方, 對方是民航機, 收不到的。
  • 文森號戰艦後在使用國際救援無線電頻率警告對方, 但對方可能沒收到, 因為沒找到黑盒子, 無法確認。
  • 文森號戰艦查詢了民用客機的起飛時間, 可惜 655 民航機延遲 27 分鐘才起飛, 所以美軍不認為那個時間點有民航機。而文森號戰艦使用了 GMT+4 的時間, 但航班時間是 GMT+3.5, 更讓事情變得棘手。
  • 再來是雷達掃描系統, 655 民航機的識別代碼是 3, 但是掃到的代碼是 2 (軍用機), 不是軟體出問題, 而是掃錯區域, 本來應該掃民用機場的方向結果掃到軍用機場。
  • 最後, 終於來到最後, 研判系統出錯, 655 民航機在明明是在升高, 但文森號戰艦的軟體系統使得軍方人員研判 655 民航機在下降。
就是這以上幾點造成了這個悲劇, 但美國死不認錯也令人不齒, 不過有賠錢, 但強國就可以這樣鴨霸嗎?

但我覺得作者把這件事情也規到軟體 bug 倒是有點牽強了, UI 的結果不好研判或是警示系統不周全說是 bug 我實在不能接受。

chapter 12 的核子武器系統 bug, 知道的人應該都會覺得現在還有個好的地球可生存, 都會為此心存感謝。

美國/蘇聯的偵測系統會互相偵測核彈, 當對方發射過來時, 我方就反擊, 但如果誤判了呢? 對方明明沒有發射核彈, 但警示系統確認為有呢? 你會怎麼辦呢?

這樣的 bug 還一邊發生一次, 有夠公平。還好這些人果真都是菁英份子, 最後什麼都沒發生, 感謝這些研判正確的軍方人員。

chapter 13 的癌症治療系統, 這個很嚴重, 讓治療的病人接受了高於需要的放射線照射, 這是加拿大的 AECL Therac-25, 本來需要的能量是 200 rad, 但病人卻接收了 15000 ~ 20000 rad 的照射, 在怎麼沒概念的人也知道這差太多了, 造成許多人死亡。

這個軟體問題是算術溢出, 8 bit 整數, 只能存放 0 ~ 255, 超過就會繞回, 就是這問題倒置整個劑量數值出錯。

chapter 18 豐田暴衝, 原來日本人也是會騙人的, 和福斯的造假測試一樣, 令我驚訝, 原來德國人也是會騙人的, 他們把自己好不容易打造的形象搞砸了。

談軟體部份, 當機械結構改成電子加上軟體之後才發生這樣的事情, 有個 Barr Group 對豐田的軟體分析, 發現在某些情形下, 某些 reltime os tast 不會去執行該做的工作, 造成這樣的原因是, stack overflow, 而其程式有 11000 全域變數也令人驚訝, 日本人的軟體好像也不怎麼高竿是吧!

ref:
豐田問題車事件的經驗與教訓