指针偏移
普通内存读取
![](https://img.haomeiwen.com/i3292520/a8a30f037d995e3b.png)
![](https://img.haomeiwen.com/i3292520/067cec1047ae4b5a.png)
分析:
- a和b的值都指向了10 ,但是地址不一样,这就是所谓的值拷贝 属于浅拷贝
- a和b的地址之间相差4个字节,取决于a、和b 的类型
对象内存读取
![](https://img.haomeiwen.com/i3292520/9d973c5746004156.png)
![](https://img.haomeiwen.com/i3292520/882759051f8d5614.png)
分析:
- person 、person2 为指针 分别指向两次[LGPerson alloc]开辟的内存地址
- & person、& person2 分别指向 person 、person2 的指针地址 、为二级指针
数组 通过指针偏移拿到数据
int a[4] = {1,2,3,4};
int *p = a;
NSLog(@"%p - %p - %p - %p", &a, &a[0], &a[1],&a[2]);
NSLog(@"%p -- %p - %p - %p", p, p+1, p+2 , p+3);
打印
![](https://img.haomeiwen.com/i3292520/88d63b98744c0dfc.png)
分析:
- &a 和 & &a[0] 地址一样。 数组名的地址拿的 元素首地址
- 元素之间相差4字节,取决于元素类型
- 可以通过首地址+偏移量 取出其他元素,偏移量为数组下标
源码分析 objc781
搜索 objc_class : 发现有两份
objc-runtime-old.h(已经废弃不研究)
/// objc-runtime-old.h
struct objc_class : objc_object {
Class superclass;
const char *name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
struct old_ivar_list *ivars;
struct old_method_list **methodLists;
Cache cache;
struct old_protocol_list *protocols;
// CLS_EXT only
const uint8_t *ivar_layout;
struct old_class_ext *ext;
...... 后面省略
objc-runtime-new.h 主要分析bits
/// objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
...... 后面省略
计算cache在 objc_class结构体的位置
- isa指针 8字节 (objc_class 继承自 objc_object 所以 他派生下来有isa)
- cache 分析看源码 cache_t是什么(去掉 static修饰的属性,已经方法 不参与计算 太长了下面只贴需要计算的)
#if defined(__arm64__) && __LP64__/// 64 真机
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__///非64位的真机
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else ///模拟器或者macOS
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
·····省略了static修饰的属性 及 方法
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; /// 结构体指针类型 8
explicit_atomic<mask_t> _mask;/// mask_t uint32_t int 类型 4
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;/// uintptr_t u unsigned long 8
mask_t _mask_unused; /// mask_t 4
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets; uintptr_t long 8
mask_t _mask_unused; mask_t 4
#if __LP64__
uint16_t _flags; ///uint 16_t short 2
#endif
uint16_t _occupied; ///2
当前环境为 CACHE_MASK_STORAGE_OUTLINED
_buckets
:结构体指针类型所以8
字节
_mask
: uint32_t
int 类型 4
字节
_flags
: 2
字节
_occupied
:2
字节
所以 cache
:占用 16
字节
- 通过上面计算 我们只需要 将 objc_class 首地址 平移 8+8+16+32 的位置 就可以拿到bits内存地址 下面我们在源码环境下 lldb进行调试
源码环境下 lldb进行调试 读取 class_rw_t信息
准备一段代码
@interface LGPerson : NSObject
@property(nonatomic,copy)NSString * name;
@property(nonatomic,copy) NSString * sex;
-(void)sayHello;
+(void)sayHappy;
@end
@implementation LGPerson
-(void)sayHello{
NSLog(@"sayHello");
}
+(void)sayHappy{
NSLog(@"sayHappy");
}
断言在此
![](https://img.haomeiwen.com/i3292520/7e91037a0430a45b.png)
![](https://img.haomeiwen.com/i3292520/429ce1e0acab93ac.jpg)
疑问:我们看到 实例方法 在 类对象里全部打印出来 那么 类方法呢?在元类里吗?快去试试吧
![](https://img.haomeiwen.com/i3292520/75ae0cade0d003e2.jpg)
struct class_rw_t
struct class_rw_t {
.......前面已省略
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>()->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
};
上面我们 我们知道了方法的归属 那么 属性 及成员变量 又再哪里呢?
我们看到了
首先继续上面在元类的环境中的调试 const property_array_t properties()
![](https://img.haomeiwen.com/i3292520/0daf4d5940c0a3b7.jpg)
在元类的环境下我们并未发现什么线索 但是我猜 属性一定在 properties() 里面 那 成员变量呢?为了更好的验证 我在继续添加两个成员变量 回到类对象 继续探索
@interface LGPerson : NSObject
{
int _age;
NSString * _hobby;
}
@property(nonatomic,copy)NSString * name;
@property(nonatomic,copy) NSString * sex;
-(void)sayHello;
+(void)sayHappy;
重新断言 调试
![](https://img.haomeiwen.com/i3292520/6ff71251094926e9.jpg)
struct class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
看到这里我们也知道了lldb的强大
LLDB命令(来自ios应用逆向与安全)
-
po
: 是 “primobject”的缩写,用于打印OC对象。在这里直接打印了该0C类的类名。如
果程序重载了 description方法,这里打印的就是description返回的值。
x/s
: x/s $x1 通过 x/s $x1命令将寄存器x1处的内存以字符串的形式显示出来,其对应 的就是调用的方法名 -
bt
:通过动态调试还可以获取程序调用的堆栈信息 -
registerread
:读取所有寄存器的值。 -
registerread $x0
: 读取某个寄存器的值。 -
registerwriteSx5
1:修改某个寄存器的值。 -
si
:跳到当前指令的内部。 -
ni
:跳过当前指令。 -
finish
:返回上层调用栈。 -
threadreturn
:不再执行下面的代码,直接从当前调用栈返回一个值。 -
brlist
:査看当前断点列表。 -
brdel
:删除当前的所有断点。 -
brdel 1.1.1
:删除指定编号的断点。 -
brdis 2.1
:使断点2.1失效。 -
brenable2.1
:使断点2.1 生效。 -
watchpointsetexpression-wwrite―0xl01801a48
:给某个地址设置观察断点,当对该地址的内存进行写操作时就会触发断点。 -
x/10xg 0xl01801a48
:读取目标地址的内存指令。这里的 “x”代表用十六进制来显结 果,“g”代表giantword(8字节)大小。所以,“x/10xg”就是用十六进制显7K0x101801a48
个64位的元素内容。常见的大小格式为“b-byte”( 1字节)“h-halfworrl”
所指空间的 10(2字节)“w- word”(4字节)“g-giantword”(8字节)。 -
dis-a$pc
:反汇编指定地址。这里是pc寄存器所对应的地址。 -
f2
:切换到当前调用栈为2的位置,也就是bt中的frame #2。 -
threadinfo
:输出当前线程的信息。 -
bptrace-cxxx
:满足某个条件之后程序才会中断。
LLDB还有很多命令,我们不可能全部记下来,但是要记住其中两个查找命令,即help和appropos。直接在LLDB命令交互模式下输入“help”,就可以查看所有的LLDB命令。如果想查看某个命令的详细用法,可以使用“help命令名”的形式。例如看看thread命令,
(lldb) help thread
(lldb) help thread
Commands for operating on one or more threads in the current process.
Syntax: thread
The following subcommands are supported:
....
如果没有记住某个命令,只记得其中的关键字,或者想根据关键字去査找相关的LLDB命
可以使用apropos来搜索相关的命令信息。搜索“watch”的相关信息,具体如下
(lldb) apropos watch
The following commands may relate to 'watch':
watchpoint
watchpoint command executed when
watchpoint command add whenever the
--Commands for operating on watchpoints.
Commands for adding, removing and examining LLDB commands
the watchpoint is hit (watchpoint 'commmands').
Add a set of LLDB commands to a watchpoint, to be executed
watchpoint is hit.
watchpoint command delete -- Delete the set of commands from a watchpoint.
....
网友评论