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

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...