IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 汇编分析Swift中的String -> 正文阅读

[移动开发]汇编分析Swift中的String

?

关于String的思考?

1、1个String变量占用多少内存?

2、下面2个String变量,底层存储有什么不同?

var str1 = "0123456789"
var str2 = "0123456789ABCDEF"

意思就是这两个字符串在内存中分别是存储在哪个区域的。

? ?内存

? ?地址

? ?从低

? ?到高

代码区
常量区
全局区(数据段)
堆空间
栈空间
动态库

3、如果对String进行拼接操作,String变量的存储会发生什么变化?

str1.append("ABCDE")
str1.append("F")

str2.append("G")

4、ASCII码表:https://www.ascii-code.com/

汇编分析String

?

str1最少是有16个字节的,从8、9行就可以看出,通过MemoryLayout.stride()打印也可以看出来。

在第10行处打断点,进行打印可以获取str1变量16个字节存储的东西:

(lldb) register read rax
     rax = 0x3736353433323130
(lldb) register read rdx
     rdx = 0xea00000000003938
(lldb) 

rip的地址加上0x409f,也就是0x100003f71 +??0x409f =?0x100008010,这个就是str1的内存地址,通过以下命令可以获取该地址存储的内容:

(lldb) x/2xg 0x100008010
0x100008010: 0x3736353433323130 0xea00000000003938
(lldb) 

发现和上面打印出来的内容是相同的,所以我们从各个方面证明了str1变量存储的内容就是?0x3736353433323130 0xea00000000003938,占16个字节。

?

以上是ASCII码值表,0对应16进制是0x30,1对应0x31,对应着看str1存储的内容,可以发现是从30一直到39的,相当于字符串的内容就直接放到了str1的内存当中了。

我们换种打印方式可以看的更加清晰:

(lldb) x 0x100008010
0x100008010: 30 31 32 33 34 35 36 37 38 39 00 00 00 00 00 ea  0123456789......
0x100008020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
(lldb) 

?0xea又代表什么内容呢?

我们把9删掉:

var str1 = "012345678"

根据同样的方式获取到str1存储的内容:

(lldb) register read rax
     rax = 0x3736353433323130
(lldb) register read rdx
     rdx = 0xe900000000000038
(lldb) 

可以发现0xea变成了0xe9,所以a\9这一位是用来存储字符串长度的,0xe表示的是字符串的存储方式,a\9这一位最大是f,f的时候就是刚刚好填满15个字节,也就是最多存储15个字符。

我们可以来测试一下:

var str1 = "0123456789ABCDE"
(lldb) register read rax
     rax = 0x3736353433323130
(lldb) register read rdx
     rdx = 0xef45444342413938
(lldb) 

?再看一下ASCII码表:

发现确实是和我们的结论一致的。

小于等于15位的时候,是直接存储在字符串变量的内存中的,类似于OC的tagger pointer。

如果再多一位会发生什么事情呢?

var str2 = "0123456789ABCDEF"
(lldb) register read rax
     rax = 0xd000000000000010
(lldb) register read rdx
     rdx = 0x8000000100003f70
(lldb) 

我们可以再次尝试,从而找到规律:

var str2 = "0123456789ABCDEFFDSFSDFDSF"
(lldb) register read rax
     rax = 0xd00000000000001a
(lldb) register read rdx
     rdx = 0x8000000100003f70
(lldb) 

两次存储内容可以对比一下,其实变化很小,也可以间接证明,字符串内容不是存储在这16个字节里面的。

那到底存储在什么地方呢?

?我们再来分析汇编:

?

?

?rax和rdx是str2存储内容,rax和rdx是callq返回的内容,超过八个字节放到rdx里面,我们可以跟进callq方法里面去看一下做了哪些事情。

通过si我们可以进到Swift.String.init方法里面,第12行cmpq ? $0xf, %rsi,就是将rsi和15进行比较,rsi就是要初始化的字符串的长度,第13行jle? ? 0x7ff825d89c47,是根据比较结果进行跳转,如果小于就跳转到别的方法,其他情况就继续往下走。

第20行movabsq $0x7fffffffffffffe0, %rdx,将0x7fffffffffffffe0放到rdx中,第21行addq ? %rdx, %rdi,rdi就是字符串的真实地址,rdi加上rdx的值再放到rdx中。

现在str2中的16个字节存储的是:

(lldb) register read rdx
     rdx = 0x8000000100003f60
(lldb) register read rax
     rax = 0xd000000000000010
(lldb) 

字符串的真正内容是和0x8000000100003f60这8个字节相关的,通过汇编代码我们可以知道,

字符串的真实地址 +?0x7fffffffffffffe0 =?0x8000000100003f60,那么可以推导出

字符串的真实地址 =?0x8000000100003f60 -?0x7fffffffffffffe0

经过计算0x100003F80就是字符串的真实地址。我们打印一下这个地址存储的内容:

(lldb) x 0x100003F80
0x100003f80: 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46  0123456789ABCDEF
0x100003f90: 00 31 00 0a 00 20 00 00 e8 fd ff ff 03 00 00 00  .1... ..........
(lldb) 

发现确实是字符串的内容。

rsi和rdi是如何确定是位数和真实地址的?

再来一次汇编:

?

第5行leaq ? 0x1f1(%rip), %rdi,将0x1f1(%rip)地址值赋值给rdi,可以通过注释看出0x1f1(%rip)地址值就是字符串的真实地址值,rip +?0x1f1 =?0x100003d8f +?0x1f1 =?0x100003F80,所以现在rdi就存放这字符串的真实地址,第6行movl ? $0x10, %esi,%esi就是%rsi,存放的就是字符串的长度0x10,也就是16,接下来就是第8行的callq? 0x100003f06,调用String.init方法,rdi和rsi就是这个方法的参数。

?第20行movabsq $0x7fffffffffffffe0, %rdx,将0x7fffffffffffffe0放到rdx中,第21行addq ? %rdx, %rdi,rdi就是字符串的真实地址,rdi加上rdx的值再放到rdx中。

所以一旦字符串的内容长度超过15,就不会将字符串的内容存储在字符串变量的16个字节里面,而是放到其他地方,然后将地址值存起来,最终我们可以根据地址值找到字符串的真实内容。

那么字符串内容的真实地址值是在内存哪块区域呢?

我们可以再看一下这里的汇编代码,0x1f1(%rip)就是真实地址值,一般这样的就是在全局区,所以是有可能在全局区内存里面的,先算出真实地址值0x1f1 +?0x100003d8f =??0x100003F80。

从编码到启动APP

OC、Swift源码 -----(编译、链接)-----> Mach-O可执行文件 ------(启动)------> 内存 (内存地址从低到高,Mach-O、动态库)

代码区、全局区、常量区全部都在Mach-O里面。一般编译后的可执行文件载入内存后都会有一个偏移量,但是Mach-O文件的偏移量可以忽略,我们可以直接看Mach-O文件,看看字符串是在内存中的哪个区域。

Mach-O格式的可执行文件。

用MachOView打开Mach-O文件:

如果00 00 00 00 00 00 00 00这8个字节在Mach-O里面是00008030的偏移量,那么他在内存中的地址就是0x100000000 +??0x8030,这个就是VM Address,虚拟内存地址。

我们想找0x100003F80,实际上在Mach-O里面就是00003F80。

可以在cstring里面找到,这里就是常量区,_TEXT整个可以叫代码区,__cstring就是常量区。

如果找不到products文件夹可以通过这个方式查找:

Xcode13 新建项目 Products 目录显示方法_蓝清水的博客-CSDN博客

超过15长度的字符串变量前8个字节表示什么:

var str1 = "01234567"
var str2 = "0123456789ABCDEF"
(lldb) register read rax
     rax = 0x3736353433323130
(lldb) register read rdx
     rdx = 0xe800000000000000
(lldb) 
(lldb) register read rax
     rax = 0xd000000000000010
(lldb) register read rdx
     rdx = 0x8000000100003f60
(lldb) 

我们可以多写一些内容看一下:?

(lldb) register read rax
     rax = 0xd000000000000014
(lldb) register read rdx
     rdx = 0x8000000100003f60
(lldb) 

?可以看出rax表示的是字符串的长度。

思考题,拼接后内存会有什么变化呢?

var str1 = "01234567"
str1.append("G")

var str2 = "0123456789ABCDEF"
str2.append("G")

我们可以用汇编看一下

看str1拼接前后的变化,

(lldb) x 0x100008050
0x100008050: 30 31 32 33 34 35 36 37 47 00 00 00 00 00 00 e9  01234567G.......
0x100008060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
(lldb) 

?append后长度不超过15就还在16个字节内存储字符串内容,超过后需要开辟新的内存,并且是动态变化的。

我们看下str2的情况:

开辟堆空间的话最终都会调用malloc方法,

刚开始是放在常量区,常量区是不能进行修改的,所以append需要另外在堆空间开辟内存,堆空间地址值在str2变量的后8个字节,堆空间中的内存前32个字节是存放和对象相关的内容,之后就是字符串的真实内容了。

总结:

字符串初始化时长度小于等于15的,字符串内容直接存放在str变量的内存中;

字符串初始化时长度大于15的,字符串内容存放在__TEXT, __cstring中(常量区),字符串的地址值信息存放在str变量的后8个字节中,但是需要通过计算才能得出真实的地址值;

append后如果字符串长度小于等于15,字符串内容依然存放在str变量内存中;

?append后如果字符串长度大于15,会开辟堆空间,因为常量区是不可以进行更改的。

dyld_stub_binder

1、符号的延迟绑定通过dyld_stub_binder完成,callq指令只是调用的占位的地址,最终会通过符号绑定找到动态库里面的地址调用;

2、jmpq *0xb31(%rip)格式的汇编指令

? ? ? ? 占用6个字节

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 01:23:00  更:2022-09-04 01:26:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:27:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码