Chapter2-3 Assembly & Simulation 汇编与仿真

Posted by LvKouKou on October 13, 2022

2-3 Assembly & Simulation 汇编与仿真

1. Assembler 汇编器

  • Input: 汇编语⾔代码(e.g. MAL) (e.g., foo.s)
  • Output: Object Code, information tables (TAL(True Assembly Language)) (e.g., foo.o)
  • Reads and Uses Directives 指示性语句

  • Replace Pseudo-instructions 替换伪指令
  • Produce Machine Language ⽣成机器语⾔
  • Creates Object File ⽣成⽬标⽂件

2. Assembly Source Language 汇编源文件

汇编源⽂件是符号表示的⽂本⽂件, 以字节编址连续装⼊内存

e.g:

1
2
3
4
5
6
7
.data 0x10000000        规定 “当前”地址, i.e., 4个字节数据存放的起始地址
.byte 1, 2, 3, 4		每个数据 1 byte 
.byte 5, 6, 7, 8		另外4个字节数据
.word 1, 2, 3, 4		4个字数据 (每个数据 4 bytes)
.asciiz "Comp 411"		以0终⽌的ASCII 字符串,也就是这行占9个bytes,最后有个结束符号‘/0’
.align 2				下⾯的16进制数据开始地址对⻬22, 即最后两位为00
.word 0xfeedbeef		16进制表示的字数据

==易错点:.asciiz “Comp 411” 以0终⽌的ASCII 字符串,也就是这行占9个bytes,最后有个结束符号‘/0’==

3. Assembler Directives 汇编指示性语句

给汇编程序指示, 但不产⽣机器指令

  • .text: 后⾯放置代码段(machine code)
  • .data: 后⾯放置⽤户的数据段 (binary rep of data in source file)
  • .globl sym: 声明 sym 全局性, 可被其它⽂件引⽤
  • .string str:将字符串 str 存储在内存中并以空值终⽌它
  • .word w1…wn: 将 n 个 32 位数值存储在连续的内存字中,允许我们初始化内存中的连续字

Thing to note:

  • “.data”汇编指令将以下字放⼊数据段
  • “.globl” directives 使“sum” 和 “i” 变量可以全局访问
  • “.space” directives 为每个变量分配4个字节
  • 与“.word”相⽐, “.space” 不用初始化变量值

4. Assembler Syntax 汇编语法

  1. Assembler COMMENTS 汇编注释

    语句后⾯加‘#’ (sharp) to the end of the line is ignored

  2. Assembler LABELS 汇编标签

    符号表示内存地址,采⽤它们声明的地⽅的地址值,可以是数据标签也可以是指令标签

.data 0x80000000
item: .word 1,2,3,4 			#  data word 数组名为item 
								#label item 表示数组中第⼀个字地址, 或者数组的名称
.text 0x00010000
start: add $3, $4, $2 	# an instruction label
sll $3, $3, 8
andi $3, $3, 0xff
beq ..., ..., start

tips:item + i × 4才是下一位地址,因为item每个数据是 4 bytes。 (.byte声明的数据才是每个数据1byte

5. Assembler MNEMONICS 汇编程序助记符

指令的符号表示:(不包括所有指令 )

add, addu, addi, addiu, sub, subu, and, andi, or, ori, xor, xori, nor, lui, sll, sllv, sra, srav, srl, srlv, div,divu, mult, multu, mfhi, mflo, mthi, mtlo, slt, sltu, slti, sltiu, beq, bgez, bgezal, bgtz, blez, bltzal, bltz, bne, j, jal, jalr, jr, lb, lbu, lh, lhu, lw, lwl, lwr, sb, sh, sw, swl, swr, rfe

6. Pseudoinstructions 伪指令

不是指令的助记符,汇编器会将其转为真正的指令(要注意如何等价替换

1
abs, mul, mulo, mulou, neg, negu, not, rem, remu, rol, ror, li, seq, sge, sgeu, sgt, sgtu, sle, sleu, sne, b, beqz, bge, bgeu, bgt, bgtu, ble, bleu, blt, bltu, bnez, la, ld, ulh, ulhu, ulw, sd, ush, usw, move, syscall, break, nop

常见伪指令:

  • la 将常量加载到寄存器中

    1
    
    见下面图片2
    
  • li 将立即数加载到寄存器中

  • move 将一个寄存器中内容移到另一个寄存器中

    1
    
    move $d,$s              becomes  addi $d,$s,0
    

image-20221010171807359

la的替换

image-20221010171930938

7. Procedures and Stacks 子程序和堆栈

为子程序设计的2条指令:

  • jal 跳转到子程序
  • jr 用于返回主程序

主程序(Caller) :能提供参数给子程序。

  • $a0 -a3(寄存器4-7):专门用于传参数,规定这4个寄存器位传递参数寄存器

子程序(Callee) :完成一系列操作,执行后的结果放在主程序能获得的地方,然后返回主程序。

  • $v0 - v1(寄存器2-3):两个放返回值的寄存器
  • $ra(寄存器31):放返回地址的寄存器(是调用子程序的下一条指令的地址)

叶子子程序(LEAF function):不再调⽤其它子程序。

Q1:分配了4个参数寄存器, 多于4个怎么办?还有局部变量?

Q2:子程序还能调⽤其它函数子程序吗?那只有一个$ra怎么办?

A:用堆栈解决

### 7.1 Procedure Call and Stack(过程调用和栈)

过程调用的执行步骤(假定过程P调用过程Q)

  1. 将参数放到Q能访问到的地⽅
  2. 将P中的返回地址存到特定的地⽅, 将控制转移到过程Q
  3. 为Q的局部变量分配空间(局部变量临时保存在栈中)
  4. 执⾏过程Q
  5. 将Q执⾏的返回结果放到P能访问到的地⽅
  6. 取出返回地址, 将控制转移到P, 即返回到P中执⾏

7.2 Stack

  • 是⼀个“先进后出”队列

  • 需⼀个栈指针sp指向栈顶元素

  • 每个元素⻓度⼀致

  • 用“⼊栈”(push) 和“出栈”(pop) 操作访问栈元素

  • 只有堆栈顶部是直接可见的, 即“栈顶” (自上向下生长)

7.3 Procedure frame 栈帧 or activation record 活动记录:

保存寄存器内容和局部变量的堆栈段

fp为帧指针

image-20221010175542155

image-20221010175659820

注意Arg[5]和Arg[4]的相对位置

image-20221106165958641

7.4 MIPS Stack Convention

image-20221010175643476

7.5 MIPS寄存器使用情况的软件约定(假定P调用Q)

image-20221010175931069

7.6 过程调用时MIPS中的栈和栈帧的变化

image-20221010180019130

7.7 Stack Management Primitives 堆栈管理

image-20221010180219254

(2-3ppt中40页有一个例子)

7.8 递归调用

调用前 ⼀个函数的运⾏期间调用另⼀个函数时, 在运⾏被调用函数之前, 系统需要完成3件事情: (1) 将所有的实参、 返回地址等信息传递给被调用函数保存; (2) 为被调用函数的局部变量分配存储区; (3) 将控制转移到被调函数的⼊⼝。

递归调用调用中 而从被调用函数返回调用函数之前, 系统也应完成3件⼯作: (1) 保存被调函数的计算结果; (2) 释放被调函数的数据区; (3) 依照被调函数保存的返回地址将控制转移到调用函数。 当有多个函数构成嵌套调用时, 按照后调用先返回的原则。 函数的调用原则和数据结构栈的实现是相⼀致。 也说明函数调用是通过栈实现的

A Procedure’s Storage Needs

  • 主程序需传递的参数, 存⼊a0-a3,存储返回地址到ra。
  • 子程序如果用到s0-s7, 则存⼊堆栈,(可以任意用t0-t9) 把返回值存⼊v0-v1, ⼦程序如果⼜调用子程序则需把上级调用的ra值放⼊堆栈, 涉及的变量也需要存入堆栈, 返回时弹出 。

例子:

image-20221010181208278

7.9总结

调用者约定

调⽤者的约定(主程序):

  • 存储后面子程序用到的临时寄存器内容到堆栈 (t0-t9, a0-a3, and v0-v1)
  • 前四个传递参数存⼊a0-a3, 后面需要传递的参数按照规定的顺序放⼊堆栈. Why?
  • 用jal 指令调用子程序, (返回地址放⼊ ra).
  • 子程序返回值在 v0-v1 中进行访问。

被调用者约定

被调⽤者的约定(子程序):

  1. 为要保存的寄存器、 局部变量和溢出的 args分配堆栈帧空间
  2. 保存要使⽤的需保存的寄存器:(ra, sp, fp, gp, s0-s7)
  3. 如果 CALLEE 有局部变量 - 或者 - 需要访问堆栈上的 args, 保存CALLER 的帧指针并将 fp 设置为 CALLEE 堆栈的第⼀个条⽬
  4. 执⾏⼦程序
  5. 放返回值到 v0-v1
  6. 恢复存储的寄存器
  7. 恢复 sp 到原来的值
  8. ⽤ jr $ra 返回主程序

  • 主程序要存到堆栈的内容:t0-t9, a0-a3, and v0-v1

  • 子程序要存到堆栈的内容:ra, sp, fp, gp, s0-s7