《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,包括x86和x86_64,并且给出了较多的网络参考资料链接。这里只摘记了NASM和MASM,测试代码仅支持Windows和Linux的x86_64。

1. 编程语言及数据的基础知识

1.1 开篇语:

GNU Assembler(GAS)是一款基于Linux的汇编器,主要供GNU项目使用,它产生于1987年。

Netwide Assembler(NASM)是一款基于Linux的开源汇编器/反汇编器,适用于x86与x86_64,它产生于1996年。

Microsoft Macro Assembler(MASM)是微软操作系统的专属汇编器,随Visual Studio一起发布,它产生于1981年。

1.3 计算机编程语言:

把汇编代码翻译成机器语言的过程通常称为编码(encoding),反向的过程,也就是把机器码翻译成汇编语言的过程,通常称为解码(decoding)。

指令集架构(Instruction Set Architecture, ISA)是计算机体系结构中与编程有关的方面。它指出了处理器所具备的指令、寄存器、内存架构、数据类型以及其它一些属性,以供程序员使用。

指令集架构分为复杂与精简两种。复杂指令集(Complex Instruction Set Computing, CISC)架构中的指令,其长度(也就是表示该指令所需的字节数)不固定。之所以说它复杂,是因为一条指令有可能要完成多项任务。与之相对的ISA设计称为精简指令集(Reduced Instruction Set Computing, RISC),其中的所有指令都一样长,而且只执行一项任务。

x86与x86_64都是CISC架构,而其它一些ISA设计方案则多为RISC架构。

反汇编是由反汇编器对包含机器语言的目标文件进行解码之后所输出的汇编代码,这实际上相当于把机器语言的二进制序列解码成汇编指令。大多数汇编器、编译器、调试器以及开发环境都提供反汇编功能,例如NASM、GDB、LLVM、Xcode以及Visual Studio。还有一些独立的反汇编器可供使用,像是Capstone、IDA、objdump与otool。

机器语言是针对特定的处理器而言的,不过,同一个系列的处理器能够解读同一种机器语言,因此,针对x86处理器所写的代码可以用在该系列的任何一款处理器上,x86_64也是如此。汇编代码无法在不同系列的处理器之间移植

x86/x86_64处理器家族,既包括Intel的Pentium、Core-Duo及Core i7,也包括AMD的Athlon、Phenom及Opteron。Intel与AMD的处理器设计方案都实现了x86指令集,但实现该指令集所用的具体技术(也就是微架构, microarchitecture)却有所不同。

编程语言与相应文件及编程工具之间的关系,如下图所示:

编译器(compiler)根据处理器的指令集,把用高级语言写成的源代码转化成汇编语言这种中介形式。然后,汇编器(assembler)把这些汇编代码编码成目标代码(object code),这是一种可重定位的机器语言,其格式与具体的操作系统(Operating System, OS)平台有关。链接器(linker)把多个目标文件及静态库(static library)合并起来,以形成一个可执行文件。最后,加载器(loader)把链接器在可执行文件中所生成的指令与动态库(dynamic library)中的指令相结合,将这些机器码载入内存,以供CPU(Central Processing Unit, 中央处理器)执行。

所谓可重定位,是指在将汇编代码翻译成目标代码时,采用一种通用的形式来表示这些代码,把第一条指令的开始位置记为0x0h这个地址,以后指令的位置都用它与前面指令相隔的字节数(或者说偏移量, offset)表示。这样的话,在将程序实际载入内存时,只需要把第一条指令放在某个真实的地址上就可以了,而后续的各条指令则可以根据其偏移量安排在适当的位置上。

1.4 数据的表示:

数据在底层可以用二进制来表示,这种表示方式只使用1与0两种数位。实际上,在执行机器语言所写的代码时,计算机只能采用二进制来运作。

在计数系统中,整数的符号可以用多种办法表示,例如原码、反码及补码。这三种表示方式都用最高有效位表示正负,0代表正,1代表负。当前的大多数架构(例如x86与x86_64)均采用补码来表示带符号的整数。原码与反码都有个缺点,就是0会以两种形式出现,一种是正0,一种是负0。与之相比,补码不会出现两个0的问题。

ASCII(American Standard Code for Information Interchange, 美国信息交换标准代码):是一种7位字符集,意味着它可以表示128种(也就是2^7种)不同的字符,其中大部分是英文字母。C++等高级语言使用ASCII作为默认的字符集。ASCII可以用很多方式来扩展,以表示其它拉丁系的字母及符号。

Unicode字符集能够以任何一种办法来编码,其中最常用的一种叫做UTF-8(UTF是Unicode Transformation Format, Unicode转换格式)。互联网上的很多网页用的都是这种字符编码(character encoding)方式,它支持1112064个字符,其中每个字符可能要用1至4个octet来表示(octet指的是由8个二进制位所构成的组,相当于一个字节)。此外,还有其它一些Unicode字符编码方式,例如UTF-16用一个或两个16位的字(word)来表示字符,UTF-32用一个32位的值来表示字符。

1.5 布尔表达式:NOT、AND、OR、XOR

2. 处理器与计算机系统体系结构

2.2 体系结构概述:

主板是连接计算机主要组件的板卡。总线(bus)是指主板中的一组线路或导线,用来在组件之间传输数据。计算机中最主要的总线叫做系统总线(system bus),它实际上是数据总线、地址总线与控制总线这三者的合称。系统总线使得CPU能够与内存及计算机中的其它I/O设备相通信,为了确保通信正常,这些与CPU通信的设备必须与对应的总线连接起来。数据总线(data bus)用来在组件之间传递指令及数据,其中的指令指的是从内存中加载数据、把数据放入内存,或是从光驱中读取数据等动作。为了正确传递信息,还必须传输指令所针对的内存地址和数据。地址总线(address bus)就负责地址方面的通信工作。此外,控制总线(control bus)会在组件之间传输信号,使得组件能够在适当的时机通信,以确保同步运作。

除了系统总线,还需要一个部件才能使组件之间得以通信。这个部件就是主板上的系统时钟(system clock)。系统时钟的基本单位叫做时钟周期(clock cycle),它的前半段称为高电平期(up-tick),此时电压由低变高(用二进制表示就是从0变成1),后半段称为低电平期(down-tick),此时电压由高变低(或者说从1变成0)。

要传输数据就必须有地方存储这些数据及指令,为此,计算机必须拥有存储器(memory)。下图描述了各类存储器之间的层次关系:

离CPU最近的存储器是静态随机存取存储器(Static Random Access Memory),简称SRAM。它实际上就在CPU芯片中。这种存储器通常称为缓存(cache)。SRAM是速度最快的存储器类型。比它稍慢一些的是动态随机存取存储器(Dynamic RAM),简称DRAM。主板上有一些靠近CPU的槽位,其中插着的内存条就属于这种类型的存储器。大家通常所说的主存(main memory)一般指的就是DRAM,也可以简称RAM。还有一种存储器比DRAM还慢,这就是机械硬盘或固态硬盘等磁盘(disk),主要相当于我们常说的硬盘。这种存储器用来长期保存数据,而不像缓存或RAM只用来暂时保存。

把权重最大的字节(或者说最高有效字节, most significant byte),保存到地址最低的地方,由于权重最大的字节(也就是”大端”,big end)出现在最前面,因此这种方式叫做大端在前的字节顺序(Big-Endian byte order),简称大端序或大尾序。还有一些计算机系统采用相反的顺序,把权重最小的字节(“小端”,little end)保存在最前面,这种保存方式称为小端在前的字节顺序(Little-Endian byte order),简称小端序或小尾序。Intel的x86与x86_64采用小端序

2.3 处理器:

CPU或者说处理器可以视为计算机的大脑。在计算机系统中,主要的算术运算与逻辑运算都靠这个部件来处理。宏观地来看,CPU由4个主要的部分组成,它们是:算术逻辑单元(Arithmetic Logic Unit, ALU)、控制单元(Control Unit, CU)、CPU时钟(CPU clock)及存储器(包括缓存和寄存器)。

ALU是CPU中执行数学运算的子组件,它所执行的算术与逻辑运算针对的是整数型操作数(要注意操作数的类型是整数,浮点数由另一个组件处理)。CU负责指挥CPU中的数据流,以确保CPU里的其它子组件能够在适当的时机接收到正确的数据,并做出相应的处理。CU还必须把指令执行周期(Instruction Execution Cycle)安排好,使得CPU指令能够据此得以执行(此外,为了正确执行这些指令,计算机还需要完成其它一些子任务)。CPU时钟与系统时钟不同,它是CPU本身的时钟,用来为CPU的操作计时。

有一个和CPU时钟相关的术语叫做时钟频率,它的单位是赫兹(Hertz, Hz)。说的简单一些:频率为1Hz的处理器其时钟每秒震荡1次。当前的处理器都是以MHz(megahertz, 兆赫)或GHz(gigahertz, 千兆赫)来描述频率的,1MHz意味着每秒震荡一百万次,1GHz意味着每秒震荡十亿次。如果要用时钟周期而不是频率来描述处理器的快慢,就给频率取倒数。CPU的时钟频率是系统时钟频率的倍数,具体是几倍由倍频系数(multiplier)决定。

由于处理器要对数据执行操作,因此必须要有地方来保存这些用作操作数的数据,此外,操作结果以及操作所涉及的地址也得有地方保存。存放这些指令与数据的存储器离ALU及CU越近越好。这种紧邻CPU的存储器就叫做cache(高速缓存,简称缓存),实际上,它跟逻辑电路一起位于CPU芯片中

当前的处理器缓存通常分为三个级别,分别是L1缓存(一级缓存)、L2缓存(二级缓存)与L3缓存(三级缓存)。缓存本身的层次结构与存储器的层次结构都遵循同一条原则:距离ALU越远容量越大。L1与L2缓存都离ALU很近,不过L2要比L1稍远一些,因此其容量也大一些。L3缓存一般出现在多核处理器中,它为所有CPU核心所共享,而L1与L2缓存则每个CPU核心都配有一套,如下图所示:L3缓存是静态存储器中的最后一层,如果数据不保存在该层及其上方的各层中,那就只好放到它下方的RAM中了。

CPU提供了一些与缓存有关的指令,然而开发者一般都不用专门编写代码去访问或操作缓存,因为缓存是由复杂的算法来控制的,以确保程序所需的数据能够尽量出现在缓存中,所以开发者通常不需要干预这套机制。比方说,如果程序频繁引用某个变量,那么处理器就有可能认为这份数据相当重要,从而将其预先获取(prefetch, 简称预取)出来并放入缓存,使得程序以后访问该数据时能够快一些。CPU会在执行程序的过程中随时根据情况来做出这种处理。

除了缓存之外,CPU中还有一种存储器叫做寄存器(register),它的内容可以通过明确的地址来访问,而且在此类存储器中它是最快的一种。它位于整个存储器层级的最顶端,其容量比缓存更小,速度也比缓存更快。寄存器是最贴近ALU的一小块存储区域,用来保存执行指令时所涉及的操作数、地址及结果

处理器的每个核心都有自己的一套L1与L2缓存,与之类似,每个核心也都有自己的一套寄存器。然而,编写汇编代码的时候你不用指出当前操作的寄存器究竟处在哪个核心上,你只需要写成寄存器的名字就可以了,至于这个寄存器到底指的是哪个核心上的寄存器则由CPU决定。下表列出了寄存器名称:

寄存器可以分成4:通用目的寄存器(General Purpose Register)、段寄存器(Segment Register)、标志寄存器(Flags Register)及指令指针寄存器(Instruction Pointer Register)。汇编程序所操作的基本上都是通用目的寄存器,其中,32位的通用寄存器有8个,64位的有16个。通用寄存器用来执行计算或移动数据。由于64位处理器是在32位设计方案的基础上构建的,而32位处理器又是在16位设计方案的基础上构建的,因此新式处理器不仅可以通过寄存器本身的名字来使用该寄存器,而且还能通过旧式处理器所用的名字将其当成旧式的寄存器来使用。此外,如果你要使用的数据或是你要执行的运算只需占据8个二进制位,那么可以把16位的寄存器想象成两个8位的寄存器,这样就可以用它来保存两份数据了。凡是以小寄存器的名义来操作大寄存器的,其实操作的都是大寄存器中与这个小寄存器相对应的那一部分二进制位

某些64位及32位寄存器的特殊用法

(1). rax/eax通常是默认的累加寄存器。乘法等操作会将其中一部分结果自动存放到rax/eax中,调用函数的时候也需要把返回值保存在rax/eax中。因此,执行这些操作时不要用rax/eax保存一般的数据。

(2). rcx/ecx用来在执行循环的过程中记录循环计数器的值。因此,在循环内部不要用rcx/ecx保存一般的数据。

(3). rbp/ebp用作栈帧中的帧指针。该寄存器用来指向栈中的数据。建议只把它当做专门的寄存器来用。

(4). rsp/esp是栈指针寄存器,这也是个与栈管理有关的寄存器,它一般指向活动栈帧的顶部。与前一个寄存器一样,这个寄存器也只应该当成专门的寄存器来用。

(5). rsi/esi与rdi/edi是索引寄存器(index register, 也称为变址寄存器),它和STOSB、MOVSB与SCASB这样的字符串操作结合起来使用,以便保存、加载或扫描大量的数据。这些操作实际上会把CPU置于一种自动循环模式中,这要比开发者手工编写循环更有效率。

(6). rip/eip是扩展版的指令指针寄存器。这个寄存器用来指向内存中的地址,以表示接下来应该获取、解码并执行的指令,它是在程序运行过程中自动调整的,不应该通过编程的手段修改。

(7). rflags/eflags是状态与控制寄存器。LAHF与SAHF等特殊指令可以把CPU的一些状态标志载入ah寄存器,或是将ah寄存器里的值保存到状态寄存器中。除此以外,不应该用其他手段直接修改rflags/eflags。该寄存器里的二进制位是在执行完算术运算之后根据一套布尔规则自动设置的。尽管rflags是64位,但其中能够用到的只有低32位,因此,x86与x86_64处理器用的是同一套状态标志

CPU标志(CPU flag)是一些二进制位的统称,这些二进制位分别用来以某种方式控制CPU操作或反映CPU操作的状态。下表列出了大多数开发环境中值得关注的8个标志位,其中某些标志可以由开发者通过LAHF及SAHF指令来操作。

64位处理器:x86_64指令集是对x86指令集(这是一种32位指令集)的扩充,因此能够在32位环境下执行的操作,同样可以放在64位的处理器中执行。由于x86_64处理器是64位的,因此其数据与地址都可以用64个二进制位来表示,然而,当前的x86_64处理器只用到了其中的低48位,所以说,尽管理论上能够在2^64字节的地址空间中寻址,但实际上最多只支持2^48字节的地址空间48个二进制位来表示物理地址空间中的地址意味着RAM(内存)容量可达256TB,这比32位处理器所支持的4GB要高出很多。除了支持更大的RAM地址空间,x86_64处理器还多提供了8个通用的寄存器R8~R15.

指令的执行:要执行指令(例如ADD)必须遵循一系列步骤,这些步骤合起来构成指令执行周期(Instruction Execution Cycle)。指令执行周期一般表示成Fetch(获取)、Decode(解码)及Execute(执行)这三个大的阶段。

2.4 输入与输出:

键盘、显示器、网卡等外部设备需要通过I/O模块(Input/Output Module)与电脑相连,而这些I/O模块同时也与系统总线相连,使得外部设备能够与计算机中的其它组件(例如CPU)通信。除了在处理器与外部设备之间提供缓冲通信(buffered communication)机制之外,I/O模块还可以完成其它一些重要的操作,例如传输数据、对命令进行解码,以及查询设备状态等。由于速度不同,I/O模块必须提供数据缓冲区,使CPU的工作速度不会因为外部设备而受到影响,同时也令外部设备不会为CPU发来的大量数据所淹没。

处理器执行I/O操作通常可以采用4种方式:程序I/O(Programmed I/O, PIO)、中断驱动I/O(Interrupt-driven I/O)、直接内存访问(Direct Memory Access, DMA)以及I/O通道(I/O Channel)。

程序加载:程序是由一个叫做程序加载器的工具来加载的。加载进来之后,CPU(主要是说它的eip/rip寄存器)指向程序的入口点,这个入口点可能叫做main或start等。下面描述启动程序的一般步骤。当用户开启程序(例如用鼠标双击程序的图标)时:

(1). 操作系统把文件的大小以及该文件在磁盘中的物理位置等信息获取出来。

(2). 操作系统在内存中寻找合适的地点分配空间,并把必要的信息放在描述符表(descriptor table)中。

(3). 操作系统开始执行程序的第一条指令(也就是位于入口点的那条指令)。这时程序变为进程,并获得由系统所赋予的ID。

(4). 该进程自行运作,而操作系统则会对进程所发出的资源请求予以响应。

(5). 进程结束并让出它所占据的内存。

GitHub:https://github.com/fengbingchun/CUDA_Test

汇编程序设计与计算机体系结构软件工程师教程笔记:处理器、寄存器简介相关推荐

  1. 汇编程序设计与计算机体系结构软件工程师教程笔记:内联汇编与宏

    <汇编程序设计与计算机体系结构: 软件工程师教程>这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译.中文版是2019年出版的.个人感觉这本书真不错,书中介绍了 ...

  2. 汇编程序设计与计算机体系结构软件工程师教程笔记:函数、字符串、浮点运算

    <汇编程序设计与计算机体系结构: 软件工程师教程>这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译.中文版是2019年出版的.个人感觉这本书真不错,书中介绍了 ...

  3. 汇编程序设计与计算机体系结构软件工程师教程笔记:指令

    <汇编程序设计与计算机体系结构: 软件工程师教程>这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译.中文版是2019年出版的.个人感觉这本书真不错,书中介绍了 ...

  4. 汇编程序设计与计算机体系结构软件工程师教程笔记:总结

    <汇编程序设计与计算机体系结构: 软件工程师教程>这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译.中文版是2019年出版的.个人感觉这本书真不错,书中介绍了 ...

  5. 汇编程序设计与计算机体系结构软件工程师教程笔记:汇编语法基础知识

    <汇编程序设计与计算机体系结构: 软件工程师教程>这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译.中文版是2019年出版的.个人感觉这本书真不错,书中介绍了 ...

  6. 计算机四级c语言题库及答案,2016计算机四级软件工程师考试题库及答案

    2016计算机四级软件工程师考试题库及答案 (B)软件故障是指软件代码中的错误 (C)在软件的一次运行期间,软件故障一定会导致软件失效 (D)通常修改软件故障可以降低软件发生失效的概率,从而提高软件可 ...

  7. Udacity机器人软件工程师课程笔记(五)-样本搜索和找回-基于漫游者号模拟器-自主驾驶

    9.自主驾驶 在接下来的环节中,我们要实现漫游者号的自动驾驶功能. 完成这个功能我们需要四个程序,第一个为感知程序,其对摄像头输入的图片进行变换处理和坐标变换使用.第二个程序为决策程序,功能是帮助漫游 ...

  8. 全国计算机等级考试二级c语言程序设计,全国计算机等级考试二级教程:C语言程序设计(2016年版) pdf epub mobi txt 下载...

    全国计算机等级考试二级教程:C语言程序设计(2016年版) pdf epub mobi txt 下载 图书介绍 ☆☆☆☆☆ 教育部考试中心 编 下载链接在页面底部 发表于2021-05-17 类似图书 ...

  9. 非核心版本的计算机上_计算机四级网络工程师知识点笔记(备考指南)

    计算机四级网络工程师是先要通过计算机三级网络技术. (计算机三级网络技术笔记翻公众号历史文章) 计算机四级是考两个科目 操作系统30个选择题10个多选题 计算机网络30个选择题10个多选题 两科各拿3 ...

最新文章

  1. colorAccent,colorPrimary,colorPrimaryDark 作用的地方
  2. 在pcDuino上运行Python
  3. Microsoft.Office.Interop.Excel的用法
  4. 【Git/Github】第一次提交和再次添加文件
  5. bert简介_BERT简介
  6. ftp 工具_ftp工具,ftp工具有哪些
  7. navicat for mysql 用户_Navicat for MySQL 怎么/怎么添加管理用户?Navicat for MySQL 添加管理用户教程_37游游网...
  8. AC日记——红色的幻想乡 洛谷 P3801
  9. 物联网解决方案应用之智能安防运维解决方案
  10. 使用readelf和objdump剖析目标文件
  11. Activity启动模式之SingleTask
  12. Java经典兔子问题(10个月幼兔,小兔,成兔数量各多少对?)
  13. linux huge模式设置,Linux 下 Hugepages的配置
  14. 水果食用大全 -- 果品食疗 - 雪梨
  15. 三只松鼠3次方新品魅力何在?
  16. HDU1859 最小长方形 (水
  17. wireshark的usb抓包分析 2 - 分析数据
  18. virtual Box与Vagrant的安装与踩坑
  19. 用SQL语句创建数据库和表
  20. 张小军详解华为区块链政务应用,指出区块链的应用领域正从金融向全领域拓展...

热门文章

  1. 3. 5种常见卷积论文、解读、使用方法、实现代码整理(conv)
  2. 命令行创建React项目
  3. GameMaker Studio从头开始学习设计和开发3款游戏
  4. 【UE5教程】影棚拍摄于虚拟场景合成制作流程学习
  5. grep 在HP-UX下的递归查找
  6. 基于持久内存的 单机上亿(128B)QPS -- 持久化 k/v 存储引擎
  7. HashMap 和 Hashtable 的 6 个区别,最后一个没几个人知道!
  8. http 权威指南 目录
  9. 国外物联网平台初探(四):Ayla Networks
  10. [冲昏头脑]IDEA中的maven项目中学习log4j的日志操作