您的位置:首页 > 其它

nasm : 屏幕打印语句的实现

2015-09-23 16:29 337 查看
就像应用层编程一样,如果不借助第三方工具和调试器,要知道程序哪里出了问题,需要记录日志.

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 ; 结束标记
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: