8086汇编_用户程序_字符串打印

一、环境

1、notepad++编写

2、硬盘写入工具 vhd writer 链接

3、nasm编译

4、bochs调试

5、virtual box测试

二、要求

编写一个用户程序加载器

具体要求:
用户程序在磁盘的第100个扇区
将用户程序从磁盘加载到内存的0x10000位置
进入用户程序执行,最后返回程序加载器

编写一个用户程序

具体要求:
在屏幕上打印字符串
ascii字符0x0d为回车字符,光标回车
ascii字符0x0a为换行字符,光标换行
ascii字符0x0为字符串结尾,结束输出
其他字符在光标处进行打印输出,然后推进光标
显存写满时(光标位置>=2000),则向上滚屏,整体向上移动一行

资源

源码以及vhd虚拟硬盘
链接:https://pan.baidu.com/s/1bOcA0w17Nh0RKXF2uZ1WkQ
提取码:undc

三、思路

用户程序解析

加载器

第一步:设置栈段
设置栈段后要紧跟着设置栈顶位置,设置栈段后系统会在下一条指令执行前禁止中断,防止栈段已设置,但栈顶未设置的期间中断例程造成不合法的访问,所以栈顶设置尽量紧跟着栈段设置

第二步:设置数据段

第三步:计算用户程序加载地址

第四步:加载用户程序前512字节的内容(1个扇区大小)

第五步:计算用户程序大小,根据结果确定是否继续读取磁盘

第六步:用户程序段重定位表处理

第七步:填入调用用户程序的地址

第八步:调用用户程序

第九步:用户程序执行完毕,进入死循环

用户程序字符串打印过程


四、效果

开始

加载器程序加载完成,准备跳转

跳转至用户程序

即将执行字符串打印例程

第一段字符串打印效果

第二段字符串打印效果

用户程序执行完毕,准备跳转回加载器

成功跳转回加载器

virtual box演示效果

五、代码

加载器代码

		app_lba_start equ 100				;常数申明
section loader align=16 vstart=0x7c00
		
		;设置堆栈指针,起始地址0x07e0,长度256字节
		mov ax,0x07e0
		mov ss,ax
		mov sp,0x100
		
		;用户程序加载段地址计算
		mov ax,[cs:phy_base]
		mov dx,[cs:phy_base+0x02]
		mov bx,0x10
		div bx								;商在ax中,余在dx中
		
		;ds、es指向userapp加载段地址
		mov ds,ax
		mov es,ax
		
		;读取userapp头部512字节
		xor di,di							;传递userapp逻辑扇区号
		mov si,app_lba_start				;传递userapp逻辑扇区号
		mov bx,0x0							;传递userapp加载偏移地址
		call read_hard_disk_0
		
		;填入
		
		;计算userapp大小
		mov ax,[ds:0x04]
		mov dx,[ds:0x06]
		mov bx,512
		div bx
		cmp dx,0x0							;如果未除尽,说明读取扇区等于ax+1,减去已读取的还有ax个,ax也就不用减了
		jnz @1
		dec ax
	@1:
		cmp ax,0x0							;表示userapp长度<=512字节,已经读取过一个扇区,就不用读取了
		jz @3
		
		;读取剩余扇区
		xor bx,bx
		mov cx,ax
		
	@2:
		inc si               				;逻辑扇区号递增
		add bx,0x200						;userapp加载偏移地址递增
		call read_hard_disk_0
		loop @2
		
	@3:
		;段重定位表处理
		mov bx,0x10							;段重定位表起始地址
		mov cx,[ds:0x0e]					;段重定位表项数
		
	@4:
		mov ax,[ds:bx]
		mov dx,[ds:bx+0x02]
		call calc_section_base
		mov [ds:bx],ax
		add bx,0x04
		loop @4
		
		;计算userapp的main代码段地址
		mov ax,[ds:0x0a]
		mov dx,[ds:0x0a+2]
		call calc_section_base
		mov [ds:0x0a],ax

		;填入调用地址
		mov ax,@5
		mov [ds:0x0],ax
		mov ax,cs
		mov [ds:0x2],ax
		
		jmp far [0x08]
		
	@5:	
		jmp $
	
read_hard_disk_0:
		push ax
		push bx
		push cx
		push dx
		
											;di:si为扇区号,ds:bx为写入地址
		
		mov dx,0x1f2						;0x1f2:设置读取扇区数
		mov al,0x01
		out dx,al							;格式固定,dx指定端口,al传输或接收数据
		
		inc dx								;0x1f3:设置 lba 0~7位
		mov ax,si
		out dx,al
		
		inc dx								;0x1f4:设置 lba 8~15位
		mov al,ah
		out dx,al
		
		inc dx								;0x1f5:设置 lba 16~23位
		mov ax,di
		out dx,al
		
		inc dx								;0x1f6:设置 lba 24~27位,28~31位为选择设置
		and ah,0x0F							;清除ah高4位,低4位存储 LBA 信息
		mov al,0xe0							;al高4位设置选择信息,0xe0表示 主硬盘 LBA模式
		or al,ah							;将ah低4位 lba信息 传送到al
		out dx,al							;此时al 0~3 存储 lba信息,4~7 存储选择信息
		
		inc dx								;0x1f7:控制命令端口
		mov al,0x20							;0x20表示读取硬盘命令
		out dx,al
		
		;等待硬盘准备完毕
	waits:
		in al,dx							;0x1f7端口每时每刻返回自身状态信息
		and al,0x88							;清除7、3位之外的信息
											;第7位为0表示硬盘不忙,为1表示硬盘忙
											;第3位为0表示硬盘没有准备好,为1表示硬盘准备好进行传输
		cmp al,0x08
		jnz waits							;若未准备好,则持续等待
		
		;准备读取,ds:bx为写入地址
		mov cx,256							;读取字数
		mov dx,0x1f0						;dx:数据传输端口
	read:
		in ax,dx
		mov [bx],ax
		add bx,0x02
		loop read

		pop dx
		pop cx
		pop bx
		pop dx
		
		ret
		
calc_section_base:							;接收dx:ax userapp分段地址
											;返回段地址,填入ax
		push dx								;不将ax压栈是因为ax用于返回计算结果
		
		add ax,[cs:phy_base]				;将低16位加到ax中
		adc dx,[cs:phy_base+0x02]			;adc加法会中进位标志位中取来自上一次加法的进位加到结果中,这样就完成了两个32位数的相加
		
		;高16位在dx中,低16位在ax中
		;x86一共20位地址,高4位在dx低4位中,低16位在ax中
		;因为我们要计算的是段地址,ax段中低4位应该为0
		;于是仅需要将dx低4位,ax高12位组合传输到ax
		;shr位右移指令,ror为循环右移指令
		ror dx,0x04							;左移12位,低12位被置0
		shr ax,0x04							;右移4位,高4位被置0
		and dx,0xf000
		or ax,dx
		pop dx
		
		ret
		
	phy_base:								;userapp加载的内存地址
		dd 0x10000
	
	times 510-($-$$) db 0
	db 0x55,0xaa

用户程序代码

section header align=16 vstart=0                
		;跳转至该程序前cs:ip的内容,由loader填写
comeback_addr:
		dd 0		

program_length:
		dd program_end
		
code_main_entry:
		dw start_main
		dd section.code_main.start
		
realloc_tbl_length:
		dw (header_end-code_main_section) / 4
					
code_main_section:  dd section.code_main.start
code_0_section:		dd section.code_0.start
data_0_section:		dd section.data_0.start
data_1_section:		dd section.data_1.start
stack_0_section:	dd section.stack_0.start

header_end:
section code_main align=16 vstart=0
start_main:
		mov ax,[es:stack_0_section]
		mov ss,ax
		mov sp,stack_0_end
		
		mov ax,[es:data_0_section]
		mov ds,ax		
		mov bx,string_0
		call put_string					;打印第一段字符串
		
		mov ax,[es:data_1_section]
		mov ds,ax		
		mov bx,string_1
		call put_string					;打印第二段字符串
		
		
		;返回调用位置
		push dword [es:comeback_addr]
		retf
		
;----------------------------------------------------------------------
put_string:								;接受字符串地址ds:bx并在光标处显示
		push ds							;ascii码=0x0d 回车,ascii码=0x0a 换行
		push bx							;ascii码= 0x0 返回,否则正常显示
		push cx
	
	put_cnt:
		mov cl,[ds:bx]					;字符读取
		or cl,cl
		jz exit							;cl为0,结束输出
		
		call acting						;传入字符cl,根据情况打印
		inc bx
		jmp put_cnt
		
	exit:
		pop cx
		pop bx
		pop ds
		ret
		
;----------------------------------------------------------------------		
acting:									;接收字符cl,根据情况打印
		push ax
		push bx
		push cx
		push dx
		
		cmp cl,0x0d						;回车符
		jnz @2							;不是回车符则跳到@2
		call ac_enter					;是回车符,光标回车
		jmp @acting_end
	
	@2:
		cmp cl,0x0a						;换行符
		jnz @3							;不是换行符则跳到@3
		call ac_n						;是换行符,光标换行
		jmp @acting_end
	
	@3:
		call get_cursor					;获取光标位置,存于ax
		mov bx,ax
		mov dh,0x07
		mov dl,cl
		call put_char					;传入字符dl,属性dh,显示位置bx
		add ax,0x01						;光标推进1格
		call over_						;接收光标位置ax,返回合法位置ax
		mov bx,ax
		call set_cursor
	
	@acting_end:
		pop dx
		pop cx
		pop bx
		pop ax
		ret


;----------------------------------------------------------------------
put_char:								;接收ascii码dl,属性dh,显示位置bx
		push es
		push ax
		push bx
		push dx
		
		mov ax,0xb800
		mov es,ax
		push dx
		mov ax,bx
		mov bx,0x02
		mul bx							;位置*2得到传送地址,乘法结果<4000
		mov bx,ax
		pop dx
		mov [es:bx],dx
		
		pop dx
		pop bx
		pop ax
		pop es
		ret

;----------------------------------------------------------------------
get_cursor:								;返回光标位置,存于ax中
		push dx
		
		mov dx,0x3d4					;索引端口设置
		mov al,0x0e						
		out dx,al						;索引设置
		
		mov dx,0x3d5					;数据端口设置
		in al,dx						;光标高8位读取
		mov ah,al
		
		mov dx,0x3d4					;索引端口设置
		mov al,0x0f
		out dx,al						;索引设置
		
		mov dx,0x3d5					;数据端口设置
		in al,dx						;光标低8位读取
		
		pop dx
		ret

;----------------------------------------------------------------------		
set_cursor:								;接收位置信息bx,设置光标
		push ax
		push bx
		push dx
		
		mov dx,0x3d4					;索引端口设置
		mov al,0x0e						
		out dx,al						;索引设置
		
		mov dx,0x3d5					;数据端口设置
		mov al,bh
		out dx,al						;光标高8位写入
		
		mov dx,0x3d4					;索引端口设置
		mov al,0x0f						
		out dx,al						;索引设置
		
		mov dx,0x3d5					;数据端口设置
		mov al,bl
		out dx,al						;光标低8位写入
	
		pop dx
		pop bx
		pop ax
		ret

;----------------------------------------------------------------------
cal_enter:								;计算光标若回车,返回其位置存于ax
		push bx
		
		call get_cursor					;获取光标位置,存于ax
		push ax
		mov bl,0x50
		div bl							;ax/80,商在al,余在ah
		xor bx,bx
		mov bl,ah
		pop ax
		sub ax,bx						;计算光标行首位置
		
		pop bx
		ret

;----------------------------------------------------------------------
cal_n:									;计算光标若换行,返回其位置存于ax
		
		call get_cursor					;获取光标位置,存于ax
		add ax,0x50						;计算光标换行位置
		
		ret

;----------------------------------------------------------------------
ac_enter:								;字符为回车符时,光标回到行首
		push ax
		push bx
		
		call cal_enter					;获取回车后光标显示位置ax
		mov bx,ax
		call set_cursor					;设置光标位置bx
		
		pop bx
		pop ax
		ret

;----------------------------------------------------------------------
ac_n:									;字符为换行符,换行
		push ax
		push bx
		
		call cal_n						;获取换行后光标显示位置ax
		call over_						;接收位置信息ax,返回正常显示位置ax
		mov bx,ax
		call set_cursor					;设置光标位置bx
		
		pop bx
		pop ax
		ret

;----------------------------------------------------------------------
over_:									;接收光标将要显示的位置ax,并返回合法位置ax
		
		cmp ax,0x7d0					;是否小于2000
		jl @1							;小于则跳转至@1
		call scrolling					;若不小于则进行滚屏
		sub ax,0x50						;滚屏后,处理越界值ax
	
	@1:
		ret

;----------------------------------------------------------------------
scrolling:								;滚屏操作
		push ds
		push es
		push si
		push di
		push ax
		push cx
		
		mov ax,0xb800
		mov ds,ax						;源地址
		mov si,160						;从第二行开始传送
		
		mov es,ax						;目的地址
		mov di,0						;从第一行开始接收
		
		mov cx,0x0780					;传送字数 1920
		cld								;由低地址向高地址遍历
		
		rep movsw						;批量传送
		
		mov bx,3840                     ;清除屏幕最底一行
		mov cx,80
	clear:
		mov word[es:bx],0x0720
		add bx,2
		loop clear
		
		pop cx
		pop ax
		pop di
		pop si
		pop es
		pop ds
		ret
		
section code_0 align=16 vstart=0
		push ax
		pop ax

section data_0 align=16 vstart=0
string_0:
		db '  This is NASM - the famous Netwide Assembler. '
		db 'Back at SourceForge and in intensive development! '
		db 'Get the current versions from http://www.nasm.us/.'
		db 0x0d,0x0a,0x0d,0x0a
		db 'Back at SourceForge and in intensive development! '
		db 'Get the current versions from http://www.nasm.us/.'
		db 0x0d,0x0a,0x0d,0x0a
		db 'Back at SourceForge and in intensive development! '
		db 'Get the current versions from http://www.nasm.us/.'
		db 0x0d,0x0a,0x0d,0x0a
		db 'Back at SourceForge and in intensive development! '
		db 'Get the current versions from http://www.nasm.us/.'
		db 0x0d,0x0a,0x0d,0x0a0
		db 'Back at SourceForge and in intensive development! '
		db 'Get the current versions from http://www.nasm.us/.'
		db 0x0d,0x0a,0x0d,0x0a

		db '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
		db '     xor dx,dx',0x0d,0x0a
		db '     xor ax,ax',0x0d,0x0a
		db '     xor cx,cx',0x0d,0x0a
		db '  @@:',0x0d,0x0a
		db '     inc cx',0x0d,0x0a
		db '     add ax,cx',0x0d,0x0a
		db '     adc dx,0',0x0d,0x0a
		db '     inc cx',0x0d,0x0a
		db '     cmp cx,1000',0x0d,0x0a
		db '     jle @@',0x0d,0x0a
		db '     ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
		db 0
string_0_end:

section data_1 align=16 vstart=0
string_1:
		db 'this is my own OS!!!'
		db 0
string_1_end:

section stack_0 align=16 vstart=0
		times 256 db 0

stack_0_end:
section tail align=16

program_end:
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 判断一个字符串是否是回文字符串的方法是将字符串正序和倒序都保存下来,然后比较这两个字符串是否相等。下面是8086汇编实现的示例代码: ``` assume cs:code data segment str db 'level', '$' len dw $-str buf db 20 dup(0) data ends code segment start: mov ax, data mov ds, ax ; 将字符串复制到缓冲区中 mov si, offset str mov di, offset buf mov cx, len cld rep movsb ; 反转缓冲区中的字符串 mov si, offset buf mov di, offset buf+len-1 mov cx, len/2 std rep movsb cld ; 比较原字符串和反转后的字符串 mov si, offset str mov di, offset buf mov cx, len repe cmpsb jne not_palindrome ; 是回文字符串 mov ah, 9 mov dx, offset str int 21h jmp exit not_palindrome: ; 不是回文字符串 mov ah, 9 mov dx, offset not_palindrome_msg int 21h exit: mov ax, 4c00h int 21h not_palindrome_msg db 'Not palindrome', '$' code ends end start ``` 在这个程序中,我们首先将字符串复制到缓冲区中,然后反转缓冲区中的字符串。最后,我们比较原字符串和反转后的字符串,如果它们相等,那么原字符串就是回文字符串。如果它们不相等,那么原字符串就不是回文字符串。 ### 回答2: 回文字符串是指正序和倒序排列都相同的字符串。要判断一个字符串是否是回文字符串,可以采取以下步骤: 1. 将字符串存储在内存中,假设字符串的起始地址为SI。 2. 用CX寄存器保存字符串的长度,即字符串的字符个数。 3. 设置两个指针分别指向字符串的起始位置和末尾位置,分别保存在DI和BX寄存器中。 4. 使用循环来比较指针指向的字符是否相同。 - 将SI指针指向的字符与DI指针指向的字符进行比较。 - 如果相同,继续比较下一个字符。 - 如果不同,认为该字符串不是回文字符串。 - 递增SI指针,递减DI指针,并将CX计数器递减2。 - 重复以上步骤,直到CX计数器的值为0。 5. 判断CX计数器的值是否为0。 - 若为0,则字符串是回文字符串。 - 若不为0,则字符串不是回文字符串。 以下是8086汇编代码示例: ```assembly ; 数据段 data segment str db "abcba$" ; 要判断的字符串,以$结尾 data ends ; 代码段 code segment assume cs:code, ds:data start: mov ax, data mov ds, ax ; 设置数据段寄存器 lea si, str ; SI指向字符串起始地址 mov bx, si ; BX保存字符串起始地址 lea di, [bx+2] ; DI指向字符串末尾位置,跳过$符号 mov cx, 0 ; 初始化CX计数器 ; 计算字符串长度 count: cmp byte ptr [si], '$' ; 判断是否达到字符串末尾 je compare ; 如果是,则跳转到compare inc si ; SI指针递增,指向下一个字符 inc cx ; CX计数器递增 jmp count ; 继续计算字符串长度 compare: dec cx ; 将计数器减一,因为比较的次数为长度减二 jz palindrome ; 如果计数器为0,说明字符串是回文字符串 loop: cmpsb ; 比较SI和DI指针指向的字符 jne not_palindrome ; 如果不相等,跳转到not_palindrome loop compare ; 继续循环比较下一个字符 palindrome: mov ah, 09h lea dx, [si-2] ; SI指向字符串起始地址 int 21h ; 打印是回文字符串的提示信息 jmp exit not_palindrome: mov ah, 09h lea dx, [si-2] ; SI指向字符串起始地址 int 21h ; 打印不是回文字符串的提示信息 exit: mov ah, 4Ch int 21h ; 程序结束 code ends end start ``` 该汇编代码的基本思路是通过循环比较字符串的正序和倒序字符是否相同,若每一对字符都相同,则认为字符串是回文字符串。代码中的注释有详细的解释。 ### 回答3: 判断一个字符串是否是回文字符串,即正向读和反向读都相同,可以采用以下步骤来实现: 1.设置指向字符串开头的指针和指向字符串末尾的指针。 2.比较指针所指向的字符是否相等,如果相等继续移动指针,如果不相等则说明不是回文字符串。 3.重复上述比较直到指针相遇或者指针交叉。 4.指针相遇或者交叉则判断为是回文字符串,否则不是回文字符串。 下面是用8086汇编语言实现该逻辑的代码: ``` DATA SEGMENT STRING DB 'ABCBA$' ; 要判断的字符串,以$结尾 DATA ENDS CODE SEGMENT START: MOV AX, @DATA MOV DS, AX MOV SI, OFFSET STRING ; 指向字符串开头 MOV DI, OFFSET STRING ; 指向字符串开头 ; 计算字符串长度 MOV CX, 0 MOV AL, [SI] ; 取出当前字符 CMP AL, '$' ; 如果当前字符是$,说明已到字符串末尾 JE PALINDROME ; 如果是回文字符串直接跳转到尾部 COUNT_LENGTH: INC SI ; 移动指针到下一个字符 INC CX ; 字符数加1 MOV AL, [SI] CMP AL, '$' ; 如果当前字符是$,说明已到字符串末尾 JNE COUNT_LENGTH ; 如果没有遇到末尾符号则继续循环 PALINDROME: DEC SI ; 指向末尾符号之前的字符 PUSH CX ; 保存字符数寄存器CX CHECK: MOV AL, [SI] CMP AL, [DI] JNE NOT_PALINDROME ; 如果字符不一致,不是回文字符串 INC SI ; 移动指向末尾字符的指针 DEC DI ; 移动指向开头字符的指针 LOOP CHECK ; 继续比较下一个字符 IS_PALINDROME: POP CX ; 恢复字符数寄存器CX MOV AH, 4CH ; 程序退出 INT 21H NOT_PALINDROME: MOV AH, 9 ; 显示字符串的中断调用 MOV DX, OFFSET NOT_PALINDROME_MSG INT 21H MOV AH, 4CH ; 程序退出 INT 21H NOT_PALINDROME_MSG DB 'Not a palindrome string$' CODE ENDS END START ``` 以上汇编程序实现了判断字符串是否回文的功能。可以根据具体的需求修改字符串的内容(STRING DB 'ABCBA$'),将代码保存为asm文件,并使用汇编工具进行编译和运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值