汇编之调试环境搭建及调试步骤详解

调试环境搭建

思路

学习一门语言,上手上机调试是非常重要的,它会带来一个感性的认知,所以学习汇编第一件事,推荐把开发环境搞起来。

安装 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 0x7c00

jmp start

start:
    mov ah, 0x0e ; 寄存器ax的高8位设置0x0e 表示 "在 tty 模式下向屏幕打印字符'
    mov al, 'H' ; 寄存器ax的低8位设置 'H'
    int 0x1H ;触发中断  VGA BIOS,参数AX,打印al字母 

    mov al, 'e'
    int 0x10

    mov al, 'l'
    int 0x10

    mov al, 'l'
    int 0x10

    mov al, 'o'
    int 0x10

hang:
    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 now

Please 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 ;ffff
    mov dx, 0
    mov bx, 10 ; a
    div bx  ; AX=商(6553),DX=余数(5)
     
    add dl,0x30 ; 将数字转成数字字符 5->3->5->5->6
    
    mov 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 =0x33
    mov [0x7c00+buffer+1], dl ;  0x7c8c
    
    xor dx, dx
    div bx 
    add dl, 0x30 ; 0x30 +0x05 =0x35
    mov [0x7c00+buffer+2], dl ;0x7c8d
    
    xor dx, dx
    div bx
    add dl, 0x30
    mov [0x7c00+buffer+3], dl
    
    xor dx, dx
    div bx
    add dl, 0x30
    mov [0x7c00+buffer+4], dl
		; 使用附加段寄存器
    mov cx,0xb800
    mov 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.asm

dd if=/dev/zero of=boot.img bs=1024 count=1440
dd if=boot.bin of=boot.img conv=notrunc

vim  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=1
	Data segment, base=0x000b8000, limit=0x0000ffff, Read/Write, Accessed
cs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
	Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ss:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
	Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ds:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
	Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
	Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
	Data 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:02

         jmp near start

 message db '1+2+3+...+100='

 start:
         mov ax,0x7c0           ;设置数据段的段基地址 
         mov ds,ax

         mov 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=0x0000
         mov [es:di],al
         inc di					;di用做显存段地址的相对偏移,字符内容信息放在低一个字节
         mov byte [es:di],0x07 ; 放字符颜色
         inc di					;字符显示信息放在高一个字节
         inc si					;si用作寻址字符串相对偏移,每加载完一个字符 ++1
         loop @g

         ;以下计算1到100的和 
         xor ax,ax				;清空ax寄存器,存放结果
         mov cx,1
     @f:
         add ax,cx
         inc cx					;cx做累加器
         cmp cx,100
         jle @f					;小于等于时跳转

         ;以下计算累加和的每个数位 
         xor cx,cx              ;设置堆栈段的段基地址
         mov ss,cx							; SS栈段寄存器  Stack Segment Register 此时 cx:0
         mov sp,cx				;堆栈段指针和段基址都在0x0000处,堆栈段从高地址向低地址生长 
         ;注意: 0x0000-2=0xfffd => 一减就回到以0x0000为段基地址+0xffff偏移 能表示的最大地址处

         mov bx,10
         xor cx,cx
     @d:
         inc cx			;压栈中用cx记录一共压入栈元素个数,以便之后出栈时能及时停止pop
         xor 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		; 是否计算完 商为0
         jne @d			;为0后循环跳出时,结果5050每一位被放在栈中

         ;以下显示各个数位 
     @a:
         pop dx			;出栈,栈顶元素是千位,百位,十位,个位 pop 栈顶数据到dx寄存器中
         mov [es:di],dl ;寄存器dx中的数据 数字的ASCII 
         inc di
         mov byte [es:di],0x07 ; + 颜色款色值
         inc di
         loop @a

         jmp 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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值