精华内容
下载资源
问答
  • 3.2 程序编码第一章已经介绍过代码如何编译成...基本概念:程序计数器 成为%eip,表示将要执行的下一条指令在存储器中的地址。整数寄存器有8个,可以存储32位的值。条件码寄存器保存着最近执行的算术指令的状态信息...

    85c1ed3f83efd75dfe97e306883fd4f9.png

    3.2 程序编码

    第一章已经介绍过代码如何编译成可执行程序的几个步骤,最终的可执行程序是二进制的,那么系统是怎么读这些二进制的呢?

    3.2.1 机器级代码

    能够理解汇编代码以及它是如何与原始的C代码相对应的,是理解计算机如何执行程序的关键一步。

    基本概念:

    • 程序计数器 成为%eip,表示将要执行的下一条指令在存储器中的地址。
    • 整数寄存器有8个,可以存储32位的值。
    • 条件码寄存器保存着最近执行的算术指令的状态信息,用来实现条件控制。
    • 浮点寄存器也有8个。

    到了汇编这一层,已经不区分有符号和无符号,各种数据结构在汇编代码中只是连续的字节。

    程序存储器包含程序的目标代码,使用虚拟地址寻址。又操作系统转换成最终实际的存储器物理地址。

    各种缓存、主存、显存,共同构成了一个连续的虚拟地址空间。

    3.2.2 代码示例

    本书以GCC为例分析编译的结果。

    GCC使用自己的格式来产生汇编代码,这种格式被称为GAS(Gnu ASsembler,GNU汇编器)。

    使用 gcc -O2 -S test.c 可以生产test.s汇编文件
    使用 gcc -O2 -c test.c 可以产生test.o文件
    使用 objdump -d test.o 可以看到一些汇编内容。 
    objdump也就是反汇编器,可以将二进制一定程度上还原为汇编代码。

    反汇编之后的特性:

    • IA32的指令长度1到15字节不等,指令编码被设计为常用的指令就比较短,不常用的就比较长
    这个设计方法好啊,开发中的一些功能函数常常也如此设计。频繁使用的函数尽量减少参数数量。
    • 反汇编器仅根据字节序列来确定汇编代码,无需访问原始代码或者汇编。
    • 反汇编器使用的指令名称跟GAS略有不同,例如,省去很多指令结尾的“l”后缀。

    GCC产生的汇编代码中有一些.开头的行,这些事指导汇编器和链接器的命令,可以忽略不管。例如:

    .file "sum.c"
      .globl  _accum
      .bss
      .align 4

    汇编代码解释:

    // 源代码
    int simple(int *xp, int y)
    {
      int t = *xp + y;
      *xp = t;
      return t;
    }
    // 使用gcc -S 生成的代码段如下
    simple:
      pushl %ebp        保存帧指针
      movl %esp,%ebp    创建新的帧指针
      movl 8(%ebp),%eax   获取xp
      mov1 (%eax),%edx    获取*xp
      addl 12(%ebp),%edx  y加到t上
      movl %edx,(%eax)    t赋值给*xp
      mov1 %edx,%eax      设置t为返回值
      movl %ebp,%esp      重置栈指针
      pop1 %ebp           重置帧指针
      ret
    之前看过一点点汇编语言,在那本书上 ,例如 mov ax,10 表示把ax寄存器设为10 ,就是说第一参数作为目标寄存器。而GAS这边是反的,第二个参数(如果有)才是目标寄存器。 比如 movl %esp,%ebp 表示的是把esp里面的内容赋值给 ebp。

    3.3 数据格式

    GAS很多指令后面会有一个后缀,例如前面出现的l,这个是小写的L,不是数字1 。

    918edd94743f01659b3edd292c8b2446.png

    如图所示,GAS中每个操作符都有一个后缀,用来表明操作数的大小。例如mov就有 movb movw movl等。movel既可以表示4字节整数,也可以表示8字节浮点,这不会产生歧义,因为浮点数使用独立的一组寄存器。

    3.4 访问信息

    以IA32为例,CPU包含8个32位寄存器。大多数情况下,前六个寄存器都可以看成是通用的寄存器。少数情况下,特定指令会使用特定的寄存器。最后两个寄存器ebp和esp,保存着程序栈的指针,不可随意使用。寄存器的布局如下:(其中前四个寄存器还可以通过ah al等方位低位的两个字节,这是为了兼容早起8086等早起处理器)

    e90e800ca0978e6fd81280a3ea481227.png
    寄存器其实也没什么神秘的,就是一些放数据的地方,只不过位置特殊,在cpu上,所以就是访问特别快。

    3.4.1 操作数指示符

    一个典型的指令,有一个或多个操作数,就如同函数有一个或多个参数一样。这些操作数的作用是指出源数据的位置,和结果存储的位置。

    好比一个C函数 void move(int src, int dest); 把src的值赋给dest,这也就是 movl xxx,xxx 的作用。

    而这个源数据怎么得到呢?有下面三种方式:

    1. 立即数。以<img src="https://www.zhihu.com/equation?tex= 开头就是直接给出,无需去内存中查找。例如" alt=" 开头就是直接给出,无需去内存中查找。例如" class="ee_img tr_noresize" eeimg="1">0x123
    2. 寄存器。%加寄存器的名称形式。例如 %eax,%esp。以
      表示寄存器a,用
      来表示寄存器的值,相当于把整个寄存器看做是数组,寄存器作为索引。
    3. 存储器引用。源数据在内存中,通过地址来访问。可以将内存看成是大型数组M,则可以表示为M[Addr] 。而这个Addr的计算成为寻址模式。

    寻址模式

    一个寻址表达式的完整形态为

    ,表达的地址是:
    。其中
    • Imm表示写死的地址, 这里不需要加 $前缀,与立即数区别
    • 表示对应寄存器中存储的值。
    • s表示缩放因子,取值范围为1,2,4,8
    • 这四个部分都可以省略

    例如 0x123, 0x123(%eax), (%eax), (%eax,%ebx), (,%ebx,2), 0x123(%eax,%ebx,4)都是合法的表示方式。各自表达的值如下图:

    57cefa67a484cbb637187f8b0d1293ec.png
    寻址说起来吓人,其实就是根据前面那个公式来计算一个地址,然后通过M[Addr]来得到地址所在内存的值。就好比计算数组下标,然后取数组中的值。

    3.4.2 数据传送指令

    即mov指令。有两个参数,一个源,一个目标。例如movl %eax,%edx 表示把eax中的值复制到edx中。

    IA32中的一个限制,两个参数不可以都为存储器位置,就是说必须要经过寄存器,或者使用立即数。所以代码a=b;的汇编代码不是movl Addra,Addrb,而可能是

    movl Addra,%eax
    movl %eax,Addrb
    这是为什么呢?一条指令只能访问一次内存?暂时还不明确

    movl指令的五种组合情况:

    movl $0x123,%eax        // 立即数 - 寄存器
    movl %eax,%ebx          // 寄存器 - 寄存器
    movl (%eax,%ebx),%ecx   // 内存   - 寄存器
    movl $0x123, (%eax)     // 立即数 - 内存
    movl %eax,(%ebx)        // 寄存器 - 内存
    // 少了内存到内存的方式,因为不支持
    到了这里,终于明白前面simple函数的汇编代码第三行 movl 8(%ebp),%eax 获取xp的意思了。 8(%ebp)表示 8 + %ebp的值代表的地址存储的值。就是把*xp读出来放到eax中。

    特殊指令

    movsblmovzbl是两条特殊的mov指令,他们只拷贝一个字节,不同于movb只修改目标单个字节,他们会修改目标寄存器的其他三个字节。movsbl 执行算术扩展,即目标数值的高位扩展到其他三字节,而movzbl则以0扩展。

    等同 int a = (char)123; 和 unsign a = (char)123; 的逻辑。

    push和pop更为特殊,像是语法糖,各自包含了两个操作。但是有实际的好处,就是字节码大大减少。push指令是有一个字节,它代表的两个指令需要6个字节。

    pushl %eax
    等价于
    subl $4,%esp        // esp是栈顶指针,栈的地址是向下扩张的,所以栈大小增加,则栈顶地址减小4
    movl %eax,(%esp)    // 填空目标值
    
    pop %eax
    等价于
    movl (%esp),%eax    // 先把栈中的值拿出来,然后再把寄存器中的指针缩回来
    addl $4,%esp
    上面代码注意%esp 有无括号的区别,无括号表示寄存器值,有括号是内存中的值。
    转换成编程语言来理解就是,%esp是操作指针的值,(%esp) 则是对指针指向的值 例如 int p = 0x112321; sub $4, %esp -> p -= 1; (int指针减1就是4个字节了) mov %eax,(%esp) -> p = 100; (假设eax中存的就是100吧)
    再有一点就是,pop缩回来之后,是没有处理源栈中的值的,值其实还保留在内存中。

    3.4.3 数据传送实例

    按照惯例,所有返回整数或指针的函数,都是通过将结果放在寄存器%eax中来达到目的。

    一些临时变量,可能会存储在寄存器中,如果寄存器够用的话。寄存器的访问比存储器访问无疑是要快的多。

    根据这个说法,那么现代CPU是不是应该使用更多的寄存器呢? 知乎上倒是有个问题说这个问题,不太懂,无法辨别。为什么X86的寄存器数量没有随着性能的提升而增加?
    通过前面这段内容的学习,我们知道了一些基本的指令,了解了指令的操作数如何访问数据,即寻址模式的几种情况。寻址模式是非常重要的,贯彻整个汇编指令的始终。寻址模式的公式为
    ,汇编中表达式为
    四个部分都可以选填。

    函数的栈地址是向下扩展的,压栈会使栈顶地址变小,弹出栈则会使栈顶地址回缩,而回缩后,并不会修改栈中的内存,会遗留以前的数据,等待下一次修改使用。
    返回值通过最后修改eax来实现。寄存器比存储器快,这点暂时也不知道如何利用。临时变量比直接使用参数、指针要快?
    展开全文
  • 3.2 程序编码第一章已经介绍过代码如何编译成...基本概念:程序计数器 成为%eip,表示将要执行的下一条指令在存储器中的地址。整数寄存器有8个,可以存储32位的值。条件码寄存器保存着最近执行的算术指令的状态信息...

    68bd0a8984e80c0a2a9a65425efda0ae.png

    3.2 程序编码

    第一章已经介绍过代码如何编译成可执行程序的几个步骤,最终的可执行程序是二进制的,那么系统是怎么读这些二进制的呢?

    3.2.1 机器级代码

    能够理解汇编代码以及它是如何与原始的C代码相对应的,是理解计算机如何执行程序的关键一步。

    基本概念:

    • 程序计数器 成为%eip,表示将要执行的下一条指令在存储器中的地址。
    • 整数寄存器有8个,可以存储32位的值。
    • 条件码寄存器保存着最近执行的算术指令的状态信息,用来实现条件控制。
    • 浮点寄存器也有8个。

    到了汇编这一层,已经不区分有符号和无符号,各种数据结构在汇编代码中只是连续的字节。

    程序存储器包含程序的目标代码,使用虚拟地址寻址。又操作系统转换成最终实际的存储器物理地址。

    各种缓存、主存、显存,共同构成了一个连续的虚拟地址空间。

    3.2.2 代码示例

    本书以GCC为例分析编译的结果。

    GCC使用自己的格式来产生汇编代码,这种格式被称为GAS(Gnu ASsembler,GNU汇编器)。

    使用 gcc -O2 -S test.c 可以生产test.s汇编文件
    使用 gcc -O2 -c test.c 可以产生test.o文件
    使用 objdump -d test.o 可以看到一些汇编内容。 
    objdump也就是反汇编器,可以将二进制一定程度上还原为汇编代码。

    反汇编之后的特性:

    • IA32的指令长度1到15字节不等,指令编码被设计为常用的指令就比较短,不常用的就比较长
    这个设计方法好啊,开发中的一些功能函数常常也如此设计。频繁使用的函数尽量减少参数数量。
    • 反汇编器仅根据字节序列来确定汇编代码,无需访问原始代码或者汇编。
    • 反汇编器使用的指令名称跟GAS略有不同,例如,省去很多指令结尾的“l”后缀。

    GCC产生的汇编代码中有一些.开头的行,这些事指导汇编器和链接器的命令,可以忽略不管。例如:

    .file "sum.c"
      .globl  _accum
      .bss
      .align 4

    汇编代码解释:

    // 源代码
    int simple(int *xp, int y)
    {
      int t = *xp + y;
      *xp = t;
      return t;
    }
    // 使用gcc -S 生成的代码段如下
    simple:
      pushl %ebp        保存帧指针
      movl %esp,%ebp    创建新的帧指针
      movl 8(%ebp),%eax   获取xp
      mov1 (%eax),%edx    获取*xp
      addl 12(%ebp),%edx  y加到t上
      movl %edx,(%eax)    t赋值给*xp
      mov1 %edx,%eax      设置t为返回值
      movl %ebp,%esp      重置栈指针
      pop1 %ebp           重置帧指针
      ret
    之前看过一点点汇编语言,在那本书上 ,例如 mov ax,10 表示把ax寄存器设为10 ,就是说第一参数作为目标寄存器。而GAS这边是反的,第二个参数(如果有)才是目标寄存器。 比如 movl %esp,%ebp 表示的是把esp里面的内容赋值给 ebp。

    3.3 数据格式

    GAS很多指令后面会有一个后缀,例如前面出现的l,这个是小写的L,不是数字1 。

    01f09e43dd139f11851a931ee36f5270.png

    如图所示,GAS中每个操作符都有一个后缀,用来表明操作数的大小。例如mov就有 movb movw movl等。movel既可以表示4字节整数,也可以表示8字节浮点,这不会产生歧义,因为浮点数使用独立的一组寄存器。

    3.4 访问信息

    以IA32为例,CPU包含8个32位寄存器。大多数情况下,前六个寄存器都可以看成是通用的寄存器。少数情况下,特定指令会使用特定的寄存器。最后两个寄存器ebp和esp,保存着程序栈的指针,不可随意使用。寄存器的布局如下:(其中前四个寄存器还可以通过ah al等方位低位的两个字节,这是为了兼容早起8086等早起处理器)

    44e9c7604fd9dec8e9381b8ac436c3fc.png
    寄存器其实也没什么神秘的,就是一些放数据的地方,只不过位置特殊,在cpu上,所以就是访问特别快。

    3.4.1 操作数指示符

    一个典型的指令,有一个或多个操作数,就如同函数有一个或多个参数一样。这些操作数的作用是指出源数据的位置,和结果存储的位置。

    好比一个C函数 void move(int src, int dest); 把src的值赋给dest,这也就是 movl xxx,xxx 的作用。

    而这个源数据怎么得到呢?有下面三种方式:

    1. 立即数。以<img src="https://www.zhihu.com/equation?tex= 开头就是直接给出,无需去内存中查找。例如" alt=" 开头就是直接给出,无需去内存中查找。例如" class="ee_img tr_noresize" eeimg="1">0x123
    2. 寄存器。%加寄存器的名称形式。例如 %eax,%esp。以
      表示寄存器a,用
      来表示寄存器的值,相当于把整个寄存器看做是数组,寄存器作为索引。
    3. 存储器引用。源数据在内存中,通过地址来访问。可以将内存看成是大型数组M,则可以表示为M[Addr] 。而这个Addr的计算成为寻址模式。

    寻址模式

    一个寻址表达式的完整形态为

    ,表达的地址是:
    。其中
    • Imm表示写死的地址, 这里不需要加 $前缀,与立即数区别
    • 表示对应寄存器中存储的值。
    • s表示缩放因子,取值范围为1,2,4,8
    • 这四个部分都可以省略

    例如 0x123, 0x123(%eax), (%eax), (%eax,%ebx), (,%ebx,2), 0x123(%eax,%ebx,4)都是合法的表示方式。各自表达的值如下图:

    9de16667ac3013ce49419190a358c421.png
    寻址说起来吓人,其实就是根据前面那个公式来计算一个地址,然后通过M[Addr]来得到地址所在内存的值。就好比计算数组下标,然后取数组中的值。

    3.4.2 数据传送指令

    即mov指令。有两个参数,一个源,一个目标。例如movl %eax,%edx 表示把eax中的值复制到edx中。

    IA32中的一个限制,两个参数不可以都为存储器位置,就是说必须要经过寄存器,或者使用立即数。所以代码a=b;的汇编代码不是movl Addra,Addrb,而可能是

    movl Addra,%eax
    movl %eax,Addrb
    这是为什么呢?一条指令只能访问一次内存?暂时还不明确

    movl指令的五种组合情况:

    movl $0x123,%eax        // 立即数 - 寄存器
    movl %eax,%ebx          // 寄存器 - 寄存器
    movl (%eax,%ebx),%ecx   // 内存   - 寄存器
    movl $0x123, (%eax)     // 立即数 - 内存
    movl %eax,(%ebx)        // 寄存器 - 内存
    // 少了内存到内存的方式,因为不支持
    到了这里,终于明白前面simple函数的汇编代码第三行 movl 8(%ebp),%eax 获取xp的意思了。 8(%ebp)表示 8 + %ebp的值代表的地址存储的值。就是把*xp读出来放到eax中。

    特殊指令

    movsblmovzbl是两条特殊的mov指令,他们只拷贝一个字节,不同于movb只修改目标单个字节,他们会修改目标寄存器的其他三个字节。movsbl 执行算术扩展,即目标数值的高位扩展到其他三字节,而movzbl则以0扩展。

    等同 int a = (char)123; 和 unsign a = (char)123; 的逻辑。

    push和pop更为特殊,像是语法糖,各自包含了两个操作。但是有实际的好处,就是字节码大大减少。push指令是有一个字节,它代表的两个指令需要6个字节。

    pushl %eax
    等价于
    subl $4,%esp        // esp是栈顶指针,栈的地址是向下扩张的,所以栈大小增加,则栈顶地址减小4
    movl %eax,(%esp)    // 填空目标值
    
    pop %eax
    等价于
    movl (%esp),%eax    // 先把栈中的值拿出来,然后再把寄存器中的指针缩回来
    addl $4,%esp
    上面代码注意%esp 有无括号的区别,无括号表示寄存器值,有括号是内存中的值。
    转换成编程语言来理解就是,%esp是操作指针的值,(%esp) 则是对指针指向的值 例如 int p = 0x112321; sub $4, %esp -> p -= 1; (int指针减1就是4个字节了) mov %eax,(%esp) -> p = 100; (假设eax中存的就是100吧)
    再有一点就是,pop缩回来之后,是没有处理源栈中的值的,值其实还保留在内存中。

    3.4.3 数据传送实例

    按照惯例,所有返回整数或指针的函数,都是通过将结果放在寄存器%eax中来达到目的。

    一些临时变量,可能会存储在寄存器中,如果寄存器够用的话。寄存器的访问比存储器访问无疑是要快的多。

    根据这个说法,那么现代CPU是不是应该使用更多的寄存器呢? 知乎上倒是有个问题说这个问题,不太懂,无法辨别。为什么X86的寄存器数量没有随着性能的提升而增加?
    通过前面这段内容的学习,我们知道了一些基本的指令,了解了指令的操作数如何访问数据,即寻址模式的几种情况。寻址模式是非常重要的,贯彻整个汇编指令的始终。寻址模式的公式为
    ,汇编中表达式为
    四个部分都可以选填。

    函数的栈地址是向下扩展的,压栈会使栈顶地址变小,弹出栈则会使栈顶地址回缩,而回缩后,并不会修改栈中的内存,会遗留以前的数据,等待下一次修改使用。
    返回值通过最后修改eax来实现。寄存器比存储器快,这点暂时也不知道如何利用。临时变量比直接使用参数、指针要快?
    展开全文
  • 多核cpu通信相关寄存器

    千次阅读 2017-08-02 09:27:06
    loongson3A-4核cpu为每个...首先是总的介绍下寄存器的作用: 下面分别是每个核的寄存器,以及地址。 核0: 核1: 核2: 核3: 这里的手册说的十分笼统,对于寄存器怎么使用,以及相关的位表示什么意思

    loongson3A-4核cpu为每个处理器核都实现了8个核间中断寄存器(IPI)以支持多核BIOS启动核操作系统运行时在处理器核之间进行中断核通信。

    这里主要说一下硬件相关的寄存器。

    1,中断框图

    先总的看一张关于多核cpu的中断管理示意图:



    这个图范围4列:

    第一列表示中断源,也就是中断时由那个设备产生的;

    第二列表示中断源对应的中断控制器上的那一位;

    第三列表示中断路由控制寄存器,可以控制这个中断到底由那个cpu来进行处理;

    第四列表示4个cpu的中断位(CP0寄存器中的位)

    PS:这里的图示由错误,最后一列也就是第四列,这里的IP0-IP3,应该时IP2-IP5


    2,中断控制器寄存器

    上面图片上看到的32个中断,都是可以进行控制的,看一下中断控制时怎么对中断进行控制的,以及由那些寄存器:


    intisr       :中断状态(那个中断来了)

    intenset :设置中断使能,inenset寄存器写1的位对应的中断被使能。

    intenclr :清除中断使能

    inten      :表示当前中断由那些被使能

    intedge :设置中断的触发方式,写1表示脉冲触发,写0表示电平触发.


    对于中断控制器,每个寄存器都有其权限,由的寄存器可读写,有的寄存器只可读,有的寄存器只可写

    看一下mips架构中对于这几个寄存器的权限设置:



    3,中断路由寄存器

    框图中示例的32个中断,每个中断都有自己的中断路由寄存器,只要配置对应中断的寄存器就可以把这个中断路由到对应的cpu以及引脚上。

    看一下每个中断对应的寄存器:



    那有了寄存器,那怎么设置才能路由到对应的cpu的对应的引脚上呢?

    这里呢也有专门的说明使用的:



    OK拉,就这样设置就好了,还时不清楚?好吧,我当时也是这样的,那就说两个例子就好了。


    例子1:路由LPC中断到cpu0的IP2上:

    *(volatile unsigned char*)0x3ff0140b = 0x11


    例子2:路由LPC中断到cpu0的IP3上

    *(volatile unsigned char*)0x3ff0140b = 0x21


    例子3:路由LPC中断到cpu1的IP2上

    *(volatile unsigned char*)0x3ff0140b = 0x12


    例子2:路由LPC中断到cpu2的IP3上

    *(volatile unsigned char*)0x3ff0140b = 0x24



    4,核间通信寄存器

    结下来看一下每个cpu中的核间通信寄存器以及其起到的作用:


    下面分别是每个核的寄存器,以及地址。

    核0:


    核1:


    核2:


    核3:

    上面几个就是cpu间的通信寄存器,其功能就在第一个里面说了,对于mailbox的4个寄存器,具体功能还不清楚,再代码中还没有看到其是怎么使用的。

    这里说一下核间通信的一个过程:

    假如有一个中断要让cpu1进行处理,这样怎么做呢?

    1,首先设置core1_IPI_set寄存器中该中断对应的位,那么此时再core1_IPI_status中就会显示出这个中断来了。

    2,那么此时呢,再核1的CP0辅助寄存器中cause寄存器的IP6位就会置位,表示一个核间中断来了。

    3,再中断处理函数里面,判断一下这个中断是否是IP6引起的,如果是呢,这就是一个核间中断拉,

    4,再设置core1_IPI_clear的对应位,进行清中断操作

    5,此时再调用对应引脚上的中断函数,进行处理。









    展开全文
  • 程序计数器 成为%eip,表示将要执行的下一条指令在存储器中的地址。 整数寄存器有8个,可以存储32位的值。 条件码寄存器保存着最近执行的算术指令的状态信息,用来实现条件控制。 浮点寄存器也有8个。 到了汇编这一...

    3.2 程序编码

    第一章已经介绍过代码如何编译成可执行程序的几个步骤,最终的可执行程序是二进制的,那么系统是怎么读这些二进制的呢?

    3.2.1 机器级代码

    能够理解汇编代码以及它是如何与原始的C代码相对应的,是理解计算机如何执行程序的关键一步。

    基本概念:

    • 程序计数器 成为%eip,表示将要执行的下一条指令在存储器中的地址。
    • 整数寄存器有8个,可以存储32位的值。
    • 条件码寄存器保存着最近执行的算术指令的状态信息,用来实现条件控制。
    • 浮点寄存器也有8个。

    到了汇编这一层,已经不区分有符号和无符号,各种数据结构在汇编代码中只是连续的字节。

    程序存储器包含程序的目标代码,使用虚拟地址寻址。又操作系统转换成最终实际的存储器物理地址。

    各种缓存、主存、显存,共同构成了一个连续的虚拟地址空间。

    3.2.2 代码示例

    本书以GCC为例分析编译的结果。

    GCC使用自己的格式来产生汇编代码,这种格式被称为GAS(Gnu ASsembler,GNU汇编器)。

    使用 gcc -O2 -S test.c 可以生产test.s汇编文件
    使用 gcc -O2 -c test.c 可以产生test.o文件
    使用 objdump -d test.o 可以看到一些汇编内容。 
    objdump也就是反汇编器,可以将二进制一定程度上还原为汇编代码。
    

    反汇编之后的特性:

    • IA32的指令长度1到15字节不等,指令编码被设计为常用的指令就比较短,不常用的就比较长

      这个设计方法好啊,开发中的一些功能函数常常也如此设计。频繁使用的函数尽量减少参数数量。

    • 反汇编器仅根据字节序列来确定汇编代码,无需访问原始代码或者汇编。

    • 反汇编器使用的指令名称跟GAS略有不同,例如,省去很多指令结尾的“l”后缀。

    GCC产生的汇编代码中有一些.开头的行,这些事指导汇编器和链接器的命令,可以忽略不管。例如:

    	.file	"sum.c"
    	.globl	_accum
    	.bss
    	.align 4
    

    汇编代码解释:

    // 源代码
    int simple(int *xp, int y)
    {
    	int t = *xp + y;
    	*xp = t;
    	return t;
    }    
    
    // 使用gcc -S 生成的代码段如下
    simple:
      pushl %ebp        保存帧指针
      movl %esp,%ebp    创建新的帧指针
      movl 8(%ebp),%eax   获取xp
      mov1 (%eax),%edx    获取*xp
      addl 12(%ebp),%edx  把y加到t上
      movl %edx,(%eax)    把t赋值给*xp
      mov1 %edx,%eax      设置t为返回值
      movl %ebp,%esp      重置栈指针
      pop1 %ebp           重置帧指针
      ret
    

    之前看过一点点汇编语言,在那本书上 ,例如 mov ax,10 表示把ax寄存器设为10 ,就是说第一参数作为目标寄存器。而GAS这边是反的,第二个参数(如果有)才是目标寄存器。 比如 movl %esp,%ebp 表示的是把esp里面的内容赋值给 ebp。

    3.3 数据格式

    GAS很多指令后面会有一个后缀,例如前面出现的l,这个是小写的L,不是数字1 。

    在这里插入图片描述

    如图所示,GAS中每个操作符都有一个后缀,用来表明操作数的大小。例如mov就有 movb movw movl等。movel既可以表示4字节整数,也可以表示8字节浮点,这不会产生歧义,因为浮点数使用独立的一组寄存器。

    3.4 访问信息

    以IA32为例,CPU包含8个32位寄存器。大多数情况下,前六个寄存器都可以看成是通用的寄存器。少数情况下,特定指令会使用特定的寄存器。最后两个寄存器ebp和esp,保存着程序栈的指针,不可随意使用。寄存器的布局如下:(其中前四个寄存器还可以通过ah al等方位低位的两个字节,这是为了兼容早起8086等早起处理器)

    在这里插入图片描述

    寄存器其实也没什么神秘的,就是一些放数据的地方,只不过位置特殊,在cpu上,所以就是访问特别快。

    3.4.1 操作数指示符

    一个典型的指令,有一个或多个操作数,就如同函数有一个或多个参数一样。这些操作数的作用是指出源数据的位置,和结果存储的位置。

    好比一个C函数 void move(int src, int dest); 把src的值赋给dest,这也就是 movl xxx,xxx 的作用。

    而这个源数据怎么得到呢?有下面三种方式:

    1. 立即数。以$ 开头就是直接给出,无需去内存中查找。例如$0x123
    2. 寄存器。%加寄存器的名称形式。例如 %eax,%esp。以EaE_a表示寄存器a,用R[Ea]R[E_a]来表示寄存器的值,相当于把整个寄存器看做是数组,寄存器作为索引。
    3. 存储器引用。源数据在内存中,通过地址来访问。可以将内存看成是大型数组M,则可以表示为M[Addr] 。而这个Addr的计算成为寻址模式。

    寻址模式

    一个寻址表达式的完整形态为Imm(Eb,Ei,s)Imm(E_b, E_i, s) ,表达的地址是:Imm+Eb+EisImm + E_b + E_i * s 。其中

    • Imm表示写死的地址, 这里不需要加 $前缀,与立即数区别
    • Eb,EiE_b,E_i 表示对应寄存器中存储的值。
    • s表示缩放因子,取值范围为1,2,4,8
    • 这四个部分都可以省略

    例如 0x123, 0x123(%eax), (%eax), (%eax,%ebx), (,%ebx,2), 0x123(%eax,%ebx,4)都是合法的表示方式。各自表达的值如下图:

    在这里插入图片描述

    寻址说起来吓人,其实就是根据前面那个公式来计算一个地址,然后通过M[Addr]来得到地址所在内存的值。就好比计算数组下标,然后取数组中的值。

    3.4.2 数据传送指令

    即mov指令。有两个参数,一个源,一个目标。例如movl %eax,%edx 表示把eax中的值复制到edx中。

    IA32中的一个限制,两个参数不可以都为存储器位置,就是说必须要经过寄存器,或者使用立即数。所以代码a=b;的汇编代码不是movl Addra,Addrb,而可能是

    movl Addra,%eax
    movl %eax,Addrb
    

    这是为什么呢?一条指令只能访问一次内存?暂时还不明确

    movl指令的五种组合情况:

    movl $0x123,%eax        // 立即数 - 寄存器
    movl %eax,%ebx          // 寄存器 - 寄存器
    movl (%eax,%ebx),%ecx   // 内存   - 寄存器
    movl $0x123, (%eax)     // 立即数 - 内存
    movl %eax,(%ebx)        // 寄存器 - 内存
    // 少了内存到内存的方式,因为不支持
    

    到了这里,终于明白前面simple函数的汇编代码第三行 movl 8(%ebp),%eax 获取xp的意思了。 8(%ebp)表示 8 + %ebp的值代表的地址存储的值。就是把*xp读出来放到eax中。

    特殊指令

    movsblmovzbl是两条特殊的mov指令,他们只拷贝一个字节,不同于movb只修改目标单个字节,他们会修改目标寄存器的其他三个字节。movsbl 执行算术扩展,即目标数值的高位扩展到其他三字节,而movzbl则以0扩展。

    等同 int a = (char)123; 和 unsign a = (char)123; 的逻辑。

    push和pop更为特殊,像是语法糖,各自包含了两个操作。但是有实际的好处,就是字节码大大减少。push指令是有一个字节,它代表的两个指令需要6个字节。

    pushl %eax
    等价于
    subl $4,%esp        // esp是栈顶指针,栈的地址是向下扩张的,所以栈大小增加,则栈顶地址减小4
    movl %eax,(%esp)    // 填空目标值
    
    pop %eax
    等价于
    movl (%esp),%eax    // 先把栈中的值拿出来,然后再把寄存器中的指针缩回来
    addl $4,%esp
    

    上面代码注意%esp 有无括号的区别,无括号表示寄存器值,有括号是内存中的值。

    转换成编程语言来理解就是,%esp是操作指针的值,(%esp) 则是对指针指向的值
    例如 int* p = 0x112321;
    sub $4, %esp -> p -= 1; (int指针减1就是4个字节了)
    mov %eax,(%esp) -> *p = 100; (假设eax中存的就是100吧)

    再有一点就是,pop缩回来之后,是没有处理源栈中的值的,值其实还保留在内存中。

    3.4.3 数据传送实例

    按照惯例,所有返回整数或指针的函数,都是通过将结果放在寄存器%eax中来达到目的。

    一些临时变量,可能会存储在寄存器中,如果寄存器够用的话。寄存器的访问比存储器访问无疑是要快的多。

    根据这个说法,那么现代CPU是不是应该使用更多的寄存器呢? 知乎上倒是有个问题说这个问题,不太懂,无法辨别。为什么X86的寄存器数量没有随着性能的提升而增加?

    通过前面这段内容的学习,我们知道了一些基本的指令,了解了指令的操作数如何访问数据,即寻址模式的几种情况。寻址模式是非常重要的,贯彻整个汇编指令的始终。寻址模式的公式为 Imm+Eb+EisImm + E_b + E_i * s,汇编中表达式为 Imm(Eb,Ei,s)Imm(E_b,E_i,s) 四个部分都可以选填。

    函数的栈地址是向下扩展的,压栈会使栈顶地址变小,弹出栈则会使栈顶地址回缩,而回缩后,并不会修改栈中的内存,会遗留以前的数据,等待下一次修改使用。

    返回值通过最后修改eax来实现。寄存器比存储器快,这点暂时也不知道如何利用。临时变量比直接使用参数、指针要快?

    展开全文
  • 那么地址V0.1对应modbus地址是否可以表示为40001.9,是不是把40001.9直接提供给DCS厂家就可以了?modbus地址在主站那里是如何使用?如果不能这样用,那例如V0.1这样地址,应该用什么方式提供给DCS厂家?有劳...
  • 例如我要往0x00403000中写入数据,我想用段寄存器:偏移地址的形式来表示,这要怎么转换?
  • 程序计数器:用%eip表示,指示将要执行的下一条指令在存储器中的地址。 整数寄存器:用以存储地址或整数数据。 条件码寄存器:保存着最近执行的算数或逻辑指令的状态信息。 浮点寄存器:存放浮点数据。 整数寄存器...
  • 直接相加还是不能匹配20位的地址线的访问能力,因此,INTEL公司变通了一下,人为地将这个组合寻址设计成:在它们组合时,让段寄存器左移四位(假设DS是1234H,左移4位就变成了12340H,至于它内部怎么实现我们不用管...
  • 程序是怎么跑起来

    千次阅读 2018-05-06 10:39:00
    地址由整数值表示。CPU很内存是由许多晶体管组成电子部件,通常称为IC(Integrated Circuit,集成电路)。CPU内部由寄存器、控制器、运算器、和时钟四个部分组成,各个部分之间由电流信号相互连通。寄存器可用来...
  • 利用PHP怎么实现一个布尔值的自增与自减功能发布时间:2020-12-24 16:35:18来源:亿速云阅读:77作者:...++a 表示取 a 的地址,增加内存中 a 的值,然后把值放在寄存器中a++ 表示取 a 的地址,把 a 的值装入寄存...
  • $当前行被汇编后的地址,$$表示一个section开始处的地址,本程序只有一个section,所以指0x7c00 times 510-($-$$) db 0 ;填充剩下空间,使生成的二进制恰好为512字节 dw 0xaa55 ;结束标志,如果发现扇区以0xAA...
  • c 语言指针

    2019-01-05 20:07:38
    想想: 这个东西的给人的感觉就是不太好, 什么指向指针就把谁的地址给指针 , ...(r1) 相当于指针, 也就是汇编表示的意思是,r1 的地址假设是0x111 0x111 里面存的是 0x1112 这个的地址, 0x1112 地址的值10...
  • ebp表示当前函数栈空间地址 esp是当前栈顶 push时候就会把esp减4,注意是减,不是加,栈从高地址向低地址扩展 也可以sub esp, 0x4,分配局部变量空间时候就是这样做 global main fun: ; 保存现场
  • 其实这与保护模式或实模式没有关系。 A20保持关闭是为了兼容历史版本的架构,所以那些比1MB...既然它有1MB的寻址能力,该怎么用只有16位的段寄存器表示呢? 采用分段的方法: segment : offset 实际的地址是 segmen
  • 80x86保护模式

    2018-03-21 22:23:00
    什么是保护模式? 通过对程序使用存储区采用分段、分页存储管理机制, 达到分组使用、互不干扰保护目的。能为每个任务提供一台虚拟处理器,使每个... 出于系统兼容问题,段寄存器都是16位, 那么如何表示6...
  • 比如:数据寄存器DS中的值是16位,要转换成20位的地址怎么转换呢? 所以左移四位后则低四位变为0了,然后再加上IP寄存器中的偏移值,就是物理地址。 DS << 4 + IP (即偏移值)= 物理地址 没有操作系统则它...
  • 嵌入式面试题(一)

    2018-04-14 16:31:00
    那么怎么见到有些 c 程序里使用二进制的地址值呢? 物理地址,二进制地址给我绕的有点晕,老师能帮我解答一下么?C 语言编程里也可以操纵寄存器,那这样用 C 编写的应用程序里面也有寄存器地址啊,这样来讲应用程序...
  • 在我们这里,外设数据化就是将寄存器地址用结构体来表示。现在不懂没关系我们会有实例。 目录一.外设数据化实例二.使用外设数据化表优化led驱动程序总结 一.外设数据化实例 我们在前面章节中用到了很多的寄存器都...
  • 关于lis3dh使用问题

    2019-08-06 18:32:51
    前8位是要写入的寄存器地址0x0f(第一位置高表示要从该寄存器中读值),后8位是该寄存器数据(读不出来,一直为0)。 这是配置寄存器部分 ```void lis3dh_init(void){ spi_init(); LIS3DH_...
  • 所以要用两个寄存器表示地址,看下面例子: 比如现在有这样一个问题,一个学校有50个班(1班,2班,...,50班),每个班有60名学生,怎么标记识别出每个学生呢?简单:把所有学生从1开始编号,到3000为止。每个...
  • windows用户称拦截api

    2015-04-06 17:22:24
    即在a进程里映射的kernel32.dll的地址和在进程b里的kernel32.dll的地址是一样的。对于文件映射是一种特殊的方式,使得程序不需要进行磁盘i/o就能对磁盘文件进行操作,而且支持多种保护属性。对于一个被映射的文件,...
  • 以STM32为例,有提供个固件库,也就是好几个文件夹和几十个文件,里面有会汇编写的,也有用C写的,貌似也有地址映射,貌似里面的地址映射语句不是像51里面用sfr来表示的,貌似是用#define来定义的,这边搞糊涂了。...
  • 大厂爆光台

    2018-08-05 00:05:00
    2. 苹果开发库,怎么还是像MFC那样用前缀表示类别?命名空间是干什么?天天重新这个重新那个,Framework却是如此地守旧。 3. 截止2018年8月,Mac五笔输入法还远远不如17年前发布XP。 4. TiTAS2505音频...
  • 新版Android开发教程.rar

    千次下载 热门讨论 2010-12-14 15:49:11
    行业进入门槛,移动互联网发展远没有拥有统一标准传统互联网发展迅速,此次推出开源手机操 作 系统平台就是出于这个目的。 也有分析认为,谷歌并不想做一个简单手机终端制造商或者软件平台开发商,而意在...
  • c语言编写单片机技巧

    2009-04-19 12:15:17
    答:汇编语言是一种用文字助记符来表示机器指令符号语言,是最接近机器码一种语言。其主要优点是占用资源少、程序执行效率高。但是不同CPU,其汇编语言可能有所差异,所以不易移植。 C语言是一种结构化...

空空如也

空空如也

1 2
收藏数 32
精华内容 12
关键字:

寄存器的地址怎么表示