编写一个分支程序,比较寄存器AL与BL中两个8位无符号数的大小,如果两个数相等

 
 
 
 
 

From:ARM汇编语言入门(一):



ARM 使用 加載(Load)/ 存储(Stroe)指令来读写内存这意味着你只能使用 LDR 和 STR 指令访问内存。在 ARM 上数据必须从内存中加载到寄存器之后才能进行其他操作而茬 x86 上大部分指令都可以直接访问内存中的数据。如前所述在 ARM 上增加内存里的一个 32-bit 数据值,需要三个指令( loadincrement,store )为了解释 ARM 上的 Load 和 Store 操作嘚基本原理,我们从一个基本示例开始然后再使用三个基本偏移形式,每个偏移形式具有三种不同的寻址模式为了简单化,每个示例我们将在同一段汇编代码中使用不同  LDR/STR 偏移形式的。遵循这本段教程的最佳方法是在你的测试环境中用调试器(GDB)运行代码示例

偏移形式:立即数作为偏移量

偏移形式:寄存器作为偏移量

偏移形式:缩放寄存器作为偏移量

LDR 用于将内存中的值加载到寄存器中,STR 用于将寄存器內的值存储到内存地址

LDR : 把 R0 内保存的值作为地址值,将该地址处的值加载到寄存器 R2 中

STR : 把 R1 内保存的值作为地址值,将寄存器 R2 中的值存储到該地址处

下面是汇编程序的样子:

当加载数据到寄存器中时,使用 [] 符号意思时:取寄存器中的值作为地址值然后再从该地址处加载数據到目标寄存器中,如果不加 [] 那就是将寄存器中保存的值直接加载到目标寄存器

同样 STR 命令中也是一个意思。

这听起来比实际要复杂的多没关系,下面是一个更直观的演示图:

下面我们看一下调试器中的这段代码:

相对寻址因为我们使用了标签,所以编译器可以计算出攵本池中标签的地址相对位置(pc+12)您可以使用这种精确的方法自行计算位置,也可以像前面一样使用标签唯一的区别是,相较于使用標签你需要计算值在文本池中的确切位置。在这种情况下它距离有效的 PC 位置有3个跳转(4+4+4=12)。本章稍后将介绍有关PC相对寻址的介绍

如果你忘了为什么有效PC指向当前指位置后两个指令,在第二部介绍了[...在执行过程中在ARM模式下,PC将当前指令的地址加上8(两个ARM指令)作为最終值存储起来在Thumb模式下,将当前指令加上 4(两个Thumb指令)作为最终值存储起来而x86中PC始终指向要执行的下一个指令...]

1. 偏移模式:立即数作为偏移量

这里,我们使用立即(整数)作为偏移量从基寄存器(以下示例中的 R1)中增加或减去此值,在编译时可以用已知的偏移量访问数據

将R2中的值(0x03)存储到该地址处,其中R1中的值不会被修改 str r2, [r1, #4]! @ 前变址寻址:以R1中的值为基准加上立即数4作为最终地址, 将R2中的值(0x03)存储箌该地址处其中R1中的值被修改为:R1+4。 ldr r3, [r1], #4 @ 后变址寻址:将R1中的值作为最终地址获取该地址处的数据加载到R3, 其中R1中的值被修改为:R1+4

假设鉯上程序文件为ldr.s,编译并用GDB允许看看会发生什么。

GDB(包含gef)中在_start处设置断点,运行程序

系统上的寄存器现在填充了以下值(注意,這些地址在你的系统上可能有所不同):

下一条指令将在偏移地址模式下执行STR指令它将把R2中的值(0x)存储在:R10xc)+偏移(#2)= 0x1009e地址处,运荇完该条指令后用x/w命令查看0xc处的值为0x3完全正确。

再下一条~指令是前变址寻址可以根据“!”来识别该模式。唯一区别是基准寄存器会被更新为最终访问地址。这意味着我们将R2 (0x3) 中的值存储到 地址:R1 (0x1009c)+ 偏移量(#4

最后一条LDR指令是后变址寻址。意思是R1中的值作为最终訪问地址获取最终访问地址处的值加载到R3。然后将R10x100A0)更新为R1(0x100A0)+ 偏移(#4)= 0x100a4运行完该命令看看寄存器R1R3的值。

下图是实际发生的事情:

2. 偏移模式:寄存器作为偏移量(寄存器基址变址寻址

这种偏移是使用寄存器作为偏移量下面的示例是,代码在运行时计算要访问的數组索引

str r2, [r1, r2] @ 以R1中的值为基准地址,R2中的值(0x03)为偏移量获得最终访问地址, 将R2中的值(0x03)存储到该地址处基准寄存器R1中的值保存不变。 str r2, [r1, r2]! @ 前变址寻址:以R1中的值为基准地址R2中的值(0x03)为偏移量,获得最终访问 地址将R2中的值(0x03)存储到该地址处,基准寄存器R1中的值更新為R1+R2 ldr r3, [r1], r2 @ 后变址寻址:以R1中的值为最终访问地址,获取该地址处的数据并加载到R3 基准寄存器R1中的值更新为R1+R2。

第二条STR指令操作是前变址寻址莋了同样的操作,不同的一点是R1的值会被更新:R1=R1+R2

3. 偏移模式:缩放寄存器作为偏移量(寄存器基址变址寻址)

第三中偏移形式是缩放寄存器作为偏移量。这种情况下Rb是基地址寄存器,Rc是一个被左移或右移(<shifter>位移操作)缩放过的立即数(Rc中保存的值)意思是桶型位移操作鼡来缩放偏移量。下面是一个在数组上循环遍历的例子可以在GDB中运行看一下:

最终访问地址,将R2中的值(0x03)存储到该地址基准寄存器R1中嘚值不变。

下面是程序运行时的样子:

最后一条LDR指令操作使用了后变址寻址意思是,加载R1中的值0x100a8地址处的数据到寄存器R3然后将R2中的值咗移两位(0x03<<2=0xc)得到值0xC,再加上R1中的值0x100a8得到0x100b4最后R1的值更新为0x100a8R1

  • 如果带有!,就是前变址寻址
  • 如果基地值寄存器(R1)带中括号就是后变址寻址
  • 其他的都是带偏移量的寄存器间接寻址

LDR是唯一用来加载数据到寄存器中的指令。语法如下:

这些指令被称为伪指令我们可以使用此语法来引用文本池中的数据。在上面的示例中我们使用这些伪指令引用一个函数的偏移量,在指令中将一个32位常量加载到寄存器中我需偠使用此语法在一个指令中将 32 位常量移动到寄存器中的原因是,ARM 只能一次加载 8 位值什么?要了解原因您需要了解 ARM 上如何处理立即数的。

在ARM上加载一个立即数到寄存器中并不像x86上那么简单ARM对于立即数有很多限制。这些限制是什么以及如何处理它们并不是ARM汇编所关心的泹请相信我,这只是为了有助于你理解并且有一些技巧可以绕过这些限制(提示:LDR)。

我们知道ARM指令长度是32位并且所有指令都是可条件执行指令。其中有16种条件码就要占用4位(2^4=16),然后还要2位代指目标寄存器2位代指操作寄存器,1位作为状态标志加起其他一些操作码占用的位。到这里分配完指令类型寄存器以及其他位段,最后只剩下12位用来操作立即数最多只能表示4096个数。

这意味着ARM中MOV指令只能操作┅定范围内的立即数如果不能直接被调用,就必须被分割成多个部分用众多小数字拼起来。

还没完这12位还不全是用来表示一个整数,其中8位用来表示0-255范围的数n4位表示旋转循环右移(其实ARM中只有一种位移,就是旋转循环右移左移也是通过旋转循环右移得到)的次数r(范围0-30)。所以一个立即数的表示形式是:v = n ror 2*r也就是说,只能以偶数进行旋转循环右移一次移动两位,n组成的有效位图必须能放到一个芓节(8位)中

下面是一些有效和无效的立即数:

译注:1.以上立即数都是32位长度。2.旋转循环右移:每位都向右移动末位不断放到最前位,类似首尾相连3.有效位图要能放到一个字节中:例子中#511的二进制为00 01 1111 1111,有效位图为1 超过一个字节。#0x的二进制位?01 00 0000?有效位图110 超过一个芓节。

其结果是无法一次加载完整的 32 位地址我们可以通过使用以下两个选项之一来绕过此限制:

  1. 用较小的值构造较大的值
  • 使用加载方式“ldr r1, =value”,编译器会很乐意将其转换位MOV指令或者是PC相对寻址来加载。
  • 如果你加载了一个无效的立即数那么编译器会报错:“Error: invalid constant”。如果遇到這种问题你应该知道怎么做

    如果尝试编译,编译器会输出类似以下错误:

    你应该把511拆成几个小数值或者用前面介绍的LDR方式。

    如果你想判断一个立即数是否是有效的立即数你可以用我写的python脚本 :

    From:ARM 汇编语言入门(五):

    有时你想要更有效率,一次加载(或存储)多个值为此我们可以使用 LDM(load multiple)和 STM(stroe multiple)指令。这些指令有各种变体基本上只因访问初始地址的方式而异。这是我们本节将要使用的代码将一步步地认识这些指令。

    开始之前你一定要记住.word是指内存中的数据是32位,也就是4字节这对理解地址偏移量很重要。程序中的.data段分配了一個空白的数组有5个元素。我们将它作为可写内存来进行数据存储.text段包含我们的代码,以及包含两个标签的只读数据段一个标签是包含7个元素的数组,第二个标签用来桥接.text段和.date段以便我们可以访问保存在.data中的array_buff。

    使用ADR指令(惰性方法)获取words的第四个元素(words[3])的地址存儲到R0。定位到words数组的中间以便接下来向前和向后操作。

    我们一条指令就加载了两个数据让R4=0xR5 = 0x

    很好,现在再用STM指令一次存储多条数据徝代码中STMR4R5分别获取值0x030x04,然后依次存储到R1指定的地址处前面的指令让R1通过array_buff_bridge指向了数组array_buff的开始位置,最终运行结果:array_buff[0]

    before)这些变种依据第一个操作数(保存源地址或目标地址的寄存器)指定的不同的内存访问方式而不同。在实践中LDMLDMIA相同,意思是第一个操作数(寄存器)内的地址随着元素的加载而不断增加通过这种方式我们根据第一个操作数(保存了源地址的寄存器)获取一连串(正向)的数据。

    LDMIB指令先将源地址加4个字节(一个字)然后再执行加载这种方式下我们仍然会得到一串加载的数据,但是第一个元素是从源地址偏移4个芓节开始的这就是为什么例子中LDMIB指令操作后R4中的值是0x (words[4])而不是R0所指的0xwords[3])的原因。

    当使用LDMDA指令所有的操作都是反向的R0当前指向words[3],当執行指令时反方向加载words[3]words[2]words[1]到寄存器R6R5R4是的,寄存器也是按照反向顺序执行完指令后R6 0x。这里的逻辑是每次加载后都将源地址递减┅次。加载时寄存器按照反方向是因为:每次加载时地址在减小寄存器也跟着反方向,逻辑上保证了高地址上对应的是高寄存器中的值再看一下LDMIA(或LDM)的例子,我们首先加载低寄存器是因为源地也是低地址然后加载高寄存器是因为源地址也增加了。

    执行后R4、R5和R6的值:

    執行后R4、R5和R6的值:

    进程中有一个叫做的内存位置栈指针(SP)寄存器总是指向栈内存中的地址。程序应用中通常使用栈来存储临时数据前面讲的ARM中只能使用加载和存储来访问内存,就是只能使用LDR/STR指令或者他们的衍生指令(LDMSTMLDMIALDMDASTMDA等等)进行内存操作在x86中使用PUSH和POP从栈內取或存,ARM中我们也可以使用这条指令

    当我们将数据 PUSH 入向下生长的栈(详见Part 7:堆栈与函数)时,会发生以下事情:

    1. 首先SP中的地址减少4(译注:4字节=32位)。
    2. 然后数据存储到SP的新地址值处。

    当数据从栈中 POP 出时发生以下事情:

    1. 当前SP中地址处的数据加载到指定寄存器中。

    再GDBΦ调试运行一下:

    运行完头两条指令后先查看一下SP指向的地址以及地址处的数值下一条PUSH指令会将SP减去8,并且将R1R0中的值按顺序压入栈中

    接下来栈中的值0x03和0x04弹出到寄存器中。

    From:ARM 汇编语言入门(六):

    在探讨 CPSR 时我们已经接触了条件状态我们通过跳转(分支)或者一些只有滿足特定条件才执行的指令来控制程序在运行时的执行流。通过CPSR寄存器中的特定bit位来表示条件状态这些位根据指令每次执行的结果而不斷变化。例如比较运算时如果两个数相等,那么就置CPSR中的Zero位(Z=1)实际上是因为:a - b = 0,这种情况下就是相等状态如果第一个数大,那么僦是大于状态如果第二个数大,就是小于状态除此之外,还有小于等于大于等于等等

    下面的表格列出了可用的条件状态码,描述囷标志位:

    在下面代码片段中看一下执行条件加法时的实际用法L:

    第一条cmp指令结果导致CPSR中的负数位置1(2- 3 = -1)意思是R0小于R3因为满足小于条件(CPSR中的溢出位不等于负数位V != N)所以接下来的ADDLT指令执行。在执行下一条cmp指令时R0 = 3。所以清除负数位(3 - 3 = 0负数位清零),零位置位(Z = 1)现在溢出位是0,负数位是0不满足小于条件。所以最后一条ADDLT指令不执行R0值保持3不变。

    Thumb 模式下的 条件执行

    我们在介绍指令集的章节讨论了Thumb状态丅的不同具体而言是Thumb-2版本支持条件执行。某些 ARM 处理器版本支持"IT"指令允许在 Thumb 状态下支持多达4个条件执行指令。参考:

    • cond 指定 IT 块的第一个指令的条件。
    • x 指定 IT 块中第二个指令的条件开关
    • y 指定 IT 块中第三个指令的条件开关。
    • z 指定 IT 块中第四个指令的条件开关

    其实IT指令的结构就是“IF-Then-(Else)”,语法都是由字母“T”和“E”构成:

    • IT:If-Then(下一条指令是条件的);

    IT块中的每条指令必须指定相同或逻辑相反的条件后缀意思是,如果使用ITE那么前两个指令必须有相同的后缀,而第三个必须是逻辑相反的后缀下面是 ARM 参考手册中的一些示例,说明了这些逻辑:

    ITTE NE ; 接下来嘚3条指令都是有条件的
    ITE GT ; 接下来的2条指令都是有条件的。
    ITTEE EQ ; 接下来的4条指令都是有条件的
    BNE.W dloop ; 分支指令只能在IT块的最后一个指令中使用。
    IT NE ; 下一條指令是条件的 
     
    下面是条件代码和相反代码:





    现在使用以下代码来测试:

    bx r3 @ 跳转到R3中的地址处,并切换运行模式 ->切换到Thumb模式因为R3最低有效位(LSB) = 1。



    示例中的代码开始在ARM模式下第一条指令将PC中的地址值加1并存储到
    R3,然后bx指令跳转到R3中的地址位置并且模式切换成Thumb模式,因為R3中的值最低有效位为1(0不切换)为此使用bx(分支+交换)非常重要。

    Thumb模式下首先比较R010,结果将负数位N置位(0 - 10 = -10)之后使用If-Then-Else块,因為零位Z(Zero)没有被置位所以ADDEQ指令被跳过然后因为结果不相等所以执行ADDNE指令。
    GDB 中单步执行此代码会干扰结果因为你要在 ITE 块中执行这两個指令。 但是在 GDB 中运行代码而不设置断点并单步执行每个指令将生成正确的结果设置 R1 = 3。
     
    分支(跳转)允许我们跳转到另一个代码段当伱需要跳过(或者重复)某块代码或者跳转到指定的函数的时候,分支很有用此类情形中最佳的示例是IF和循环。先来看看IF案例
    上面代碼是比较两个初始值并返回最大值,C语言伪代码:
    现在再看一下怎么使用条件分支实现循环:
     
    有三种类型的分支指令:
      • 简单的跳转到一个函数
      • 将PC+4的值保存到LR寄存器,然后跳转
    • 带状态切换的跳转(BX)和带状态切换及链接的跳转(BLX)
      • 与 B 和 BL 一致,只是添加了工作状态的切换( ARM模式 - Thumb模式 )
      • 需要寄存器作为第一个操作数。
     

    这里的技巧是获得当前PC的值加1然后保存到一个寄存器,然后跳转(并且切换状态模式)到這个寄存器内的地址可以看到加指令(add r2, pc, #1)获取到有效的PC地址值(当前PC内的值+8=0x805C)然后加1(0x805C + 1 = 0x805D)。接下来我们跳转的地址( 0x805D = 11101)最低有效位为1,那么意味着地址不是4字节(32bit)对齐的跳转到这样的地址不会导致非对齐问题。在GDB中运行的样子(含GEF):

    注意上面的 gif 图片是在低版本的 GEF 丅创建的所以你的显示界面可能不一样,但是逻辑是一样的
     
    分支也可以有条件地执行,用于在满足特定条件时跳转到函数我们看一個使用BEQ应用条件分支的例子,这是一段没太有用的汇编代码只不过是在寄存器等于特定值时将一个值移动到寄存器并跳转到另一个函数嘚过程。

     
    ARM汇编语言入门(七):
    在这一部分我们来看一下进程中叫做的内存区域本章涵盖了栈的用途和相关操作。此外我们将介绍 ARM 中函数的实现、类型和差异
     
    一般而言,栈就是进程中的一段内存这段内存是在进程创建时分配的。我们使用栈来保存一些临时数据如函数中的局部变量,函数之间转换的环境变量等使用PUSH和POP指令与栈进行交互。在Part 4:内存指令:加载与存储中我们讲到PUSH和POP是一些其他内存操莋指令的别名这里为简单起见我们使用PUSH和POP指令。
    在看实例之前我们先要明白栈有多种实现方式。首先当我们说栈增长了,意思是一個数据(32位)被放入了栈中栈可以向上增长(当栈是按照降序方式实现)或者向下增长(当栈是按照升序方式实现)。下一条信息将被放置的实际位置是由栈指针定义的准确的说是保存在寄存器SP中的地址指定的。地址可以是栈中的当前(最后入栈)项或者下一个可用的內存位置如果SP指向的是栈中的最后一个项(完整栈实现方式),那么是先增加(向上增加栈)或减小(向下增长栈)SP再放入数据;如果SP指向的是栈内下一个有效的空位置那么是数据先入栈后再增加SP(向上增加栈)或减少SP(向下增长栈)。

    总结了栈的不同实现我们可以鼡以下表格列出了不同情况下使用不同的多数据存储或多数据加载指令。

    我们的例子中使用了完整降序栈(Full descending)下面是一个简单例子,看┅下这种栈是如何处理栈指针的
    在一开始,栈指针指向地址0xbefff6f8 (你的环境中可能不同)代表栈中的最后一项值这时我们看一下这个地址處的值(同样,你的环境中可能不同):
    当执行完第一条MOV指令后栈内数据没有变化。当执行PUSH指令时将发生以下事情:首先SP的值减4(4 bytes = 32 bits);然后R0中的值保存到SP指定的地址处。现在再看一下SP中指定的地址处的值:
    例子中的指令mov r0, #3用来模拟R0中的数据被覆盖的情形然后使用POP再将之湔的数据恢复。所以当执行POP指令时,实际发生了以下事情:首先从当前SP指向的内存地址(0xbefff6f4)处读取一个32位的数据(前面PUSH时保存的2)然後SP寄存器的值减4(变成0xbefff6f8 ),最后将从栈中读取的数值2保存到R0
    (注意,下面的gif展示的栈的低地址在上面高地址在下面。不是前面展示不哃堆栈实现时的图片的那种方式这样是为了让栈看起来跟GDB中展示一样):

    我们看一下函数如何利用Stack来保存本地变量、保留寄存器状态。為了让一切变得井然有序函数使用栈帧(专门用于函数中使用的局部内存区域)。栈帧是在函数开始调用时创建的(下一节将详细介绍)栈帧指针(FP)被置为栈帧的底部,然后分配栈帧的缓冲区栈帧中通常(从底部)保存了返回地址(前面的LR寄存器值)、栈帧指针、其他一些需要保存的寄存器、函数参数(如果超过4个参数)、局部变量等等。虽然栈帧的实际内容可能有所不同但基本就这些。最后栈幀在函数结束时被销毁
    下面是栈中栈帧的示意图:

    为了直观点,再看一段代码:
    下面的GDB截图中我们可以看一下栈帧的样子:

    从上图中我們可以看到当前我们即将离开函数max(反汇编代码底部的箭头)时,这时FPR11寄存器)指向栈帧最底部的0xbefff254。看栈中的绿色地址保存了返回哋址0x(前面的LR寄存器)再往上4字节的地址处(0xbefff250)保存值0xbefff26c,这是前一个栈帧指针(FP)地址0xbefff24c0xbefff248处的0x10x2是函数max运行时的局部变量。所以刚才汾析的这个栈帧只包含了LRFP和两个局部变量。
     
    要理解 ARM 中的函数首先要熟悉函数体的结构:开始、执行体和收尾。
    开始时需要保存程序前媔的状态(LR和R11分别入栈)然后为函数的局部变量设置堆栈虽然开始部分的实现可能因编译器而异,但通常是用PUSH/ADD/SUB指令来完成的大体看起來是下面这样:
    函数体部分就是你程序的实际逻辑区,包含了你代码逻辑的各种指令:
    上面的代码展示了为函数设置局部变量并跳转到另┅个函数的过程同时还展示了通过寄存器为另一个函数(max)传递参数的过程。在某些情况下当要传递的参数超过4个时,我们需要另外使用栈来存储剩余的参数还要说明一下,函数通过寄存器R0返回结果所以不论max函数结果是什么,最后都要在函数结束返回后从R0中取返回徝在某些情况下,结果可能是 64 位的长度(超过 32 位寄存器的大小)这时候就需要结合R0和R1来存储返回值。
    函数的最后部分用于将程序的状態还原到它初始的状态(函数调用前)这样就可以从函数被调用的地方继续执行。所以我们需要重新调整栈指针(SP)这是通过加减帧指针寄存器(R11)来实现的。重新调整栈指针后将之前(函数开始处)保存的寄存器值从堆栈弹出到相应的寄存器来还原这些寄存器值。根据函数类型一般POP指令是函数最后结束的指令。但是在还原寄存器值后,我们需要使用 BX 指令来离开函数示例如下: pop {r11, pc} /* 恢复栈帧指针, 通過加载之前保存的LR到PC,程序跳转到之前LR保存位置函数的栈帧被销毁 */
    1. 函数在开始时设置相应的环境。
    2. 函数体中执行相关逻辑然后通过R0保存返回值。
    3. 函数收尾时恢复所有的状态以便程序可以在函数调用前的位置继续执行。

    另一个重要的知识点时函数类型:叶子函数和非叶孓函数叶子函数在函数内不会调用/跳转到另一个函数。非叶子函数则会在自己的函数逻辑中调用另一个函数这两种函数的实现方式类姒。不过也有一些不同。我们用下面的代码分析一下:

    add r11, sp, #0 /* 设置栈帧的底部(译注:其实是将sp的值给R11栈指针指向初始的栈帧指针位置(栈幀底部)) */ sub sp, sp, #16 /* 在栈上分配一些内存作为接下来局部变量要用的缓存区(译注:栈指针减16,相当于将栈帧指针往下移动了16字节)) */

    上面的例子包含两个函数:main函数是一个非叶子函数max函数是叶子函数。之前说了非叶子函数有跳转到其他函数的逻辑(bl , max)而max中没有(最后一条是跳轉到LR指定的地址,不是函数分支)这类代码所以是叶子函数。

    另一个不同点是函数的开始与收尾的实现有差异来看一段代码,这是叶孓函数与非叶子函数在开始部分的差异:

    不同之处是非叶子函数保存了更多的寄存器原因也很自然,因为非叶子函数中执行时LR会被修改因此要先保存LR以便最后恢复。当然如果有必要也可以在函数开始时保存更多的寄存器

    下面这段代码可以看到,叶函数与非叶函数在收尾时的差异主要是在于叶子函数在结尾直接通过LR中的值跳转回去,而非叶子函数需要先通过POP恢复LR寄存器再进行分支跳转。

    最后我们偠再次强调一下在函数中BL和BX指令的使用。在我们的示例中通过使用BL指令跳转到叶子函数中。在汇编代码中我们使用了标签在编译过程Φ,标签被转换为相对应的内存地址在跳转到对应位置之前,BL会将下一条指令的地址存储到LR寄存器中这样我们就能在函数max结束的时候返囙了

    BX指令在被用在我们离开一个叶函数时,使用LR作为寄存器参数刚刚说了LR存放着函数调用返回后下一条指令的地址。由于叶函数不会茬执行时修改LR寄存器所以就可以通过LR寄存器跳转返回到main函数了。同样可以使用BX指令帮助我们切换ARM模式和Thumb模式可以通过LR寄存器的最低比特位来完成,0代表ARM模式1代表Thumb模式。

    换一种方式看一下函数及其内部下面的动画说明了非叶子函数和叶子函数的内部工作过程。

}

1.(10分)2764的容量是:地址范围昰:

6116的容量是:,地址范围是:

8255各端口地址是:PA口PB口,

2.(4分)8255A的PA口、PB口分别工作在何种方式

3.(4分)对于图示电路,当显示程序执荇时显示器自左向右显示的字符分别为:

4.(6分)若采用共阳极LED显示器,而程序不改动则接口电路硬件需如何改动?

若采用共阳极LED显礻器而硬件不改动,则软件的数据段和程序段如何改动

5.(6分)若将原四位共阴极LED显示器,扩展至八位共阴极LED显示器则硬件需改动嘚是:

学年第学期微机原理及应用(A)课程试卷

一、选择题:(每题1.5分,共18分)

1、 CPU经加电复位后执行第一条指令的地址是()。

2、在用端口寻址方式寻址外设的CPU中区分对外设还是对内存操作是由

?软件包?数据线?控制线?地址线

3、CPU响应中断后,通过()完成断点的保护

?执行开中断指囹?执行关中断指令

?执行PUSH指令?内部自动操作

4、常用的虚拟存储系统由()两级存储器组成

? CACHE—辅存?通用寄存器—主存

}

1.微处理器内部结构由哪几部分组荿阐述各部分的主要功能。

2.微处理器级总线有哪几类各类总线有什么作用?

3.为什么地址总线是单向的而数据总线是双向的?

4.微处理器内部有哪些寄存器其主要作用是什么?

5.如果某微处理器有20条地址总线和16条数据总线:

(1)假定存储器地址空间与I/O地址空间是分开的則存储器地址空间有多大?

(2)数据总线上传送的有符号整数的范围有多大

6.将十六进制数62A0H与下列各数相加,求出其结果及标志位CF、AF、SF、ZF、OF和

7.从下列各数中减去4AE0H求出其结果及标志位CF、AF、SF、ZF、OF和PF的值:

8.什么是逻辑地址?什么是物理地址它们之间的关系如何?

9.写出下列存储器地址的段地址、偏移地址和物理地址:

10.给定一个数据的有效地址为2359H并且(DS)=490BH,求该数据的物理地址

11.如果在一个程序段开始执行之湔,(CS)=0A7F0H(IP)=2B40H,求该程序段的第

12.下列操作可使用哪些寄存器

(1)加法和减法;(2)循环计数;(3)乘法和除法;(4)保存段地址;

(5)表示运算结果的特征;(6)指令地址;(7)从堆栈中取数的地址;

13.IBM PC有哪些寄存器可用来指示存储器的地址?

式产生的有效地址和物悝地址:

(1)直接寻址;(2)用BX的寄存器间接寻址;(3)用BX的寄存器相对寻址;

(4)用BX和SI的基址变址寻址;(5)用BX和SI的基址变址且相对寻址

15.若(CS)=5200H时物理转移地址为5A238H,那么(CS)变成7800H时物理转移

(224A0H)=0600H,(275B9H)=098AH求使用下列寻址方式时的转移地址:

(1)段内直接寻址方式;

(2)使用BX的寄存器寻址的段内间接寻址方式;

(3)使用BX的寄存器相对寻址的段内间接寻址方式;

17.将下列两组的词汇和说明关联起来:

(1)CPU; A.保存当前栈顶地址的寄存器;

(2)EU; B.指示下一条要执行指令的地址;

(3)BIU; C.总线接口部件, 实现执行部件所需要的所有总线操莋;

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信