调试环境搭建
思路
学习一门语言,上手上机调试是非常重要的,它会带来一个感性的认知,所以学习汇编第一件事,推荐把开发环境搞起来。
安装 nasm: 程序中的汇编代码需要转译为处理器指令,在提交给处理器执行 ,nasm负责这个事
安装bochs:因为处理器指令无法像java,go语言直接跑在现有的mac,window等笔记本机器上,是直接跑在处理器上的命令,所以需要安装模拟原生处理器的软件。bochs可模拟多种处理器,包括x86和x86-64的处理器模拟器。
安装sdl : Bochs模拟器需要调用显示库代码来渲染输出界面
只提供mac的样例,其他服务器思路相差不大,自行搜索
安装操作
需要安装好brew工具
brew install bochs
brew install sdl
brew install nasm$ bochs --help
Usage: bochs [flags] [bochsrc options]-n no configuration file-f configfile specify configuration file-q quick start (skip configuration interface)-benchmark N run Bochs in benchmark mode for N millions of emulated ticks-dumpstats N dump Bochs stats every N millions of emulated ticks-r path restore the Bochs state from path-log filename specify Bochs log file name-unlock unlock Bochs images leftover from previous session-rc filename execute debugger commands stored in file-dbglog filename specify Bochs internal debugger log file name--help display this help and exit--help features display available features / devices and exit--help cpu display supported CPU models and exit$ nasm --help
如果遇上安装困难,一般是你的环境被你之前装的命令破坏了。
但是大部分的安装问题都可以搜索到解决,推荐问chatgpt,再根据它回答的上下文,去搜索解决方案
调试步骤
思路
写好汇编代码 => nasm 翻译汇编代码到二进制的处理器指令 => 制作软盘 => 导入 处理器指令程序到软盘,制成带该软盘的img镜像 => 编写启动bochs模拟处理器的配置文件 => 启动bochs => 调试汇编代码
汇编Hello world
hello.asm
bits 16
org 0x7c00jmp startstart:mov ah, 0x0e ; 寄存器ax的高8位设置0x0e 表示 "在 tty 模式下向屏幕打印字符'mov al, 'H' ; 寄存器ax的低8位设置 'H'int 0x1H ;触发中断 VGA BIOS,参数AX,打印al字母 mov al, 'e'int 0x10mov al, 'l'int 0x10mov al, 'l'int 0x10mov al, 'o'int 0x10hang:jmp hang ; 反复重复执行 jmp 回到 hang 处 ;挂住程序times 510-($-$$) db 0
dw 0xaa55
0x7c00
是 BIOS 加载主引导扇区(Master Boot Record, MBR)的默认内存地址, 即加电检查后处理器引导执行第一行命令开始执行的地方主引导扇区(Master Boot Record, MBR)的规定大小是512字节, 所以在代码的末尾要对剩余的空间反复填充 0,再以 aa 55 两个字节结尾
制作软盘
# 编译asm汇编代码
nasm -f bin -o hello.bin hello.asm
# 制作一个1.44MB的虚拟软盘镜像,并将程序写入镜像
dd if=/dev/zero of=floppy.img bs=1024 count=1440
dd if=hello.bin of=floppy.img conv=notrunc
Bochs配置文件
在一个名为bochsrc.bxrc
的文件中,输入以下内容:
# 设置32mb 模拟机器的内存大小
megs: 32
romimage: file=/usr/local/Cellar/bochs/2.7/share/bochs/BIOS-bochs-latest
vgaromimage: file=file=/usr/local/Cellar/bochs/2.7/share/bochs/VGABIOS-lgpl-latest
# boot: a 指定了启动设备的顺序,a 表示首选启动设备为第一个软盘驱动器 floppya
floppya: 1_44=floppy.img, status=inserted
boot: a
log: bochsout.txt
# 静默鼠标
mouse: enabled=0
# 设置显示库 需要根据实际情况安装
display_library: sdl2, options="gui_debug"# romimage vgaromimage 搜索实际的目录地址
sudo find / -name "BIOS-bochs-latest" 2>/dev/null
执行镜像
bochs -f bochsrc.bxrc
# 运行时根据提示 continue 运行程序 输入继续执行命令 弹出屏幕会打印hello
1. Restore factory default configuration
2. Read options from...
3. Edit options
4. Save options to...
5. Restore the Bochs state from...
6. Begin simulation
7. Quit nowPlease choose one: [6] 6 # 输入6
Please choose one: [6]
00000000000i[ ] lt_dlhandle is 0x600001d34000
00000000000i[PLUGIN] loaded plugin libbx_sdl2_gui.so
00000000000i[ ] installing sdl2 module as the Bochs GUI
00000000000i[SDL2 ] maximum host resolution: x=2560 y=1600
00000000000i[ ] using log file bochsout.txt
Next at t=0
(0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
<bochs:1> c # 输入c continue
<bochs:1> r # 显示寄存器
rax: 00000000_00000000
rbx: 00000000_00000000
rcx: 00000000_00000000
rdx: 00000000_00000000
rsp: 00000000_00000000
rbp: 00000000_00000000
rsi: 00000000_00000000
rdi: 00000000_00000000
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_0000fff0
eflags 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf
<bochs:2> s # 执行下一步 step / n next 同理
Next at t=1
(0) [0x0000000fe05b] f000:e05b (unk. ctxt): xor ax, ax ; 31c0
<bochs:4> u /20 # 显示接下去20行汇编代码 以及其内存地址
00000000000fe05b: ( ): xor ax, ax ; 31c0
00000000000fe05d: ( ): out 0x0d, al ; e60d
00000000000fe05f: ( ): out 0xda, al ; e6da
00000000000fe061: ( ): mov al, 0xc0 ; b0c0
00000000000fe063: ( ): out 0xd6, al ; e6d6
00000000000fe065: ( ): mov al, 0x00 ; b000
00000000000fe067: ( ): out 0xd4, al ; e6d4
00000000000fe069: ( ): mov al, 0x0f ; b00f
00000000000fe06b: ( ): out 0x70, al ; e670
00000000000fe06d: ( ): in al, 0x71 ; e471
00000000000fe06f: ( ): mov bl, al ; 88c3
00000000000fe071: ( ): mov al, 0x0f ; b00f
00000000000fe073: ( ): out 0x70, al ; e670
00000000000fe075: ( ): mov al, 0x00 ; b000
00000000000fe077: ( ): out 0x71, al ; e671
00000000000fe079: ( ): mov al, bl ; 88d8
00000000000fe07b: ( ): cmp al, 0x00 ; 3c00
00000000000fe07d: ( ): jz .+36 (0x0000e0a3) ; 7424
00000000000fe07f: ( ): cmp al, 0x0d ; 3c0d
00000000000fe081: ( ): jnb .+32 (0x0000e0a3) ; 7320
<bochs:8> b 0x00000000000fe063 #设置断点
<bochs:9> c # 继续执行 直到断点
(0) Breakpoint 1, 0x00000000000fe063 in ?? ()
Next at t=5
(0) [0x0000000fe063] f000:e063 (unk. ctxt): out 0xd6, al ; e6d6
<bochs:10> u # 下一行指令 输出 al 内容
00000000000fe063: ( ): out 0xd6, al ; e6d6
<bochs:11> r
rax: 00000000_000000c0
...
<bochs:n> c # 执行完
整个调试的过程就是,写好代码,反复执行上述的流程,编译打包如虚拟软盘,bochs运行镜像,运行主引导扇区0x7c00处的512字节的代码
bochs内的命令
$ bochs
> sreg 看段寄存器内容
> r 显示寄存器内容
> s 单步执行 step 并显示下一条指令
> b 设置break断点,到指定内存地址
> b 0x7c00
> c 持续执行 知道断点
> xp /512xb 0x7c00 从0x7c00处开始显示512xb字节数的内容
> xp /16xb 0xb8000 显示显示器内容 注意找到正确的内存地址值
> next/n
汇编代码样例
访问显卡输出字符
在内存地址B8000 ~ BFFFF 隐射到显存 32kb,即把ASCII的二进制数输出到该段内存,即可在屏幕上打印字符
打印65535在屏幕上
boot.asm
start:mov ax, 65535 ;ffffmov dx, 0mov bx, 10 ; adiv bx ; AX=商(6553),DX=余数(5)add dl,0x30 ; 将数字转成数字字符 5->3->5->5->6mov cx, 0 ;将数据段(Data Segment)的基址设置为零,以便使用偏移地址直接访问内存mov ds,cx mov [0x7c00+buffer], dl ; 寄存器数据存入 通过偏移地址计算的内存地址 ds[0]+ [0x7c00+buffer];查看内存 ds:0x7c8b 处的值xor dx, dx div bx ; AX=商(00000000_0000028f),DX=余数(3)add dl, 0x30 ; 0x30 +0x05 =0x33mov [0x7c00+buffer+1], dl ; 0x7c8cxor dx, dxdiv bx add dl, 0x30 ; 0x30 +0x05 =0x35mov [0x7c00+buffer+2], dl ;0x7c8dxor dx, dxdiv bxadd dl, 0x30mov [0x7c00+buffer+3], dlxor dx, dxdiv bxadd dl, 0x30mov [0x7c00+buffer+4], dl; 使用附加段寄存器mov cx,0xb800mov es, cx ;传送段地址mov al, [0x7c00+buffer+4]mov [es:0x00], al ;es:段超越前缀mov byte [es:0x01], 0x2f mov al, [0x7c00+buffer+3]mov [es:0x02], al mov byte [es:0x03], 0x2f mov al, [0x7c00+buffer+2]mov [es:0x04], al mov byte [es:0x05], 0x2f mov al, [0x7c00+buffer+1]mov [es:0x06], al mov byte [es:0x07], 0x2f mov al, [0x7c00+buffer]mov [es:0x08], al mov byte [es:0x09], 0x2f
again:jmp again
buffer:db 0,0,0,0,0 ;开辟5字节空间
current:times 510-(current-start) db 0 dw 0xaa55
编译操作以及调试步骤
nasm -f bin -o boot.bin boot.asmdd if=/dev/zero of=boot.img bs=1024 count=1440
dd if=boot.bin of=boot.img conv=notruncvim bootsrc.bxrc
megs: 32
romimage: file=/usr/local/Cellar/bochs/2.7/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/Cellar/bochs/2.7/share/bochs/VGABIOS-lgpl-latest
floppya: 1_44=boot.img, status=inserted
boot: a
log: bochsout.txt
mouse: enabled=0
display_library: sdl2, options="gui_debug"bochs -f bootsrc.bxrc
$ bochs -f bootsrc.bxrc
<bochs:1> b 0x7c00 # 下断点
<bochs:4> c
(0) Breakpoint 1, 0x0000000000007c00 in ?? ()
Next at t=14034555
(0) [0x000000007c00] 0000:7c00 (unk. ctxt): mov ax, 0xffff ; b8ffff
<bochs:3> n
Next at t=14034556
(0) [0x000000007c03] 0000:7c03 (unk. ctxt): mov dx, 0x0000 ; ba0000
<bochs:4> r # 查看寄存器
rax: 00000000_0000ffff
rbx: 00000000_00000000
rcx: 00000000_00090000
rdx: 00000000_00000000
rsp: 00000000_0000ffd6
rbp: 00000000_00000000
rsi: 00000000_000e0000
rdi: 00000000_0000ffac
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_00007c03
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
<bochs:5> u /16 #查看写入的代码
0000000000007c00: ( ): mov ax, 0xffff ; b8ffff
0000000000007c03: ( ): mov dx, 0x0000 ; ba0000
0000000000007c06: ( ): mov bx, 0x000a ; bb0a00
0000000000007c09: ( ): div ax, bx ; f7f3
0000000000007c0b: ( ): add dl, 0x30 ; 80c230
0000000000007c0e: ( ): mov cx, 0x0000 ; b90000
0000000000007c11: ( ): mov ds, cx ; 8ed9
0000000000007c13: ( ): mov byte ptr ds:0x7c8b, dl ; 88168b7c
0000000000007c17: ( ): xor dx, dx ; 31d2
0000000000007c19: ( ): div ax, bx ; f7f3
0000000000007c1b: ( ): add dl, 0x30 ; 80c230
0000000000007c1e: ( ): mov byte ptr ds:0x7c8c, dl ; 88168c7c
0000000000007c22: ( ): xor dx, dx ; 31d2
0000000000007c24: ( ): div ax, bx ; f7f3
0000000000007c26: ( ): add dl, 0x30 ; 80c230
0000000000007c29: ( ): mov byte ptr ds:0x7c8d, dl ; 88168d7c
<bochs:6> n
Next at t=14034556
(0) [0x000000007c03] 0000:7c03 (unk. ctxt): mov dx, 0x0000 ; ba0000
# .... 除数除完后查看内存数据
<bochs:17> xp /512xb 0x7c00 # 65535 已经存入依次位置
0x0000000000007c88 <bogus+ 136>: 0x2f 0xeb 0xfe 0x35 0x33 0x35 0x35 0x36
<bochs:38> u /16
0000000000007c43: ( ): mov cx, 0xb800 ; b900b8
0000000000007c46: ( ): mov es, cx ; 8ec1
0000000000007c48: ( ): mov al, byte ptr ds:0x7c8f ; a08f7c
0000000000007c4b: ( ): mov byte ptr es:0x0000, al ; 26a20000
0000000000007c4f: ( ): mov byte ptr es:0x0001, 0x2f ; 26c60601002f
0000000000007c55: ( ): mov al, byte ptr ds:0x7c8e ; a08e7c
0000000000007c58: ( ): mov byte ptr es:0x0002, al ; 26a20200
0000000000007c5c: ( ): mov byte ptr es:0x0003, 0x2f ; 26c60603002f
0000000000007c62: ( ): mov al, byte ptr ds:0x7c8d ; a08d7c
0000000000007c65: ( ): mov byte ptr es:0x0004, al ; 26a20400
0000000000007c69: ( ): mov byte ptr es:0x0005, 0x2f ; 26c60605002f
0000000000007c6f: ( ): mov al, byte ptr ds:0x7c8c ; a08c7c
0000000000007c72: ( ): mov byte ptr es:0x0006, al ; 26a20600
0000000000007c76: ( ): mov byte ptr es:0x0007, 0x2f ; 26c60607002f
0000000000007c7c: ( ): mov al, byte ptr ds:0x7c8b ; a08b7c
0000000000007c7f: ( ): mov byte ptr es:0x0008, al ; 26a20800
<bochs:46> sreg # 查看段寄存器
es:0xb800, dh=0x0000930b, dl=0x8000ffff, valid=1Data segment, base=0x000b8000, limit=0x0000ffff, Read/Write, Accessed
cs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ss:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ds:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1
gdtr:base=0x00000000000f9ad7, limit=0x30
idtr:base=0x0000000000000000, limit=0x3ff
# 挨个查看输出到屏幕的字符和内存对应的关系
<bochs:9> xp /16xb 0x000b8000
[bochs]: # 6
0x00000000000b8000 <bogus+ 0>: 0x36 0x2f 0x6f 0x0b 0x63 0x0b 0x68 0x0b
0x00000000000b8008 <bogus+ 8>: 0x73 0x0b 0x20 0x0b 0x56 0x0b 0x47 0x0b
...
<bochs:22> n
Next at t=14034599
(0) [0x000000007c89] 0000:7c89 (unk. ctxt): jmp .-2 (0x00007c89) ; ebfe
<bochs:23> xp /16xb 0x000b8000
[bochs]:
0x00000000000b8000 <bogus+ 0>: 0x36 0x2f 0x35 0x2f 0x35 0x2f 0x33 0x2f
0x00000000000b8008 <bogus+ 8>: 0x35 0x2f 0x20 0x0b 0x56 0x0b 0x47 0x0b
全部调试完毕之后 显示如下
计算1+2+3…+100 结果并打印
编写代码,通过栈指令,和上一段代码的基础实现
;代码清单7-1;文件名:c07_mbr.asm;文件说明:硬盘主引导扇区代码;创建日期:2011-4-13 18:02jmp near startmessage db '1+2+3+...+100='start:mov ax,0x7c0 ;设置数据段的段基地址 mov ds,axmov ax,0xb800 ;设置附加段基址到显示缓冲区mov es,ax;以下显示字符串 mov si,message ; 把message的地址传入si -> source index mov di,0 ; destination index 偏移从0开始mov cx,start-message ; @g处的loop代码循环的次数 -> message的字符数@g:mov al,[si] ;因为这是硬盘主引导扇区代码,因此被加载到0x7c00,[si]=[ds:si],就是相对于代码段开头的相对偏移,这个相对偏移就是标签message的值 ->此时: ds=0x0000mov [es:di],alinc di ;di用做显存段地址的相对偏移,字符内容信息放在低一个字节mov byte [es:di],0x07 ; 放字符颜色inc di ;字符显示信息放在高一个字节inc si ;si用作寻址字符串相对偏移,每加载完一个字符 ++1loop @g;以下计算1到100的和 xor ax,ax ;清空ax寄存器,存放结果mov cx,1@f:add ax,cxinc cx ;cx做累加器cmp cx,100jle @f ;小于等于时跳转;以下计算累加和的每个数位 xor cx,cx ;设置堆栈段的段基地址mov ss,cx ; SS栈段寄存器 Stack Segment Register 此时 cx:0mov sp,cx ;堆栈段指针和段基址都在0x0000处,堆栈段从高地址向低地址生长 ;注意: 0x0000-2=0xfffd => 一减就回到以0x0000为段基地址+0xffff偏移 能表示的最大地址处mov bx,10xor cx,cx@d:inc cx ;压栈中用cx记录一共压入栈元素个数,以便之后出栈时能及时停止popxor dx,dx ;被除数[dx:ax]div bx ;除数bx 16位除法是 AX / BX ,结果存放: 商在AL,余数在寄存器AH; cpu现在64位=> 余数存在dx中or dl,0x30 ;余数在dx中,但是余数最多到9,因此在dl中就够了,加0x30得到ASCII码push dx ;dx中只有dl有意义,但是压栈的单位必须是字(两个字节)cmp ax,0 ; 是否计算完 商为0jne @d ;为0后循环跳出时,结果5050每一位被放在栈中;以下显示各个数位 @a:pop dx ;出栈,栈顶元素是千位,百位,十位,个位 pop 栈顶数据到dx寄存器中mov [es:di],dl ;寄存器dx中的数据 数字的ASCII inc dimov byte [es:di],0x07 ; + 颜色款色值inc diloop @ajmp near $ ; 反复跳回当前行 卡住times 510-($-$$) db 0 ; $ 当前汇编指令地址
; $$ 当前汇编段地址
db 0x55,0xaa
dw 0xaa55
重复上面的调试步骤,成功如下
参考资料
-
书籍《x86汇编语言-从实模式到保护模式》
http://www.lizhongc.com/index.php/157.html
-
配套代码清单 https://blog.csdn.net/qq_40345544/article/details/101698213