C/C++知识点之Advanced+Apple+Debugging(10)
小标 2019-06-10 来源 : 阅读 781 评论 0

摘要:本文主要向大家介绍了C/C++知识点之Advanced+Apple+Debugging(10),通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

本文主要向大家介绍了C/C++知识点之Advanced+Apple+Debugging(10),通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

C/C++知识点之Advanced+Apple+Debugging(10)

到目前位置, 当执行JIT代码的时候(例如:Objective-C, Swift, C等等. 代码是通过你的Python脚本执行的), 你用了一小部分API去执行代码.
例如, 你使用了SBDebugger和SBCommandReturnObject的HandleCommand方法去执行脚本.SBDebugger的HandleCommand直接输出到stderr, 同时你可以控制SBCommandReturnObject的结果结束的位置. 一旦执行起来以后, 你不得不手动解析返回的输出你感兴趣的部分. 从JIT代码的输出中手动的搜索
有点难看. 没有人喜欢文字型的事情!
因此是时候介绍一下lldb Python模块中的一个新类, SBValue, 已经他如何让解析JIT代码的输出变的简单.
打开本章中starter目录下的Allocator项目.这是一个可以根据输入框中的内容动态生成类的一个简单的应用程序.


图片.png


这是一个完成了的将输入框的字符串传入NSClassFromString函数生成一个类. 如果返回了一个有效的类, 就会使用古老的init方法初始化. 否则, 就会输出一个错误.
iPhone 7 Plus模拟器上构建并运行这个应用程序. 你不需要对这个程序做任何修改, 而且你将会使用SBValue查看对象在内存里的布局, 以及通过LLDB手动的指针.
内存布局的弯路


真的非常感谢强大的SBValue类, 你将会浏览Allocator应用程序中三个唯一对象的内存布局. 你将会以一个Objective-C类为开始, 然后浏览一个没有父类的swift类, 最后浏览一个继承自NSObject的swift类.
这三个类都有下面三个属性:
• 一个叫做eyeColor的color属性
• 一个叫做firstName的字符串(String/NSString)属性.
• 一个叫做lastName的字符串(String/NSString)属性.
它们中的每一个类都用同样的初始化值. 它们是:
• eyeColor的值将是UIColor.brown或者[UIColor brownColor], 这取决语言.
• firstName的值将是"Derek"或者@"Derek"这也取决于语言.
•  lastName的值将是"Selander"或者@"Selander"这也取决于语言.


Objective-C 内存布局


你首先会浏览Objective-C类, 因为它是这些对象在内存中如何布局的基础. 跳到DSObjectiveCObject.h中然后观察一下它. 这里是给你的参考:


@interface DSObjectiveCObject : NSObject
@property (nonatomic, strong) UIColor eyeColor;
@property (nonatomic, strong) NSString firstName;
@property (nonatomic, strong) NSString *lastName;br/>@end<br <="" a="">正如前面提到的, 这里有三个属性: eyeColor, firstName以及lastName.
跳到实现文件DSObjectiveCObject.m里, 然后看一下并理解一下Objective-C对象初始化的时候做了哪些事情:


@implementation DSObjectiveCObject



(instancetype)init
{
self = [super init];
if (self) {
self.eyeColor = [UIColor brownColor];
self.firstName = @"Derek";
self.lastName = @"Selander";
}
return self;br/>}
@end<br <="" a="">并没有什么疯狂的事情. 这些属性都会别初始化为上面描述的内容.
这些代码在编译的时候, 这个Objective-C类看起来实际上像一个C结构体. 编译器会创建一个类似下面的结构体的伪代码:



struct DSObjectiveCObject {
Class isa;
UIColor eyeColor;
NSString firstName
NSString *lastName
}
注意一下这个类的作为第一个参数的isa变量. 这就是一个Objective-C 类被认为是一个Objective-C 类的背后的魔法.在对象实例的内存布局中isa总是第一个第一个值, 而且总是指向这个实例所属的类. 然后, 属性按照你再代码中实现的顺序被加到这个结构体上.
让我们通过LLDB看看这些操作. 执行下面的步骤:


确保在UIPickerView中选中了DSObjectiveCObject.
在Allocate Class按钮上点击.
一旦引用地址输出到了控制台上, 复制那个地址到你的剪贴板上.
暂停执行然后提出LLDB控制台窗口.
图片.png


一个DSObjectiveCObject的实例已经被创建了. 现在你将使用LLDB探索这个对象内容的偏移.
从控制台的输出中复制这个内存地址然后确保po这个内存地址以后给了你一个有效的引用(例如. 当打印出这个地址的时候你没有停在swift的栈帧上).
在我这里, 我得到的指针是0x600000031f80. 正如往常一样, 你的可能与我的有所不同. 通过LLDB打印出这个地址:
(lldb) po 0x600000031f80
你应该会得到下面这行期望的输出:


<DSObjectiveCObject: 0x600000031f80>
因为这个可以作为一个C结构体使用, 你将开始探索这个指针内容的偏移.
在LLDB控制台中, 输入下面的内容(用你的指针替换下面的指针):


(lldb) po (id )(0x600000031f80)
这行代码指明这个指针是id型然后解引用它. 这将会访问这个对象的isa指针.
你应该会看到这些输出:


DSObjectiveCObject
这就是这个类的描述, 跟我们期望的一样.
让我们用另外一种方式查看一下这个内存. 使用x命令(又名examine, 一个从GDB流传下来的命令)来跳到起始指针的位置, 然后po它. 输入下面内容:


(lldb) x/gx 0x600000031f80
这行命令做了下面的事情:
• 检查这一块内存(x)
• 打印出大端单词的大小, (64 位, 或者 8 字节) (g)
• 最后, 用16进制格式化它 (x).
如果, 假设, 你只想查看二进制文件中这个位置第一个字节的内容, 你可以输入x/bt 0x600000031f80替代. 这将会解释为检查 (x), 一个字节 (b) 在二进制文件中 (t). 这examine命令在浏览内存的时候是哪些好用的命令里比较好的一个.
你将会看到下面的输出(或者至少, 类似的输出, 尽管你的值与我的肯能会有不同):


0x600000031f80: 0x0000000108b06568
这些输出告诉你在内存地址0x600000031f80里包含的值是0x0000000108b06568. 很好, 这就是我得到的值!
回到我们手里的任务里去, 取到x/gx命令打印出的地址然后用po命令打印出新地址.


(lldb) po 0x0000000108b06568
再一次, 这将打印出isa类, 也就是DSObjectiveCObject这个类. 这是一个打印出isa实例的备选方法, 这也许可以让你一些更深刻的理解发生了什么事. 然而, 它用两个LLDB命令替代了一个, 因此你将会解引用这个指针而且不使用x/gx命令.
让我们更深入eyeColor属性一点. 在LLDB控制台中:


(lldb) po (id )(0x600000031f80 + 0x8)
这行指令是说"从0x600000031f80开始, 增加8字节然后获取这里的指针指向的内容." 你将会得到下面的输出:


UIExtendedSRGBColorSpace 0.6 0.4 0.2 1
怎么知道我得到的数量是8呢? 在LLDB中试一下面的命令:


(lldb) po sizeof(Class)
isa变量是Class类型. 因此通过知晓一个类有多大, 你就可以知道这个它在这个结构体总占了多大的空间, 因此你就可以知道eyeColor的偏移.


注意:当我们使用64位架构工作的时候(x64 or ARM64), 所有NSObject子类的指针都是8字节. 此外, Class这个类他自己是8字节. 这就意味着在64位架构上, 要在不同的类之间切换只需要移动8个字节!
这里有一些饿不同尺寸大小的类型, 例如int, short, bool 和其他基本类型, 而且在64位机器上编译器可能会补足预定义的8字节. 然而, 现在这里不需要担心这些, 因为DSObjectiveCObject只包含NSObject子类的指针, 沿着类对象可以拿到isa变量.
继续进行. 在LLDB中将偏移量再增加8个字节:


(lldb) po (id )(0x600000031f80 + 0x10)
现在你增加了另外8个字节, 用十六进制就是0x10(或者十进制的16).你得到的结果将是@"Derek", 这就是firstName属性的值. 再增加8位来获取lastName属性的值:


(lldb) po (id )(0x600000031f80 + 0x18)
你将会得到@"Selander". 很酷, 对吧?
让我们用一个链表图看一下你刚才做的事情:


图片.png


你从指向一个DSObjectiveCObject实例的基地址开始. 在这个例子中, 这个起始地址是0x600000031f80. 你开始解引用这个指针, 这可以给你提供isa变量, 然后你增加8个字节的偏移获取下一个Objective-C属性, 解引用偏移以后的地址, 将它映射为id型然后将它输出到控制台中.
探索内存是很有趣的而且指明了查看现象后面发生的事情的道路. 这让你更加感激SBValue类. 但是现在还不是讨论SBValue类的时候, 因为你还有两个类要浏览. 第一个就是没有父类的swift类, 第二个是继承自NSObject的swift类. 你首先会浏览那个没有父类的swift类.
没有父类的swift类的内存布局


注意: 有一件事需要提前注意: swift的API仍然在变动. 这就意味着下面的信息可能会改变在swift的ABI完成(稳定下来)之前.Xcode的新版本发布的时间可能会破坏下面的下面这一部分的内容.
到了浏览没有父类的swift类的时间了!
在Allocator项目中, 跳到ASwiftClass.swift类里然后看一下那里的代码.


class ASwiftClass {
let eyeColor = UIColor.brown
let firstName = "Derek"
let lastName = "Selander"
required init() { }
}
在这里, 你有一个等同于DSObjectiveCObject的swift风格的对象.
在一次, 你可以将swift类想象成与Objective-C的C结构体类似但是有一些有趣的不同的一个C结构体. 查看下面的伪代码:


struct ASwiftClass {
Class isa;
uint64_t refCounts;
UIColor *eyeColor;
struct _StringCore {
uintptr_t _baseAddress;
uintptr_t _countAndFlags;
uintptr_t _owner;
} firstName;
struct _StringCore {
uintptr_t _baseAddress;
uintptr_t _countAndFlags;
uintptr_t _owner;
} firstName;
}
相当有趣对吧? 你仍然有isa变量作为第一个参数. 在isa变量后面, 是一个8位的保留的refCounts变量. 这与典型的没有没有在这个位置包含这些引用计数器的Objective-C对象有点不同.注意uint64_t这种类型, 这种类型占用8字节内存--甚至在32位机器上也是这样.这不同于uintptr_t类型, 这种类型即可以是32位也可以是64位这取决于机器的硬件.
接下来是普通的UIColor.
swift的String是一个非常有趣的对象. 事实上, 一个swift的String是一个结构体内部包含ASwiftClass结构体. 当你查看内存的时候每一个swift的String都包含三个参数:
• 这是string的字符数据的实际地址.
• 接下来是长度和标志混合在一个参数里;它即可以是Unichar, 也可以是ASCII, 或者是其他我不知道如何解释的疯狂的东西.
• 最后, 是一个它的持有者的引用.
因为你是在编译时声明的这些string(用那些let声明的), 这里不需要持有者因为编译器将会只需要应用string的实际位置的偏移, 因为他们是不可改变的.
这个swiftString结构体实际上让汇编调用约定变得相当有趣. 如果你将一个String传给一个函数, 他实际上会穿进三个参数(并且使用三个寄存器)来取代一个指向包含三个参数的结构体的指针(在一个寄存器里). 不相信我?当这一章结束的自己检查一下吧!
回到LLDB中并且跳转到一个对象上.
用⌘ + K清空控制台, 然后通过LLDB或者Xcode继续运行应用程序.
你将会在ASwiftClass上做与DSObjectiveCObject上同样的事情. 使用开发者/设计者 "认可的"UIPickerView然后选择Allocator.SwiftClass. 记住, 正确的引用一个swift类(例如, NSClassFromString和其它类似的类), 你需要将模块的名字作为类名的前缀并用一个句点将两者分割开.
点击Allocate Class按钮并且复制控制台输出的内存地址.


图片.png


你将会得到一些类似下面的输出:


<Allocator.ASwiftClass: 0x61800009d830>
通常情况下, swift隐藏了description和debugDescription的指针, 但是有一些鬼祟的东西被编译进这个项目里, 你稍后就会看到.
但是现在, 抓取内存地址并且将他复制到剪切板上.
首先用LLDB确保它是有效的, po它一下:


(lldb) po 0x61800009d830
如果你得到了一些与下面的不同的输出, 你可能会有点惊讶:


<Allocator.ASwiftClass: 0x61800009d830>
尽管这是一个纯净的swift对象, 你依然能够在Objective-C环境中获取他的动态的description. 这就意味着你可以爬去他的继承树来看他的父类!


(lldb) po [0x61800009d830 superclass]
你将会得到一个有趣的类名的类:


SwiftObject
稍后你会查看这个类的更多信息.现在, 开始跳到内存里查看内存. 解引用这个地址的指针然后自己验证一下第一个参数是isa类变量:


(lldb) po (id )0x61800009d830
你将会得到Allocator.ASwiftClass. 现在检查一个引用计数器变量:


(lldb) po (id )(0x61800009d830 + 0x8)
你将会得到一些类似下面的输出:


0x0000000200000004
很明显这个地址不是一个 Objective-C 地址, 因为(id)0x0000000200000004将会指向一个类如果它是一个有效的实例/类的话.取而代之的是, 这是swift类唯一的引用计数器. 让我们看一下这些是怎么工作的.
使用LLDB手动的retain这个类:


(lldb) po [0x61800009d830 retain]
按下向上的箭头按钮再次重新执行一下之前的命令:


(lldb) po (id )(0x61800009d830 + 0x8)
你将会得到一个稍微不同的数字:


0x0000000200000008
注意最后的非常重要的十六进制值向前跳4个字节, 同时2^33 (是的, 那就是0x0000000200000000)就是同样的值. 看一下release是否减少这个引用的计数:


(lldb) po [0x61800009d830 release]
同样的, 按两下上箭头键, 然后回车.


(lldb) po (id )(0x61800009d830 + 0x8)
你将会得到一个一个让你很开心的值, 原始值:


0x0000000200000004
图片.png


这一次尝试retain这个对象两次, 然后打印出这个引用计数值:
(lldb) po [0x61800009d830 retain]
(lldb) po [0x61800009d830 retain]
(lldb) po (id )(0x61800009d830 + 0x8)
你将会得到这样一个值:


0x000000020000000c
每一次retain, 这个值都会增加4. 那看起来好像是加法, 但实际上, 它只是最低的2位没有被使用而且保留的值每次增加1. 换句话说, 每次增加0x100.
最后, release这个对象两次来平衡那两个retains:


(lldb) po [0x61800009d830 retain]
(lldb) po [0x61800009d830 retain]
现在你已经看过了isa变量和refCounts变量是时候将你的注意力放在ASwiftClass实例上那些可爱的属性上了.
清空屏幕刷新一下, 在LLDB中增加你的偏移量.


(lldb) po (id )(0x61800009d830 + 0x10)
你将会得到内核中代表UIColor’中brown色的值:


UIExtendedSRGBColorSpace 0.6 0.4 0.2 1
跳过另外8个字节然后开始浏览firstName String 结构体:


(lldb) po (id )(0x61800009d830 + 0x18)
0x0000000101f850d0
正如你在伪代码结构体上看到的, 这是swift string类起始地址的实际基地址. 本质上这个基地址可以被看做一个C char或者一个C unichar(对所有的emojis字符串很有用). 因此你所需要做的就是正确的指定它的类型. 因为swift string"Derek"
一定属于ASCII范围, 将这个地址指明为char*型来替代id:


(lldb) po (char *)(0x61800009d830 + 0x18)
"Derek"
现在看一下_countAndFlags偏移. 将你的偏移增加到0x20然后将指明的类型恢复到id然后继续浏览. id是一个很好的默认类型, 因为它可以解决他能解决的Objective-C类型, 如果它不能解析会转化为十六进制地址.


(lldb) po (id )(0x61800009d830 + 0x20)
你将会得到下面的输出:


0x0000000000000005
再一次, 这代表着flags和length. 因为"Derek"的长度是5, 在这个十六进制数的最后位置你得到的是5. 其余的0指示着这里没有应用flags(也就是说, 用unichar格式替代了char).
最后, 增加你的偏移量然后越过swift string的_owner.


(lldb) po (id )(0x61800009d830 + 0x28)
这将会提取出nil因为这个Objective-C等同于0x0000000000000000.正如先前提到的, 这里不需要"owner"因为这个字符串是在编译吃创建的.
不需要去看后面的lastName属性. 你已经知道了这是如何工作的.

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程