2018年1月28日 星期日

C++ 能否用 memcpy 複製 class / struct 的資料?

答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹:

Specifies that the type is POD (Plain Old Data) type. This means the type is compatible with the types used in the C programming language, can be manipulated using C library functions: it can be created with std::malloc, it can be copied with std::memmove, etc, and can be exchanged with C libraries directly, in its binary form.

C++11 開始可用 std::is_pod 判斷

在看 POD 定義的過程發現 Standard layout type 的定義也重要的:

Specifies that a type is standard layout type. Standard layout types are useful for communicating with code written in other programming languages.

符合 standard layout type 才能用 reinterpret_cast 轉型成第一個 member 的 type

不過最穩的最法是直接用 C++11 提供的 std::is_trivially_copyable 判斷, 成立的話就可以安心地用 memcpy。

如何讓 C++ STL, smart pointer 和 forward declaration

有時在 class 內訂義 member 用 std::vector 或 std::unique_ptr, 可以用 forward declaration 宣告 MyObject, 有時不行。原因在於是否有觸發 compiler inline vector / unique_ptr 的 ctor / dtor。

所以避免造成 inline ctor / dtor, 就可以用 forward declaration 宣告 MyObject, 降低程式之間不必要的 dependency, 減少重新編譯時間。

參考資料:

2017年9月3日 星期日

讀出 gcc/g++ 編譯的參數

這裡看來的,官方文件說 -grecord-gcc-switches 預設有開,所以滿可靠的。

使用例子:

$ g++ f.cpp -std=c++11 -g -o f
$ readelf --debug-dump f | grep DW_AT_producer
       DW_AT_producer    : (indirect string, offset: 0xded): \
  GNU C++11 5.4.0 20160609 -mtune=generic -march=x86-64 -g -std=c++11 -fstack-protector-strong
    DW_AT_producer     DW_FORM_strp

但用 clang 編的資訊就沒存完整:

$ clang++ f.cpp -std=c++11 -g -o f
$ readelf --debug-dump f | grep DW_AT_producer
       DW_AT_producer    : \
  (indirect string, offset: 0x0): clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
    DW_AT_producer     DW_FORM_strp

2017年4月29日 星期六

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

以使用 LevelDB 為例。

抓好並編好相關檔案,編譯方式見第三方函式庫附的說明:

$ ls include/  # header files
leveldb/
$ ls out-shared/libleveldb.so*  # shared library
out-shared/libleveldb.so@  out-shared/libleveldb.so.1@  out-shared/libleveldb.so.1.20*

下面的例子用 clang++ 編譯,這裡用到的參數和 g++ 一樣。

問題一:找不到 header

$ clang++ sample.cpp
sample.cpp:5:10: fatal error: 'leveldb/db.h' file not found
#include "leveldb/db.h"
         ^
1 error generated.

解法:用 -I 指定 header 位置

問題二:找不到 shared library

$ clang++ sample.cpp -I include/
/tmp/sample-2e7dd8.o: In function `main':
sample.cpp:(.text+0x1e): undefined reference to `leveldb::Options::Options()'
sample.cpp:(.text+0x6f): undefined reference to `leveldb::DB::Open(leveldb::Options const&, std::string const&, leveldb::DB**)'
sample.cpp:(.text+0x10c): undefined reference to `leveldb::Status::ToString() const'
sample.cpp:(.text+0x7d0): undefined reference to `leveldb::Status::ToString() const'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

要求 linker 連結 libleveldb.so (linker 的參數由 clang++ / g++ 轉傳):

$ clang++ sample.cpp -I include/ -l leveldb
/usr/bin/ld: cannot find -lleveldb
clang: error: linker command failed with exit code 1 (use -v to see invocation)

但 compiler 說找不到要連結的 library

補上 libleveldb 的位置:

$ clang++ sample.cpp -I include/ -l leveldb -L out-shared/

問題三:執行時找不到 shared library

$ ./a.out
./a.out: error while loading shared libraries: libleveldb.so.1: cannot open shared object file: No such file or directory

編出 executable file 或 shared library 表示 static linker 成功,但執行時會用到 dynamic linker 載入函式庫。這錯誤訊息是dynamic linker 回報的。

用 ldd 可以檢查 shared library 的路徑是否正確:

$ ldd a.out | grep leveldb
        libleveldb.so.1 => not found

幾種解法:

1. 用 LD_LIBRARY_PATH 指定位置 (man ld.so 查看細節)

$ LD_LIBRARY_PATH=`pwd`/out-shared ./a.out

若 out-shared 的位置有固定的話,可以在 /.bashrc 加上

export LD_LIBRARY_PATH=/path/to/out-shared 

2. 將 library path 寫到 executable 裡 (man ld.sh 查看細節):

$ clang++ sample.cpp -I include/ -l leveldb -L out-shared/ -Wl,-rpath,`pwd`/out-shared 
$ objdump -p a.out | grep PATH  # 確認有記錄
  RPATH                /home/fcamel/dev/study/leveldb/out-shared
$ ldd a.out  | grep leveldb  # 也可用 ldd 確認
        libleveldb.so.1 => /home/fcamel/dev/study/leveldb/out-shared/libleveldb.so.1 (0x00007fc1f091e000)

這裡我用絕對路徑減少潛在的問題。

3. 搬到系統函式庫

$ ldd a.out  | grep leveldb
        libleveldb.so.1 => not found
$ sudo su
$ cp out-shared/libleveldb.so* /usr/lib
$ ldd a.out  | grep leveldb
        libleveldb.so.1 => /usr/lib/libleveldb.so.1 (0x00007f1717026000)

但這樣和系統內建的混在一起,不好維護。改放到 /usr/local/lib/leveldb/ 下:

$ mkdir /usr/local/lib/leveldb
$ cp --preserve=links out-shared/libleveldb.so* /usr/local/lib/leveldb/
$ echo "/usr/local/lib/leveldb" > /etc/ld.so.conf.d/leveldb.conf
$ ldconfig # Update ldconfig's cache
$ ldd a.out | grep leveldb
        libleveldb.so.1 => /usr/local/lib/leveldb/libleveldb.so.1 (0x00007f0314b32000)

由 man ldconfig 得知 ldconfig 會讀 /etc/ld.so.conf。我在 Ubuntu 14.04 看到的設定如下:

$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf

所以在 /etc/ld.so.conf.d/ 建新檔案寫入 /usr/local/lib/leveldb,然後更新 ldconfig cache 即可。

參考資料

  1. The Linux Programming Interface ch41: Fundamentals of Shared Libraries
  2. man ld.so
  3. man ldconfig

2017年3月7日 星期二

除錯技巧:在 Ubuntu 上找出第三方函式庫的程式碼

這篇藉由一個小例子說明如何使用 gdb, locate, apt-file, apt-get 找出問題原因。藉由取得第三方函式庫的程式碼,可以減少瞎猜的時間。

問題

我發現某個程式會不預期的結束。但它不是 crash,沒有 core dump 可看。

用 gdb 找出怎麼結束的

先用 gdb attach 程式,繼續操作。程式結束時 gdb 顯示是呼叫 exit() 結束的。

於是再執行一次,這次用 gdb 在 exit 設中斷點再繼續執行。

取得的 backtrace 如下:

#0  __GI_exit (...) at exit.c:104
#1  0x00007fdd27f60408 in _XDefaultError (...) at ../../src/XlibInt.c:1414
#2  0x00007fdd27f6054b in _XError (...) at ../../src/XlibInt.c:1463
#3  0x00007fdd27f5d5e7 in handle_error ...) at ../../src/xcb_io.c:213
#4  0x00007fdd27f5e687 in _XReply (...) at ../../src/xcb_io.c:699
#5  0x00007fdd27f45346 in XGetWindowProperty (...) at ../../src/GetProp.c:69
#6  0x00007fdd2825db30 in XmuClientWindow () from /usr/lib/x86_64-linux-gnu/libXmu.so.6
...

看 backtrace 沒什麼頭緒,都是第三方函式庫的程式。照 XmuClientWindow() 的說明,它可能會失敗,但它不該直接呼叫 exit()。先找 XmuClientWindow 的原始碼,看看有什麼線索。

用 Ubuntu 的 package 系統找出原始碼

首先用 apt-file 找出 libXmu.so.6 在那個套件 (第一次執行需先跑 apt-file update 更新索引):

$ apt-file search /usr/lib/x86_64-linux-gnu/libXmu.so.6
libxmu6: /usr/lib/x86_64-linux-gnu/libXmu.so.6
libxmu6: /usr/lib/x86_64-linux-gnu/libXmu.so.6.2.0
libxmu6-dbg: /usr/lib/debug/usr/lib/x86_64-linux-gnu/libXmu.so.6.2.0

然後用 apt-get source 取得原始碼:

$ apt-get source libxmu6

在 src/ClientWin.c 裡找到 XmuClientWindow [*1]:

Window
XmuClientWindow(Display *dpy, Window win)
{
    ...
    XGetWindowProperty(dpy, win, WM_STATE, 0, 0, False, AnyPropertyType,
               &type, &format, &nitems, &after, &data);
    ....
}

沒什麼有用資訊,繼續找 XGetWindowProperty 的程式碼。從 backtrace 裡知道它在 ../../src/GetProp.c 裡,但我不知道 GetProp.c 是什麼套件的程式。用 Google 搜尋 GetProp.c 也許會有線索,不過這裡我用另一個作法。

從 header 找出原始碼

src/ClientWin.c 引入的 header 不多,只有這些:

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <X11/Xmu/WinUtil.h>

先用 locate 查出完整路徑:

$ locate X11/Xlib.h
/usr/include/X11/Xlib.h

再用 apt-file 找套件名稱:

$ apt-file search /usr/include/X11/Xlib.h
libx11-dev: /usr/include/X11/Xlib.h

取得原始碼:

$ apt-get source libx11-dev

附帶一提,可透過 gcc -E 展開 #include 驗證 XGetWindowProperty 確實出自 X11/Xlib.h:

$ echo "#include " | gcc - -E | grep XGetWindowProperty
extern int XGetWindowProperty(

header 太多的話,直接編譯原始檔比較快:

$ gcc -E src/ClientWin.c > t
得到的資訊如下:
1674 # 1303 "/usr/include/X11/Xlib.h" 3 4
...
3060 extern int XGetWindowProperty(

找到目標後,往回找第一個 header 路徑就是來源了。如果找不到的話,可能是少指定 include header 的來源目錄。可以從 make 產生的結果得知完整的編譯訊息:

$ ./configure
$ make V=1 | grep ClientWin.c
/bin/bash ../libtool  --tag=CC   --mode=compile gcc -std=gnu99 -DHAVE_CONFIG_H \
-I. -I..  -I../include -I../include/X11/Xmu < ... 略 ...> -c -o ClientWin.lo ClientWin.c
...

找出 -I 的部份,和 -E 一樣加在 gcc 的參數上展開 include。

關於如何讓 make 顯示編譯時的參數 (cflags),參見這裡

繼續追踪程式碼

在 src/GetProp.c 裡發現:

 69     if (!_XReply (dpy, (xReply *) &reply, 0, xFalse)) {

這和前面的 backtrace 一致:

#0  __GI_exit (...) at exit.c:104
#1  0x00007fdd27f60408 in _XDefaultError (...) at ../../src/XlibInt.c:1414
#2  0x00007fdd27f6054b in _XError (...) at ../../src/XlibInt.c:1463
#3  0x00007fdd27f5d5e7 in handle_error ...) at ../../src/xcb_io.c:213
#4  0x00007fdd27f5e687 in _XReply (...) at ../../src/xcb_io.c:699
#5  0x00007fdd27f45346 in XGetWindowProperty (...) at ../../src/GetProp.c:69
#6  0x00007fdd2825db30 in XmuClientWindow () from /usr/lib/x86_64-linux-gnu/libXmu.so.6

表示尋找的方向正確。再繼續看 src/xcb_io.c 和 src/XlibInt.c 內相關函式,得知:

// src/XlibInt.c
1463     rtn_val = (*_XErrorFunction)(dpy, (XErrorEvent *)&event); /* upcall */

對照 backtrace 得知執行 _XErrorFunction 後,是呼叫 _XDefaultError。_XDefaultError 程式如下:

1409 int _XDefaultError(
1410     Display *dpy,
1411     XErrorEvent *event)
1412 {
1413     if (_XPrintDefaultError (dpy, event, stderr) == 0) return 0;
1414     exit(1);
1415     /*NOTREACHED*/
1416 }

確認是這裡呼叫 exit()。所以換掉 _XErrorFunction 可以解決問題。

在程式碼裡搜尋 _XErrorFunction 發現可用 XSetErrorHandler() 設值。這樣至少不會造成程式結束了。

備註

1. 可用 grep 尋找,我是用 gj,找起來更有效率。

2016年6月26日 星期日

NAT64 和 DNS 64

參考資料

IPv4 要用完了 (喊了十幾年, 最近是玩真的了), 在骨幹和 server 都轉成 IPv6 的過渡期, 用戶端也要轉換, 不然 server 是轉心酸的。但是大量 server 還是用 IPv4, 所以要讓 client 能同時支援 IPv4 和 IPv6才行。

如果你是網管, 希望讓用戶可以同時連 IPv4 和 IPv6 的位置, 一個可能的作法是建置「純 IPv6 環境」, 永遠拿到 IPv6 位置, 永遠連 IPv6 的位置。但要讓 IPv6 client 也能連 IPv4 位置, 需要 NAT64+DNS64 幫忙處理。

原理是 DNS64 會同時查 IPv6 和 IPv4 的位置, 有 IPv6 就用 IPv6, 沒有就轉換 IPv4 為 IPv6, prefix 配合 NAT64 的設定, 讓 client 連往該 IP 的封包會先經過 NAT64 router。第二份 slide 有詳細的流程圖。

然後 NAT64 會再轉換 IPv6 的封包成 IPv4 出去, 無縫接上用 IPv4 位置的 server。至於 client 收到 DNS64 回應的正常 IPv6 位置, 就透過一般 router 直接出去, 不用經過 NAT 64。

對設計後端 server 的人來說, 有愈多用戶可以連 IPv6 位置, 有愈高的機會考慮用 IPv6 位置。

對 app 開發者來說, 了解有些用戶的網路環境會用 DNS64+NAT64 的配置, 所以要用 domain name 連上自家 server, 這樣自家 server 用 IPv4 或 IPv6 都會通。

C++ 能否用 memcpy 複製 class / struct 的資料?

答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹: Specifies that the type is POD (Plain Old Data) type. Thi...