上一篇博文我们用了很大的篇幅说了加载器,这一篇我们该说说用户程序了。

先看作者的源码吧。

         ;代码清单8-2
;文件名:c08.asm
;文件说明:用户程序
;创建日期:2011-5-5 18:17 ;===============================================================================
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00] ;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06] realloc_tbl_len dw (header_end-code_1_segment)/4
;段重定位表项个数[0x0a] ;段重定位表
code_1_segment dd section.code_1.start ;[0x0c]
code_2_segment dd section.code_2.start ;[0x10]
data_1_segment dd section.data_1.start ;[0x14]
data_2_segment dd section.data_2.start ;[0x18]
stack_segment dd section.stack.start ;[0x1c] header_end: ;===============================================================================
SECTION code_1 align=16 vstart=0 ;定义代码段1(16字节对齐)
put_string: ;显示串(0结尾)。
;输入:DS:BX=串地址
mov cl,[bx]
or cl,cl ;cl=0 ?
jz .exit ;是的,返回主程序
call put_char
inc bx ;下一个字符
jmp put_string .exit:
ret ;-------------------------------------------------------------------------------
put_char: ;显示一个字符
;输入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es ;以下取当前光标位置
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位
mov bx,ax ;BX=代表光标位置的16位数 cmp cl,0x0d ;回车符?
jnz .put_0a ;不是。看看是不是换行等字符
mov ax,bx ;此句略显多余,但去掉后还得改书,麻烦
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor .put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,80
jmp .roll_screen .put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,1
mov [es:bx],cl ;以下将光标位置推进一个字符
shr bx,1
add bx,1 .roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,1920
rep movsw
mov bx,3840 ;清除屏幕最底一行
mov cx,80
.cls:
mov word[es:bx],0x0720 ; space
add bx,2
loop .cls mov bx,1920 .set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al pop es
pop ds
pop dx
pop cx
pop bx
pop ax ret ;---------------------------------- 用户程序入口 --------------------------------------------
start:
;初始执行时,DS和ES指向用户程序头部段
mov ax,[stack_segment] ;设置到用户程序自己的堆栈
mov ss,ax
mov sp,stack_end mov ax,[data_1_segment] ;设置到用户程序自己的数据段
mov ds,ax mov bx,msg0
call put_string ;显示第一段信息 push word [es:code_2_segment]
mov ax,begin
push ax ;可以直接push begin,80386+ retf ;转移到代码段2执行 continue:
mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
mov ds,ax mov bx,msg1
call put_string ;显示第二段信息 jmp $ ;===============================================================================
SECTION code_2 align=16 vstart=0 ;定义代码段2(16字节对齐) begin:
push word [es:code_1_segment]
mov ax,continue
push ax ;可以直接push continue,80386+ retf ;转移到代码段1接着执行 ;===============================================================================
SECTION data_1 align=16 vstart=0 msg0 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 ' 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 ;===============================================================================
SECTION data_2 align=16 vstart=0 msg1 db ' The above contents is written by LeeChung. '
db '2011-05-06'
db 0 ;===============================================================================
SECTION stack align=16 vstart=0 resb 256 stack_end: ;===============================================================================
SECTION trail align=16 program_end:

接下来我们分块分析。

SECTION header vstart=0                     ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00] ;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06] realloc_tbl_len dw (header_end-code_1_segment)/4
;段重定位表项个数[0x0a] ;段重定位表
code_1_segment dd section.code_1.start ;[0x0c]
code_2_segment dd section.code_2.start ;[0x10]
data_1_segment dd section.data_1.start ;[0x14]
data_2_segment dd section.data_2.start ;[0x18]
stack_segment dd section.stack.start ;[0x1c] header_end:

这段代码用来定义用户程序的头部。头部格式在上一篇博文已经说过了。

因为标号program_end所在的段没有指定vstart==XX,所以program_end的汇编地址就从程序开头(=0)开始计算,它所代表的汇编地址就是整个程序的大小(以字节计算)。

每个段都有一个汇编地址,它是相对于整个程序开头(0)的,为了方便取得某个段的汇编地址,NASM编译器提供了如下表达式:

section.段名称.start

如图所示:

因为程序的入口点在code_1段中,所以是

 dw start                  ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06]

其他语句源码中都有注释,很好理解。

put_char:                                ;显示一个字符
;输入:cl=字符ascii,ch=字符属性
push ax
push bx
push cx
push dx
push ds
push es ;以下取当前光标位置
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位
mov bx,ax ;BX=代表光标位置的16位数 cmp cl,0x0d ;回车符?
jnz .put_0a ;不是。看看是不是换行等字符
mov ax,bx ;此句略显多余,但去掉后还得改书,麻烦
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor .put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,80
jmp .roll_screen .put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,1
mov [es:bx],cl
mov [es:bx+1],ch ;这句是我自己加的,我想让字符属性通过ch传递 ;以下将光标位置推进一个字符
shr bx,1
add bx,1 .roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,1920
rep movsw
mov bx,3840 ;清除屏幕最底一行
mov cx,80
.cls:
mov word[es:bx],0x0720 ; space
add bx,2
loop .cls mov bx,1920 .set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al pop es
pop ds
pop dx
pop cx
pop bx
pop ax ret

以上这段代码是为了在光标位置处显示一个字符,并推进光标到下一个字符,还考虑到了滚屏。这段代码书中有详细的讲解,这里就不赘述了。

唯一需要说明的是,我希望可以显示不同颜色的字,所以在里面加了一句 mov [es:bx+1],ch; ch是字符属性

put_string:                              ;显示串(0结尾)。
;输入:DS:BX=串地址
mov cl,[bx]
or cl,cl ;cl=0 ?
jz .exit ;是的,返回主程序
call put_char
inc bx ;下一个字符
jmp put_string .exit:
ret

这段代码又是一个过程,里面调用了put_char,看来过程是可以嵌套的!

or cl,cl 这句指令不会影响到cl里面的值,但计算结果会影响标志寄存器的某些位。如果ZF置位,说明cl的内容为0,也就是串结束标志。

我们从程序入口处看,要强调的是,当加载器把执行权交给用户程序的时候,DS和ES都指向用户程序的头部段,也就是指向用户程序的最开始。
start:
;初始执行时,DS和ES指向用户程序头部段
mov ax,[stack_segment] ;设置到用户程序自己的堆栈
mov ss,ax
mov sp,stack_end

mov ax,[stack_segment] ;这句指令是到段的重定位表中取修正后的SS段的段基址。

         mov ax,[data_1_segment]          ;设置到用户程序自己的数据段
mov ds,ax mov bx,msg0
call put_string ;显示第一段信息

从重定位表中获得重定位之后data_1段的基址,赋值给ds,这样之后,ds就不再指向用户程序的头部,而是指向data_1段。

         push word [es:code_2_segment]
mov ax,begin
push ax ;可以直接push begin,80386+ retf ;转移到代码段2执行

这段代码用段超越前缀 es:code_2_segment 访问重定位表,把code_2段的基地址压栈。(因为此时ds已经不再指向用户头部了,但是es还是指向用户头部);然后再把code_2段内的一个标号begin(代表偏移地址)也压栈。

cpu执行retf指令时,相当于执行

pop ip

pop cs

这样,执行retf的时候,程序就相当于转移了,转移到代码段2执行。

;======================================================
SECTION code_2 align=16 vstart=0 ;定义代码段2(16字节对齐) begin:
push word [es:code_1_segment]
mov ax,continue
push ax ;可以直接push continue,80386+ retf ;转移到代码段1接着执行

代码段2其实什么也没有干,干的事情就是转移到代码段1的continue处,原理和上面一样。

于是开始执行:

continue:
mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
mov ds,ax mov bx,msg1
call put_string ;显示第二段信息 jmp $

这段代码就是调用过程,显示信息。

好了,下面我们可以把代码修改一下,显示自己想要的东西。

比如在显示字符串前,给ch赋值,0X02表示绿色,0X04表示红色。

我们也可以自定义要显示的字符。

我修改后的用户代码如下:

         ;代码清单8-2
;文件名:c08.asm
;文件说明:用户程序
;创建日期:2011-5-5 18:17 ;===============================================================================
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00] ;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06] realloc_tbl_len dw (header_end-code_1_segment)/4
;段重定位表项个数[0x0a] ;段重定位表
code_1_segment dd section.code_1.start ;[0x0c]
code_2_segment dd section.code_2.start ;[0x10]
data_1_segment dd section.data_1.start ;[0x14]
data_2_segment dd section.data_2.start ;[0x18]
stack_segment dd section.stack.start ;[0x1c] header_end: ;===============================================================================
SECTION code_1 align=16 vstart=0 ;定义代码段1(16字节对齐)
put_string: ;显示串(0结尾)。
;输入:DS:BX=串地址
;ch:属性
mov cl,[bx]
or cl,cl ;cl=0 ?
jz .exit ;是的,返回主程序
call put_char
inc bx ;下一个字符
jmp put_string .exit:
ret ;-------------------------------------------------------------------------------
put_char: ;显示一个字符
;输入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es ;以下取当前光标位置
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位
mov bx,ax ;BX=代表光标位置的16位数 cmp cl,0x0d ;回车符?
jnz .put_0a ;不是。看看是不是换行等字符
mov ax,bx ;此句略显多余,但去掉后还得改书,麻烦
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor .put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,80
jmp .roll_screen .put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,1
mov [es:bx],cl
mov [es:bx+1],ch ;以下将光标位置推进一个字符
shr bx,1
add bx,1 .roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,1920
rep movsw
mov bx,3840 ;清除屏幕最底一行
mov cx,80
.cls:
mov word[es:bx],0x0720 ; space
add bx,2
loop .cls mov bx,1920 .set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al pop es
pop ds
pop dx
pop cx
pop bx
pop ax ret ;---------------------------------- 用户程序入口 --------------------------------------------
start:
;初始执行时,DS和ES指向用户程序头部段
mov ax,[stack_segment] ;设置到用户程序自己的堆栈
mov ss,ax
mov sp,stack_end mov ax,[data_1_segment] ;设置到用户程序自己的数据段
mov ds,ax mov bx,msg0
mov ch,0x02 ;green
call put_string ;显示第一段信息 push word [es:code_2_segment]
mov ax,begin
push ax ;可以直接push begin,80386+ retf ;转移到代码段2执行 continue:
mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
mov ds,ax mov bx,msg1
mov ch,0x04 ;red
call put_string ;显示第二段信息
;这里我们显示出多彩的Hello
mov cx,128 ;循环次数
mov ah,0
@1:
push cx
mov bx,msg2
mov ch,ah
call put_string ;显示Hello pop cx
inc ah ;属性值增加1
loop @1 jmp $ ;===============================================================================
SECTION code_2 align=16 vstart=0 ;定义代码段2(16字节对齐) begin:
push word [es:code_1_segment]
mov ax,continue
push ax ;可以直接push continue,80386+ retf ;转移到代码段1接着执行 ;===============================================================================
SECTION data_1 align=16 vstart=0 msg0 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 ' 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 ;===============================================================================
SECTION data_2 align=16 vstart=0 msg1 db ' The above contents is written by LeeChung. '
db '2011-05-06'
db 0x0d,0x0a,0x0d,0x0a
db 0
msg2 db 'Hello'
db 0
;===============================================================================
SECTION stack align=16 vstart=0 times 256 db 0 stack_end: ;===============================================================================
SECTION trail align=16 program_end:

OK,看一下结果吧,这就是多彩的Hello

【the end 】

硬盘和显卡的访问与控制(三)——《x86汇编语言:从实模式到保护模式》读书笔记03的更多相关文章

  1. 硬盘和显卡的访问与控制(一)——《x86汇编语言:从实模式到保护模式》读书笔记01

    本文是<x86汇编语言:从实模式到保护模式>(电子工业出版社)的读书实验笔记. 这篇文章我们先不分析代码,而是说一下在Bochs环境下如何看到实验结果. 需要的源码文件 第一个文件是加载程 ...

  2. 硬盘和显卡的访问与控制(二)——《x86汇编语言:从实模式到保护模式》读书笔记02

    上一篇博文我们讲了如何看到实验结果,这篇博文我们着重分析源代码. 书中作者为了说明原理,约定了一种比较简单地用户程序头部格式,示意图如下(我参考原书图8-15绘制的,左边的数字表示偏移地址): 所以, ...

  3. 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20

    存储器的保护(三) 修改本章代码清单,使之可以检测1MB以上的内存空间(从地址0x0010_0000开始,不考虑高速缓存的影响).要求:对内存的读写按双字的长度进行,并在检测的同时显示已检测的内存数量 ...

  4. 进入保护模式(三)——《x86汇编语言:从实模式到保护模式》读书笔记17

    (十)保护模式下的栈 ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 mov cx,00000000000_11_000B ;加载堆栈段选择子 mov ss,cx mov esp,0x7c00 ...

  5. ASM:《X86汇编语言-从实模式到保护模式》第8章:实模式下硬盘的访问,程序重定位和加载

        第八章是一个非常重要的章节,讲述的是实模式下对硬件的访问(这一节主要讲的是硬盘),还有用户程序重定位的问题.现在整理出来刚好能和保护模式下的用户程序定位作一个对比. ★PART1:用户程序的重 ...

  6. ASM:《X86汇编语言-从实模式到保护模式》1-4章:处理器,内存和硬盘基础

    其实很久之前就学完了实模式了,但是一直没有总结,感觉现在直接在书上做笔记的弊端就是有些知识点不能很很深刻地记下来(毕竟手写最明显的优点就是能深刻地记住知识,但是就是用太多的时间罢了).一下内容都是一些 ...

  7. ASM:《X86汇编语言-从实模式到保护模式》越计卷:实模式下对DMA和Sound Blaster声卡的控制

    说实话越计卷作者用了16页(我还是删过的),来讲怎么控制声卡,其实真正归纳起来就那么几点. ★PART1:直接存储访问 1. 总线控制设备(bus master) 在硬件技术不发达的早期,处理器是最重 ...

  8. 《CLR.via.C#第三版》第二部分第13章节 接口 读书笔记(七)

    这章的书写感觉很普通,是些基础的认知知识. 其中一点的重要认知,泛型接口的好处(其实也是使用泛型的好处之一):编译时类型安全&处理值类型时减少装箱. 再说点书上没有的.本来这些知识我打算另外分 ...

  9. 《CLR.via.C#第三版》第二部分第12章节 泛型 读书笔记(六)

    终于讲到泛型了.当初看到这个书名,最想看的就是作者对泛型,委托,反射这些概念的理解.很多人对泛型的理解停留在泛型集合上,刚开始我也是,随着项目越做越多,对待泛型的认识也越来越深刻. 泛型的概念:泛型是 ...

随机推荐

  1. Sass和Compass设计师指南 Ben Frain 中文高清PDF扫描版​

    Sass和Compass设计师指南是<响应式Web设计:HTML5和CSS3实战>作者Ben Frain的又一力作.作者通过丰富.完整的案例,循序渐进地展示了Sass和Compass的使用 ...

  2. 程序自动化需要一个Windows服务

    前段时间,写了一个SPC to SQL数据传输的小功能,用户不太想用手执行或有可能忘记操作.解决这个问题,Insus.NET原本是使用windows的任务管理执行的,但觉得并不太理想,因此又得写一个W ...

  3. String类-小用

    字符串-string (1)string在Java中是一个引用类型,string变量可引用一个字符串对象 (2) 例1: s0,s1,s2引用同一个对象 New创建的两个string是不同的对象只是内 ...

  4. 基于vue框架项目开发过程中遇到的问题总结(一)

    (一)关于computed修改data里变量的值 问题:computed里是不能直接修改data里变量的值,否则在git commit 时会报错 解决:在computed里使用get和set来进行获取 ...

  5. sql开发技巧总结-2

    ---恢复内容开始--- 1.如何进行行列转换 需求: 列转换成行 select a.`user_name`,sum(b.kills) from user1 a join user_kills b o ...

  6. sql_trace基本用法

    sql_trace是oracle提供的一个非常好的跟踪工具,主要用来检查数据库的异常情况,通过跟踪数据库的活动,找到有问题的语句. 一.概述:    SQL_TRACE是Oracle的一个非常强大的工 ...

  7. Eclipse中使用Spring IOC容器的具体方法

    1.通过IOC容器创建对象,并为属性赋值 在IOC容器本身对象创建时(xml文件加载时),会将配置文件中配置好的bean先创建出来,按照xml文件中配置的先后顺序创建 <bean id=&quo ...

  8. Leetcode 746. Min Cost Climbing Stairs 最小成本爬楼梯 (动态规划)

    题目翻译 有一个楼梯,第i阶用cost[i](非负)表示成本.现在你需要支付这些成本,可以一次走两阶也可以走一阶. 问从地面或者第一阶出发,怎么走成本最小. 测试样例 Input: cost = [1 ...

  9. c++ string.c_str()小结

    c++ const char *c_str(); c_str()函数返回一个指向正规C字符串的指针常量, 内容与本string串相同.(其实它指向的是string对象内部真正的char缓冲区),所以返 ...

  10. [USACO08OPEN]农场周围的道路Roads Around The Farm BZOJ 1621 DFS

    Farmer John's cows have taken an interest in exploring the territory around the farm. Initially, all ...