2016年9月16日 星期五

不為人知的gdb 使用方式系統-gdb pretty printer auto load

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文:

上回我們提到了gdb 的pretty printer,現在我們就來看個範例:寫Rust 用的Rust-gdb。

其實Rust-gdb 跟gdb 本質上沒什麼不同,裡面將python pretty printer 的路徑加到gdb source 的路徑,然後執行gdb…
gdb -d "$GDB_PYTHON_MODULE_DIRECTORY" \
-iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
"$@"
這樣感覺什麼都沒加進去呀,事實上,rust 是透過比較隱晦的機制,在編譯的時候將要引入的資料插在執行檔的 .debug_gdb_scripts 裡面,這個section 會包含null-terminated 的條目(entry)指明gdb 要載入哪些script,每個script 的開頭會有一個byte 指明這是哪一種entry,拿我之前寫的racer 來試試,把它的section 印出來:
$ readelf -S racer
[17] .debug_gdb_script PROGBITS         0000000000743af8  00743af8
     0000000000000022  0000000000000000 AMS       0     0     1
然後我們把這個區段給印出來:
$ readelf -x .debug_gdb_scripts racer
「.debug_gdb_scripts」區段的十六進位傾印:
0x00743af8 01676462 5f6c6f61 645f7275 73745f70 .gdb_load_rust_p
0x00743b08 72657474 795f7072 696e7465 72732e70 retty_printers.p
0x00743b18 7900                                y.

開頭的01,指明它是在script in python file,內容則指明要載入gdb_load_rust_pretty_printers.py,一般我覺得是用不著這麼搞工啦,畢竟要用這招就要在編譯器上面動手腳,大概在自幹語言的時候用上這招比較方便,相關文件可見這兩個連結:
https://sourceware.org/gdb/onlinedocs/gdb/Auto_002dloading-extensions.html#Auto_002dloading-extensions
https://sourceware.org/gdb/onlinedocs/gdb/dotdebug_005fgdb_005fscripts-section.html#dotdebug_005fgdb_005fscripts-section

rust-gdb 所用的方法,如這份文件str_lookup_function之後的部分所示:
https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html#Writing-a-Pretty_002dPrinter

把pretty printer 寫完之後,首先要先寫一個gdb 的lookup_function,這個function 會負責回傳適當的pretty printer,不然就回傳None。
文件建議這個pretty printer 放到單獨的python package 裡面,如果這個pretty_printer是針對某個函式庫寫的,那更建議package 的名字要加上函式庫的版本號,這樣gdb 能載入不同版本的pretty printer;最後在auto load 的script 中import 這個pretty printer,用gdb.pretty_printers.append ,把這個lookup_function插入gdb的objfile 中即可。

Rust-gdb 就是這樣做的:
auto load 的script gdb_load_rust_pretty_printers.py 中,引入gdb_rust_pretty_printing,然後呼叫它的register_printers,把rust_pretty_printer_lookup_function插入目前的objfile 中。
一個lookup_function 會接受一個gdb Value,我們可以從中取得它的type的tag,從tag 中辨別它是什麼型別的資料,藉此回傳適當的pretty printer。

我用之前的QString 做了個簡單的範例,首先我們要改造一下我們的qstring.py,加上lookup function 跟register_printer,lookup function 在型別為QString 時會將這個值傳入我們寫的QStringPrinter,並回傳它:
def qstring_lookup_function(val):
  lookup_tag = val.type.tag
  if lookup_tag is None:
    return None
  regex = re.compile("^QString$")
  if regex.match(lookup_tag):
    return QStringPrinter(val)
  return None

def register_printers(objfile):
  objfile.pretty_printers.append(qstring_lookup_function)
接著我們寫一個autoload script : hello-gdb.py,雖然我們用到了gdb,但gdb 內的python 會自動引入這個gdb 這個module,所以不引入也沒關係:
import qstring
qstring.register_printers(gdb.current_objfile())
另外要注意的是,一定要使用自動載入的方式,因為gdb.current_objfile() 只有自動載入的時候才會設定為當前的objfile。
之所以叫hello-gdb.py,是因為我們採用另一個機制:objfile-gdb.py的檔案會被自動載入,我執行檔名叫hello ,hello-gdb.py會在gdb 開啟時自動載入,其他有用到的dynamic library ,檔名叫libXXX-gdb.py也可以用類似的方法載入。
https://sourceware.org/gdb/onlinedocs/gdb/objfile_002dgdbdotext-file.html#objfile_002dgdbdotext-file

當然故事不是這樣就完了,首先hello-gdb.py會用到qstring.py,所以我們必須把qstring的位置加到PYTHONPATH 變數之中;第二gdb 不會隨意自動載入第三方的script,以免造成危害(雖然說到底能有啥危害我也不知道……),因此我們必須在gdb 開始自動載入script 前,把這個script 加到安全名單之中;因此,我們的gdb 呼叫會變成下列兩者擇一,也就跟rust-gdb 相差無幾了:
PYTHONPATH="$PYTHONPATH:/tmp" gdb -iex "set auto-load safe-path /tmp" hello
PYTHONPATH="$PYTHONPATH:/tmp" gdb -iex "add-auto-load-safe-path /tmp/hello-gdb.py" hello

大概的故事就是這樣了,Rust-gdb 當然是lookup function 那邊搞了很多,把型別什麼共同的部分獨立出來,以便支援gdb 跟lldb。
一般的lookup function 就不用寫這麼多了。
又說回來,要寫到大型的lookup function 應該也只有開發語言跟函式庫的時候需要,一般人更用不到這個功能,所以這篇比起上篇其實更是廢文一篇(蓋章,感謝大家又貢獻兩分鐘的時間給小弟的blog (炸。

2016年9月14日 星期三

不為人知的gdb 使用方式系統-gdb pretty printer

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文。

這篇來介紹 gdb 的pretty printer:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing

假設我們有一個客製的struct 或是class,例如Qt4 的QString,如果我們用原本gdb 的print 去印出QString的話:
QString s("Hello World");
(gdb) p s
$2 = {static null = {<No data fields>}, static shared_null = {ref = {_q_value = 2}, alloc = 0, size = 0, data = 0x7ffff7dd70fa <QString::shared_null+26>, clean = 0,
simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, static shared_empty = {ref = {_q_value = 1}, alloc = 0, size = 0,
data = 0x7ffff7dd70da <QString::shared_empty+26>, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}},
d = 0x603dc0, static codecForCStrings = 0x0}

因為QString 是個結構的關係,我們無法單純印個char array ,反而會印出裡面所有的資訊,它真的資訊是存在那個 d 裡面,要真的印字串就要從那個d 裡面去印。
之前在寫Qt 專案的時候有遇到類似的問題,那時有查到一個printqstring 的自訂函式,把下面這段加到 ~/.gdbinit 裡面:
define printqstring
  printf "(QString)0x%x (length=%i): \"",&$arg0,$arg0.d->size
  set $i=0
  while $i < $arg0.d->size
    set $c=$arg0.d->data[$i++]
    if $c < 32 || $c > 127 
      printf "\\u0x%04x", $c
    else
      printf "%c", (char)$c
    end 
  end
  printf "\"\n"
end

就可以在debug 時使用printqstring來印出QString
(gdb) printqstring s
(QString)0xffffe650 (length=12): "Hello World!"

當然我們可以用gdb 的pretty printer 來做到類似的事,而且不需要自訂函式,單純用p s 也能做到一樣的效果。
gdb v7 開始加入對python的支援,可以讓我們透過python和gdb 互動,在python 之中import gdb 之後,就可以利用一連串定義完善的介面去跟gdb 互動,詳細上用法還不少,這裡我們只先試著寫個pretty_printer。
Python 的pretty printer 就是實作一個class,至少要實作__init__跟to_string() 兩個介面,其他的函式可以參考:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing-API.html#Pretty-Printing-API
之後使用gdb.printing的RegexpCollectionPrettyPrinter,產生一個叫QString 的printer,利用add_printer,只要型態名稱符合^QString$ 的物件,就用QStringPrinter來處理,最後用regeister_pretty_printer把這個RegexpCollectionPrettyPrinter 塞進去:
# qstring.py
import gdb.printing

class QStringPrinter(object):
  def __init__(self, val):
    self.val = val
  def to_string(self):
    return "I'm Qstring"
  def display_hint(self):
    return 'string'

pp = gdb.printing.RegexpCollectionPrettyPrinter('QString')
pp.add_printer('QString', '^QString$', QStringPrinter)
gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)
進到gdb 之後,首先source qstring.py ,接著去print 一個QString 的話,gdb 就會呼叫我們寫的QStringPrinter 的to_string 來處理,就會得到垃圾訊息:I'm QString。

to_string 回傳的規則如下:
python 的integer, float, boolean, string 都是gdb 可處理的(可轉換為gdb.Value),會由gdb 直接印出;回傳None的話則不會印東西。
傳回其他的gdb.Value,那gdb 會接續處理這個value,處理中也會呼叫其他註冊的pretty printer;真的無法處理,gdb就會丟出exception。
另外我們可以實作display_hint 的介面,告知gdb 要如何處理這個值的輸出,這會影響到gdb 如何接續處理這個值;這裡我們表示這個輸出是個字串。

上面只是舉例,要印出同樣的結果,我們的to_string 要稍微改寫:
addr = self.val.address
size = self.val['d']['size']
i = 0 
content = []
while i < length:
  c = self.val['d']['data'][i]
  if c > 127:
    content.append("\\u%x" % (int(c)))
  else:
    content.append(chr(c))
  i = i + 1 
return '(QString)%s (length=%d): "%s"' % (addr, size, "".join(content))
當然我在查這個的時候,發現有另一個人的實作生猛得多:
http://nikosams.blogspot.tw/2009/10/gdb-qt-pretty-printers.html
dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer())
content = dataAsCharPointer.string(encoding='utf-16', length=size*2)
它直接用cast 得到一個新的gdb.Value(),型態為char pointer;再呼叫string()方法用utf-16 的方式encoding,就把字串給拿出來了,更多用法可以參考文件,這裡暫時不詳細說明:
https://sourceware.org/gdb/onlinedocs/gdb/Values-From-Inferior.html

不過,這樣的pretty printer 其實有個問題的,因為它寫死了一定會去讀取self.val['d']裡面的值,但這個值未必是有初始化的,這時使用pretty printer 就會出錯:
<error reading variable: Cannot access memory at address 0xa>

其實要用pretty printer 的話,在.gdbinit 裡面加上 set print pretty on 就是個不錯的開始,光這樣就能讓一些輸出漂亮很多(其實也就是塞在一行裡跟自動換行的差別XDDD),相對開了這個print 也會佔掉比較多行數。
一般除非是客製的資料結構又有大量debug 的需求,才會需要客製化pretty printer,像是gdb 內應該已內建C++ std 的pretty printer,Qt 的開發套件應該也有提供Qt 的;寫rust時所用的rust-gdb也只是對gdb 進行擴展,在開始時先引入設定檔,裡面也有Rust 的pretty printer。
# Run GDB with the additional arguments that load the pretty printers
PYTHONPATH="$PYTHONPATH:$GDB_PYTHON_MODULE_DIRECTORY" gdb \
  -d "$GDB_PYTHON_MODULE_DIRECTORY" \
  -iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
  "$@"

但一般人似乎不太用得到這個功能,所以這篇其實是廢文一篇(蓋章,感謝大家貢獻兩分鐘的時間給小弟的blog (炸。

2016年9月5日 星期一

使用 ctags 增強vim 的功能

vim 搭配 ctags 是一款生猛的工具組,可以快速在trace 專案尋找定義和實作,大幅增加vim 瀏覽程式碼的效率。
安裝方法,首先要安裝ctags ,archlinux 的話是ctags,ubuntu 的話是exuberant-ctags,其他的就…自己找。
在專案的根目錄中使用:
ctags -R

產生tags 檔,在瀏覽原始碼的時候,就能用:
Ctrl + ] 跳到該名稱的定義
Ctrl + t 跳回到剛離開的位置

另外在搜尋的時候,找到了一個taglist 的替代品tagbar,可以使用Vundle 安裝:
https://github.com/majutsushi/tagbar
在vimrc 裡面加上:
Plugin 'majutsushi/tagbar'
map <F12> :TagbarToggle<CR>
就能用F12 開關Tagbar 的視窗,第一眼看來還不錯,比taglist 還要漂亮跟清楚很多,據說相對taglist 對Cpp 的支援也更好;雖然以個人之前的經驗,taglist 沒有想像中的好用…也可能是我不會用吧。

有關Vundle 的相關資訊,請參考之前的文章:
http://yodalee.blogspot.tw/2015/03/vundle-vim.html

其實ctags 上使用一直有個問題,導致我之前都不太使用它:一般稍大一點專案都不會是一層,而是程式碼分到樹狀的資料夾中,用ctags -R 只會在根目錄上產生tags 檔,而通常寫code 的時候都不會在根目標上作業,否則要開檔的時候光打目錄就飽了;但如此一來vim 就抓不到tags 檔了。
後來查了一下vim wiki,發現只需要在.vimrc 裡面加上一行文就可以解決這個問題……
http://vim.wikia.com/wiki/Single_tags_file_for_a_source_tree
set tags=tags;
這樣vim 就會一路往上找tags 檔。

ps. 這樣就能修掉也太詭異了吧…

2016年8月13日 星期六

新Top 介紹

top 一直是command line 上相當重要的工具,一打開就列出幾個高消耗的行程,等著kill (X,有時候不小心把電腦給炸掉的時候還滿好用的。
不過不知啥時更新後,新版的top 介面大改之後,我就不會用了OAO

最近決定把top 的用法給好好研究一遍,其實也就是把manpage 看過一遍啦,這裡做點筆記:

進到top 後,主要分成
1) Summary Area; 
2) Fields/Columns Header; 
3) Task Area

幾個沒變的命令:
h是help,q是quit沒變。
上下左右,page up/down, home, end,調位置

Summary Area 沒啥好說,顯示uptime, load average, Task 和CPU 的狀態
CPU 的狀態可用 t 來toggle顯示方式,可以關掉CPU 顯示,下面資料全部顯示,或者只顯示us+ni / sy total
用 1 來toggle 顯示全部的CPU 亦或合成一個

顯示的縮寫意思:
us, user : time running un-niced user processes
sy, system : time running kernel processes
ni, nice : time running niced user processes
id, idle : time spent in the kernel idle handler
wa, IO-wait : time waiting for I/O completion
hi : time spent servicing hardware interrupts
si : time spent servicing software interrupts
st : time stolen from this vm by the hypervisor

Memory 的狀態用 m 來選擇顯示方式 Used/Avail graph 或都純文字顯示

Field/Column 大概是跟舊版比起來變最多的,這裡可以用f 進到managing fields來設定要顯示的欄位,按f 後在想要的資訊用 space 或 d 來選擇要不要印出,用s 來設定用哪個欄位排序。

欄位基本上都有註解,我預設沒特別設定印出的欄位會是:
PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND
事實上可以印的東西很多,可以自行看manpage的介紹,不過我覺得實際上需要的其實也就預設這幾個。
其實寫這篇最主要的目的就是sort 了,因為開了top 都不知道要kill 誰了;現在自行設定用CPU 來sort,之後新的top 就像舊的top 一樣,把佔用最多CPU 的行程放在最上面,等著我們kill (X

另外也有些快捷鍵能設定排序欄位:
M: %MEM
N: PID
P: %CPU
T: TIME+

另外還有一些有趣的global command:
d: 設定更新的頻率
E/e: 設定Summary Area/Task Window記憶體的單位,從KiB 到EiB (真的有人有這麼多記憶體嗎XD)都行
g: 新的top 可以開四個不同的顯示視窗,可以有各自設定,用g 來選擇
k: 大殺四方行程,這跟原本的top 是一樣的
r: renice, 就…就是renice
L: 定位字串,如果要highlight 某個關鍵字可用,類似vim 裡面的 '/',找下一個則是 &

如果要改變畫面的顏色mapping,可以用Z 進到互動設定
用b/B toggle粗體顯示,z 設定是否彩色顯示,另外能設定各欄位的顏色:
S = Summary Data, M = Messages/Prompts,
H = Column Heads, T = Task Information
不過我是沒什麼美感的人,去改配色大概只會悲劇,所以就放著讓它用預設顏色就好。

初看新版的top ,應該是舊的top 功能不夠了,所以整個大翻修,還有許多功能本篇沒有介紹,剩下的大概都是平常用不太到的功能吧,就留給有興趣的人去研究了。

2016年8月2日 星期二

使用Facebook bot on GAE自動監控網頁更新

故事是這樣的,最近我有一位同學在申請日本留學試驗(EJU)的獎學金,最近會在網站上公佈複試的錄取名單,不過他八月又要去美洲大殺兩個星期,想託我幫他看一下複試結果什麼時候出來,出來的話跟他通知一下(說實話就算出來了在美洲是能幹嘛,還不如專心大殺四方)
不過you know,我這個人嘛,懶~~,每天上去看網頁多麻煩…要是哪天忘了看那可是賠不起呀,畢竟強者我同學成績猛高,成績都比平均高了兩個標準差,我去考大概只能考他的零頭出來QQ。
靈機一動,為啥我不找我的好友「葉闆大師」幫忙呢?心情不好的時候跟他聊聊天,他一字一句都是鼓勵的話,十足的激勵人心,檢查網頁這樣的小事能不能拜託他呢?決定就來試試看了。

概念其實很簡單,Google App Engine 本身就有cron 的設定,可以定時觸發一個function,最快每一分鐘觸發一次,到一個月一次都行;還有各種複雜的設定文件:
http://yhhuang1966.blogspot.tw/2013/03/gae-cron-job.html

首先我們先把向facebook 某user 發送訊息的函式獨立出來,再寫一個新的class 處理cron 的狀況,至於receiver 的ID (也就是我的ID) 是多少,那在之前寫bot 的時候,從log 裡面撈出來的:
CONSTANT_RECEIVER = "Fan ID Here"

class FBNotify(webapp2.RequestHandler):
    def get(self):
        logging.info("Fire periodically hello")
        send_fb_message(CONSTANT_RECEIVER, "Periodically Hello")

 app = webapp2.WSGIApplication([
     ('/webhook', FBwebhook),
     ('/fbnotify', FBNotify),
     ('/', MainPage),
 ], debug=True)

有了上面的設定,在project 中加上cron.yaml 先設定每分鐘觸發一次fbnotify get(注意雖然是 1分鐘可是要寫 minutes),時區在以分鐘為單位的狀況就不用設了,詳細請見參考資料:
cron:
- description: check eju website automatically
  url: /fbnotify
  schedule: every 1 minutes
  timezone: Asia/Taipei

用appcfg.py update之後葉闆大師就會熱情的每分鐘定期向你問好:

如果覺得它很煩,只要把cron.yaml 裡面的內容刪到剩下cron: 一行,再appcfg.py update_cron把cron 取消掉就行了。

當然這樣還不行,重點是要監看,EJU 的結果會公佈在這裡:
https://www.koryu.or.jp/taipei-tw/ez3_contents.nsf/14
方法就很正規了,把第一列的資料給拉出來,xpath 的部分先用chrome 的檢查看過,決定xpath 為:
//tr[@valign='top']/td/a[@title]/@title
受限於gae 的關係,我們只能用lxml (其實應該也可以裝 beautifulsoup 不過我有點懶),搭配urllib2把資料拉下來,取出第一列的資料若是和現在的資料不同就給我發送訊息,測試用時,就算沒變也會發一則:
URL = "https://www.koryu.or.jp/taipei-tw/ez3_contents.nsf/14"
FIRST_ROW = u"2016年度第二期日本交流協會獎學金(短期留學生)--合格發表"
res = urlfetch.fetch(self.URL)
s = res.content

root = lxml.html.fromstring(s)
firstTitle = root.xpath("//tr[@valign='top']/td/a[@title]/@title")[0]
isNew = (firstTitle != self.FIRST_ROW)
if isNew:
    send_fb_message(CONSTANT_RECEIVER, "Notification: There is new message")
else:
    send_fb_message(CONSTANT_RECEIVER, "Notification: There is no new message")
把它加到fbnotify 的get 裡面,先用cron 為1 minute 測試,確定真的會送出訊息;也可以直接開瀏覽器,造訪 xxxx.appspot.com/fbnotify 觸發檢查,看看有沒有發訊息給你:



接著就可以把cron 改為30 minutes了,安心去睡覺等葉闆大師的通知了……


才怪!

我還是超緊張的,還是會開網站檢查一下它有沒有更新,要是code 沒寫好怎麼辦,或者葉闆大師下線了呢?這個網站為啥不弄個RSS 之類的就好了Orz。老實說,寫這個花了一兩個小時,其實只要每天早上花 1 秒鐘開網頁看一下就好了,我覺得我這樣根本多此一舉。

參考文件:

GAE cron 相關文件,cron的佈署和 schedule format:
https://cloud.google.com/appengine/docs/python/config/cron
https://cloud.google.com/appengine/docs/python/config/cronref#schedule_format

其他相關文件,python built-in-libraries
https://cloud.google.com/appengine/docs/python/tools/built-in-libraries-27


ps :想靠北一下,GAE 用了兩年覺得網頁愈改愈亂,要找想要的東西都找不到,每每花一堆時間在找一些小東西。

----

按:剛剛在中午左右,已經收到葉闆大師的通知了,實驗成功,謝謝你葉闆大師XD