print.S 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2. ; 打印函数
  3. ; 1. 备寄存器现场
  4. ; 2. 获取光标坐标值,光标坐标值是下一个可打印字符的位置
  5. ; 3. 获取打印的字符
  6. ; 4. 判断字符是否为控制字符,若是回车符、换行符、退格符三种控制字符之一,则进入相应的处理流程。
  7. ; 否则,其余字符都粗暴地认为是可见字符,进入输出流程处理
  8. ; 5. 判断是否需要滚屏
  9. ; 6. 更新光标坐标值,便其指向下一个打印字符的位置
  10. ; 7. 恢复寄存器现场,退出
  11. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  12. TI_GDT equ 0
  13. RPL0 equ 0
  14. SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
  15. CR equ 0xd
  16. LF equ 0xa
  17. BACKSPACE equ 0x8
  18. [bits 32]
  19. section .data
  20. put_int_buffer dq 0 ; 定义 8 字节缓冲区用于数字到字符的转换
  21. section .text
  22. global put_char ; 导出为全局符号
  23. global put_str
  24. global put_int
  25. global set_cursor
  26. ;------------------------ put_char -----------------------------------------------
  27. ; 功能描述: 把栈中的 1 个字符写入光标所在处
  28. ;---------------------------------------------------------------------------------
  29. put_char:
  30. pushad ; 备份 32 位寄存器环境 8 个 eax->ecx->edx->ebx->esp->ebp->esi->edi
  31. mov ax, SELECTOR_VIDEO
  32. mov gs, ax ; gs 为正确的视频段选择子
  33. ;;;;;;;;;;;; 获取当前光标位置 ;;;;;;;;;;;;;;;;;;;;;;;;;
  34. ; 先获取高 8 位
  35. mov dx, 0x3d4 ; 索引寄存器
  36. mov al, 0x0e ; 用于提供光标位置的高 8 位
  37. out dx, al
  38. mov dx, 0x3d5 ; 通过读写数据端口 0x3d5 来获取或设置光标位置
  39. in al, dx ; 得到了光标位置的高 8 位
  40. mov ah, al
  41. ; 再获取低 8 位
  42. mov dx, 0x3d4
  43. mov al, 0x0f
  44. out dx, al
  45. mov dx, 0x3d5
  46. in al, dx
  47. ; 将光标存入 bx
  48. mov bx, ax
  49. mov ecx, [esp + 36] ; pushad 压入 4 X 8 = 32 字节 加上主调函数 4 字节
  50. cmp cl, CR ; 0x0d
  51. jz .is_carriage_return
  52. cmp cl, LF ; 0x0a
  53. jz .is_line_feed
  54. cmp cl, BACKSPACE ; BS(backspace) 的 asc 码是 0x8
  55. jz .is_backspace
  56. jmp .put_other
  57. ;;;;;;;;;;;;
  58. .is_backspace:
  59. ;;;;;;;;;;;; backspace 的一点说明 ;;;;;;;;;;;;;;;;;
  60. ; 当为 backspace 时,本质上只要将光标向前一个显存位置即可。后面输入的字符自然会覆盖此处
  61. ; 的字符,但有可能在键入 backspace 后并不再输入新的字符,这时光标已经向前移动到代删除的
  62. ; 字符位置,但字符还在原处。所以此处添加了空格或空字符 0
  63. dec bx ; 光标指向前一个字符
  64. shl bx, 1 ; 光标值乘 2 表示光标对应显存中的偏移字节
  65. mov byte [gs:bx], 0x20 ; 将待删除的字节补为 0 或空格(0x20)皆可
  66. inc bx
  67. mov byte [gs:bx], 0x07
  68. shr bx, 1
  69. jmp .set_cursor
  70. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  71. .put_other:
  72. shl bx, 1 ; 光标位置用 2 字节表示,将光标值乘 2 表示对应显存中的偏移字节
  73. mov [gs:bx], cl ; ASCII 字符本身
  74. inc bx
  75. mov byte [gs:bx], 0x07 ; 字符属性(黑屏白字)
  76. shr bx, 1 ; 恢复老的光标值
  77. inc bx ; 下一个光标值
  78. cmp bx, 2000
  79. jl .set_cursor ; 若光标值小于 2000,表示未写到显存的最后,则去设置新的光标值
  80. ; 若超出屏幕字符数大小(2000),则换行处理
  81. .is_line_feed: ; 换行符 LF (\n)
  82. .is_carriage_return: ; 回车符 CR (\r)
  83. ; 如果是 CR (\r),只要把光标移动到行首就行
  84. xor dx, dx ; dx 是被除数的高 16 位,清 0
  85. mov ax, bx ; ax 是被除数的低 16 位
  86. mov si, 80
  87. ; 由于是效仿 Linux,Linux 系统中 \n 表示下一行的行首,所以本系统中也把\n 和 \r 都处理
  88. ; 成 Linux 中的 \n 的意思,也就是下一行的行首
  89. div si
  90. sub bx, dx
  91. .is_carriage_return_end: ; 回车符 CR 处理结束
  92. add bx, 80
  93. cmp bx, 2000
  94. .is_line_feed_end: ;若是 LF(\n),将光标移+80
  95. jl .set_cursor
  96. ; 屏幕行范围是 0~24,滚屏的原理是将屏幕的第 1~24 行搬运到 0~23 行,再将第 24 行用空格填充
  97. .roll_screen: ; 若超出屏幕大小,开始滚屏
  98. cld ; DF = 0
  99. mov ecx, 960 ; 2000-80=1920 个字符要搬运,共 1920*2=3840 字节,一次搬运 4 字节,共 960 次
  100. mov edi, 0xb8000 ; 第 0 行行首
  101. mov esi, 0xb80a0 ; 第 1 行行首 0xc00b8000 + (80 * 2)
  102. rep movsd
  103. ; 将最后一行填充为空白
  104. mov ebx, 3840 ; 最后一行首字符的第一个字节偏移=1920*2
  105. mov ecx, 80 ; 一行 80 字符(160 字节),每次清空 1 字符,需要移动 80 次
  106. .cls:
  107. mov word [gs:ebx], 0x0720 ; 0x07 黑底白字 0x20 空格符
  108. add ebx, 2
  109. loop .cls
  110. mov bx, 1920 ;!将光标值重置为 1920,最后一行的首字符
  111. .set_cursor:
  112. ; 将光标设为 bx 值
  113. ;; 1. 先设置高 8 位
  114. mov dx, 0x3d4 ; 索引寄存器
  115. mov al, 0x0e ; 用于提供光标位置的高 8 位
  116. out dx, al
  117. mov dx, 0x3d5 ; 通过读写数据端口 0x3d5 来获取或设置光标位置
  118. mov al, bh
  119. out dx, al
  120. ;; 2. 再设置低 8 位
  121. mov dx, 0x3d4
  122. mov al, 0x0f
  123. out dx, al
  124. mov dx, 0x3d5
  125. mov al, bl
  126. out dx, al
  127. .put_char_done:
  128. popad
  129. ret
  130. ;------------------------ put_str -----------------------------------------------
  131. ; 功能描述: 通过 put_char 来打印以 0 字符结尾的字符串
  132. ;---------------------------------------------------------------------------------
  133. ; 输入:栈中参数为打印的字符串
  134. ; 输出:无
  135. put_str:
  136. push ebx ; 备份 ebx
  137. push ecx ; 备份 ecx
  138. xor ecx, ecx ; 清空 ecx
  139. mov ebx, [esp + 12] ; 从栈中得到待打印的字符串地址
  140. .goon:
  141. mov cl, [ebx]
  142. cmp cl, 0 ; 如果处理到了字符串尾巴,跳到结束处返回
  143. jz .str_over
  144. push ecx ; 为 put_char 传递参数
  145. call put_char
  146. add esp, 4 ; 回收参数所占的栈空间
  147. inc ebx ; 使 ebx 指向下一个字符
  148. jmp .goon
  149. .str_over:
  150. pop ecx
  151. pop ebx
  152. ret
  153. ;------------------------ put_int ------------------------------------------------
  154. ; 功能描述: 将小端字节序的数字变成对应 ASCII 后,倒置
  155. ; 输入:栈中参数为待打印的数字
  156. ; 输出:在屏幕在打印十六进制数字,并不会打印前缀 0X,如打印十进制 15 时,会直接打印 f
  157. ;---------------------------------------------------------------------------------
  158. put_int:
  159. pushad
  160. mov ebp, esp
  161. mov eax, [ebp+4*9] ; call 的返回地址占 4 字节+pushad 的占 8 个 4 字节
  162. mov edx, eax
  163. mov edi, 7 ; 指定在 put_int_buffer 中初始的偏移量
  164. mov ecx, 8 ; 32 位数字中,十六进制数字的位数是 8 个
  165. mov ebx, put_int_buffer
  166. ; 将 32 位数字按照十六进制的形式从低位到高位逐个处理
  167. ; 共处理 8 个数
  168. .16based_4bits: ; 每 4 位二进制是十六进制数字的 1 位
  169. and edx, 0xF ; 取低 4 位
  170. cmp edx, 9 ; 数字 0~9 和 a~f 需要分别处理成对应的字符
  171. jg .is_A2F
  172. add edx, '0' ; ASCII 码是 8 位大小。add 求和后,edx 低 8 位有效
  173. jmp .store
  174. .is_A2F:
  175. sub edx, 10 ; A~F 减去 10 所得到的差,再加上字符 A 的 ASCII 码,便是
  176. add edx, 'A' ; A~F 对应的 ASCII 码
  177. ; 将第一位数字转换成对应的字符后,按照类似“大端”的顺序存储到缓冲区 put_int_buffer
  178. ; 高位字符放在低地址,低位字符放在高位地址
  179. .store:
  180. ; 此时 dl 中是数字对应的字符的 ASCII 码
  181. mov [ebx+edi], dl
  182. dec edi
  183. shr eax, 4
  184. mov edx, eax
  185. loop .16based_4bits
  186. ; 现在 put_int_buffer 中已全是字符,打印之前,把高位连续的字符去掉,如把字符 000123 变成 123
  187. .ready_to_print:
  188. inc edi ; 此时 edi 退减为-1(0xffffffff),加 1 使其为 0
  189. .skip_prefix_0:
  190. cmp edi, 8
  191. je .full0 ; 若已经比较第 9 个字符了,表示待打印的字符串全为 0
  192. .go_on_skip:
  193. mov cl, [ebx+edi]
  194. inc edi
  195. cmp cl, '0'
  196. je .skip_prefix_0
  197. dec edi ; 若当前字符不为'0',要使 edi 恢复指向当前字符
  198. jmp .put_each_num
  199. .full0:
  200. mov cl, '0' ; 输入的数字全 0 时,只打印 0
  201. .put_each_num:
  202. push ecx ; 此时 cl 中为可打印的字符
  203. call put_char
  204. add esp, 4
  205. inc edi
  206. mov cl, [ebx+edi] ; 获取下一个字符到 cl
  207. cmp edi, 8
  208. jl .put_each_num
  209. popad
  210. ret
  211. set_cursor:
  212. pushad ; set_cursor 会改变寄存器的值,所以需要备份
  213. mov ebp, esp
  214. mov ebx, [ebp + 36] ; pushad 压入 4 X 8 = 32 字节 加上主调函数 4 字节
  215. ;; 1. 先设置高 8 位
  216. mov dx, 0x3d4 ; 索引寄存器
  217. mov al, 0x0e ; 用于提供光标位置的高 8 位
  218. out dx, al
  219. mov dx, 0x3d5 ; 通过读写数据端口 0x3d5 来获取或设置光标位置
  220. mov al, bh
  221. out dx, al
  222. ;; 2. 再设置低 8 位
  223. mov dx, 0x3d4
  224. mov al, 0x0f
  225. out dx, al
  226. mov dx, 0x3d5
  227. mov al, bl
  228. out dx, al
  229. popad
  230. ret