在lldb调试中调用c++函数

在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没有定义,上面那个明明有着我们认为信息齐全的符号根本不能够被你这么轻易用上。

怎么办,我很想利用这个模块设计自带的打印对象信息方法来了解和加深逆向有用的信息,只好试试自己构建调用栈和修改程序计数器地址来模拟出函数的调用。在windbg中可以用这种方法,lldb中应该也没有问题,我是这么想的。

首先我逆向过它的代码,知道这个类没有多继承,只要中断这个对象的其中一个成员方法,在它没有操作栈之前,利用这个this指针(就算是多继承,这时this指针也已经在入口之前经过了正确的适配),到底选用那一个函数好呢?有两个成员函数是对象基本都会调用上的,就是构造和析构,构造中的对象不可用,那么就选在析构之前。

断点在CA::Render::Layer::~Layer()方法,

在断点触发后,构建调用栈,或者利用栈保存寄存器,操作如下

-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打印了什么信息,输出到哪里了,请看下一篇介绍。

 

原文链接: https://www.cnblogs.com/bbqzsl/p/5557231.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    在lldb调试中调用c++函数

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/234458

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午4:16
下一篇 2023年2月13日 下午4:16

相关推荐