在lldb調試時,調用oc對象的方法不足爲奇,由於msgSend是有原型導出的,oc對象的方法都運行期綁定的,綁定信息都在objc_class中。只要在調試中[receiver sel]之類,lldb就自動完成的整個由SEL經過msgSend路由到receiver的IMP方法並執行的整個過程。可是要調用c++函數則沒有這麼方便,雖然c++函數(包括成員函數和非成員函數)的連接符號有着函數原型的詳細信息,但卻不包括類的定義和名字空間的定義,即便lldb翻譯出這樣一個符號(symbol)QuartzCore`CA::Render::Layer::show(unsigned int, unsigned int),在咱們看來就是一個函數原型,可是調試器不這麼認爲,咱們沒有辦法直接使用上面的符號,除非咱們有其餘人發佈的模塊的符號文件,這是不可能的。例如你要調用這個show方法時,你們都會想到將對象指定爲CA::Render::Layer*再引用show方法加上參數就搞定。事實上,lldb調試器會抱怨CA名字或結構沒有定義,CA::Render沒有定義,CA::Render::Layer沒有定義,上面那個明明有着咱們認爲信息齊全的符號根本不可以被你這麼輕易用上。python
怎麼辦,我很想利用這個模塊設計自帶的打印對象信息方法來了解和加深逆向有用的信息,只好試試本身構建調用棧和修改程序計數器地址來模擬出函數的調用。在windbg中能夠用這種方法,lldb中應該也沒有問題,我是這麼想的。c++
首先我逆向過它的代碼,知道這個類沒有多繼承,只要中斷這個對象的其中一個成員方法,在它沒有操做棧以前,利用這個this指針(就算是多繼承,這時this指針也已經在入口以前通過了正確的適配),到底選用那一個函數好呢?有兩個成員函數是對象基本都會調用上的,就是構造和析構,構造中的對象不可用,那麼就選在析構以前。框架
斷點在CA::Render::Layer::~Layer()方法,函數
在斷點觸發後,構建調用棧,或者利用棧保存寄存器,操做以下this
-0x20(%rsp) <-- %rip ; 返回地址,模擬call指令線程
-0x18(%rsp) <-- %rdx ; 模擬push %rdx翻譯
-0x10(%rsp) <-- %rsi ; 模擬push %rsi設計
-0x8(%rsp) <-- %rdi ; 模擬push %rdi指針
%rsp <-- %rsp - 0x20 ; 讓棧頂指向構建出來的調用棧調試
%rip <-- &CA::Render::Layer::show ; 模擬jmp
而後執行單步指令step into,使被中斷的線程執行CA::Render::Layer::show方法,使用的是CA::Render::Layer::~Layer()方法入口處的this指針。
我將CA::Render::Layer::~Layer()的入口地址,寫入到了跳轉前棧頂,線程從CA::Render::Layer::show返回就會再次進入個人斷點方法,這時再將%rdi,%rsi,%rdx還原,%rsp還原,%rsp <-- %rsp + 0x18,線程在show函數retq時已經將返回地址出棧。讓線程繼續到本來的軌道上運行。
大至調用的框架就麼定下來了。
這可以成功嗎?先不要着急,我先來確認一下修改%rip跳轉是否是有效。不試不知道,一試嚇一跳。修改%rip後,鍵入s指令到lldb控制檯執行,lldb控制檯失去了交互控制沒有返回,線程也沒有執行。只好用XCode的調試單步快捷鍵F6,XCode的單步F6被執行了,lldb控制檯再次返回可交互狀態,線程進入了CA::Render::Layer::show函數內的第二條指令,也就是說,調試器執行了兩次step into指令(第一次是lldb控制檯的s指令,第二次是XCode的單步F6)。而後step out 或者叫finish,函數返回到了調用CA::Render::Layer::~Layer()處的下一條指令。運行和預計的同樣,由於我沒有構建到調用棧,this指針也被正確使用。我作的行爲,讓本來要執行的CA::Render::Layer::~Layer()跳轉到了CA::Render::Layer::show,天然本來的~Layer()被忽略掉了。這裏要注意的成員方法默認是調用者平衡棧,析構函數沒有參數,返回處天然就不用平衡棧,雖然CA::Render::Layer::show有兩個參數,可是CA::Render::Layer::show本身是不會去平衡棧的,因此棧的平衡沒有問題。
不過問題也出來了,直接修改%rip,lldb控制檯會工做不正常。出於嚴謹的態度,我試了一下,我又在~Layer()入口處作了另外一次%rip的修改,不過跳轉的地址是在~Layer()的結束處,也就是函數內跳轉,結果就沒有影響到lldb控制檯的工做。可是跨函數的跳轉就不如人意。
無論怎麼樣,計劃繼續實施,此次就加上構建的調用棧。此次又嚇了一跳,當我修改完%rsp後,在lldb控制檯鍵入指令修改%rip指向到CA::Render::Layer::show之時,控制檯再一次失去了交互。仍是上面的方法使用XCode的F6單步一次,控制檯再次能夠交互,線程進入了CA::Render::Layer::show入口。但以後運行就有問題,並不是在我構建的調用棧平衡之處,而是CA::Render::Layer::show體內調用的其它函數對棧操做拋出BAD_ACCESS。從上面的是狀況來看直接修改%rsp或%rip,調試器運行有問題,並且在python腳本中,用lldb指令訪問寄存器的操做不可取,緣由請看我上一篇寫到踩過的坑。
因些經過構建調用棧的方法不合適,可是我十分想使用模塊中自有的功能方法,因爲是模擬器,用的x64處理器,調用約定優先使用寄存器,C++成員函數調用參數約定與非成員函數調用參數約定能夠看起同樣,(參數約定都是同一順序,然而32位處理器編譯出來第一個參數都是棧,this指針使用寄存器,兩種函數之間就有差別)。那麼就將計就計,將成員函數攤平爲非成員函數,同時lldb支持自定義函數類型,(在vc或windbg中,是不能夠在調試器中定義一個函數類型,來轉換入口地址的類型的)。因而能夠將CA::Render::Layer::show(unsigned int, unsigned int)的映像地址,轉換成void(*)(void*, int, int)類型就能夠在CA::Render::Layer任何一個成員函數中,經過lldb的表達式或打印等指令去調用CA::Render::Layer::show方法,利用蘋果自身的模塊自帶的功能打印出信息。這種方法在python腳本使用沒有問題,做斷點命令使用就能夠打印出建立過的對象的信息了。再次聲明不要在python腳本中使用HandleCommand來訪問寄存器,問題的討論在上一篇,有興趣請看。
最後是CA::Render::Layer::show打印了什麼信息,輸出到哪裏了,請看下一篇介紹。