就像应用层编程一样,如果不借助第三方工具和调试器,要知道程序哪里出了问题,需要记录日志.
bochs和真机还是有点区别的, 前几天搞出了打印语句, 拖到今天,实验环境搞定后,才在真机上验证通过.
现在实验用的这1个扇区,是MBR加载到内存的, 验证函数级别的问题,应该是可以了.
现在一个扇区被写的差不多满了,下一步要考虑,用BOOT代码加载更多的扇区,然后调到加载后新的扇区, 执行更多的代码才有空间保障.
打印语句如下:
* 清屏
* 设置光标位置
* 打印带背景的文本, 可以是重复的字符,也可以是一段固定的话
* 打印现有显示属性(当前前景,背景)下的文本, 可以是重复的字符,也可以是一段固定的话
* 需要完善的地方 :考虑打印寄存器,内存,这样活动的内容.
效果图:(和真机运行效果相同,已经验证过了)
nasm代码实现
; @file boot_print_debug.asm
; @brief 启动后显示一些信息
; @note 编译命令行
; cd D:\prj\nasm_prj\boot\boot_dispmsg
; d:
; C:\nasm\nasm.exe boot_print_debug.asm -d UBOOT -o boot_print_debug.bin -l boot_print_debug.list
; @note 将boot_print_debug.bin写U盘0扇区
; 实验点
; * 用栈来传递出参
; * 现在有了屏幕日志, 不用完全依赖bochs调试器来调试程序.
; 屏幕日志还需要完善, e.g. 打印一个寄存器或内存的值
; 颜色
%define COLOR_BG_RED_FG_WHITE 47h ; ///< 红底白字
; 函数内临时变量 - 以函数入口处的bp为基准
;Stack address size 2
; | STACK 0xffc6 [0x7ce7] ///< 函数内临时变量2
; | STACK 0xffc8 [0x0000] ///< 函数内临时变量1
; | STACK 0xffca [0xffcc] ///< 入口处的sp值
; | STACK 0xffcc [0x0000] ///< 入口处的bp值
; | STACK 0xffce [0x7c1e] ///< 函数返回地址, 入口处的bp值已经指到了这里
%define FUNCTION_TEMP_VAR_1 [bp - 6]
%define FUNCTION_TEMP_VAR_2 [bp - 8]
%define FUNCTION_TEMP_VAR_3 [bp - 12]
%define FUNCTION_TEMP_VAR_4 [bp - 14]
%define FUNCTION_TEMP_VAR_5 [bp - 16]
%define FUNCTION_TEMP_VAR_6 [bp - 18]
%define FUNCTION_TEMP_VAR_7 [bp - 20]
%define FUNCTION_TEMP_VAR_8 [bp - 22]
%define FUNCTION_TEMP_VAR_9 [bp - 24]
; 栈入参 - 以函数入口处的bp为基准
%define STACK_IN_PARAM_1 [bp + 2]
%define STACK_IN_PARAM_2 [bp + 4]
%define STACK_IN_PARAM_3 [bp + 6]
%define STACK_IN_PARAM_4 [bp + 8]
%define STACK_IN_PARAM_5 [bp + 10]
%define STACK_IN_PARAM_6 [bp + 12]
%define STACK_IN_PARAM_7 [bp + 14]
%define STACK_IN_PARAM_8 [bp + 16]
%define STACK_IN_PARAM_9 [bp + 18]
; 栈出参 - 以函数入口处的sp为基准, 函数出口处的sp必须和入口处相同
; 必须先执行 mov si, sp 因为没有 mov ax, [sp + 4] 这样的指令
; 用完 STACK_OUT_PARAM_X 之后, 执行 pop bp
%define STACK_OUT_PARAM_1 [si + 0]
%define STACK_OUT_PARAM_2 [si + 2]
%define STACK_OUT_PARAM_3 [si + 4]
%define STACK_OUT_PARAM_4 [si + 6]
%define STACK_OUT_PARAM_5 [si + 8]
%define STACK_OUT_PARAM_6 [si + 10]
%define STACK_OUT_PARAM_7 [si + 12]
%define STACK_OUT_PARAM_8 [si + 14]
%define STACK_OUT_PARAM_9 [si + 16]
%define BOOT_CODE_ENTRY_POINT 07c00h ; ///< boot 代码被BIOS加载后的位置
; /// 这句没用的, 自己用WinHex烧到想要的扇区
org BOOT_CODE_ENTRY_POINT ; 程序加载到0x7c00
; --------------------------------------------------------------------------------
; /// @todo ls for debug
; --------------------------------------------------------------------------------
; jmp $ ; 死循环
; /// 在真机上, 读取和设置段,均会引起异常. 跑飞了
mov ax, cs
mov ds, ax
mov es, ax
; /// ss 和 sp 依赖与bios启动时设置的值,还是不改了
; mov ss, ax
; mov sp, BOOT_CODE_ENTRY_POINT
; /// 清屏
call clear_screen
; --------------------------------------------------------------------------------
; 显示一行分隔线, 开始位置(20,1), 长度78
; 在21,22,23,24行显示调试信息
; --------------------------------------------------------------------------------
; /// 设置光标(20,1)
push 20 ; ///< y
push 1 ; ///< x
call set_cursor ; ///< set_cursor(x, y)
add sp, 4 ; ///< 堆栈平衡
; /// 显示78个-
push COLOR_BG_RED_FG_WHITE ; ///< 颜色 : 红底白字
push 78
push '='
call disp_n_char_by_attributes ; ///< disp_repeat_char(char cDispContent, int iDispCount, int iColor)
add sp, 6 ; ///< 堆栈平衡
; --------------------------------------------------------------------------------
; 在(21,1)显示信息 str_boot_msg
; --------------------------------------------------------------------------------
push 21
push 1
call set_cursor
add sp, 4 ; ///< 堆栈平衡
mov si, str_boot_msg
mov cx, len_str_boot_msg
call disp_message_by_default_attributes
; /// disp_message_by_default_attributes 是靠寄存器传参, 不需要堆栈平衡
; --------------------------------------------------------------------------------
; 在(22,1)写一行分隔符, 使用现有属性, 长度40
; --------------------------------------------------------------------------------
push 22
push 1
call set_cursor
add sp, 4 ; ///< 堆栈平衡
push 40
push '-'
call disp_n_char_by_default_attributes
add sp, 4 ; ///< 堆栈平衡
; --------------------------------------------------------------------------------
; 在(23,1)提示信息 str_debug_tip
; --------------------------------------------------------------------------------
push 23
push 1
call set_cursor
add sp, 4 ; ///< 堆栈平衡
mov si, str_debug_tip
mov cx, len_str_debug_tip
call disp_message_by_default_attributes
; --------------------------------------------------------------------------------
; 在(24,1)提示其他信息
; --------------------------------------------------------------------------------
; /// 空间不够了, 先屏蔽掉这一段
;main_read_kb:
; push 24
; push 1
; call set_cursor
; add sp, 4 ; ///< 堆栈平衡
; call getchar
; mov si, str_1_dot
; mov cx, len_str_1_dot
; call disp_message_by_default_attributes
; jmp main_read_kb
; --------------------------------------------------------------------------------
; /// @todo ls for debug
; --------------------------------------------------------------------------------
jmp $ ; 死循环
disp_str:
mov bp, sp
push bp
mov bh, 0 ; no.0 page
mov bl, 47h ; char color
mov cx, 6 ; char count is 1
mov al, '6' ; char content is '6'
mov ah, 9 ; bios 10# function = write char to UI
int 10h
pop bp
mov sp, bp
ret
; /// @fn char getchar(), al是键值
getchar:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
getchar_clear_buffer:
mov ah, 1
int 16h
jz getchar_clear_read ; ///< 键盘缓冲区都空了, 可以转"读键盘输入"
mov ah, 0
int 16h
jmp getchar_clear_buffer ; ///< 继续清键盘缓冲区
getchar_clear_read:
mov ah, 1
int 16h
jz getchar_clear_read ; ///< 如果没有键盘输入,继续死等键盘输入
mov ah, 0 ; ///< al是键盘输入
int 16h
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// @fn disp_n_char_by_default_attributes(char* pcMsg, int iLenMsg)
; si = pcMsg
; cx = iLenMsg
disp_message_by_default_attributes:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
push cx ; ///< 重复执行次数
push bx
mov bx, 0
disp_message_by_default_attributes_begin:
cmp cx, 0
jbe disp_message_by_default_attributes_end ; ///< 执行次数 <= 0, break
dec cx
mov ax, [si + bx] ; ///< 需要显示的字符
inc bx
push ax
; /// disp_one_char_by_default_attributes 必须保护 bx,cx,si
call disp_one_char_by_default_attributes
add sp, 2 ; ///< 堆栈平衡
jmp disp_message_by_default_attributes_begin
disp_message_by_default_attributes_end:
pop bx
pop cx
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// @fn disp_n_char_by_default_attributes(char cDispContent, int iDispCnt)
disp_n_char_by_default_attributes:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
mov cx, STACK_IN_PARAM_2 ; ///< 重复执行次数
disp_n_char_by_default_attributes_begin:
cmp cx, 0
jbe disp_n_char_by_default_attributes_end ; ///< 执行次数 <= 0, break
dec cx
mov ax, STACK_IN_PARAM_1 ; ///< 需要显示的字符
push ax
call disp_one_char_by_default_attributes
add sp, 2 ; ///< 堆栈平衡
jmp disp_n_char_by_default_attributes_begin
disp_n_char_by_default_attributes_end:
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// @fn disp_one_char_by_default_attributes(char cDispContent)
disp_one_char_by_default_attributes:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
push bx
push cx
push si
xor bh, bh
mov ax, STACK_IN_PARAM_1
mov ah, 0x0e
int 0x10
pop si
pop cx
pop bx
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// @fn disp_repeat_char(char cDispContent, int iDispCount, int iColor)
; /// @brief 先写显示内容,再移动光标
disp_n_char_by_attributes:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
; /// 开辟一个临时变量区
; /// STACK_IN_PARAM_1 保存光标位置
; /// STACK_IN_PARAM_2 保存 iDispCount
sub sp, 4
; /// 初始化临时变量, 使调试时,容易分辨
mov ax, 0xffff
mov FUNCTION_TEMP_VAR_1, ax ; ///< 临时变量1
mov ax, 0xffff
mov FUNCTION_TEMP_VAR_2, ax ; ///< 临时变量2
mov bh, 0 ; no.0 page
mov ax, STACK_IN_PARAM_3 ; ///< int iColor
mov bl, al ; char color
; /// 做个标记, 有利于在bochs下下断点到这里
add ax, 0x112
mov ax, STACK_IN_PARAM_2 ; ///< int iDispCount
mov FUNCTION_TEMP_VAR_2, ax ; ///< 保存字符个数, 用于设置新的光标位置
mov cx, ax ; char count
mov ax, STACK_IN_PARAM_1 ; ///< char cDispContent
mov ah, 9 ; functin sn : 按照设定的属性显示重复的字符
int 10h
; /// 移动光标到末尾
; /// 先得到光标位置(x,y)
; /// 得到光标
push 0
call get_cursor
mov si, sp
add sp, 2 ; ///< 堆栈平衡;
mov ax, STACK_OUT_PARAM_1 ; ///< 将得到的光标位置给ax 出参 ah = y, al = x
mov FUNCTION_TEMP_VAR_1, ax ; ///< 保存当前光标位置,用于设置新的光标位置
; /// 再设置光标位置为(x+ STACK_PARAM2, y)
xor ax, ax
mov cx, FUNCTION_TEMP_VAR_1
mov al, ch; ///< y
push ax ; ///< y
xor ax, ax
mov cx, FUNCTION_TEMP_VAR_1
mov al, cl
add ax, FUNCTION_TEMP_VAR_2
push ax ; ///< x
call set_cursor ; ///< set_cursor(x, y)
add sp, 4 ; ///< 堆栈平衡
add sp, 4 ; ///< 临时变量的堆栈平衡
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// 清屏
clear_screen:
; /// 保护现场 bp和sp
mov ax, bp
push ax
mov ax, sp
push ax
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
; (7)、功能 06H 和 07H
; 功能描述:初始化屏幕或滚屏
; 入口参数:AH=06H——向上滚屏,07H——向下滚屏
; AL=滚动行数(0——清窗口)
; BH=空白区域的缺省属性
; (CH、CL)=窗口的左上角位置(Y 坐标,X 坐标)
; (DH、DL)=窗口的右下角位置(Y 坐标,X 坐标)
; 出口参数:无
mov ah, 6h
mov al, 0
mov bh, 17h ; ///< 蓝底白字,光标闪烁
mov cl, 0
mov ch, 0
mov dl, 79
mov dh, 24
int 10h
; --------------------------------------------------------------------------------
; /// @todo ls for debug
; --------------------------------------------------------------------------------
; jmp $ ; 死循环
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// 设置光标位置
; /// set_cursor(x, y)
set_cursor:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
; push CURSOR_Y_0 ; ///< STACK_PARAM2
; push CURSOR_X_0 ; ///< STACK_PARAM1
mov ax, 0
mov bh, al ; ///< display page number
mov ax, STACK_IN_PARAM_1
mov dl, al ; ///< cursor column, CURSOR_X_N
mov ax, STACK_IN_PARAM_2
mov dh, al ; ///< cursor row, CURSOR_Y_N
mov ah, 2
mov al, 0
int 10h
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
; /// 得到光标位置
; /// get_cursor(x, y)
; 调用示例
; push 0 ///< y, STACK_OUT_PARAM_2
; push 0 ///< x, STACK_OUT_PARAM_1
; call get_cursor
get_cursor:
; /// 保护现场 bp和sp
push bp
push sp
; /// 使bp回到函数入口处的值
mov ax, sp
add ax, 4
mov bp, ax
; push CURSOR_Y_0 ; ///< STACK_PARAM2
; push CURSOR_X_0 ; ///< STACK_PARAM1
; (4)、功能 03H
; 功能描述:在文本坐标下,读取光标各种信息
; 入口参数:AH=03H
; BH=显示页码
; 出口参数:CH=光标的起始行
; CL=光标的终止行
; DH=行(Y 坐标)
; DL=列(X 坐标)
xor ax, ax
mov ah, 3h
mov bh, 0
int 10h
mov ax, dx
mov STACK_IN_PARAM_1, ax
; /// 恢复现场 bp和sp
pop sp
pop bp
ret
str_boot_msg: db "the boot code was witten by NASM assembler", 0
len_str_boot_msg equ ($ - str_boot_msg)
str_debug_tip: db "press any key to go next watch point", 0
len_str_debug_tip equ ($ - str_debug_tip)
; /// 空间不够了, 还有11个字节满512, 等下次再玩
; str_1_dot: db ".", 0
; len_str_1_dot equ ($ - str_1_dot)
; str_build_time: db "2015_0923_1532"
times 510-($-$$) db 0 ; 用0填充剩余空间,使该段二进制代码正好为512字节
dw 0aa55h ; 结束标记