我是靠谱客的博主 踏实刺猬,这篇文章主要介绍实现8086虚拟机(六)——中断系统,现在分享给大家,希望可以做个参考。

文章目录

    • 基础知识
    • 实现中断机制
    • 实现单步中断
    • 结束语

这个系列的最后一篇文章讲述如何实现中断系统,如何实现内置的 1 号中断——单步中断。

基础知识

内容来自《汇编语言》第12 章

任何一个通用的 CPU,比如 8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从 CPU 外部发过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU 不再接着向下执行,而是转去处理这个特殊信息。

下面是 8086 CPU 在收到中断信息后,所引发的中断过程。

  1. (从中断信息中)取得中断类型码
  2. 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
  3. 设置标志寄存器的第 8 位 TF 和第 9 位 IF 的值为0
  4. CS 的内容入栈
  5. IP 的内容入栈
  6. 从内存地址为中断类型码 *4 和中断类型码 *4+2 的两个字单元中读取中断处理程序的入口地址设置 IP 和 CS。

中断处理程序的编写方法:

  1. 保存用到的寄存器
  2. 处理中断
  3. 恢复用到的寄存器
  4. 用 iret 指令返回

单步中断:

基本上,CPU 在执行完一条指令后,如果检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。单步中断的中断类型码为 1。CPU 提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。

实现中断机制

我实现的中断机制很简单:中断不能同时发生,中断也没有优先级,哪个中断先产生就先处理。

先在 EU 结构体里最后加上几个中断相关的变量:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type EU struct { ax uint16 cx uint16 dx uint16 bx uint16 sp uint16 bp uint16 si uint16 di uint16 eflags uint16 //与 BIU 的通信接口 biuCtrl chan BIURequest biuData chan uint16 // 当前正在执行的指令 currentInstruction byte // 是否停止执行的标志位 stop bool // 是否有内部中断产生 innerInterruptOccur bool innerInterruptType uint8 // 是否有外部中断产生 externalInterruptOccur bool externalInterruptType uint8 // 是否刚执行完 iret 指令 retFrominterrupt bool }

定义产生内部中断、外部中断的方法:

复制代码
1
2
3
4
5
6
7
8
9
10
func (e *EU) generateInnerInterrupt(intNum uint8) { e.innerInterruptOccur = true e.innerInterruptType = intNum } func (e *EU) generateExternalInterrupt(intNum uint8) { e.externalInterruptOccur = true e.externalInterruptType = intNum }

定义处理中断函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 内置的几个中断类型 const ( DivideError uint8 = iota SingleStep NonMaskable OneByteIntInstruction Overflow ) func (e *EU) processInterrupt(intNum uint8) { // push flags e.pushFlags() // let temp = TF temp := e.readEFLAGS(tfFlag) // clear IT & TF e.writeEFLAGS(ifFlag, 0) e.writeEFLAGS(tfFlag, 0) // push CS & IP e.pushCS() e.pushIP() // 从中断向量表获取中断处理程序的入口地址 addr := 4 * intNum newIP := 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 方法,在执行完一条指令后,执行处理中断的逻辑:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func (e *EU) run() { var instructions []byte for { // 从BIU获取一个字节指令 e.biuCtrl <- FetchInstruction instruction := byte(<-e.biuData) // 拼接指令 instructions = append(instructions, instruction) // 解码当前的指令字节序列 decodedInstructions := Decode(instructions) // 当前是一条有效的指令 if decodedInstructions != nil { // 执行指令 e.execute(decodedInstructions) // 清空指令字节序列 instructions = instructions[:0] // 如果要求程序终止,则退出循环 if e.stop { e.stop = false break } if !e.retFrominterrupt { if e.innerInterruptOccur { // 有内部中断 e.innerInterruptOccur = false e.processInterrupt(e.innerInterruptType) } else { if e.externalInterruptOccur && // 有外部中断 e.readEFLAGS(ifFlag) == 1 { // IF == 1 e.externalInterruptOccur = false e.processInterrupt(e.externalInterruptType) } if e.readEFLAGS(tfFlag) == 1 { // TF == 1 e.processInterrupt(SingleStep) } } } else { e.retFrominterrupt = false } } } }

retFrominterrupt 标志位是执行 iret 指令时设置的,这是为了防止循环执行中断!

实现单步中断

实现了中断机制后,就可以实现单步中断。我实现的单步中断程序仅仅是休眠一秒钟。它的中断处理程序非常简单,只有两行汇编语句:

复制代码
1
2
3
int 22h iret

原因是我把实际的逻辑都放到执行 int 指令时去做了【为了偷懒,不想写汇编】:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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 函数将这个中断处理程序【二进制】加载到内存中,并修改中断向量表:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//装载单步中断程序 //cs = 0x2000 phyAddr = uint32(0x2000) << 4 //单步中断程序执行 int 22h; iret c.writeMemory(phyAddr, []byte{0b11001101, 0x22, 0b11001111}) //修改中单步中断的中断向量 phyAddr = 1 * 4 // ip c.writeMemory(phyAddr, []byte{0, 0}) // cs c.writeMemory(phyAddr+2, []byte{0x00, 0x20}) // CPU 开始执行程序,第3个参数为是否开启单步调试 c.Run(uint16(cs), uint16(programHeader.codeEntryProgOffset), true)

CPU 的 Run 方法修改为添加了一个参数,值为 true 时,就开启单步调试功能:

复制代码
1
2
3
4
5
6
7
8
9
10
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秒。大功告成!

结束语

本系列文章到此就结束了,了解了这些编译器和虚拟机的基本原理及代码实现后,更进一步可以实现编译《汇编语言》书中所有的程序的编译器和运行它们的虚拟机,我本人没有更多精力投入在这方面了,感兴趣的朋友可以去实现!

最后

以上就是踏实刺猬最近收集整理的关于实现8086虚拟机(六)——中断系统的全部内容,更多相关实现8086虚拟机(六)——中断系统内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部