2014年12月29日 星期一

Ubuntu 12.04 安裝 graphviz 遇到 confclit

在 Ubuntu 12.04 裝 graphviz 遇到 liggd2-xpm 和 libgd2-noxpm 衝突的問題:

$ sudo aptitude install graphviz
The following NEW packages will be installed:
  fonts-liberation{a} graphviz libcdt4{a} libcgraph5{a} libgd2-noxpm{ab} libgraph4{a} libgvc5{a} libgvpr1{a} libpathplan4{a} ttf-liberation{a}
0 packages upgraded, 10 newly installed, 0 to remove and 427 not upgraded.
Need to get 2,716 kB of archives. After unpacking 6,188 kB will be used.
The following packages have unmet dependencies:
 libgd2-xpm:i386 : Conflicts: libgd2 which is a virtual package.
                   Conflicts: libgd2-noxpm but 2.0.36~rc1~dfsg-6ubuntu2 is to be installed.
 libgd2-noxpm : Conflicts: libgd2:i386 which is a virtual package.
                Conflicts: libgd2-xpm:i386 but 2.0.36~rc1~dfsg-6ubuntu2 is installed.
Internal error: the solver Install(fontconfig-config:amd64 2.8.0-3ubuntu9  {fontconfig-config:amd64 2.8.0-3ubuntu9}>) of a supposedly unresolved dependency is already installed in step 125

$ dpkg -l  | grep libgd2
ii  libgd2-xpm:i386                      2.0.36~rc1~dfsg-6ubuntu2            GD Graphics Library version 2

用 apt-cache depends graphviz 了解 graphviz 用到的套件 (可參考這篇說明輸出的內容):

$ apt-cache depends graphviz
graphviz
  Depends: libc6
  Depends: libcdt4
  Depends: libcgraph5
  Depends: libexpat1
 |Depends: libgd2-noxpm
  Depends: libgd2-xpm
  Depends: libgraph4
  Depends: libgvc5
  Depends: libgvpr1
  Depends: libx11-6
  Depends: libxaw7
  Depends: libxmu6
  Depends: libxt6
  Suggests: gsfonts
  Suggests: graphviz-doc
  Recommends: ttf-liberation
    fonts-liberation
  Conflicts: 
  Conflicts: 
  Conflicts: graphviz:i386

我想說既然 conflict 的原因是 libgd2-xpm 和 libgd2-noxmp, 試看看一起指定要裝的東西,不要讓 aptitude 自己選,結果就 OK 了:

$ sudo aptitude install graphviz libexpat1 libgd2-xpm
The following NEW packages will be installed:
  fonts-liberation{a} graphviz libcdt4{a} libcgraph5{a} libgd2-xpm libgraph4{a} libgvc5{a} libgvpr1{a} libpathplan4{a} ttf-liberation{a}
0 packages upgraded, 10 newly installed, 0 to remove and 427 not upgraded.
Need to get 2,719 kB of archives. After unpacking 6,197 kB will be used.
Do you want to continue? [Y/n/?] Y
...

2014年9月16日 星期二

善用 C++ istream 的 eof() 和 fail()

當函式需要讀入資料做處理時, 讓函式接收一個 std::istream* 會比直接呼叫 std::cin 好一些, 必要時可以改傳 std::istringstream 提供更多彈性, 比方說方便作測試。

不過實測的結果, std::cin 和 std::istringstream 的行為略有不同。std::cin 讀到 EOF 後, 呼叫 eof() 才會回傳 true, 但是 std::istringstream 在讀完最後一個 byte 後, eof() 就回傳 true 了。

以下是一個小範例, 可以正確處理兩者, 關鍵是讀完資料後用 fail() 檢查剛才的輸入是否有效。

#include <iostream>
#include <sstream>

void foo(std::istream* s)
{
  int n;
  while (!s->eof()) {
    *s >> n;
    if (!s->fail()) {
      std::cout << n << std::endl;
    }
  }
}

int main(void)
{
  std::istringstream ss("1 2 3 4 5");
  foo(&ss);
  foo(&std::cin);
  return 0;
}

另外附上別人截錄的官方說明。

2014年9月8日 星期一

備忘 Mac OS X 下的一些設定

加減寫下一些設定, 備忘在這篇裡。那天買新的 Mac 裝置時, 才知道要做那些設定

iTerm2 ssh

除 preference -> terminal 要設 UTF-8外,/etc/ssh_config 也要加上 SendEnv LANG LC_ALL=en.US.UTF-8

參考資料: iterm2 ssh和openssh客户端中文乱码的解决方案 | 线筝

2014年8月31日 星期日

ph: python HTML generator without using any template syntax

偶而需要用 python 產生 HTML 報表, 自己手刻 HTML 頗麻煩的。寫個小 script 不會像寫網頁那頁使用 MVC 將資料和顯示分離, 不方便套 template (況且學 template 的語法也有點麻煩)。

找了一下發現 pyh, 語法滿直覺的, 大致符合我的需求。於是自己重做了一個 ph, 改成更合自己習慣的語法, 順便練習自己設計一套簡單的 DSL, 寫起來像這樣:

from ph import *
doc = HTML()
doc.body() << p('Hello, world!') << (p('What a ') << strong('wonderful') << ' world!')
print unicode(doc)
輸出像這樣 (有手動排版過):
<!DOCTYPE html>
<html>
<head><title>No Title</title></head>
<body>
<p>Hello, world!</p>
<p>What a <strong>wonderful</strong> world!</p>
</body>
</html>

以前沒想過覆寫 operator, 覺得這種作法容易讓人困惑。但是以 HTML generator 的例子來說, 用 << 組合 HTML 元素挺方便的。

2014年8月5日 星期二

在 vim 內 indent JSON

太容易忘了,乾脆寫篇文章加強記憶:

:%!python -m json.tool

是的,其實就是呼叫 python module 來處理

2014年7月23日 星期三

wmic: Windows Management Instrumentation Command-line

Windows 方面的技能毫無長進, 這就是典型的同樣的事做個十年, 沒有學習的話也不會有所長進...。最近重灌電腦才發覺 wmic 這個好東西, 可以取得硬體資訊 (如主機版型號), 管理 process 等。搜尋 "wmic tips" 會看到很多介紹它的好處和用法。寫這篇廢文只是想降低我忘掉 wmic 的機率...。

Wikipedia 有相關介紹, 其中有幾個關鍵字等要用的時候再深入研究一下, 像是 CIM, DMI, SNMP, 概念是對硬體裝置定好統一的 API, 方便本機或網路查詢、更新設定。

找出 dangle pointer 的方法

有時候 C++ 程式會莫明地 crash 在呼叫 method 的時候, 像是 obj->method()。檢查 obj 的值有幾種情況:

  • NULL: 一看就知道有問題
  • 某種規律的數字, 像是 0x23232323, 0xCDCDCDCD: 99% 有問題, 還沒猜錯過
  • 特定的位置, 如 0xDEADBEEF: 看了也知道有問題, 這是 debugger 特意覆寫的值
  • 看起來像個正常的指標, 可能是 dangle pointer

需要注意的是, 透過 dangle pointer 呼叫 method, 不一定會 crash。不過呼叫 virtual method 時一定會 crash, 因為 destructor 會重置 virtual method table 內的值 (相關文章見《C++ 執行後掛在 __cxa_pure_virtual》)。

以下是一個測試例子:

$ cat a.cpp
#include 

class Rect {
public:
    Rect(int x, int y, int w, int h)
        : x(x), y(y), w(w), h(h) {}

    int area() const { return w * h; };
    virtual int area2() const { return w * h; };

private:
    int x, y, w, h;
};

int main(void) {
    int w, h;
    scanf("%d%d", &w, &h);
    Rect *r = new Rect(10, 20, w, h);
    printf("area: %d\n", r->area());
    printf("area2: %d\n", r->area2());
    delete r;
    printf("area: %d\n", r->area());
    printf("area2: %d\n", r->area2());
    return 0;
}
$ ./a
30 40
area: 1200
area2: 1200
area: 1200
Segmentation fault (core dumped)

以前我判斷 dangle pointer 的小撇步是印出 pointer 指向的 object 的某些 member field, 若出現很怪的數字, 十之八九是 dangle pointer。不過有時還是會出包, 像上面的例子就是反例。看來用 virtual method 一定會 crash 這點做判斷, 會更準確。

在不改 code 的前提下, 可以設中斷點在懷疑是 dangle pointer 的 object 的 destructor, 再重執行程式。但若有大量同 class 的 object, 在 destructor log object 的記憶體位置會比較方便。此外, Scott 提到可以用 glibc MALLOC_PERTURB_, 看起來滿不錯的。不過我試了一下覺得不太管用, 沒有深入研究。

若使用的 gcc 比較新 (>=4.8, Ubuntu 14.04 有支援) 或用 llvm 的話, 可以考慮用 AddressSanitizer, 重點是執行效率比以往的替代工具快上不少。

2014-07-23 更新

Thinker 提到對於有 virtual method 的 object, 多數平台可以藉由印出 vptr 的值來確認是否已變成 dangle pointer。下面是用這個概念檢查 dangle pointer 的例子:

$ gdb a
...
(gdb) b 22  # break on "delete r"
Breakpoint 1 at 0x400746: file a.cpp, line 22.
(gdb) r
Starting program: /home/fcamel/dev/tmp/a
30 40
area: 1200
area2: 1200

Breakpoint 1, main () at a.cpp:22
22          delete r;
(gdb) p (void**)r[0]
$1 = (void **) 0x400950
(gdb) n
23          printf("area: %d\n", r->area());
(gdb) p (void**)r[0]
$2 = (void **) 0x0
(gdb) p *r
$3 = {_vptr.Rect = 0x0, x = 10, y = 20, w = 30, h = 40}

雖然 vptr (pointer to Virtual Method Table) 在 object 內的位置依實作而定, 在自己的平台實驗一下, 就可以用這招配合 debugger 除錯了。

2014年7月6日 星期日

用 regexp 的 lookahead 尋找符合 pattern A 但不符合 pattern B 的字串

偶而會需要找內含字串 A 但不含字串 B 的字串, 若剛好得用 regexp 表示的話, 會有點麻煩 (像是用 Android logcat filter 的時候)。查了一下, 發現 regular expression 有個強大的語法叫作 lookaround, 用它可相對容易地達到此需求, 還可以應付各種情況。詳見 Regex Lookarounds: Lookahead and Lookbehind 的介紹。關鍵在於 lookaround 的語法只是從「目前的位置」往前或往後「看看是否符合目標 pattern」,不會實際占去符合的字串。看文件的例子會比較好理解。

以下是用 Python RE 比對「內含 abc 但 abc 之後不含 def 的字串」:

In [70]: re.search('(?!.*def)abc', 'abcdef')

(?!.*def)abc 的意思是每一個位置都做以下的事:

  1. 先往後找看看有沒有符合 .*def, 找不到才算成立。(?!...) 的意思是 negative lookahead
  2. negative lookahead 成立後, 看看目前位置是否能找到 abc

反之, 下面這個寫法是錯的, 比對 abcdef 仍會有結果:

In [69]: re.search('(?!def)abc', 'abcdef')
Out[69]: <_sre.SRE_Match at 0x2f2fd98>

因為它的意思是在目前位置看看是否不符合 def, 於是一開始比對 abc 時就成立了, 然後再比對成功 abc, 於是回傳比對成功的結果。

(?!.*def)abc 看起來很美好, 但它無法避開 defabc, 也就是 abc 之前有 def 的情況。雖然有 lookbehind 的語法, 但它只能比對固定長度的 pattern, 無法應付 xxxdefxxxabc, 所以得換個方式表示。

regex - Regular expression to match string not containing a word? 說明如何用 regexp 表示不含目標字串的字串, 並有圖解說明運作的過程。了解之後, 可運用同樣的技巧表示「內含 abc 但 abc 之前不含 def 的字串」:

In [145]: re.search('^((?!def).)*abc', 'defabc')
  1. (?!def) 檢查目前位置是否不含 def
  2. (?!def). 注意多加了一個 '.', 表示檢查完後占去一個字元
  3. ((?!def).)* 比對 0 到多個符合 (?!def). 的字元

利用 regexp greedy 的特性, pattern 3 會盡可能占去符合這個的字元, 於是 ^((?!def.)*abc 就會比對出「內含 abc 但 abc 之前不含 def 的字串」。

再和一開始用的 lookahead 合在一起, 就能表示「內含 abc 但不含 def 的字串了」:

In [147]: re.search('^((?!def).)*(?!.*def)abc', 'abcdef')

In [148]: re.search('^((?!def).)*(?!.*def)abc', 'defabc')

In [149]: re.search('^((?!def).)*(?!.*def)abc', 'abc')
Out[149]: <_sre.SRE_Match at 0x2f2de40>

In [150]: re.search('^((?!def).)*(?!.*def)abc', 'xxxabcxdefxx')

In [151]: re.search('^((?!def).)*(?!.*def)abc', 'xdefxxabcxxx')

In [152]: 
就算一時之間無法消化也無所謂, 記得關鍵字 lookaround, lookahead, lookbehind, 之後比較方便找 regexp 進階用法。每次找 regexp 的說明, 都會學到新東西, 真是博大精深的表示法。

2014/07/07 更新

經 weiyu 提醒, 移動 abc 到中間效率會比較好, 以下是程式和測試結果:

$ cat a.py
import re
import time

begin = time.time()
pattern = re.compile('^((?!def).)*(?!.*def)abc')
for i in xrange(1000000):
    pattern.search('xxxxxxabcxxxxxx')
    pattern.search('xxxdefxxxabcxxxxxx')
    pattern.search('xxxxxxabcxxxdefxxx')
print time.time() - begin

begin = time.time()
pattern = re.compile('^((?!def).)*abc(?!.*def)')
for i in xrange(1000000):
    pattern.search('xxxxxxabcxxxxxx')
    pattern.search('xxxdefxxxabcxxxxxx')
    pattern.search('xxxxxxabcxxxdefxxx')
print time.time() - begin
$ python a.py
4.64261507988
3.08146381378

2014年5月23日 星期五

讓 git diff 顯示 utf-16 (或其它binary) 檔案的差異

兩種作法:

透過 difftool

unicode - Can I make git recognize a UTF-16 file as text? - Stack Overflow

$ git difftool commit1 commit2

我直接跑 difftool, 沒設定的情況會問我要不要用 vimdiff。

用 textconv

Textconv - Git SCM Wiki

這個作法比較好, 適用 git diff, show, blame。

比方說對於 iOS L10N 的檔案, 可以這麼設:

  1. 編輯 PROJECT/.git/info/attributes: 加入 *.strings diff=localizablestrings
  2. 編譯 /.gitconfig: 加入
 [diff "localizablestrings"]
 textconv = "iconv -f utf-16 -t utf-8"

2014年5月14日 星期三

使用 OAuth2 refresh token 的好處

初次看到 refresh token, 想說這東西真礙事, 原本 OAuth2 基本的流程已將使用者的密碼和授權分離, 看起來很完美了, 為什麼授權的 access token 之外, 還要多一個 refresh token? 看了 security - Why Does OAuth v2 Have Both Access and Refresh Tokens? 才明白 refresh token 更進一步提升安全性。

access token 解決了:

  • 使用者不需告訴應用程式密碼
  • 使用者可以細分授權項目
  • 使用者可隨時撤消授權

當 access token 外洩的時候, 只要使用者立即撤消授權, 就可以中止傷害, 相較於修改密碼方便許多 (一組密碼可能用在一到多個帳號上, 甚至可能忘了那些帳號使用同一密碼)。

但是為了減少使用者的認證的麻煩, 一個 access token 通常會授權使用很長一段時間 (比方說一個月)。使用者不容易知道什麼時候發出去的 access token 外洩了, 傷害期可能會太長。外洩的原因可能是使用 access token 取存服務時被竊聽破解, 或是在裝置端的應用程式 (PC、smartphone) 存放的 access token 直接外洩。

有沒有辦法可以兼顧減少使用者認證, 又降低 access token 外洩的傷害期呢? 答案是加上 refresh token。設定授權的 access token 在很短的時間內會過期 (如一小時), 然後 client(應用程式) 要用 refresh token + client id + client secret 取得新的 access token。

client 不需要透過使用者就可以取得新的 access token, 不會打擾到使用者。沒有 client secret 的情況下, 單是外洩 access token, 別人用一小段時間後, token 就過期了, 傷害時間很短。

不過要達到上面的好處, client 必須在不同地方存下 refresh token 和 client secret, 偷懶存在同一地方, 一但破台也是一樣。

security - Why Does OAuth v2 Have Both Access and Refresh Tokens? 還有人提供其它回答, 提到 refresh token 有機會減輕認證伺服器的負擔, 不知是意外的副作用, 或是設計之初就有考慮到。愈了解 OAuth2, 愈覺得制定 OAuth2 的人們考慮的真詳細, 提供很彈性的機制讓實作者可自行調整安全的程度、使用者操作的便利性以及伺服器負擔。

Btw, Google 提供 refresh token 的方式還有一點小撇步。使用預設的方式認證時, Google 只有第一次認證時才會傳回 refresh token, 之後重新認證不會再傳回 refresh token。除非使用者到 Google Account 的 Security 分頁取消對 app 的授權, 接著再認證才會再收到 refresh token。或是在認證 URL 的參數加上 approval_prompt=force。詳細的內容寫在 Using OAuth 2.0 for Web Server Applications - Google Accounts Authentication and Authorization — Google Developers:

Important: When your application receives a refresh token, it is important to store that refresh token for future use. If your application loses the refresh token, it will have to re-prompt the user for consent before obtaining another refresh token. If you need to re-prompt the user for consent, include the approval_prompt parameter in the authorization code request, and set the value to force.

2014年4月25日 星期五

讓 Eclipse 連接到小米3 的方法

找了一陣子才找到:

  1. 「設定」->「關於手機」, 連點「Android 版本」開啟開發人員選項
  2. 在 ~/.android/adb_usb.ini 加入以下代碼:
0x2717
0x8087
0x2080

過濾 iOS log 的方法

平常開發 Linux 程式需要即時觀察 log, 又有太多 log 要過濾的時候, 會用

$ tail -f LOG_FILE | grep PATTERN  # 只留符合 PATTERN 的 log

或是

$ tail -f LOG_FILE | grep -v PATTERN  # 去掉符合 PATTERN 的 log

command line 的 pipe 可以無限接, 方便做很多延伸運用。

開發 Android 的時候可以透過命令列的 adb logcat, 或是 Eclipse 也有提供介面過濾 log。

開發 iOS 時比較困擾, 通俗是用 NSLog 寫到 console, 但是沒有找到用 XCode 過濾的方法。

轉念一想, 既然 iOS 裝置接上 XCode 後可以看到過去的 console log, 表示 log 存在檔案裡。直接取 log 的原始檔案, 比照在 Linux 的習慣直接對檔案操作應該比較簡單。找了一陣子找到檔案放在 $HOME/Library/Application Support/iPhone Simulator/7.1/Library/Logs/system.log, 接著套上在 Linux 的習慣, 就方便多了。

2014年3月29日 星期六

Ctrl+C 運作的原理以及 session, terminal 和 process group

簡短版說明

前陣子遇到一個神祕的問題:

執行程式 P 的時候, 可以用 ^C 中止它。但透過 shell script S 執行 P (S 內只有該程式名稱和參數而已), 就無法用 ^C 中止它。^\ 也是一樣情況, 不過 ^Z 可以正常 suspend。

檢查其它的環境因素發現:

  • kill -INT PID 可以中止 P, 表示程式沒有攔截 SIGINT。
  • stty -a -F TTY_DEVICE 顯示 intr = ^C 且 isig, 表示輸入 ^C 後會被轉譯成 SIGINT。按 ^C 後螢幕上有顯示 ^C。

綜合以上的資訊, 無法理解還有什麼原因會讓 ^C 轉送 SIGINT 失效。

卡了一陣子後, 回頭翻 TLPI ch34 Process Groups, Sessions, and Job Control 以及 ch62 Teriminals 才找到答案。

答案是: P 有用 setpgid() 產生新的 process group, 於是 P 就不是 terminal foreground process group, 而 ^C 只會送給 terminal foreground process group。

詳細說明

在沒有透過 shell script 執行 P 的情況, P 會自成一個新的 process group, 同時也是 terminal foreground process group (這是 shell fork process 後設定的)。之後即使程式有呼叫 setpgid(), 也不會改變 pgid, 因為 P 本來就是自己 process group leader。這時, P 仍然是 terminal foreground process group, 收得到傳給 terminal 的 ^C。

另一方面, 透過 shell script S 執行的情況, S 自成一個新的 process group 也會是 terminal foreground process group (同樣的, 是 shell 設定的)。S 執行 P, P 又用 setpgid() 自己成立一個新的 process group, 這個新的 process group 不是 terminal foreground process group, 也就收不到 ^C 了。

若希望 P 能收到 ^C, 呼叫 setpgid() 後, 記得要呼叫 tcsetpgrp()。

像是這樣:

setpgid(0, 0);
tcsetpgrp(0, getpgid(0));

Terminal 相關知識

TLPI ch62 Terminals 介紹 terminal 相關參數的意思。可以用指令 tty 得知目前 shell 連接的 terminal, 像這樣:

$ tty
/dev/pts/10

之後就可以在別的 terminal 用 stty -F TTY 觀察或修改該 terminal 的設定, 方便了解程式是否有修改 terminal 參數:

$ stty -F /dev/pts/10 -a
speed 38400 baud; rows 47; columns 183; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

stty 顯示的含意可看 man sttyTLPI ch62。透過 stty 可以確定 intr 和 isig 都有正常運作, 問題不在這裡。

Session, Process Group 相關知識

為了方便管理 process, Linux 有 session 和 process group 兩層結構。一個 session 可以沒有 terminal, 或是有唯一一個 terminal。一個 terminal 也只能屬於一個 session。

一個 session 可有多個 process group, 有些 system call 或指令可操作整個 process group, 所以切成多個 process group 易於管理 process。

比方說:

  • kill(-PID, SIG) 會送 signal SIG 給所有 pgid 為 PID 的 process, 方便一次暫停或殺掉整群 process (shell 用 pipe 串起多個指令時, 這些指令就是同一個 pgid)
  • terminal 收到 ^C、^Z、^\ 這類會轉成 signal 的控制字元時, 會送給整個 terminal foreground process group (有設 isig 時才會轉成 signal, 預設有設 isig)。

若想查看 pid, ppid, pgid, sid, tpgid (terminal foreground process group), 可用 ps -o pid,ppid,pgid,sid,tpgid。細節見 ps 或 man proc, ps 也只是讀 /proc/PID/stat 和 /proc/PID/ 下其它檔案。

完整的介紹見 TLPI ch34 Process Groups, Sessions, and Job Control。

2014年2月26日 星期三

如何找出 iOS crash log 中的 symbol name

從 Sam 那看到的參考文章: 别用symbolicatecrash来解析crash log了 - Wonderffee's Blog

若 binary 是別人產生, crash log 也是別人產生時, 可以照以下的方式對回 symbols:

  1. 取得產生 crash log 對應 app binary 的 archive 目錄下的 dSYMs 目錄。
  2. 在命令列下輸入: $ mdimport dSYMs
  3. 將 crash log 存成 xxx.crash (副檔名必須是 .crash)。
  4. 打開 XCode 的 Oraganizer, 點選 Library 下的 Devices Logs, 點選 Import, 選擇 xxx.crash。

結果會顯示在畫面右側。

2014年2月24日 星期一

lldb to gdb command

官方連結: LLDB to GDB Command Map

在這備忘自己常用的指令:

  • return EXPR -> thread return EXPR

顯示 step out 後的傳回值

出處:

#/usr/bin/env python
 
import lldb
 
# Put _this_ file into ~/Library/lldb/thread_return.py
# Put the following line into ~/.lldbinit
#     command script import ~/Library/lldb/thread_return.py
 
def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f thread_return.thread_return thread_return')
 
def thread_return(debugger, command, result, internal_dict):
    '''Prints the return value of the last function you stepped out from.
    
    This is very useful if the return was a complex expression. This lldb command
    prevents you from needing to create a temporary variable just to inspect the return value'''
 
    # If anyone knows a way of doing this without using needing to script lldb in python...
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    thread = process.GetSelectedThread()
    print >> result, str(thread.return_value)
    return lldb.thread.return_value

2014年2月21日 星期五

用 vim tab 批次處理多個檔案

最近才發覺 vim 有強大的批次處理功能, 相見恨晚啊。

在命令列同時開啟多個檔案到多個 tab

vim -p module/*.h

開啟 module 目錄下全部 .h 檔, 用一個 tab 編輯一個檔案。

需配合 tabpagemax 使用 (set tabpagemax=1000), 預設 tabpagemax=10, 不太夠用。

在 vim 內開啟多個檔案到多個 tab

:args module/*.h
:argdo :tabe

效果同上一個作法。但 argdo 為了加速讀檔會先關掉 syntax, 要自己加指令開啟 syntax:

:args module/*.h | execute 'argdo tabe %' | syntax on

在 vim 內更改全部 tab 內容並存檔

:tabdo :% s/xxx/ooo/
:tabdo :w

第一行對全部 tab 逐一執行 :% s/xxx/ooo/, 第二行則是逐一執行 :w。一但有 tab 無法執行指令, 就會停下來。

:tabdo 可用的場合很多, 比方說我習慣用 vim -p 一次開啟多個 core dump 的 backtrace, 然後用:tabdo normal zR unfold 全部檔案, 還有用 :tabdo 下指令 highlight 關鍵字:

:tabdo hi KeywordTemp ctermfg=red
:tabdo syn match KeywordTemp /SOME_KEYWORD/
方便閱讀不同 core dump 中共通的部份。

2014年2月19日 星期三

git 的入門文件

記錄一下我學 git 的過程。這些文件看起來都滿不錯的, 應該也適用沒接觸過 DVCS 的人 (我先前有用 mercurial 的經驗)。

Pro Git

先在網路查些基礎指令 (介紹愈短愈好), 還有抄別人的 git config 簡化成自己的版本, 就這樣上工一陣子。

然後需要 merge branch 時, 看這本第三章講 branch 的部份, 有許多圖示說明 git 內部資料的變化, 一目了然。看不懂的時候就回頭翻第二章, 滿容易上手的。

Git Tutorials and Training | Atlassian

平時要用什麼指令就 google 一下, 再點進 stackoverflow 看別人的說明。這樣瞎用了一陣子後, 回來快速掃一遍這裡的說明, 才比較有系統地記下常用指令。

Btw, 以前學 mercurial 的時候是先有系統地掃一遍常用指令, 再開始用。這回學 git 反過來, 覺得這樣學也不錯。或許基礎比較紮實後, 學類似的新東西時, 採取要用再補的方式會比較有效率。

2014年2月9日 星期日

從 third-party library 的 console 錯誤訊息找出更多線索

最近遇到一個不錯的小例子, 可以比較完整地記錄用到的相關指令。更多關於 gdb 的說明, 見 gdb 初步心得

問題

Ubuntu 下執行程式後看到 "Gtk-WARNING **: cannot open display:", 然後程式就掛了。

解決過程

為了獲取更多線索, 我想找出是那段程式輸出這個錯誤訊息。由 "Gtk-WARNING" 可知是 Gtk+ 函式庫輸出的錯誤訊息。所以得先取得 Gtk+ 的 debug symbol 和原始碼:

$ dpkg -l | grep gtk
...
ii  libgtk2.0-0
...

找出已安裝的 Gtk+ 函式庫叫做 libgtk2.0, 接著找出它對應的 debug symbol package 和原始碼:

$ aptitude search libgtk2.0
i   libgtk2.0-0             - GTK+ graphical user interface library
...
i   libgtk2.0-0-dbg         - GTK+ libraries and debugging symbols
...
i   libgtk2.0-dev           - development files for the GTK+ library
...

然後安裝 debug symbol, 還有取得 Gtk+ 的原始碼:

$ aptitude install libgtk2.0-0-dbg
$ apt-get source libgtk2.0-dev

從取得的原始碼找看看有沒有 "cannot open display":

$ cd gtk+2.0-2.24.10/
$ grep "cannot open display" -R .
./gtk/gtkmain.c:      g_warning ("cannot open display: %s", display_name_arg ? display_name_arg : "");
./gdk/gdk.c:      g_warning ("cannot open display: %s", display_name ? display_name : "");
./.pc/071_no_offscreen_widgets_grabbing.patch/gtk/gtkmain.c:      g_warning ("cannot open display: %s", display_name_arg ? display_name_arg : "");
./.pc/100_overlay_scrollbar_loading.patch/gtk/gtkmain.c:      g_warning ("cannot open display: %s", display_name_arg ? display_name_arg : "");
./ChangeLog.pre-2-12:   * gtk/gtkmain.c: (gtk_init): Fix "cannot open display" error message

讀一下字串出現相關的位置, 找出函式 gtk_init_check 和 gdk_init_check。看來沒有找錯方向, 於是可以用 cgdb 執行程式, 進入 gdb 後輸入:

(gdb) b gtk_init_check
(gdb) b gdk_init_check
(gdb) directory /my/path/to/gtk+2.0-2.24.10/
(gdb) set substitute-path /.../gtk+2.0-2.24.10/ /my/path/to/gtk+2.0-2.24.10/
(gdb) r
  • directory 用來載入 Gtk+ 原始碼。debug symbol package 裡沒有含這部份。
  • 編譯封裝 Gtk+ 的路徑十之八九和我放 Gtk+ 原始碼的路徑不同, 所以要用 set substitute-path 替換路徑, gdb 才知道如何顯示對應的原始碼。上面的 "..." 是執行 backtrace 時看到的路徑, 這裡簡化用 "..." 表示。

再來就在中斷點附近輸出收到的值, 看看 backtrace, 看看附近的原始碼, 獲得更多線索後就解決問題了。原來是我忘了呼叫 setenv("DISPLAY", ":0"), gdk_init_check() 發現無法產生 GdkDisplay, 所以就掛了。

2014年1月30日 星期四

初次使用 paper prototyping 做使用性測試

雖然時常看到有人推薦用紙筆畫 app 介面做使用性測試, 但是要自己動手畫重覆的東西, 實在是提不起勁。前陣子聽阿修介紹 Paper Prototype, 也有同事動手畫了幾頁, 實際跟著流程走一次, 感覺還不錯。剛好我有在 iPad 裡裝 POP, 當場用 POP 結合同事做的紙張雛型, 互動效果出乎意料的好。

最近想幫爸媽做一款「待做事項」的軟體, 順便練習從頭開發一款 app 要經過的流程。中午花了大概一小時畫草稿加上用 POP 制作雛型, 過程滿順暢的。下面是部份原稿和 POP Android 版的畫面。

和爸媽分別測試後有找到一些小問題, 不過確定大方向應該可行。後來找我哥測試, 原以為應該會更順暢, 但是因為我哥使用 iPhone, 我是針對 Android 畫的草圖, 結果不容易融入情境。再加上我哥不是我的目標對象, 主要操作流程和他的期望不同, 也影響測試的流暢度。

歸納一下目前對使用 POP 做 paper prototyping 的看法。

目的:

  • 做使用性測試。了解使用者是否了解如何操作。功能是否有滿足使用者的需求。

注意事項:

  • 需要篩選目標使用者。
  • 測試前要做情境描述, 使用者才能進入狀況 (看著草圖想像要做的事)。
  • 盡量觀察使用者的反應, 少開口提醒。

優點:

  • 制作速度快, 適合早期討論, 甚至能在開會中當場做。
  • 易於專注在操作流程, 不會討論配色、字型、畫面排版等細節。
  • 任何人都可以製作, 工程師和設計師以外的人也可藉此展示自己的想法。

限制:

  • 只適合能融入情境的使用者。也許可透過幾次體驗後克服。
  • 還是有可能插題討論圖示, 所以也不能畫得太糟。

總結來說, 現在我覺得用 POP 做 paper prototyping 滿快的, 但若是只用紙張互動, 一來我做得比較慢, 二來使用者也比較難聯想。我自己不適合用純紙張版本的 paper prototyping。

附帶一提, 做得過程中要自己用紙筆畫出概要, 才留意到一些 Android UI 的小細節。還有實際測試流程不像影片裡那樣順。看了再多資料, 還是得親身走一遍才行。

2014年1月27日 星期一

C++ 下 thread-safe 的 lazy initialization

《clang 避免 non-local static 物件初始化順序的方法》提到可用 static local variable, 然後用 method 傳回的方式避免產生 global static variable (藉此避免不同編譯單元的初始化問題)。以下是一個例子:

const struct Point* center()
{
  static Point* s_center = CreateCenterPoint();
  return s_center;
}

但是, 在 multi-thread 的環境下, s_center 可能被初始化兩次。假設 CreateCenterPoint() 會傳回不同的值, 或是外界可以改變取得的 Point, 有兩份 Point 會造成問題。

好消息是:

所以情況比想像中的安全。

不過, 其它 lazy initialization 的實作方式有可能出錯。更一般化的 lazy initialization 會用 Double-Checked Lock Pattern, 但是這個作法有不易察覺的漏洞

直覺的作法如下:

Singleton* Singleton::instance()
{
  if (pInstance == 0) { // 1st test
    Lock lock;
    if (pInstance == 0) { // 2nd test
      pInstance = new Singleton;
    }
  }
  return pInstance;
}

注意初始化 singleton 是三個步驟組成的:

  1. 配置一塊新記憶體
  2. 初始化新記憶體
  3. 將新記憶體的位置指向目標指標 (即 pInstance)

compiler 有可能更動三者的順序。最壞的情況下, thread A 執行了 1, 3, 還沒執行 2, 這時 thread B 發覺 pInstance 不是 0, 於是回傳尚未初始化的 pInstance。解法是要使用 memory barrier。或是在 C++11 後更可用跨平台的解法。詳情見 Double-Checked Locking Is Fixed In C++11

另外, Java 1.4 以前 Double-Checked Locking 也有問題。Java 1.5 後多了 volatile 表示「取出最新的值」, 才有辦法修正此問題。Effective Java 2/e Item 66 "Synchronize access to shared mutable data" 和 Item 71 "Use lazy initialization judiciously" 有詳細的討論。

2014年1月20日 星期一

尋找 memory error 的強力工具: Address Sanitizer (ASan)

官網有很詳細的介紹, ASan 可以偵測出以下的問題:

  • Use after free (dangling pointer dereference)
  • Heap buffer overflow
  • Stack buffer overflow
  • Global buffer overflow
  • Use after return

都是出錯時很難察覺, 之後會造成奇異的行為, 或是讓程式掛在莫明奇妙的地方。

clang 3.1 和 gcc 4.8 開始內建 ASan 的功能。號稱執行速度平均慢兩倍, 比起 Valgrind 的 20 倍, 實在相當誘人。這裡有說明 ASan 怎麼做的。這樣在 Linux 上也有不錯的 memory error detector 了。不知什麼時候能追上 OS X 的腳步

除了 Chromium 有用之外, Firefox 也有使用

clang 避免 non-local static 物件初始化順序的方法

C++ 沒有定義 non-local static 物件在不同編譯單元 (translation unit) 之間初始化的順序, 所以要極力避免 non-local static 物件相互間的存取。

今天試用 clang 編譯程式, 發覺它有個不錯的選項: -Wglobal-constructors。用這選項編譯, 遇到有 non-local static 物件會依賴其它函式 (包含 constructor) 設值時, 會輸出 warning。

這裡引用 Address Sanitizer 提供的例子:

$ cat a.cc
#include <stdio.h>
extern int extern_global;
static int __attribute__((noinline)) read_extern_global() {
  return extern_global;
}
int x = read_extern_global() + 1;
int main() {
  printf("%d\n", x);
  return 0;
}

$ cat b.cc
int foo();
int foo() { return 42; }
int extern_global = foo();
$ clang++ a.cc b.cc && ./a.out
1
$ clang++ b.cc a.cc && ./a.out
43
$ clang++ a.cc b.cc -Weverything
a.cc:6:5: warning: declaration requires a global constructor [-Wglobal-constructors]
int x = read_extern_global() + 1;
    ^   ~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
b.cc:3:5: warning: declaration requires a global constructor [-Wglobal-constructors]
int extern_global = foo();
    ^               ~~~~~
1 warning generated.

由上可知, 編譯 a.cc 和 b.cc 的順序不同, 輸出的結果不同。加上 -Weverything 後, -Wglobal-constructors 有在第一時間抓出有問題的部份。附帶一提, clang 的錯誤訊息不止有標示錯誤的位置, 而且還是彩色的!

解決這個 warning 的方法, 和 Effective C++ Item 4 的說法一樣, 就是改用函式傳回 local static method, 這樣就會依執行的順序在執行期間初始化。不過實際情況稍微複雜了一點, 後述。

clang 另有一個參數 -Wexit-time-destructors, 會找出在結束程式時執行 destructor 的物件。雖然 destructor 執行的順序有明確的定義 (和初始化的順序相反), 不過開發者八成沒有考慮週全, 很容易在一連串 destructor 執行中用到已執行完 destructor 的物件。這個問題和 non-local static 物件初始化一樣棘手。

clang 的解決方案一樣單純: 「本來無一物, 何處惹塵埃。」統統不準用, 就不會出亂子。

$ cat p.cc
struct Point
{
  Point() {}
  ~Point() {}
  int x, y;
};


const struct Point& center();
const struct Point& center()
{
  static Point s_center;
  return s_center;
}
$ clang++ p.cc -c -Weverything
p.cc:12:16: warning: declaration requires an exit-time destructor [-Wexit-time-destructors]
  static Point s_center;
               ^
1 warning generated.

那要怎麼解決這個 warning 呢? 就是 new 一個物件, 並且不要釋放它。若懶得修改已經存取它的程式, 可以這麼做:

const struct Point& center()
{
  static Point& s_center = *new Point();
  return s_center;
}

在這兩個 warning 的夾擊下會少掉很多難以察覺的錯誤, 不過寫程式時也會有一點點不便。比方說需要用到常數字串時, 不能直接寫

const std::string kMyString = "...";

得改用

const char* kMyString = "...";

對於用 std::string 做為函式參數或 STL container 的物件, 得付出一點生成 std::string 的成本。

2014年1月5日 星期日

使用 CSS position 組合 div 的佈局小技巧

這篇談的方法在舊的瀏覽器大概會出包, 沒有實際研究。跨瀏覽器的議題太複雜, 希望在我不得不面對這議題前, 跨瀏覽器的痛苦可以減少很多 (大概要等 Windows XP 絕跡吧)。

以下圖為例, 這篇討論兩個使用 CSS position 的小技巧。

使用 relative position 重疊版型

上圖的書本和內文, 實際上是兩個 div 組成的, 還原後如下圖:

組合的方式如下:

HTML

<div class="wrapper">
  <div class="content"></div>
  <div class="book"></div>
</div>
CSS

.wrapper {
  height: 750px;
  overflow: hidden;
}

.content {
  height: 500px;
  position: relaltive;
  z-index: 1;
}

.book {
  height: 700px;
  width: 500px;
  position: relative;
  top: -600px;
}
數據是我大概填的, 示意用。

幾個要點:

  • .book 用 position: relative 從原有的位置往上移到 .content 做為背景。
  • 為了讓 .content 在上方顯示, 設 z-index: 1。由於 z-index 只有在 non-static position 下才有效, 所以改一下 position。
  • .book 使用 position: relative 往上移後, 仍會在原位置留下空間, 就像 visibility: hidden 一樣。為了避免占用原位置的空間, 限製 .wrapper 的 height。
這樣做的好處是內文和背景分離, 兩者各自依需求配置 div 結構, 不會互相影響。不然要顧及內文的排版而調整背景書本的 div, 切版型會滿辛苦的。

缺點是必須限制高度, 不像一般網頁彈性地隨內文增加而自動增加高度。

使用 absolute position 疊出下方的書緣

下方展開後, 中間長這個樣子, 我隱藏了左側一個 div, 方便看拆開的 div。

HTML

<div class="page-container-bottom">
  <div class="page-bottom page-bottom-left">
    <div class="page-bottom-inner page-bottom-left-inner"></div>
  </div>
  <div class="page-bottom page-bottom-right">
    <div class="page-bottom-inner page-bottom-right-inner"></div>
  </div>
</div>
CSS

.page-bottom {
  height: 30px;
  position: absolute;
  bottom: 0;
  background-color: #8F8F8F;
}

.page-bottom-left {
  border-top-right-radius: 90px 30px;
  width: 50%;
}

.page-bottom-inner {
  height: 15px;
  width: 100%;
  bottom: 0px;
  position: absolute;
  background-color: #FFF;
}

.page-bottom-left-inner {
  border-top-right-radius: 90px 15px;
}
CSS 部份只列出左半邊, 右半邊作法一樣, 只是改用 border-top-left-radius。

幾個要點:

  • 左右各一個 div, 外層的 div ( .page-bottom ) 用 position: absolute + bottom: 0 做到貼齊下緣的效果。注意使用 position: absolute 的 tag 會跟著上層第一個使用 non-static position 的 tag, 在這個例子裡, 是前面提過的 div.book。
  • 內層的 div ( .page-bottom-inner ) 用一樣的方式貼齊底部, 但是 height 比較矮, 且圓角弧度比較小。兩者疊出書緣的效果。

用 position: absolute 貼齊底步, 做起來出呼意料地容易。

2014年1月4日 星期六

Ubuntu 安裝 package 出現 conflicts 時的解法

這篇只討論我自己常遇到的一個情境: 明明只是升級某個套件或加裝相關套件, aptitude 卻發瘋似的說有些相關套件的相依版本不合, 要移掉一大堆套件才能繼續。

解法是下指令時同時安裝目標套件和 aptitude 抱怨不合的相依套件。

實例如下:

$ sudo aptitude install git-svn
[100%] Reading package lists
...
The following NEW packages will be installed:
  git-svn libsvn-perl{a} libterm-readkey-perl{a}
The following packages will be upgraded:
  perl perl-base perl-modules
3 packages upgraded, 3 newly installed, 0 to remove and 793 not upgraded.
Need to get 10.3 MB of archives. After unpacking 4,449 kB will be used.
The following packages have unmet dependencies:
 libperl5.14 : Depends: perl-base (= 5.14.2-6ubuntu2.1) but 5.14.2-6ubuntu2.3 is to be installed.
Internal error: the solver Install(libc-bin:amd64 2.15-0ubuntu10.5 <libc6:i386 2.15-0ubuntu10.5 -> {libc-bin:amd64 2.15-0ubuntu10.5 libc-bin:i386 2.15-0ubuntu10.5}>) of a supposedly unresolved dependency is already installed in step 153
Internal error: the solver Install(libc-bin:amd64 2.15-0ubuntu10 <libc6:i386 2.15-0ubuntu10 -> {libc-bin:amd64 2.15-0ubuntu10 libc-bin:i386 2.15-0ubuntu10}>) of a supposedly unresolved dependency is already installed in step 335
Internal error: the solver Install(fontconfig-config:amd64 2.8.0-3ubuntu9 <libfontconfig1:i386 2.8.0-3ubuntu9 -> {fontconfig-config:amd64 2.8.0-3ubuntu9}>) of a supposedly unresolved dependency is already installed in step 337
The following actions will resolve these dependencies:

      Remove the following packages:
1)      gtk2-engines-murrine:i386
2)      hplip
3)      libatk1.0-0:i386
...
73)     printer-driver-hpijs
74)     telepathy-haze
75)     zlib1g:i386

      Leave the following dependencies unresolved:
76)     empathy recommends telepathy-haze
77)     foomatic-db-compressed-ppds recommends printer-driver-hpijs
78)     libsane-hpaio recommends hplip (= 3.12.2-1ubuntu3)
79)     ubuntu-desktop recommends hplip


Accept this solution? [Y/n/q/?] q
Abandoning all efforts to resolve these dependencies.
Abort.
$ sudo aptitude install git-svn libperl5.14
...
The following NEW packages will be installed:
  git-svn libsvn-perl{a} libterm-readkey-perl{a}
The following packages will be upgraded:
  libperl5.14 perl perl-base perl-modules
4 packages upgraded, 3 newly installed, 0 to remove and 792 not upgraded.
Need to get 10.3 MB of archives. After unpacking 4,449 kB will be used.
Do you want to continue? [Y/n/?] y
Selecting previously unselected package git-svn.
Unpacking git-svn (from .../git-svn_1%3a1.7.9.5-1_all.deb) ...
Processing triggers for man-db ...
Setting up libperl5.14 (5.14.2-6ubuntu2.3) ...
Setting up perl-modules (5.14.2-6ubuntu2.3) ...
Setting up perl (5.14.2-6ubuntu2.3) ...
Setting up libsvn-perl (1.6.17dfsg-3ubuntu3.3) ...
Setting up libterm-readkey-perl (2.30-4build3) ...
Setting up git-svn (1:1.7.9.5-1) ...
Processing triggers for libc-bin ...
ldconfig deferred processing now taking place
...
Current status: 792 updates [-4].
[fcamel@fc-vm ~/dev ]
$ git svn
fatal: Not a git repository (or any of the parent directories): .git
Already at toplevel, but .git not found
 at /usr/lib/git-core/git-svn line 308

2015/01/04 更新

可配合 apt-cache show PKG 了解套件的相依性, 實用例子見《Ubuntu 12.04 安裝 graphviz 遇到 confclit》

另外這個不知要記在那裡, 順便記在這裡: 安裝指定版本的方法: aptitude install PKG=VERSION。PKG=VERSION 的語法可用在 isntall, show, download, forbid-version 等指令。

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