x86汇编加载用户程序-4-2

╰+哭是因爲堅強的太久メ 2022-09-04 04:51 225阅读 0赞

基于上篇加载了应到程序后,这一篇对用户程序内容进行增加,有屏幕显示多行内容,并实现超出屏幕,滚动屏幕,光标移动等。

引言

简单介绍本次实践,需要用的知识点。

屏幕光标控制

索引寄存器的端口号是 0x3d4,可以向它写入一个值,用来指定内部的某个寄存器。比如,
两个 8 位的光标寄存器,其索引值分别是 14(0x0e)和 15(0x0f),分别用于提供光标位置的高 8 位和低 8 位。
指定了寄存器之后,要对它进行读写,这可以通过数据端口 0x3d5 来进行。
高八位 和第八位里保存这光标的位置,显卡文本模式显示标准是25x80,这样算来,当光标在屏幕右下角时,该值为 25×80-1=1999

mul指令

第一种执行 8 位操作数与 AL 寄存器的乘法;
第二种执行 16 位操作数与 AX 寄存器的乘法;
下述语句实现 AL 乘以 BL,乘积存放在 AX 中。由于 AH(乘积的高半部分)等于零,因此进位标志位被清除(CF=0):
mov al, 5h
mov bl, 10h
mul bl ; AX = 0050h, CF = 0
如果 DX 不等于零,则进位标志位置 1,这就意味着隐含的目的操作数的低半部分容纳不了整个乘积。

resb指令

伪指令 resb(REServe Byte)的意思是从当前位置开始,保留指定数量的字节,但不初始化它们的值。在源程序编译时,编译器会保留一段内存区域,用来存放编译后的内容。当它看到这条伪指令时,它仅仅是跳过指定数量的字节,而不管里面的原始内容是什么。内存是反复使用的,谁也无法知道以前的使用者在这里留下了什么。也就是说,跳过的这段空间,每个字节的值是不确定的。

  1. resb 256 ;使用

retf指令

  1. ; retf 相当于执行了两次pop,CPU将执行CS:IP的指令
  2. ; POP IP
  3. ; POP CS

回车和换行

回车换行在ASCII码表内是两个码分别是0x0d,0x0a
回车的功能就是光标移动到行首,换行就是到下一行。
在显卡文本模式下25x80,换行就是+80,移动到行首就是
除以80取商再乘以80

疑问

汇编有函数吗?

一下是我的理解,有如果错误欢迎批评指正。
万不能把标号下的内容当作一个函数,这只是一个程序的开始地址,当一个标号下的内容运行结束后,不会返回到调用那,需要使用ret,或retf来返回,
这个指令会返回到调用call那。
由于错把标号当作一个函数的缘故,导致我在写这段程序没有注意到顺序,
将.put_other和.set_cursor的标号里的内容调换了位置,结果程序在运行了put_other标号下最后一条指令会执行start标号的内容,导致错误。
所以必须明确汇编在运行的时候没有遇到转移指令,call和ret或retf的时候都是一步一步向下执行的。
正确操作

程序环境

NASM 编译器版本 :nasm-2.07
IDE :vs code
虚拟机: oracle vm virtualBox 最新版
写入工具:fixvhdwr.exe

程序逻辑

程序运行流程图

代码

引导程序 user2.asm

  1. ; 用户程序,
  2. ; 封装打印函数,
  3. ; 多次调用
  4. ; 移动屏幕光标
  5. SECTION header vstart=0
  6. program_length dd program_end
  7. code_entry dw start
  8. dd section.code_1.start
  9. realloc_tbl_len dw (header_end-code_1_segment)/4
  10. ; 段重定位表
  11. code_1_segment dd section.code_1.start
  12. code_2_segment dd section.code_2.start
  13. data_1_segment dd section.data_1.start
  14. data_2_segment dd section.data_2.start
  15. stack_segment dd section.stack.start
  16. header_end:
  17. SECTION code_1 align=16 vstart=0
  18. put_string:
  19. mov cl,[bx]
  20. ; 影响零标志位,和符号标志位
  21. ; 结果=0,零标志位为1
  22. ; 结果为负数,符号标志位为1
  23. or cl,cl
  24. ; jz 零标志位为1,跳转
  25. ; 0时退出
  26. jz .exit
  27. call put_char
  28. inc bx
  29. jmp put_string
  30. .exit:
  31. ret
  32. put_char:
  33. push ax
  34. push bx
  35. push cx
  36. push dx
  37. push ds
  38. push es
  39. ; 索引寄存器的端口号是 0x3d4,可以向它写入一个值,用来指定内部的某个寄存器。比如,
  40. ; 两个 8 位的光标寄存器,其索引值分别是 140x0e)和 150x0f),分别用于提供光标位置的
  41. ; 8 位和低 8 位。
  42. mov dx,0x3d4
  43. mov al,0x0e
  44. out dx,al
  45. mov dx,0x3d5
  46. in al,dx
  47. mov ah,al
  48. mov dx,0x3d4
  49. mov al,0x0f
  50. out dx,al
  51. mov dx,0x3d5
  52. in al,dx
  53. ; bx光标位置
  54. mov bx,ax
  55. cmp cl,0x0d ; 是否是回车符
  56. jnz .put_0a ; 不是,看看是不是换行等字符
  57. mov ax,bx ; 多余
  58. mov bl,80
  59. div bl
  60. mul bl
  61. mov bx,ax
  62. jmp .set_cursor
  63. .put_0a:
  64. cmp cl,0x0a
  65. jnz .put_other
  66. ; 标准VGA文本模式 25x80
  67. add bx,80
  68. jmp .roll_screen
  69. .put_other:
  70. mov ax,0xb800
  71. mov es,ax
  72. ; 一个字符在显存中对应两个字节
  73. ; 乘以2来得到在显存里光标的偏移地址
  74. shl bx,1
  75. mov [es:bx],cl
  76. ; 光标位置+1
  77. shr bx,1
  78. add bx,1
  79. .roll_screen:
  80. cmp bx,2000
  81. ; 超出屏幕需要滚屏
  82. ; 这句如果执行了,将会返回call调用处,下面的滚屏操作将不再执行
  83. jl .set_cursor
  84. ; 滚屏操作
  85. ; 复制内存
  86. mov ax,0xb800
  87. mov ds,ax
  88. mov es,ax
  89. cld
  90. mov si,0xa0
  91. mov di,0x00
  92. mov cx,1920
  93. rep movsw
  94. ; 清楚屏幕最底一行,其开始的偏移地址是1920x2
  95. mov bx,3840
  96. mov cx,80
  97. .cls:
  98. mov word[es:bx],0x0720
  99. add bx,2
  100. loop .cls
  101. mov bx,1920
  102. ; 未超出屏幕,
  103. ; 参数(光标位置:bx)
  104. .set_cursor:
  105. ; 修改光标高八位
  106. mov dx,0x3d4
  107. mov al,0x0e
  108. out dx,al
  109. mov dx,0x3d5
  110. mov al,bh
  111. out dx,al
  112. ; 修改光标高八位
  113. mov dx,0x3d4
  114. mov al,0x0f
  115. out dx,al
  116. mov dx,0x3d5
  117. mov al,bl
  118. out dx,al
  119. pop es
  120. pop ds
  121. pop dx
  122. pop cx
  123. pop bx
  124. pop ax
  125. ; 返回到call
  126. ret
  127. ; 参数(光标位置:bx,打印字符:cl)
  128. start:
  129. mov ax,[stack_segment]
  130. mov ss,ax
  131. mov sp,stack_end
  132. mov ax,[data_1_segment]
  133. mov ds,ax
  134. mov bx,msg0
  135. call put_string
  136. push word [es:code_2_segment]
  137. mov ax,begin
  138. push ax
  139. ; retf 相当于执行了两次pop,CPU将执行CS:IP的指令
  140. ; POP IP
  141. ; POP CS
  142. retf
  143. continue:
  144. mov ax,[es:data_2_segment]
  145. mov ds,ax
  146. mov bx,msg1
  147. call put_string
  148. jmp $
  149. SECTION code_2 align=16 vstart=0
  150. begin:
  151. push word [es:code_1_segment]
  152. mov ax,continue
  153. push ax
  154. retf
  155. SECTION data_1 align=16 vstart=0
  156. msg0 db ' This is NASM - the famous Netwide Assembler. '
  157. db 'Back at SourceForge and in intensive development! '
  158. db 'Get the current versions from http://www.nasm.us/.'
  159. db 0x0d,0x0a,0x0d,0x0a
  160. db ' Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
  161. db ' xor dx,dx',0x0d,0x0a
  162. db ' xor ax,ax',0x0d,0x0a
  163. db ' xor cx,cx',0x0d,0x0a
  164. db ' @@:',0x0d,0x0a
  165. db ' inc cx',0x0d,0x0a
  166. db ' add ax,cx',0x0d,0x0a
  167. db ' adc dx,0',0x0d,0x0a
  168. db ' inc cx',0x0d,0x0a
  169. db ' cmp cx,1000',0x0d,0x0a
  170. db ' jle @@',0x0d,0x0a
  171. db ' ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
  172. db 0
  173. SECTION data_2 align=16 vstart=0
  174. msg1 db ' The above contents is written by LeeChung. '
  175. db '2011-05-06'
  176. db 0
  177. SECTION stack align=16 vstart=0
  178. resb 256
  179. stack_end:
  180. SECTION trail align=16
  181. program_end:

实践结果

  1. nasm.exe -f bin .\mbr.ASM -o mbr.bin
  2. nasm.exe -f bin .\user2.ASM -o use2r.bin
  3. 编译代码,生成二进制文件。
  4. 分别写入到虚拟机的vhd0号位,和100号位。运行虚拟机显示如下

运行结果

资源

汇编代码及二进制文件:https://github.com/duofanCoder/x86-NASM/tree/master/ASM-Learn-4-2/code

虚拟机固定大小硬盘vhd文件:https://github.com/duofanCoder/x86-NASM/tree/master/ASM-Learn-4-2

vhd写入工具:https://github.com/duofanCoder/x86-NASM/tree/master/tools

发表评论

表情:
评论列表 (有 0 条评论,225人围观)

还没有评论,来说两句吧...

相关阅读

    相关 X86汇编简要说明

    一、抽象 在经典的计算机体系结构中,往往将计算机系统表示为一些抽象的层次,来隐藏其实现细节。 ![20150104215854639][] 机器码: 机器码由操作码

    相关 x86汇编如何延时

    思路一:NOP指令联合循环来延时 思路二:利用BIOS中断 关于思路一,因为循环次数不好把握,这里就不尝试了。 关于思路二: > 中断号:15H > 入口参数:

    相关 80x86汇编6-jCC

    CMP指令: 指令格式:CMP R/M,R/M/IMM 该指令是比较两个操作数,实际上它相对于SUB指令,但是相减的结构并不保存到第一个操作数中,只是根据相减的结果来改