robot
最新文章(10)
Mqskit 和其它相關工具
CPython 的 GC 二、三事
寫 Mecurial Extension 是件快樂的事!
Mozilla 台灣辨公室徵人啟事
關於 Apple 的兩項專利
core dump 之前的 frame
怎麼發出 beep 聲?
先承認你要找的是奴才吧!
程式碼要清的多乾淨?
FreeBSD 的 Thread-Local Storage 實作
首頁
新編
最新留言
Entries RSS
重要關鍵字(10)
coding (122)
Python (93)
FreeBSD (71)
WEB (61)
URL (48)
hardware (46)
javascript (36)
Linux (34)
blog (30)
C++ (16)
所有關鍵字
新增 URL
真 C 之三
by thinker
2 Columns
關鍵字:
C
coding
在 linkname:[真 $C$ 語言實做...] http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/326 一文中,第二部分是將 data 和 function 包裝在一起,使之有 first class function 的感覺,或者是 partial function 的外觀。 重的觀念是, $C$ 的 function name,其實可以看成是 function 的進入點,也就是 function 第一個指令的 address 。因此,我們可以將任何 pointer casting 成一個 function pointer ,並呼叫之。只是,該位址必需要有適當的程式碼。我的計謀是,透過將 function 的程式碼複製至 data buffer 的起使位址,將 data 接在程式碼之後,並將該 data buffer casting 成 function pointer 。如此一來,當 function 在執行時,我們可以透過檢視 CPU 的 PC (program counter; IP/EIP in x86) ,算出在程式碼之後的 data 起始位址。程式碼如下: {{{#!cpp int obj_diff = 0; int add_part(void *a) { char *buf; curry_t *co; buf = *(char **)((char *)&a - sizeof(void *)) + obj_diff; co = (curry_t *)buf; partial(co, sizeof(int), a); return 0; } void *partial_call(int a) { return (void*)add_part(&a); } }}} partial_call() 的內容(程式碼),會被 copy 至 data buffer 的開頭並將之 casting 成 function pointer ,如下。 {{{#!cpp typedef (void *(*intf)(int)); char *data_buf; intf func; data_buf = (char *)malloc(.....); memcpy(data_buf, partial_call, size_of_partial_call); memcpy(data_buf + size_of_partial_call, your_data, size_of_your_data); func = (intf) data_buf; func(3); }}} 如此,當 func 被呼叫時, CPU 就會跳入 data_buf 的起始位址執行,也就是先前被 copy 至該位址的 partial_call() 的內容。而 partial_call() 只是呼叫 add_part() ,於是在 add_part() 我們透過計算參數 `a' 的位址,就能取得 return address ,也就是 partial_call() 這個 function 所在的位址,更進一步,也就是 data_buf 的位址 (partial_call() 已 copy 至這個位址)。然而,該 return address 是在 partial_call() 的中間,並不是第一個指令的位址。因此,我們必需加上一個算好的差異值 `obj_diff' ,以取得在程式碼之後的 data 起始位址。在算好 data 的位址 (buf) 之後,就能呼叫一開始的 target function (partial()),並將 data 位址 (buf) 做為第一個參數傳入。如此,我們就能造出 partial function 的外觀,將資料和 function 綁在一起。 由於 $C$ 的語法,並無法讓我們取得目前正在執行的指令的位址。於是我們透過呼叫另一個 function ,從 callee (被呼叫者) 的 return address 取得這項資訊。一旦我們取得該資訊之後,我們就能算出接在程式碼之後的資料起始位址。 在前面,我們也提到呼叫 add_part() 時,取得的 return address 並不是 buf 的起始位址。因此,我們加上一個位移 `obj_diff'。位移的算法,可透過呼叫 function 時,取得的 return address 和實際資料起始位置比對之後,計算而得。為計算該值, partial_call() 被改成: {{{#!cpp int obj_diff = 0; int set_diff(void *a) { obj_diff = (int)*(char **)((char *)(&a) - sizeof(void *)); return 0; } int (*partial_worker)(void *) = (int(*)(void *))&set_diff; void *partial_call(int a) { return (void*)partial_worker(&a); } }}} 透過 function pointer (partial_worker) 呼叫,並且改成呼叫 set_diff() 而非之前的 add_part()。 set_diff() 將 return address 的值,存入 obj_diff 。在我們呼叫 partial_call() 之後 obj_diff 就存有 return address ,於是我們能透過一個製造好的 buffer ,計算出這個位移: {{{#!cpp void partial_init(void) { void *buf; buf = malloc(partial_sz + sizeof(curry_t)); memcpy(buf, partial_call, size_of_partial_call); ((void (*)(void))buf)(); obj_diff = (int)buf + size_of_partial_call - obj_diff; partial_worker = add_part; free(buf); } }}} 我們透過製造一個 buffer (buf) ,將 partial_call() 的程式碼 copy 至該 buffer ,並 casting 成 function pointer ,加以執行。於是我們取得 return address ,並將 data 的起始位址 (buf + size_of_partial) 減去 return address ,以算出位移值。在算好位移值之後,我們就能將 partial_worker 指到 add_part() ,如此我們就能在不修改 function 的 binary code 的情況下,就從呼叫 set_diff() 改成呼叫 add_part() 。 前面 copy 程式碼時,並沒有提到程式碼的大小如何計算。我們可以亂猜,但也可以認真的計算。在 $C$ 的設計裡,在同一個檔案裡的 function ,依照前次序存放 binary code。因此,我們可以將前、後兩個 function 的 address 相減,就得到前一個 function 的大小值。這個值會稍大於實際的值,但總比亂猜好。 {{{#!cpp int foo() { ...... } int boo() { ...... } size_of_foo = (char *)boo - (char *)foo; }}} function 之所以可以這樣複製,是因為存在一個假設。我們假設一個簡單如 partial_call() 的 function ,其程式碼並不會依靠於所在位置。也就是,就算程式碼被搬動也不會出問題,這稱為 PIC (position independent code)。有些人應該知道, gcc 其實有一個 -fpic 參數。 -fpic 能確保所 compile 的 code 是屬於 PIC 。若在您的環境下,需要 -fpic 才能運作的話,請加 -fpic ,沒什麼好商量 :p 其實,這樣的 code 要考量的事情還很多。例如,有些平台並不允許在 data section 執行程式碼,這時,這本文的技巧就動不了。另外,每個 CPU 的 call convention 不太一樣,這也是必需注意的。本文純是好玩,若有人認真看待,純屬天意 :D
最後更新時間: 2008-06-20 16:22:24 CST |
引用
查詢:
COMMENTS: