网站建设 数据上传 查询/网络营销案例ppt课件
文章目录
- 基础知识
- 实现中断机制
- 实现单步中断
- 结束语
这个系列的最后一篇文章讲述如何实现中断系统,如何实现内置的 1 号中断——单步中断。
基础知识
内容来自《汇编语言》第12 章
任何一个通用的 CPU,比如 8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从 CPU 外部发过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU 不再接着向下执行,而是转去处理这个特殊信息。
下面是 8086 CPU 在收到中断信息后,所引发的中断过程。
- (从中断信息中)取得中断类型码
- 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
- 设置标志寄存器的第 8 位 TF 和第 9 位 IF 的值为0
- CS 的内容入栈
- IP 的内容入栈
- 从内存地址为中断类型码 *4 和中断类型码 *4+2 的两个字单元中读取中断处理程序的入口地址设置 IP 和 CS。
中断处理程序的编写方法:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用 iret 指令返回
单步中断:
基本上,CPU 在执行完一条指令后,如果检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。单步中断的中断类型码为 1。CPU 提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。
实现中断机制
我实现的中断机制很简单:中断不能同时发生,中断也没有优先级,哪个中断先产生就先处理。
先在 EU 结构体里最后加上几个中断相关的变量:
type EU struct {ax uint16cx uint16dx uint16bx uint16sp uint16bp uint16si uint16di uint16eflags uint16//与 BIU 的通信接口biuCtrl chan BIURequestbiuData chan uint16// 当前正在执行的指令currentInstruction byte// 是否停止执行的标志位stop bool// 是否有内部中断产生innerInterruptOccur boolinnerInterruptType uint8// 是否有外部中断产生externalInterruptOccur boolexternalInterruptType uint8// 是否刚执行完 iret 指令retFrominterrupt bool
}
定义产生内部中断、外部中断的方法:
func (e *EU) generateInnerInterrupt(intNum uint8) {e.innerInterruptOccur = truee.innerInterruptType = intNum
}func (e *EU) generateExternalInterrupt(intNum uint8) {e.externalInterruptOccur = truee.externalInterruptType = intNum
}
定义处理中断函数:
// 内置的几个中断类型
const (DivideError uint8 = iotaSingleStepNonMaskableOneByteIntInstructionOverflow
)func (e *EU) processInterrupt(intNum uint8) {// push flagse.pushFlags()// let temp = TFtemp := e.readEFLAGS(tfFlag)// clear IT & TFe.writeEFLAGS(ifFlag, 0)e.writeEFLAGS(tfFlag, 0)// push CS & IPe.pushCS()e.pushIP()// 从中断向量表获取中断处理程序的入口地址addr := 4 * intNumnewIP := e.readMemoryWord(uint32(addr))newCS := e.readMemoryWord(uint32(addr + 2))e.writeSeg(CS, newCS)e.writeIP(newIP)// 如果 TF 标志为为 1,并且发生的中断不是单步中断,那么先执行单步中断if temp == 1 && intNum != SingleStep {e.processInterrupt(SingleStep)}
}
processInterrupt 的函数最后判断 如果 TF 标志为为 1,那么先执行单步中断。因为单步中断是要在执行完一条指令后就必须要执行的,它的优先级要比其他的内部,外部中断优先级高。
修改 EU 的 run 方法,在执行完一条指令后,执行处理中断的逻辑:
func (e *EU) run() {var instructions []bytefor {// 从BIU获取一个字节指令e.biuCtrl <- FetchInstructioninstruction := byte(<-e.biuData)// 拼接指令instructions = append(instructions, instruction)// 解码当前的指令字节序列decodedInstructions := Decode(instructions)// 当前是一条有效的指令if decodedInstructions != nil {// 执行指令e.execute(decodedInstructions)// 清空指令字节序列instructions = instructions[:0]// 如果要求程序终止,则退出循环if e.stop {e.stop = falsebreak}if !e.retFrominterrupt {if e.innerInterruptOccur { // 有内部中断e.innerInterruptOccur = falsee.processInterrupt(e.innerInterruptType)} else {if e.externalInterruptOccur && // 有外部中断e.readEFLAGS(ifFlag) == 1 { // IF == 1e.externalInterruptOccur = falsee.processInterrupt(e.externalInterruptType)}if e.readEFLAGS(tfFlag) == 1 { // TF == 1e.processInterrupt(SingleStep)}}} else {e.retFrominterrupt = false}}}
}
retFrominterrupt 标志位是执行 iret 指令时设置的,这是为了防止循环执行中断!
实现单步中断
实现了中断机制后,就可以实现单步中断。我实现的单步中断程序仅仅是休眠一秒钟。它的中断处理程序非常简单,只有两行汇编语句:
int 22h
iret
原因是我把实际的逻辑都放到执行 int 指令时去做了【为了偷懒,不想写汇编】:
func (e *EU) singleStep() {time.Sleep(1 * time.Second)
}func (e *EU) executeInt(instructions []byte) {if instructions[1] == 0x21 {e.stop = true} else if instructions[1] == 0x22 {e.singleStep()}
}
接下来就是要修改 main 函数将这个中断处理程序【二进制】加载到内存中,并修改中断向量表:
//装载单步中断程序//cs = 0x2000phyAddr = uint32(0x2000) << 4//单步中断程序执行 int 22h; iretc.writeMemory(phyAddr, []byte{0b11001101, 0x22, 0b11001111})//修改中单步中断的中断向量phyAddr = 1 * 4// ipc.writeMemory(phyAddr, []byte{0, 0})// csc.writeMemory(phyAddr+2, []byte{0x00, 0x20})// CPU 开始执行程序,第3个参数为是否开启单步调试c.Run(uint16(cs), uint16(programHeader.codeEntryProgOffset), true)
CPU 的 Run 方法修改为添加了一个参数,值为 true 时,就开启单步调试功能:
func (c *CPU) Run(cs, ip uint16, debug bool) {c.biu.run()if debug {c.eu.writeEFLAGS(tfFlag, 1)}c.eu.writeSeg(CS, cs)c.eu.writeIP(ip)c.eu.run()
}
接下来执行程序时,CPU 每执行完一条指令后都会 sleep 1秒。大功告成!
结束语
本系列文章到此就结束了,了解了这些编译器和虚拟机的基本原理及代码实现后,更进一步可以实现编译《汇编语言》书中所有的程序的编译器和运行它们的虚拟机,我本人没有更多精力投入在这方面了,感兴趣的朋友可以去实现!