2013年6月27日 星期四

XCode 新增子專案編譯 static library 的方法

這種事沒記下來立即就忘了, 我是用 XCode 4.6。

新增子專案

  1. 在 Project Navigator 點專案 A
  2. 點左下角的 '+', 選擇加新專案, 選擇使用 static shared library, 完成剩下的步驟
  3. 點專案 A: Build Phases -> Target Dependencies -> 加入專案 B 的 static shared library
  4. 點專案 A: Build Phases -> Link Binary with Libraries: 加入專案 B。若專案 A 之上有專案 M, 這步要加在專案 M

雜七雜八問題

  • 找不到 header: 在 Build Settings 搜尋 header 會找到 Header Search Paths 和 User Header Search Paths, 記得填入正確的值。可以對一下編譯的原始訊息了解設下去轉換成什麼東西。這裡的路徑是相對於專案位置的。
  • 連結失敗, 找不到 static shared library 內的 symbol: 確定 Link Binary with Libraries 裡面有加到這個 library, 還有有在正確的 project 內設 Link Binary with Libraries。
  • 連結失敗, static shared library 內有 undefined symbol: 注意在 Link Binary with Libraries 裡和其它 libraries 的順序。同樣地, 對一下編譯的原始訊息會比較清楚情況。
  • static shared library 一直是紅字: google 一下有人說疑是 XCode bug, 在實體裝置上編譯過後, 就沒有紅字了。我試的結果確實如此。
  • 找不到 C++ STL 的 symbol: 錯誤訊息像這樣:
    std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__init(char const*, unsigned long)", referenced from:
    檢查 Build Settings -> C++ Standard Library, 這裡有三個選項:
    • libstd++ (GNU C++ standard library)
    • libc++ (LLVM C++ standard library)
    • Compilation default (be consistent)
    記得所有專案要設成一致的選項, 新增專案預設是 libc++。

結論是先有基本觀念, 出錯時比較知道怎麼找原因。雖然一樣是 google 關鍵字, 至少讀搜尋結果時, 知道應該優先讀那些文章, 效率會好很多。再來是盡量在 Build Settings 和 Build Phases 裡找和編譯以及連結相關的參數。

2015-06-09 更新

XCode 6 新增專案的方式變了,改成在 Project Navigator 點專案 A 然後按右鍵選 "New Project"。然後選 "Framework & Library" -> "Cocoa Touch Static Library"。

2013年6月25日 星期二

hg-svn 速度極慢的原因

簡短說明

使用 hg-svn 時, 底層需要操作 svn 的 python 模組。hg-svn 有封裝兩組不同的實作, 在 Ubuntu 下的套件分別是 python-subvertpy 和 python-subversion。記者要裝 python-subvertpy, 個人試用的感覺, 前者比後者快了百倍以上吧。

冗長苦水心得

每次用 hg-svn 更新一堆檔案時 (大概數十個左右), 不管是 push 還是 pull 都相當地久, 有時等上一小時都不會好, 但用 strace 看, 持續有些動靜, 但是不知道在做什麼。

原本以為這是 hg 和 svn 溝通的極限, 今天問一下 cmd 平時用 git-svn 速度如何, 結果 cmd 說沒特別感到慢過, 當場試了一下, 果真迅速無比。差點想跳糟到 git-svn 陣營, 更何況安裝容易, sudo aptitude install git-svn 就結束了。

回家後想想不太甘心, 就用老方法在 hg-svn 原始碼內塞 print 找看看瓶頸卡在那裡 (慢到這種程度不太可能是 CPU bound, 不適合用 perf 之類的工具找)。

雖然有找到瓶頸, 但是找得過程中發覺 hg-svn 使用兩個不同的模組作為 svn 的接口, 一個是我原本用 svnwrap/svn_swig_wrapper.py, 對應到 Ubuntu 的套件為 python-subversion。另一個是 svnwrap/subvertpy_wrapper.py, 對應到的套件為 python-subvertpy, 而且 hg-svn 優先使用 python-subvertpy

接著查 python-subvertpy:

$ aptitude show python-subvertpy
...
Description: Alternative Python bindings for Subversion
 Python bindings for the Subversion version control system. The aim of these bindings is to be fast, complete and to provide an API that feels native to Python programmers.
...

真相大白, 改用 python-subvertpy 後, 龜速更新的問題就好了。

2013-07-17 更新

今天疑似用了 hg mv 後踏到 subvertpy 的 bug, hg push 時出現 exception:

subvertpy.SubversionException: ("'...' path not found", 160013)

後來改回用 python-subversion 的套件就可正常 push。所以說提供兩種實作做為選擇, 還是有好處的, 至少一個不行時有個希望試看另一個。

2013年6月23日 星期日

計算 Linux 記憶體使用量

雖然直覺上會使用 top 或 free 觀看結果, 但是 top 或 free 顯示的資訊包含 kernel 使用的 cache 量, 不是「所有 process 使用的量」。因此, 我習慣看 htop 顯示的值

Viller Hsiao 提醒, 才知道原來 htop 顯示的記憶體用量和 free 第二行的數值一樣, 都是讀 /proc/meminfo, 然後得出實際使用量 = MemTotal - MemFree - Buffers - Cached。若需要寫 script 記錄系統的 memory peak, 可用 free 的輸出, 畢竟 htop 沒有 batch mode 可用。

以下是一個 free 顯示的例子:

$ free
             total       used       free     shared    buffers     cached
Mem:      11418888    3247916    8170972          0    1274988     693072
-/+ buffers/cache:    1279856   10139032
Swap:            0          0          0

我之前一直誤會第二行的意思, 原來它的意思是「考慮第一行顯示的 buffers 和 cached 後, 重新計算 used 和 free 的值」。

Viller Hsiao 翻了一下原始碼證實這件事。我自己也試著練習翻閱原始碼, 增加翻原始碼查資料的能力。

以下是查詢過程的一些記錄。

free

找 free 原始碼的過程比較麻煩一點:

$ apt-file search /usr/bin/free | grep "free$"
procps: /usr/bin/free
$ aptitude show procps
...
Description: /proc file system utilities
 This package provides command line and full screen utilities for browsing procfs, a "pseudo" file system dynamically generated by the kernel to provide information about the status of
 entries in its process table (such as whether the process is running, stopped, or a "zombie").

 It contains free, kill, pkill, pgrep, pmap, ps, pwdx, skill, slabtop, snice, sysctl, tload, top, uptime, vmstat, w, and watch.
Homepage: http://procps.sf.net/
$ apt-get source procps

但原始碼滿好讀的:

free.c:

 63         meminfo();
...
 93             unsigned KLONG buffers_plus_cached = kb_main_buffers + kb_main_cached;
 94             printf(
 95                 "-/+ buffers/cache: %10Lu %10Lu\\n",
 96                 S(kb_main_used - buffers_plus_cached),
 97                 S(kb_main_free + buffers_plus_cached)
 98             );

利用上面的關鍵變數和已知關鍵資訊存在 /proc/meminfo 裡 (man proc 然後搜 meminfo), 進一步找到 proc/sysinfo.c, 從 memoinfo() 的實作證實 free 是讀 /proc/meminfo 的特定欄位, 計算出實際使用量。

htop

htop 本身套件名就是 htop, 但程式碼比較複雜一些。

先假設 htop 也是讀 /proc/meminfo, 以 meminfo 作為關鍵字下手, 可以找到關鍵的程式碼:

ProcessList.h

 20 #define PROCDIR "/proc"
...
 28 #define PROCMEMINFOFILE PROCDIR "/meminfo"

ProcessList.c

750 void ProcessList_scan(ProcessList* this) {
751    unsigned long long int usertime, nicetime, systemtime, systemalltime, idlealltime, idletime, totaltime, virtalltime;
752    unsigned long long int swapFree = 0;
753
754    FILE* file = fopen(PROCMEMINFOFILE, "r");
755    assert(file != NULL);
756    int cpus = this->cpuCount;
757    {
758       char buffer[128];
759       while (fgets(buffer, 128, file)) {
760
761          switch (buffer[0]) {
762          case 'M':
763             if (String_startsWith(buffer, "MemTotal:"))
764                sscanf(buffer, "MemTotal: %llu kB", &this->totalMem);
765             else if (String_startsWith(buffer, "MemFree:"))
766                sscanf(buffer, "MemFree: %llu kB", &this->freeMem);
767             else if (String_startsWith(buffer, "MemShared:"))
768                sscanf(buffer, "MemShared: %llu kB", &this->sharedMem);
769             break;
770          case 'B':
771             if (String_startsWith(buffer, "Buffers:"))
772                sscanf(buffer, "Buffers: %llu kB", &this->buffersMem);
773             break;
774          case 'C':
775             if (String_startsWith(buffer, "Cached:"))
776                sscanf(buffer, "Cached: %llu kB", &this->cachedMem);
777             break;
778          case 'S':
779             if (String_startsWith(buffer, "SwapTotal:"))
780                sscanf(buffer, "SwapTotal: %llu kB", &this->totalSwap);
781             if (String_startsWith(buffer, "SwapFree:"))
782                sscanf(buffer, "SwapFree: %llu kB", &swapFree);
783             break;
784          }
785       }
786    }
787
788    this->usedMem = this->totalMem - this->freeMem;
789    this->usedSwap = this->totalSwap - swapFree;
790    fclose(file);

另一方面, 以下是唯一用到 memory 欄位的程式碼:

MemoryMeter.c

 28 static void MemoryMeter_setValues(Meter* this, char* buffer, int size) {
 29    long int usedMem = this->pl->usedMem;
 30    long int buffersMem = this->pl->buffersMem;
 31    long int cachedMem = this->pl->cachedMem;
 32    usedMem -= buffersMem + cachedMem;
 33    this->total = this->pl->totalMem;
 34    this->values[0] = usedMem;
 35    this->values[1] = buffersMem;
 36    this->values[2] = cachedMem;
 37    snprintf(buffer, size, "%ld/%ldMB", (long int) usedMem / 1024, (long int) this->total / 1024);
 38 }

Meter.h

 21 typedef struct Meter_ Meter;
...
 53 struct Meter_ {
...
 62    ProcessList* pl;
...
 65 };

證實兩者計算方式相同。

結語

有疑問的時候, 還是翻原始碼最穩, 多做幾次之後會更順手。

另外藉由這次的經驗, 發覺寫純 C 程式時, 作者自己會有一套封裝資料結構和控制的方法。不像 C++ 已內建物件觀念, 不同的 C 程式會有不同的結構和控制的手法, 讀不同專案時, 需要理解一下該專案使用的方式。也許多讀幾次後, 會發覺大家的手法也差不多吧。

除錯小技巧: 在程式中直接中斷及偵測是否被 gdb 監控中

在程式中直接中斷

《Binary Hack》 #92 所言, 在 gdb 監控 process 的情況, 使用下列程式碼會進入中斷:

raise(SIGTRAP);

對 x86 環境, 下列作法也許會更好, 少了一層 function call

__asm__ __volatile__("int3");

Scott 有個 debugbreak 有處理 x86 以外的情況, 有需要時再來參考看看。

偵測是否被 gdb 監控中

比起在程式中直接中斷, 我比較需要知道目前程式是否被 gdb 監控 (attach) 中。以下是兩個實用的情境:

  • 原本已有 log 錯誤訊息, 在 log 極為嚴重錯誤訊息 (如 severity = FATAL) 時, 檢查看看是否有 gdb 在監控, 有的話不如順便進入中斷, 提前察看錯誤點。
  • 寫 multi-thread 時, 有時會有特定 thread 負責特定的事, 像是 UI 或 IO。有時會監控這個 thread 是否正常運作。或是有網路連線時, 有時會想監控另一端連線是否仍然正常運作。其中一種監控 thread 或網路連線的方式是週期性察看有無更新資料, 超過一定時間沒更新, 就中止程序或關掉連線重連。在使用 gdb 除錯時, 有可能在中斷點待太久而觸發前述安全防護機制, 反而無法長時間除錯。這時自動關閉防護措施會比較方便。

《Binary Hack》 #92 的作法利用設 gdb 會覆寫 SIGTRAP 的 signal handler 做檢查, 這個作法可以跨比較多平台, 但可用時機太窄。我偏好 Chromium 的作法, 直接讀 /proc/PID/status, 由 "TracerPid" 的值得知目前是否有 process 控控自己。程式執行中的任何時間都可用這方法得知正確的結果, 不過只適用於 Linux 和 Android。以下是參考 Chromium 實作稍作修改的版本:

2013年6月9日 星期日

High Performance Browser Networking ch4 Transport Layer Security (TLS) 筆記

原文: http://chimera.labs.oreilly.com/books/1230000000545/ch04.html

  • TLS 1.0 相當於 SSL 3.0, 兩者沒有顯著的差異。由於 SSL 屬於 Netscape 的協定, IETF 另定了 TLS。現在我們用的 "SSL" 其實是指 TLS
  • TLS 屬於 session layer 的協定, 在 application (例如 HTTP) 之下, transport (例如 TCP) 之上
  • TLS 三個要點:
    • Encryption: 加密資料, 其他人無法竊聽
    • Authentication: 認證對方身份, 傳錯人的話, 加密也沒用了
    • Integrity: 確保資料沒有被修改。中間人無法得知資料內容, 但有可能竄改內容。多了 checksum 可以避免使用被改過的資料
  • TLS handshake: 連 TCP handshake 在內要三次 round trip time (RTT)。若一次 RTT 要個 170ms, 這表示 0.5s 後才會開始傳第一筆資料。
  • TLS 多加的兩個 RTT 是:
    • -> ClientHello
    • <- ServerHello, Certificate, ServerHelloDone
    • -> ClientKeyExChange, ChangeCipherSpec, Finished
    • <- ChangeCipherSpec, Finished
  • TLS 的主要缺點是較高的 latency, CPU、記憶體和傳輸資料量到是沒差 (後述)。
  • TLS 主要的計算量在用 public/private key 加解密 symmetric key。整體的 CPU、記憶體和網路負擔都很小。
    • 依 2010 Google 使用 Gmail 的發言:
      "... we had to deploy no additional machines and no special hardware. On our production frontend machines, SSL/TLS accounts for less than 1% of the CPU load, less than 10 KB of memory per connection and less than 2% of network overhead. "
    • Facebook 也有類似的發言:
      "We have found that modern software-based TLS implementations running on commodity CPUs are fast enough to handle heavy HTTPS traffic load without needing to resort to dedicated cryptographic hardware. We serve all of our HTTPS traffic using software running on commodity hardware."
  • 若 server 想在同一個 IP 上使用多個不同 domain name 的 TLS certificate, client 可透過 server name indication (SNI), 指定要取得那份 certificate
  • TLS 有機會減少最後一個 RTT, 其中一個方法是使用 Session Ticket:
    • server 在 handshake 最後一步時, 用額外的 secret key 加密 symmetric key 回傳給 client。
    • client 存下加密後的 symmetric key
    • 下次要建立連線時, 在 ClientHello 裡帶上加密後的 symmetric key, 由於只有 server 知道解密的 secret key, 可保證由同一個 server 取回 symmetric key
    • 這個作法的好處是: server 不用 cache 大量 session ticket, 不過要在多台 server 上部署同一個 secret key。還有免不了的維護 secret key 的生命週期, 定期換 secret key 增加安全性。
  • 傳 certificate 給 client 時, server 最好傳給 client 所有需要的 intermediate certificate, 避免 client 額外花時間去取得它們。另外沒有必要傳 root certificate, client 一定要有 root certificate, 沒有的話也不會相信剩下的 intermediate certificate 了。
  • 若 TLS record size > TCP packet size, 會造成一筆 TLS record 拆成多個 TCP packets 傳, 導致 client 要收完全部 TCP packets 才能解開這筆 TLC record。最好設定 TLC record size <= TCP packet size
  • 不要開啟 TLS compression。
  • 文末有提到用 Qualys SSL Server Test 和 command line 的 openssl 測試是否有設好 TLS。

2013年6月6日 星期四

vim-protodef: 從 C++ header 產生實作樣板的好幫手

C++ 寫一陣時間後, 覺得每次在標頭檔寫完宣告又要在實作檔裡重打一次類似的函式名稱, 有點麻煩。原本想說自己寫個 vim script 處理這件事, 轉念一想, 應該有人做過了。結果找到別人滿完整的實作: derekwyatt/vim-protodef

作法滿聰明的, 用 ctags 讀標頭檔產生索引, 然後用 perl script 清理 ctags 產生的結果, 最後用 vim script 搜尋目標函式是否已出現在實作檔裡, 沒有的話就輸出到實作檔裡。日後有需要寫比較複雜的 vim script 時, 可以回頭來參考語法。

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...