美文网首页技术周边swift开发知识收集
汇编窥探Swift底层(一):汇编基础

汇编窥探Swift底层(一):汇编基础

作者: 冰风v落叶 | 来源:发表于2020-02-24 19:53 被阅读0次

    汇编基础

    一、程序的本质

    1. 程序的执行过程

    如下图所示,执行软件的时候,会将软件从硬盘装载到内存中,然后由CPU控制内存读与写,同时也会控制计算机的其他设备进行响应

    软件执行过程
    2. CPU

    如下图所示,CPU包括寄存器、运算器、控制器,寄存器负责信息存储,运算器负责信息处理,控制器负责分析指令并发出相应的控制信号

    CPU

    二、寄存器和内存

    • 在软件执行过程中,CPU一般会将内存中的数据放到寄存器中,在对寄存器的数据进行运算,如下图的例子
    寄存器和内存
    • 假设内存中有块红色的内存空间存放着3,现在想,把3加1,并将结果存放到蓝色内存空间中,让我们来看看CPU是怎么做的:

      • (1) . 首先CPU会将红色的内存中的值放到寄存器RAX中,对应的汇编语句是这样的:movq 红色空间地址,%rax

      • (2) . 然后让rax寄存器与1相加,对应的汇编语句是这样的:addq $0x1,%rax

      • (3) . 最后将值赋值给蓝色内存空间,对应的汇编语句是这样的:movq %rax,蓝色内存空间的地址

        (PS:汇编代码看不懂没关系,学完下面的就会了,一会可以回过头来再看看)

    三、编程语言的发展

    • 最开始是机器语言,由0和1构成

    • 为了方便记忆,人们用符号代替了0和1,这就是汇编语言

    • 为了更接近人类自然语言,又发明了高级语言来代表符号,例如C、C++、Python、JAVA、OC、Swift、JS等等

    • 举个例子,如果我们想将寄存器bx的值放到寄存器ax中,用三种语言表示如下,是不是感觉到越来越容易阅读了呢

      • (1) . 机器语言:10001000100010001

      • (2) . 汇编语言:movw %bx,%ax

      • (3) . 高级语言:ax = bx;

    • 本例子中高级语言执行过程,如下图所示,从高级语言编译成汇编,再编译成机器语言才能被计算机所执行


      高级语言执行过程

    总结

    • 汇编语言和机器语言一一对应,从汇编语言可以编译成机器语言,从机器语言也可以反编译成汇编语言,是一对一的关系

    • 高级语言可以通过编译得到汇编语言,但是汇编语言几乎不可能反编译成高级语言,因为这是一对多的关系,多个高级语言有可能编译成同一条汇编语言。

    • 汇编语言的种类非常多,例如8086汇编、x86汇编、x64汇编等等,汇编语言大部分都非常相似,学会一种,其他的自然也能看懂了 。在iOS领域,最主要的汇编语言就两种:AT&T汇编对应着iOS模拟器ARM汇编对应着iOS真机,以下重点讲述AT&T汇编 。

    四、常见汇编指令

      1. AT&T汇编中有16个常见的寄存器rax、rbx、rcx、rdx、rsi、rdi、rbp、rsp ; r8、r9、r10、r11、r12、r13、r14、r15
      1. 寄存器是CPU内部存放数据的地方,常见的用途如下:
      • 1>. rax、rdx寄存器中经常存放函数的返回值

      • 2>. rdi、rsi、rdx、rcx、r8、r9等寄存器经常用于存放函数的参数

      • 3>. rsp、rbp经常用于栈操作

      • 4>. rip一般存放指令指针,存储着CPU下一条要执行的指令的地址

      1. 下图是两种汇编语言的常用指令,格式非常相似,大家学会一种,另一种自然也就懂了,下面我来详细解释一下这张图的意思
    常见汇编指令
    • 1>. 第一行是寄存器名称的命名规范,在AT&T中需要加上%,在Intel中不需要加,rax就代表寄存器的名称

    • 2>. 第二行是操作顺序,AT&T中,是将左边的rax中的值赋值给右边rdx中;Intel中,是将右边的值赋值给左边,两者顺序不同而已。

    • 3>. 第三行是常数的赋值,将3赋值给寄存器,AT&T是把3放到左边,Intel是把3放到右边

    • 4>. 第四行是movq $0xa,0x1ff7(%rip),将0xa赋值给地址为rip+0x1ff7的内存空间中,在AT&T中,内存地址的加法是用0x1ff7(%rip)表示,意思是将rip中存放的内存地址与0x1ff7相加;在Intel中的写法为[rip+ox1ff7]

    • 5>. 第五行leqp -0x18(%rbp),%rax,是取内存地址,将rbp寄存器中的地址减去0x18后得到的值,赋值给rax寄存器中

    • 6>. 第六行jmp是跳转指令,CPU会直接跳转到目标地址去执行

    • 7>. 大家应该发现了AT&T汇编总比Intel汇编多一位,例如:AT&T是movl,Intel就是mov, AT&T汇编的最后一位代表操作数,意思是要操作多少个字节的空间。

      • 例如movl $3,%rax,这句汇编的最后一位l,是long的缩写,代表操作数的长度是4个字节,要操作4字节的空间,将来要存放3的时候,CPU就分配4个字节的寄存器空间用来存放3

    注意: movq和leaq的区别:

    • movq 是根据内存地址找出数据后,再将数据赋值给寄存器。

      • 例如:mov -0x18(%rbp),%rax,就是拿rbp寄存器存的地址减去 0x18 得到的地址,再从这个地址取出具体数据,存放到rax寄存器中
    • leaq 是将算出来的地址值,直接给寄存器。

      • 例如:leaq -0x18(%rbp),%rax,就是将rbp寄存器存的地址减去0x18得到的地址,直接存放到rax寄存器中

    注意: jmp和call的区别:

    • jmp跳到某个地址后,中间的指令就不执行了。

      • 例如:刚开始执行在0x00000001处,执行jmp 0x00000008后,就直接跳转到目标指令了,中间的指令就跳过不执行了
    • call指令一般都是执行某个函数,经常与ret指令配合使用,会在函数执行完后返回到函数调用处

      • 例如:call *%rax 调用寄存器rax中存的地址(*代表间接调用,函数地址可能是动态的),调用结束后,会返回到调用函数的这一行指令

    五、寄存器

      1. 寄存器是用来存放数据的,肯定需要存储空间才行,寄存器的大小和CPU有关,你是32位的CPU,那么你的寄存器也都是32位的
      1. 寄存器也是逐渐发展的,从开始的1个字节的寄存器,慢慢发展为2个字节的,4个字节的,8个字节的。

        • 为了兼容以前的寄存器,8个字节的寄存器会分出来4个字节当做4个字节的寄存器,如下图,为了兼容2个字节的寄存器,4个字节的寄存器又拿出最低的2个字节作为2个字节的寄存器,这样做,8位的寄存器,就能兼容之前的4个字节的寄存器、2个字节的寄存器、1个字节的寄存器了。


          寄存器
      1. 在AT&T汇编中:
      • r开头的寄存器都是占8个字节,也就是这个寄存器可以存储8个字节的数据,例如rax、rbx、rcx等寄存器;

      • 以e开头的寄存器占4个字节,也就是这个寄存器可以存储4个字节的数据;

      • ax、bx、cx寄存器占2个字节,也就是这个寄存器可以存储2个字节的数据;

      • ah、al、bh、bl寄存器占用1个字节,也就是这个寄存器可以存储1个字节的数据;

      1. 我们来看两句汇编代码巩固一下以上知识
      • 1>.movq $0xa,%rax,这句汇编的意思是将16进制0xa(也就是十进制的10)存放到rax寄存器中,操作数是q,代表操作 8 个字节的空间,所以寄存器rax中的数据就应该是0xa 0x0 0x0 0x0 0x0 0x0 0x0 0x0,如下图所示,由于rax、eax、ax、ah等寄存器共用一段存储空间,所以表面是你修改了rax寄存器,实际上eax、ax、ah等寄存器的值全都变成了0x0了
        movq $0xa,%rax
    • 2>.movl $0xa,%eax,这句汇编的意思是将16进制0xa存放到eax寄存器中,操作数是l,代表操作 4 个字节的空间,所以寄存器eax中的数据就应该是0xa 0x0 0x0 0x0,由于与rax寄存器共用存储空间,表面是是修改了eax寄存器,实际上rax寄存器也被修改了
      movl $0xa,%eax.png

    六、AT&T汇编语言的常用技巧(这些技巧在以后分析汇编的时候会经常用到,建议熟记)

    - 1. 0x712a(%rip) 一般都是全局变量,全局区 (这句汇编的意思是:rip寄存器的值 + 0x712a)
    - 2. -0x10(%rbp) 一般都是局部变量,栈空间 (这句汇编的意思是:rbp寄存器的值 - 0x10)
    - 3. 0x10(%rax)一般是堆空间 (这句汇编的意思是:rax寄存器的值 + 0x10)
    - 4. rax、rdx寄存器一般存储函数返回值
    - 6. rdi、rsi、rdx、rcx、r8、r9等寄存器常用来存放函数参数
    - 7. rsp、rbp常用于存放栈操作
    - 8. rip作为指令指针,存储着CPU下一条要执行的指令的地址,一旦CPU读取一条指令,rip会自动指向下一条指令

    七、LLDB常用调试命令(加粗标红的命令以后会经常用,建议熟记)

      1. 读取某个寄存器的值:register read/格式,例如: register read/x rax
      1. 修改某个寄存器的值:register write 寄存器名称 数值,例如:register write rax 0
      1. 读取某个内存地址中的值x/数量-格式-字节大小 内存地址,例如: x/3xw 0x0100010 ,这句命令的中的第一个x代表读取内存,3代表3组,第二个x代表以16进制的格式,w代表4个字节,所以意思就是从0x0100010地址开始,以16进制的格式输出3组4个字节的内存数据,也就是以16进制的格式输出12个字节的数据。
      1. 修改内存中的值:memory write 内存地址 数值,例如:memory write 0x100010
      1. 上述命令中的的格式补充 :x代表16进制,f是浮点,d是十进制

        上述命令中字节大小补充:b - byte - 1字节,h - half word - 2字节,w - word - 4字节,g - giant word - 8字节

      1. 源码单步运行,子函数当做一个整体:next或者n
      1. 源码单步运行,遇到子函数会进入子函数:step或者s
      1. 汇编单步运行,子函数当做一个整体:nexti或者ni
      1. 汇编单步运行,遇到子函数会进入子函数:stepi或者si
      1. 直接执行完当前函数所有代码,返回函数调用处:finish
      1. 打印函数调用栈:bt

    八、内存常用知识

      1. iOS领域,内存地址从低到高分为:代码区、常量区、全局区(数据段)、堆空间、栈空间、动态库
      1. 我们平常写的代码、函数都是不占用内存的,放在代码区
      1. 在程序运行过程中是不能往常量区放东西的,全局区是允许的
    • 4.基于引用计数的ARC内存管理是针对堆空间的,代码区、常量区、全局区、栈空间的内存不需要我们管理

      1. iOS平台中内存是以字节为单位的分配的;malloc分配的堆空间的的内容是以16个字节为单位的,也就是堆空间最少会分配16个字节的空间。
      1. MacO可执行文件的虚拟内存地址是0x10000000,在MacO文件里找地址需要用真实地址减去虚拟内存的地址,才是MacO文件里的地址

    相关文章

      网友评论

        本文标题:汇编窥探Swift底层(一):汇编基础

        本文链接:https://www.haomeiwen.com/subject/bpwhrctx.html