转载声明:本笔记转载自北航OS官方教程:GXemul模拟器
GXemul 模拟器介绍
GXemul(Gavare’s eXperimental Emulator)是一款计算机架构模拟器,可以模拟所需硬件环境,例如本实验需要的 MIPS 架构下的 CPU。尽管模拟器的开发仍在进行中,但自 2004 年以来,它已经足够稳定,可以让各种未经修改的操作系统运行就好像它们在真正的硬件上运行一样。目前模拟的处理器架构包括 ARM,MIPS,Motorola 88K,PowerPC 等。 经验证可在模拟器内工作的操作系统是 NetBSD,Linux,HelenOS,Ultrix 和 Sprite。
除了运行整个操作系统外,还可以使用模拟器用于较小规模的实验,例如爱好者们的操作系统开发等等。
GXemul 的基本工作方式
GXemul 的动态翻译器通常从模拟架构的汇编代码表示(例如 MIPS)转换成一种中间表示(IR),然后转换成可由本机执行的原生汇编代码(通常为 x86_64 或 x86 架构的代码,例如我们的跳板机为 x86_64 架构)。由于 GXemul 的主要目标之一是尽可能地保持所有内容的可移植性,因此 GXemul 的 IR 无论最终步骤(从 IR 到本机代码的转换)是否已实现,都能确保可以被执行。
这部分并不是课程要求掌握的内容,有兴趣的同学可自行参考 :GXemul: Dynamic Translation Internals
GXemul 的基本使用
GXemul 是我们运行 MOS 操作系统的模拟器,它可以帮助我们运行和调试 MIPS 体系架构下的代码。直接输入
会显示帮助信息。
运行 GXemul
GXemul 运行选项:
-E
模拟机器的类型
-C
模拟 CPU 的类型
-M
模拟的内存大小
-V
进入调试模式
假设我们通过编译得到了一个 MIPS 架构的可执行文件 hello
,我们通过如下方式运行 hello
1 $ gxemul -E testmips -C R3000 -M 64 hello
我们还可以用如下命令以调试模式打开 GXemul,对 hello 进行调试(进入后直接中断,输入 continue 或 step 才会继续运行,在此之前可以进行添加断点等操作)
1 $ gxemul -E testmips -C R3000 -M 64 -V hello
上方命令中:-E testmips
表示模拟器类型为 MIPS 架构,-C R3000
表示模拟 CPU 类型为 MIPS R3000,-M 64
代表模拟的内存大小为 64Mb,-V
代表进入调试模式。
GXemul 模拟器的一些基本命令
进入 GXemul 后使用 Ctrl+C 可以中断运行。中断后可以进行单步调试,执行如下指令:
breakpoint add addr
添加断点
continue
继续执行
step [n]
向后执行 n 条汇编指令
lookup name|addr
通过名字或地址查找标识符
dump [addr [endaddr]]
查询指定地址的内容
reg [cpuid][,c]
查看寄存器内容,添加”,c”可以查看协处理器
help
显示各个指令的作用与用法
quit
退出
(以上中括号表示内容可以没有)
更多 GXemul 相关的信息参考: GXemul - Documentation
如何退出 GXemul
按 Ctrl+C,以中断模拟器;
输入 quit
以退出模拟器。
Warning
一定要区分好”退出模拟器“,和“把模拟器挂在后台”这两件事。模拟器是相当占用系统资源的。可能有同学不小心按下 ctrl+z 把模拟器挂到后台,或是不确定自己有没有把挂起的进程关掉,可以通过 ps -ef | grep gxemul
命令观察后台有多少正在运行的作业。如果发现有多个模拟器正在运行的话,可以使用 kill
命令直接杀死。也可以通过 pkill -9 gxemul
,一步杀死后台运行的模拟器。同学们可以自行了解一下 Linux 后台进程管理的知识。
GXemul 实际调试
可以看到我们运行调试可执行文件,需要敲入较长的一条指令,但实验中已经将启动的 GXemul 的命令融进 Makefile 中了,当构建完成后下将产生可执行的文件,此时可通过下方指令运行:
同时,可通过下方指令调试。
另外,可通过下方指令加载用户程序的符号表,其中 user/fktest.b
是用户程序 .b
文件的路径。(预习实验中不需要该特性,正式课设实验中会提供该特性。)
1 $ make dbg prog=user/fktest.b
同时,可通过下方指令导出内核与所有用户程序 *.b
的反汇编。
得到诸如 target/mos.objdump
的反汇编文件后,我们可以通过 vim 查看其内容,以 target/mos.objdump
为例:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 target/mos: 文件格式 elf32-tradlittlemips target/mos Disassembly of section .tlb_miss_entry: 80000000 <tlb_miss_entry>: tlb_miss_entry(): /home/git/你的学号/kern/entry.S:6 80000000: 08000020 j 80000080 <exc_gen_entry> 80000004: 00000000 nop Disassembly of section .exc_gen_entry: 80000080 <exc_gen_entry>: exc_gen_entry(): /home/git/你的学号/kern/entry.S:10 80000080: 03a0d025 move k0,sp 80000084: 07a00002 bltz sp,80000090 <exc_gen_entry+0x10> 80000088: 00000000 nop ... 800117f0 <strcmp>: strcmp(): /home/git/你的学号/lib/string.c:83 int strcmp(const char *p, const char *q) { 800117f0: 27bdfff8 addiu sp,sp,-8 800117f4: afbe0004 sw s8,4(sp) 800117f8: 03a0f025 move s8,sp 800117fc: afc40008 sw a0,8(s8) 80011800: afc5000c sw a1,12(s8) /home/git/你的学号/lib/string.c:84 while (*p && *p == *q) { 80011804: 10000009 b 8001182c <strcmp+0x3c> 80011808: 00000000 nop /home/git/你的学号/lib/string.c:85 p++, q++; 8001180c: 8fc20008 lw v0,8(s8) 80011810: 00000000 nop 80011814: 24420001 addiu v0,v0,1 80011818: afc20008 sw v0,8(s8) 8001181c: 8fc2000c lw v0,12(s8) 80011820: 00000000 nop 80011824: 24420001 addiu v0,v0,1 80011828: afc2000c sw v0,12(s8) /home/git/你的学号/lib/string.c:84 while (*p && *p == *q) { 8001182c: 8fc20008 lw v0,8(s8) 80011830: 00000000 nop 80011834: 80420000 lb v0,0(v0) 80011838: 00000000 nop 8001183c: 1040000a beqz v0,80011868 <strcmp+0x78> 80011840: 00000000 nop /home/git/你的学号/lib/string.c:84 (discriminator 1) 80011844: 8fc20008 lw v0,8(s8) 80011848: 00000000 nop 8001184c: 80430000 lb v1,0(v0) 80011850: 8fc2000c lw v0,12(s8) 80011854: 00000000 nop 80011858: 80420000 lb v0,0(v0) 8001185c: 00000000 nop 80011860: 1062ffea beq v1,v0,8001180c <strcmp+0x1c> 80011864: 00000000 nop /home/git/你的学号/lib/string.c:88 } if ((u_int)*p < (u_int)*q) { 80011868: 8fc20008 lw v0,8(s8) 8001186c: 00000000 nop 80011870: 80420000 lb v0,0(v0) 80011874: 00000000 nop 80011878: 304300ff andi v1,v0,0xff 8001187c: 8fc2000c lw v0,12(s8) 80011880: 00000000 nop 80011884: 80420000 lb v0,0(v0) 80011888: 00000000 nop 8001188c: 304200ff andi v0,v0,0xff 80011890: 0062102b sltu v0,v1,v0 80011894: 10400004 beqz v0,800118a8 <strcmp+0xb8> 80011898: 00000000 nop /home/git/你的学号/lib/string.c:89 return -1; 8001189c: 2402ffff li v0,-1 800118a0: 10000012 b 800118ec <strcmp+0xfc> 800118a4: 00000000 nop /home/git/你的学号/lib/string.c:92 } if ((u_int)*p > (u_int)*q) { 800118a8: 8fc20008 lw v0,8(s8) 800118ac: 00000000 nop 800118b0: 80420000 lb v0,0(v0) 800118b4: 00000000 nop 800118b8: 304300ff andi v1,v0,0xff 800118bc: 8fc2000c lw v0,12(s8) 800118c0: 00000000 nop 800118c4: 80420000 lb v0,0(v0) 800118c8: 00000000 nop 800118cc: 304200ff andi v0,v0,0xff 800118d0: 0043102b sltu v0,v0,v1 800118d4: 10400004 beqz v0,800118e8 <strcmp+0xf8> 800118d8: 00000000 nop /home/git/你的学号/lib/string.c:93 return 1; 800118dc: 24020001 li v0,1 800118e0: 10000002 b 800118ec <strcmp+0xfc> 800118e4: 00000000 nop /home/git/你的学号/lib/string.c:96 } return 0; 800118e8: 00001025 move v0,zero /home/git/你的学号/lib/string.c:97 } 800118ec: 03c0e825 move sp,s8 800118f0: 8fbe0004 lw s8,4(sp) 800118f4: 27bd0008 addiu sp,sp,8 800118f8: 03e00008 jr ra 800118fc: 00000000 nop ...
可以看到,上述 target/mos.objdump
中标出了目标文件汇编与每一行对应的源码,有助于我们进行调试。
Launch GXemul In DEBUG Mode
当我们以调试模式启动 GXemul 后,我们可以通过 help
查看 GXemul 内的可用指令:
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 GXemul> help Available commands: allsettings show all settings breakpoint ... manipulate breakpoints continue continue execution device ... show info about (or manipulate) devices dump [addr [endaddr]] dump memory contents in hex and ASCII emuls print a summary of all current emuls focus x[,y[,z]] changes focus to cpu x, machine x, emul z help print this help message itrace toggle instruction_trace on or off lookup name|addr lookup a symbol by name or address machine print a summary of the current machine ninstrs [on|off] toggle (set or unset) show_nr_of_instructions pause cpuid pause (or unpause) a CPU print expr evaluate an expression without side-effects put [b|h|w|d|q] addr, data modify emulated memory contents quiet [on|off] toggle quiet_mode on or off quit quit the emulator reg [cpuid][,c] show GPRs (or coprocessor c's registers) step [n] single-step one (or n) instruction(s) tlbdump [cpuid][,r] dump TLB contents (add ',r' for raw data) trace [on|off] toggle show_trace_tree on or off unassemble [addr [endaddr]] dump memory contents as instructions version print version information x = expr generic assignment In generic assignments, x must be a register or other writable settings variable, and expr can contain registers/settings, numeric values, or symbol names, in combination with parenthesis and + - * / & % ^ | operators. In case there are multiple matches (i.e. a symbol that has the same name as a register), you may add a prefix character as a hint: '#' for registers, '@' for symbols, and '$' for numeric values. Use 0x for hexadecimal values.
下面将介绍几个常用的功能。
Breakpoint,Step & Unassemble,Dump,Trace
GXemul 提供了断点、单步执行(指令级别)的调试功能;除此之外,GXemul 还提供了反汇编、内存导出等功能。
ADD BREAKPOINT
如下,启动模拟器后,在内部控制台中输入 breakpoint add page_insert
,即可为 page_insert
函数增加断点。随后使用 c
(continue)命令让模拟器继续运行,模拟器将运行至下一个断点处。
以 page_insert
为例:
1 2 3 4 5 6 7 8 9 GXemul> breakpoint add page_insert 0: 0x80014e38 (page_insert) GXemul> c init.c: mips_init() is called Memory size: 65536 KiB, number of pages: 16384 to memory 80430000 for struct Pages. pmap.c: mips vm init success BREAKPOINT: pc = 0x80014e38 (The instruction has not yet executed.)
STEP
如下,使用 step
即可让模拟器执行 1
条汇编指令(基本指令)。
进一步地,使用 step 100
即可让模拟器执行 100
条汇编指令(基本指令)。
仍以上面的 page_insert
为例:
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 GXemul> breakpoint add page_insert 0: 0x80014e38 (page_insert) GXemul> c init.c: mips_init() is called Memory size: 65536 KiB, number of pages: 16384 to memory 80430000 for struct Pages. pmap.c: mips vm init success BREAKPOINT: pc = 0x80014e38 (The instruction has not yet executed.) GXemul> step 20 <page_insert> 80014e38: 27bdffe0 addiu sp,sp,-32 80014e3c: afbf001c sw ra,28(sp) [0x803fff7c] 80014e40: afbe0018 sw fp,24(sp) [0x803fff78] 80014e44: 03a0f025 or fp,sp,zr 80014e48: afc40020 sw a0,32(fp) [0x803fff80] 80014e4c: afc50024 sw a1,36(fp) [0x803fff84] 80014e50: afc60028 sw a2,40(fp) [0x803fff88] 80014e54: afc7002c sw a3,44(fp) [0x803fff8c] 80014e58: 27c20014 addiu v0,fp,20 80014e5c: 00403825 or a3,v0,zr 80014e60: 00003025 or a2,zr,zr 80014e64: 8fc5002c lw a1,44(fp) [0x803fff8c] 80014e68: 8fc40020 lw a0,32(fp) [0x803fff80] 80014e6c: 0c005311 jal 0x80014c44 <pgdir_walk> 80014e70: 00000000 (d) nop <pgdir_walk> 80014c44: 27bdffc8 addiu sp,sp,-56 80014c48: afbf0034 sw ra,52(sp) [0x803fff5c] 80014c4c: afbe0030 sw fp,48(sp) [0x803fff58] 80014c50: afb0002c sw s0,44(sp) [0x803fff54] 80014c54: 03a0f025 or fp,sp,zr 80014c58: afc40038 sw a0,56(fp) [0x803fff60]
UNASSEMBLE
如下,使用 unassemble
命令,导出某一个地址后续(或附近)的汇编指令序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 GXemul> unassemble 80014c5c: afc5003c <- sw a1,60(fp) 80014c60: afc60040 sw a2,64(fp) 80014c64: afc70044 sw a3,68(fp) 80014c68: 8fc2003c lw v0,60(fp) 80014c6c: 00000000 nop 80014c70: 00021582 srl v0,v0,22 80014c74: 00021080 sll v0,v0,2 80014c78: 8fc30038 lw v1,56(fp) 80014c7c: 00000000 nop 80014c80: 00621021 addu v0,v1,v0 80014c84: afc20018 sw v0,24(fp) 80014c88: 8fc20018 lw v0,24(fp) 80014c8c: 00000000 nop 80014c90: 8c420000 lw v0,0(v0) 80014c94: 00000000 nop 80014c98: 30420200 andi v0,v0,0x0200 80014c9c: 14400031 bne zr,v0,0x80014d64 <pgdir_walk+0x120> 80014ca0: 00000000 nop 80014ca4: 8fc20040 lw v0,64(fp) 80014ca8: 00000000 nop
DUMP DATA
如下,使用 dump
命令,导出某一个地址后续(或附近)的内存信息。下面以查看 curenv
的值以及其指向的进程控制块为例:
1 2 3 4 5 6 7 GXemul> dump curenv 0x800167a0 804320e8 00000003 00000000 .C ......... 0x800167b0 00000002 804321d0 00000000 00000000 .....C!......... 0x800167c0 80432000 00000001 00000000 00000000 .C ............. 0x800167d0 00000000 00000000 00000000 00000000 ................ 0x800167e0 00000000 00000000 00000000 00000000 ................ ..........
根据 curenv
的定义,其类型为 struct Env *
,因此 0x800167a4
中存储的是一个地址(指向一个 struct Env
)。根据上述 dump
结果,可以知道这个全局指针指向了 0x804320e8
这一地址。再通过 dump 0x804320e8
即可找到当前 curenv
指向的 struct Env
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GXemul> dump 0x804320e8 0x804320e0 00000000 00000000 ........ 0x804320f0 00000001 00000025 00400920 0040d2c4 .......%.@. .@.. 0x80432100 0040d2c4 7f3fdfcc 00000000 00000000 .@...?.......... 0x80432110 00000000 00000000 00000000 00000000 ................ 0x80432120 00000000 00000000 0040d2cc 00000007 .........@...... 0x80432130 00000000 7f3fdfcc 00000000 00400920 .....?.......@. 0x80432140 00000000 00000000 00000000 00000000 ................ 0x80432150 7f3fdb80 82000000 00000000 7f3fdb80 .?...........?.. 0x80432160 00000000 004009d0 10081004 00000000 .....@.......... 0x80432170 00000000 00408000 00001000 00400aa0 .....@.......@.. 0x80432180 00400aa0 804321d0 800167b4 00000c01 .@...C!...g..... 0x80432190 00000000 00000001 83ff3000 03ff3000 ..........0...0. 0x804321a0 00000000 80019270 00000001 00000000 .......p........ 0x804321b0 00000000 00000000 00000000 00000000 ................ 0x804321c0 00000000 00000000 00000002 00000000 ................ 0x804321d0 00000000 00000000 00000000 00000000 ................ 0x804321e0 00000000 00000000 ........
即可查看当前正运行的进程控制块的信息。类似地,可以查看任何全局变量的值,以及大部分内核数据结构的信息。
DUMP REGISTERS
通过 reg
命令导出通用寄存器的值:
1 2 3 4 5 6 7 8 9 10 11 GXemul> reg cpu0: pc = 80012620 cpu0: hi = 00000000 lo = 00000000 cpu0: at = 00000000 v0 = 00000000 v1 = 82000000 cpu0: a0 = 803fffb8 a1 = 82000000 a2 = 82000000 a3 = 00000000 cpu0: t0 = 7f3fdf48 t1 = 80012a60 t2 = 80012b20 t3 = 00000000 cpu0: t4 = 00000000 t5 = 00000000 t6 = 00000000 t7 = 00000000 cpu0: s0 = 7f4002b8 s1 = 00001c03 s2 = 00410000 s3 = 00000003 cpu0: s4 = 00000000 s5 = 00000000 s6 = 00000000 s7 = 00000000 cpu0: t8 = 00000000 t9 = 00000000 k0 = 7f3fdf48 k1 = 803fffb8 cpu0: gp = 00000000 sp = 803ffeec fp = 00000000 ra = 80012b50
通过 reg, 0
命令导出协处理器 0(CP0)中寄存器的值:
1 2 3 4 5 GXemul> reg, 0 cpu0: index=00000f00 random=00003700 entrylo0=03fbb600 entrylo1=00000000 cpu0: context=00001008 pagemask=00001fff wired=00000000 reserv7=00000000 cpu0: badvaddr=00402b2c count=00000000 entryhi=00402080 compare=00000000 cpu0: status=10081004 cause=00001020 epc=004000c0 prid=00000220
通过 tlbdump
命令导出 TLB 中的信息:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 GXemul> tlbdump cpu0: (index=0xf random=0x37) 0: (invalid) 1: (invalid) 2: (invalid) 3: (invalid) 4: (invalid) 5: (invalid) 6: (invalid) 7: (invalid) 8: vaddr=0x00404000 (asid 01), paddr=0x03feb000 D 9: vaddr=0x7f82f000 (asid 01), paddr=0x00430000 D 10: vaddr=0x7fd80000 (asid 01), paddr=0x03fd9000 D 11: vaddr=0x7fdff000 (asid 01), paddr=0x03ff3000 D 12: vaddr=0x00408000 (asid 01), paddr=0x03fe7000 D 13: vaddr=0x00402000 (asid 01), paddr=0x03fed000 D 14: vaddr=0x00409000 (asid 01), paddr=0x03fe6000 D 15: (invalid) 16: vaddr=0x7f400000 (asid 01), paddr=0x00432000 D 17: vaddr=0x0040d000 (asid 01), paddr=0x03fe2000 D 18: vaddr=0x7f3fd000 (asid 01), paddr=0x03ff2000 D 19: vaddr=0x00401000 (asid 01), paddr=0x03fee000 D 20: vaddr=0x00400000 (asid 01), paddr=0x03ff0000 D 21: vaddr=0x7f400000 (asid 03), paddr=0x00432000 D 22: vaddr=0x00403000 (asid 04), paddr=0x03fa0000 23: vaddr=0x00403000 (asid 05), paddr=0x03b69000 D 24: vaddr=0x10041000 (asid 01), paddr=0x03b79000 D 25: (invalid) 26: vaddr=0x10040000 (asid 01), paddr=0x03b7a000 D 27: (invalid) 28: vaddr=0x7f400000 (asid 02), paddr=0x00432000 D 29: vaddr=0x1003f000 (asid 01), paddr=0x03b7b000 D 30: (invalid) 31: vaddr=0x00403000 (asid 03), paddr=0x03fa0000 32: vaddr=0x7f3fd000 (asid 03), paddr=0x03b88000 D 33: vaddr=0x61000000 (asid 05), paddr=0x03fd8000 D 34: vaddr=0x5fc04000 (asid 05), paddr=0x03b5f000 D 35: vaddr=0x7fd7f000 (asid 05), paddr=0x03b62000 D 36: vaddr=0x00408000 (asid 05), paddr=0x03b64000 D 37: vaddr=0x1003e000 (asid 01), paddr=0x03b7c000 D 38: (invalid) 39: vaddr=0x00402000 (asid 03), paddr=0x03fa1000 40: vaddr=0x00400000 (asid 03), paddr=0x03fa4000 41: vaddr=0x00409000 (asid 04), paddr=0x03f9a000 42: vaddr=0x00407000 (asid 04), paddr=0x03b82000 D 43: vaddr=0x7f400000 (asid 04), paddr=0x00432000 D 44: vaddr=0x0080d000 (asid 04), paddr=0x03fa7000 D 45: vaddr=0x7f3fd000 (asid 04), paddr=0x03b86000 D 46: vaddr=0x00402000 (asid 04), paddr=0x03fa1000 47: vaddr=0x00400000 (asid 04), paddr=0x03fa4000 48: vaddr=0x1003d000 (asid 01), paddr=0x03b7d000 D 49: (invalid) 50: vaddr=0x5fc01000 (asid 05), paddr=0x03fff000 D 51: vaddr=0x00400000 (asid 05), paddr=0x03b6d000 D 52: vaddr=0x00402000 (asid 05), paddr=0x03b6a000 D 53: vaddr=0x00409000 (asid 05), paddr=0x03b63000 D 54: vaddr=0x7f3fd000 (asid 05), paddr=0x03b70000 D 55: vaddr=0x00402000 (asid 02), paddr=0x03fbb000 D 56: vaddr=0x7f3fd000 (asid 02), paddr=0x03fc1000 D 57: vaddr=0x00401000 (asid 02), paddr=0x03fbc000 D 58: vaddr=0x00400000 (asid 02), paddr=0x03fbe000 D 59: vaddr=0x10042000 (asid 01), paddr=0x03b78000 D 60: vaddr=0x7fc40000 (asid 01), paddr=0x03fde000 D 61: vaddr=0x10002000 (asid 01), paddr=0x03fdc000 D 62: vaddr=0x10001000 (asid 01), paddr=0x03fdf000 D 63: vaddr=0x10003000 (asid 01), paddr=0x03fd8000 D
TRACE
使用 trace 可以帮助同学们了解程序的运行轨迹。
1 2 3 4 5 6 7 8 GXemul> trace show_trace_tree = ON (was: OFF) GXemul> c <env_run(0x80432488,0x804321d0,0,&env_sched_list,..)> <bcopy(0x81ffff64,0x804321d0,156)> <lcontext(0x83b81000,0x8043226c,0x8043226c,&env_sched_list,..)> <env_pop_tf(0x80432488,0x140,0x8043226c,&env_sched_list,..)> ........
Reference