01-汇编初学

01-汇编初学

0、前言

对于一个iOS App来说,它其实就是一个安装在手机中的可执行文件,这个可执行文件本质上是二进制文件,它由iPhone手机上的CPU执行。如果我们需要对操作系统、App进行深入了解,以及App的逆向都需要我们熟悉汇编语言

1、汇编语言的发展和简介

1.1 机器语言

  • 机器语言是由0、1组成的机器指令,比如:

    • 加:0100 0000
    • 减:0100 1000
  • 计算机间机器指令转变为一列高低电平,以使计算机的电子器受到驱动,进行运算

1.2 汇编语言

  • 汇编语言使用助记符代替机器语言,也就是我们常说的汇编指令,例如

    • 加:INC EAX通过编译器 0100 0000
    • 减:DEC EAX通过编译器 0100 1000
  • 汇编指令,是机器指令便于记忆的书写格式,它通过我们的编译器编译后转化成1010的机器指令

1.3 高级语言

  • 高级语言是更接近人类自然语言的编程语言,例如C/C++/Objective-C/Swfit等

    • 加:A+B通过编译器0100 0000
    • 减:A-B通过编译器0100 1000
  • 高级语言编写的程序通过编译器先转为汇编语言,然后在转为机器语言,最后运行在我们的设备上(由CPU执行)

小结1

  • 由高级语言编写的代码的运行过程

  • 汇编语言与机器语言一一对应,每一条机器指令都有与之对应的汇编指令(注意:汇编指令与CPU架构有关系,不同的CPU架构模式下汇编指令是不一样的,一般不通用,举个例子:我们的iPhone模拟器的CPU架构是x86,iPhone6是ARM64,汇编指令会有区别

  • 汇编语言可以通过编译得到机器语言,机器语言可以通过反汇编得到汇编语言

  • 高级语言可以通过编译得到汇编语言 \ 机器语言,但汇编语言 \ 机器语言几乎不可能还原成高级语言

1.4、汇编语言的特点

  • 可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能
  • 能够不受编译器的限制,对生成的二进制代码进行完全的控制
  • 目标代码简短,占用内存少,执行速度
  • 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集\汇编指令集,所以汇编语言不具备可移植性
  • 知识点过多,开发者需要对CPU等硬件结构有所了解,不易于编写、调试、维护
  • 不区分大小写,比如mov和MOV是一样的

1.5 汇编语言的种类

  • 目前讨论比较多的汇编语言有

    • 8086汇编(8086处理器是16bit的CPU)
    • Win32汇编
    • Win64汇编
    • ARM汇编(嵌入式、Mac、iOS)
    • ......
  • 我们iPhone里面用到的是ARM汇编,但是不同的设备也有差异.因CPU的架构不同

    CPU架构iPhone设备armv6iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Toucharmv7iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4armv7siPhone5, iPhone5C, iPad4(iPad with Retina Display)arm64iPhone5S 以后至 iPhoneX , iPad Air, iPad mini2以后

1.6 学习汇编的用途

  • 编写驱动程序操作系统(比如Linux内核的某些关键部分)

  • 性能要求极高的程序或者代码片段,可与高级语言混合使用(内联汇编)

  • 软件安全

    • 病毒分析与防治
    • 逆向\加壳\脱壳\破解\外挂\免杀\加密解密\漏洞\黑客
  • 理解整个计算机系统的最佳起点和最有效途径

  • 为编写高效代码打下基础

  • 弄清代码的本质
    • 函数的本质究竟是什么?
    • ++a + ++a + ++a 底层如何执行的?
    • 编译器到底帮我们干了什么?
    • DEBUG模式和RELEASE模式有什么关键的地方被我们忽略
    • ......

小结2

  • 作为一个普通iOS开发者,学习汇编可以熟悉软件、程序的执行过程

  • 了解操作系统,了解CPU/内存等硬件

  • iOS逆向的基础

2、CPU与内存

在汇编中,大部分指令都是和CPU与内存相关的,因此学习汇编指令之前,我们需要了解CPU与内存的知识,不然无法理解汇编指令

2.1 了解CPU

  • 每一个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互

  • 总线:一根根导线的集合, 总线分为:地址总线, 数据总线, 控制总线

  • CPU对内存的操控简单示例

2.2 了解总线

  • 地址总线

    • CPU是通过地址总线来指定存储器单元(要访问的内存地址)
    • 一个CPU有N根地址线,CPU地址总线的宽度为N, 最多可以寻找2的N次方个内存单元(单位:字节)
    • 地址总线的宽度决定了CPU的 寻址能力
    • 举例: 8086的地址总线宽度是 20 ,所以寻址能力是 1M( 2^10=1024, 2^20=1024*1024, 基本单位是Byte 字节)

寻址能力说明

直观举例:8086的寻址能力是1M,如果内存大小是2M,那么剩余的1M内存CPU无法使用,或者说无法访问到。

  • 数据总线

    • CPU与内存或其他器件之间的数据传送通过数据总线来进行的
    • 数据总线的宽度决定了CPU的单次数据传送量,也就是数据 传送速度
    • 一个数据传输示例:

CPU传输数据说明

一根数据总线,传递一个bit的数据

  • 控制总线

    • 它的宽度决定了CPU对其他器件的 控制能力、能有多少种控制(当前对这块知道的比较少)

2.3 CPU小结练习

  • 一个CPU 的寻址能力为8KB,那么它的地址总线的宽度为____
  • 8080,8088,80286,80386 的地址总线宽度分别为16根,20根,24根,32根.那么他们的寻址能力分别为多少____KB, ____MB,____MB,____GB?
  • 8080,8088,8086,80286,80386 的数据总线宽度分别为8根,8根,16根,16根,32根.那么它们一次可以传输的数据为:____B,____B,____B,____B,____B
  • 从内存中读取1024字节的数据,8086至少要读____次,80386至少要读取____次.
1: 1024=2^10, 8KB = 1024*8 = 2^13, 所以:132:8080: 2^16=2^6*1024=64KB, 8088: 2^20=1024*1024=1MB, 80286: 2^24=16MB, 80386: 2^32=1024*1024*1024*4=4GB3: 8080: 8bit=1Byte,  8088:8bit=1Byte, 8086:2B, 80286:2B, 80386:4B4: 8086: 1024/2 = 512(至少), 80386: 1024/4=256(至少)

2.4 了解内存

  • 各类存储区的逻辑连接

  • 各类存储器的逻辑连接-物理地址对应

  • 内存分类

      1. 随机存储器RAM;随机存储器可读可写,但必须带电存储,关机后存储的内容丢失
      1. 只读存储器ROM;只能读取不能写入,关机后其中的内容不丢失
  • 各类存储器的物理地址情况(8086)

    • 上图中:内存地址空间的大小受CPU地址总线宽度的限制。8086的地址总线宽度为20,可以定位2^20个不同的内存单元(内存地址范围0x00000~0xFFFFF),所以8086的内存空间大小为1MB
    • 0x00000~0x9FFFF:主存储器。可读可写
    • 0xA0000~0xBFFFF:向显存中写入数据,这些数据会被显卡输出到显示器。可读可写
    • 0xC0000~0xFFFFF:存储各种硬件\系统信息。只读

2.5 内存小结

  • CPU的地址总线的宽度,决定了电脑能够使用的内存大小
  • 对于CPU来说,外接的内存设备其实有一个地址的范围的,对某个地址范围内进行操作,也就是对某个设备的操作。(参照内存图2)

3、进制的深入理解

3.1 进制的定义

  • 八进制由8个符号组成: 0 1 2 3 4 5 6 7 逢八进一
  • 十进制由10个符号组成: 0 1 2 3 4 5 6 7 8 9逢十进一
  • N进制就是由N个符号组成: 逢N进一

3.2 进制的计算

通常我们进行进制计算总以十进制为依托去考虑其他进制,需要运算的时候也总是先转换成十进制,但是,其实按照十进制的加法表和乘法表,我们可以写出其他进制的加法表和乘法表,然后按照表进行加减乘除,可以准确得出结果,而不必依赖十进制(像我们做十进制的加减乘除一样)

做个练习
  • 八进制运算( 根据下面的加法表和乘法表去进行
    • 2 + 3 = __ , 2 * 3 = __ ,4 + 5 = __ ,4 * 5 = __.
    • 277 + 333 = __ , 276 * 54 = __ , 237 - 54 = __ , 234 / 4 = __ .
八进制数和加法表
 0  1  2  3  4  5  6  7 10 11 12 13 14 15 16 1720 21 22 23 24 25 26 27...1+1 = 21+2 = 3   2+2 = 41+3 = 4   2+3 = 5   3+3 = 61+4 = 5   2+4 = 6   3+4 = 7   4+4 = 10  1+5 = 6   2+5 = 7   3+5 = 10  4+5 = 11  5+5 = 121+6 = 7   2+6 = 10  3+6 = 11  4+6 = 12  5+6 = 13  6+6 = 141+7 = 10  2+7 = 11  3+7 = 12  4+7 = 13  5+7 = 14  6+7 = 15  7+7 = 16
八进制乘法表
0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27...1*1 = 11*2 = 2  2*2 = 41*3 = 3  2*3 = 63*3 = 111*4 = 4  2*4 = 103*4 = 14  4*4 = 201*5 = 5  2*5 = 123*5 = 17  4*5 = 24  5*5 = 311*6 = 6  2*6 = 143*6 = 22  4*6 = 30  5*6 = 36  6*6 = 441*7 = 7  2*7 = 163*7 = 25  4*7 = 34  5*7 = 43  6*7 = 52  7*7 = 61

3.3 二进制的简写形式

       二进制: 1 0 1 1 1 0 1 1 1 1 0 0三个二进制一组: 101 110 111 100       八进制:   5   6   7   4四个二进制一组: 1011 1011 1100     十六进制:    b    b    c二进制:从0 写到 11110000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 这种二进制使用起来太麻烦,改成更简单一点的符号:0 1 2 3 4 5 6 7 8 9 A B C D E F 这就是十六进制了

3.4 自己定义进制符号

思考1 + 1 在____情况下等于 3?

如果我们这样定义十进制的10个符号组成: 0 1 3 2 8 A B E S 7 逢十进一

那么这个时候,1 + 1 = 3!就对了!

这样的目的何在

传统我们定义的十进制和自定义的十进制不一样.那么这10个符号如果我们不告诉别人这个符号表,别人是没办法拿到我们的具体数据的!用于加密!

十进制由十个符号组成,逢十进一,符号是可以自定义的!!

练习

  • 现在有10进制数 10个符号分别是:2,9,1,7,6,5,4, 8,3 , A 逢10进1 那么: 123 + 234 = ____(1A6)
十进制:    0  1  2  3  4  5  6  7  8  9自定义:    2  9  1  7  6  5  4  8  3  A         92 99 91 97 96 95 94 98 93 9A         12 19 11 17 16 15 14 18 13 1A         72 79 71 77 76 75 74 78 73 7A         62 69 61 67 66 65 64 68 63 6A         52 59 51 57 56 55 54 58 53 5A         42 49 41 47 46 45 44 48 43 4A         82 89 81 87 86 85 84 88 83 8A         32 39 31 37 36 35 34 38 33 3A         922

那么刚才通过10进制运算可以转化10进制然后查表!但是如果是其他进制.我们就不能转换,要直接学会查表

3.5 进制小结

  • 进制可以用于数据间的计算
  • 进制可以用于表示地址值
  • 自定义进制可以用于数据加密

3.6 计算机中常见的数据宽度

  • 位(Bit): 1个位就是1个二进制位.0或者1
  • 字节(Byte): 1个字节由8个Bit组成(8位).内存中的最小单元Byte.
  • 字(Word): 1个字由2个字节组成(16位),这2个字节分别称为高字节和低字节.
  • 双字(Doubleword): 1个双字由两个字组成(32位)
  • 计算机中有符号数正负判定:最高位的数值进行判定(0~7为正,8~F为负)

3.7 计算机中数据的存储

  • 数学上的数字,是没有大小限制的,可以无限的大。但在计算机中,由于受硬件的制约,数据都是有长度限制的(我们称为数据宽度),超过最多宽度的数据会被丢弃。
  • 数据的长度根据数据的类型判定,比如int 4字节 longlong 8字节,超出的就会被丢弃,例如
#import <UIKit/UIKit.h>#import "AppDelegate.h"int test(){    int cTemp = 0x1FFFFFFFF;    return cTemp;}int main(int argc, char * argv[]) {    printf("%x\n",test());    @autoreleasepool {        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    }}

xcode内存调试:Debug->Debug workFlow->view memory, 发现高位丢失了

  • 图1:lldb调试

  • 图2:内存调试

  • 经过调试发现,超出数据宽度的高位数据直接被丢弃了,并不是像我们想象的存储在其他地方

  • 补充:关于数据是如何存储的,需要看C语言知识(反码、补码。。。)

4、CPU寄存器了解

  • CPU内部结构

    • 对程序员来说,CPU中最主要部件是寄存器,通过改变寄存器的内容我们可以实现对CPU的控制
    • 不同的CPU,寄存器的个数、结构是不相同的

    4.1了解一些AMR64寄存器

    X0-X31

    ARM64拥有有31个64位的通用寄存器 x0 到 x30,这些寄存器通常用来存放一般性的数据,有时也有特定用途

    使用iPhone真机(注意,模拟器的CPU是x86,和真机的架构不一样,寄存器是不同的)运行一个简单的Demo,可以查看CPU寄存器。通用寄存器x0-x28, x29-x30被用作特殊用途了(fp, lr)

    w0-w28是这些是32位的,由于64位CPU需要兼容32位CPU,所以可以只使用64位寄存器的低32位. 比如w0的值就是x0寄存器的低32位值通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算,例如

    假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间

     CPU首先会将红色内存空间的值放到X0寄存器中:mov X0,红色内存空间 然后让X0寄存器与1相加:add X0,1 最后将值赋值给内存空间:mov 蓝色内存空间,X0

    分析下面一段汇编,体验下

      ```  .text                   // 表示是代码段  .global _A, _B          // 全局的代码段入口  _A:                     // 入口A      mov x27, #0xa0       // #表示立即数,mov表示写入到寄存器      mov x28, #0x00             add x28, x27, #0x14      mov x27, x28      bl _B               // bl指令,专门用于操作pc寄存器      mov x27, #0x0      ret  _B:      add x27, x27, #0x10      ret      void A();  int main(int argc, char * argv[]){      A();        // 调用A代码段            return 0;  }  ```

    Xcode调试截图

    汇编指令内容在内存中的存储

    • pc寄存器(program counter)

      • 指令指针寄存器,它指示了CPU当前要读取指令的地址

      • 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息

      • CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义(可以参照上面的图理解)

        • 比如 0001 1100 0000 0000 0100 0000 1101 0010
        • 可以当做数据 0x1C0080D2
        • 也可以当做指令 mov x28, #0x0
      • CPU根据什么将内存中的信息看做指令?

        • CPU将pc指向的内存单元的内容看做指令
        • 如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过
      • bl指令

        • CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
        • ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值,但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能
        • ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令

    4.2 寄存器与CPU小总结

    • 对寄存器的理解

      • CPU除了有控制器、运算器还有寄存器。其中寄存器的作用就是进行数据的临时存储
      • CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
      • 对于arm64系的CPU来说, 如果寄存器以x开头则表明的是一个64位的寄存器,如果以w开头则表明是一个32位的寄存器,在系统中没有提供16位和8位的寄存器供访问和使用。其中32位的寄存器是64位寄存器的低32位部分并不是独立存在的。(直观点说: 对w0修改其实就是对x0的修改,只不过数据只存放在x0的低32位)
    • 高度缓存的了解

      • CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.
      • iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M.
    • AMR中寄存器种类的了解

      • 数据地址寄存器(主要学习)

        • 数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
        • 64位: X0-X30, XZR(零寄存器)
        • 32位: W0-W30, WZR(零寄存器)
      • 浮点和向量寄存器

        • 浮点寄存器 64位: D0 - D31 32位: S0 - S31
        • 向量寄存器 128位:V0-V31, 现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.
免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部