程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25

前面几篇博文最终把代码分析完了。这篇就来说说代码的编译、运行和调试。

1.代码的编译及写入镜像文件

之前我们都是在命令行输入命令进行编译和写入。源文件少的时候还不认为麻烦,当源文件多了,就会认为特别麻烦。有没有简单的方法呢?

当然有,就是用make工具。

1.1.什么是make工具

make是一个命令工具,它解释Makefile中的指令。在Makefile文件里描写叙述了整个project全部文件的编译顺序、编译规则。

注意:make命令不只用于编译程序。不管何时,当须要通过多个输入文件来生成输出文件时,我们都能够利用它来完毕任务。

1.2.关于Makefile

Makefile有自己的书写格式、关键字、函数。

像C语言有自己的格式、关键字和函数一样。

并且在Makefile中能够使用系统shell所提供的不论什么命令来完毕想要的工作。

以上不过非常简略地对makeMakefile进行介绍。

关于他们的使用。能够搜索相关关资料来学习。

1.3.针对第13章源文件的Makefile

1.3.1.我的Makefile文件

BIN = c13_mbr.bin c13_core.bin c13.bin empty
A_DIR = /home/cjy/a.img
C_DIR = /home/cjy/c.img all:$(BIN) .PHONY:all clean c13_mbr.bin:c13_mbr.asm
nasm $< -o $@
dd if=$@ of=$(A_DIR) c13_core.bin:c13_core.asm
nasm $< -o $@
dd if=$@ of=$(C_DIR) bs=512 seek=1 conv=notrunc c13.bin:c13.asm
nasm $< -o $@
dd if=$@ of=$(C_DIR) bs=512 seek=50 conv=notrunc empty:diskdata.txt
dd if=$< of=$(C_DIR) bs=512 seek=100 conv=notrunc
touch $@ clean:
$(RM) $(BIN)

这就是我自己的写的Makefile。至于为什么这样写,还有Makefile的入门知识。我以后会写博文来介绍。

1.3.2.使用说明

  1. 依据自己的Bochs的配置文件里A盘和C盘的路径改动A_DIR=C_DIR=后面的路径;
  2. 把改动后的内容保存为文本文件,命名为Makefile,放在第13章的文件夹下;例如以下图所看到的:

    程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP
  3. 在命令行键入make,回车,坐等编译和写入完毕。例如以下图所看到的:

    程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

    能够看到,我们须要的.bin文件都生成了,对A盘和C盘的写入也完毕了。

2.运行结果

最终能够看结果了,我们启动Bochs,运行结果如图:

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

3.在源代码的基础上修改动改

只得到书上的结果是不够的,不爱折腾的程序猿不是好程序猿。

3.1.写代码就像写作文

我认为写代码和写作文是一样一样的。想想我们大多数人学写作文的过程:開始不会写。怎么办?抄呗。

(这个就是学习人家的源代码。跑出人家的结果。

)再然后呢。我们不是全抄。而是在人家的基础上改动成自己的。(这个就是我们如今要做的事情,在人家代码的基础上加上自己的想法,看看结果会怎么样。)最后呢。我们不须要抄了,上了考场就能够自己写出来,结果得分还挺高。(这就是我们的终极目标。博採众长。自成一家。)

我针对第13章的代码。制作了自己的补丁包。有须要的朋友能够去下载。下载地址是:

http://download.csdn.net/detail/u013490896/9486717

或者

https://github.com/LeslieChe/from-real-mode-to-protected-mode

接下来。我会针对补丁包,对改动的部分加以解说。

3.2.让字符显示出不同的颜色

看了上面的运行结果。你是否认为颜色有点单调?好的。我们改动源代码。把字符的属性作为參数传给过程。

首先我们定义一些常量。表示不同的颜色。

     ;字符属性(都是黑底)
GREEN equ 0x02
RED equ 0x04
BLUE_LIGHT equ 0x09
YELLOW equ 0x0e
put_string:   ;字符串显演示样例程
;显示0终止的字符串并移动光标
;输入:(1) push 属性值
; (2) DS:EBX=串地址

除了要把字符串的首地址传入DS:EBX之外,还要压入属性值。

在Beyond Compare软件中比較改动后和改动前的差异,例如以下图

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

另外,过程put_char有两个地方须要改动。第二个地方是一个小BUG.

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

这样改动后。我们调用put_string的时候,须要先压栈字符属性。

例如以下图:

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

改动后的运行效果例如以下图:

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

3.3.对过程put_hex_dword的改动

3.3.1.配书源代码解说

之前的博文没有解说这个过程,所以先说一下这个过程。

源代码是:

201;汇编语言程序是极难一次成功,并且调试非常困难。这个例程能够提供帮助
202put_hex_dword: ;在当前光标处以十六进制形式显示
203 ;一个双字并推进光标
204 ;输入:EDX=要转换并显示的数字
205 ;输出:无
206 pushad
207 push ds
208
209 mov ax,core_data_seg_sel ;切换到核心数据段
210 mov ds,ax
211
212 mov ebx,bin_hex ;指向核心数据段内的转换表
213 mov ecx,8
214 .xlt:
215 rol edx,4
216 mov eax,edx
217 and eax,0x0000000f
218 xlat
219
220 push ecx
221 mov cl,al
222 call put_char
223 pop ecx
224
225 loop .xlt
226
227 pop ds
228 popad
229 retf
374         bin_hex          db '0123456789ABCDEF'

这段代码的原理非常easy。EDX寄存器是32位的,从右到左,4位一组,一共分成8组。

每组的值都在0x0~0xF之间,我们把它的值转换成相应的字符0~F;

第218行用了查表指令xlat,该指令要求事先在DS:EBX(32位模式)或者DS:BX(16位模式)处存放一张表格。指令运行时,用AL的值作为偏移量,从表格相应位置取回一个字节。传送到AL;举例来说,如果在DS:EBX处存放了第374行定义的表格,那么当AL=0的时候。运行xlat后。AL中的值就是字符0的ASCII码。

第215行用了循环左移指令rol,第一次循环将EDX的高4位移到最右边,和0x0000_000F相与,于是AL中就得到高四位相应的值,然后查表,就得到相应的字符。

第221~222,把这个字符打印到屏幕上(打印位置是当前光标所在处,并推进光标)。

3.3.2.我的改动

改动前,如果在用户程序中。我们要输出寄存器EAX的值,那么我们须要

    mov edx,eax
call far [fs:put_hex_dword]

如今我希望能够这么用:

    push 'eax'
push eax
call far [fs:put_hex_dword]

也就是通过栈传递參数,第一个參数是字符串'eax',第二个參数是寄存器EAX的值。

运行效果例如以下(浅蓝色第一行):

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

或许有的朋友会奇怪。push 'eax'这样的写法能够吗?

对于NASM编译器,这样的写法是同意的。'eax'属于字符常数。

一个字符常数最多由包括在双引號或单引號中的四个字符组成。一个具有多个字符的字符常数会被序列化成小端序。

    mov eax,'abcd'

相当于

    mov eax,0x64636261

所以。我们能够把'eax'这样的字符常数压入栈中(由于在32位模式下,所以默认按4个字节压入,最高位会补零)。作为參数传递给过程。在过程中把这个參数的每一个字符提取出来。显示在屏幕上。

下图显示这个过程的第一处改动:

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

从标号.p_char.ok之间的代码。就是从栈中依次取出我们要显示的字符(遇到0值为止)。输出到屏幕。

.ok后面的2行,是为了打印等号=;

这个过程的第二处改动例如以下图:

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

3.3.3.本地Label

在源代码中,会发现作者在非常多地方都使用了以.开头的标号,这样的标号属于本地标号。

下面摘自NASM的官方手冊

http://www.nasm.us/doc/nasmdoc3.html#section-3.9

label1  ; some code

.loop
; some more code jne .loop
ret label2 ; some code .loop
; some more code jne .loop
ret

我认为这样做能够方便用户。不用为给label起名字而伤脑筋。

3.4.符号表的重定位

我的博文

程序的载入和运行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23

已经指出在重定位符号表的时候,有一个小BUG.

我准备增加调试信息打印,证明这确实是一个BUG,同一时候也证明我的改动是对的。

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

第575~583行。我增加了一些代码,用于打印将要比較的用户符号和内核符号。

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

运行完573行时候。DS:ESI指向了内核符号表的某个条目,ES:EDI指向了用户符号表的某个条目。红色代码就是把这两个条目打印到屏幕上,左边是用户符号,右边是内核符号。

过程put_usr_salt的代码例如以下:

输入:push 属性
es:ebx 中是符号的起始地址
输出:无
64  put_usr_salt:  ;打印用户的符号
65 push ecx
66 mov ebp,esp
67 mov ch,[ebp+3*4]
68 .getc: ;本地Label
69 mov cl,[es:ebx]
70 or cl,cl
71 jz .out
72 call put_char
73 inc ebx
74 jmp .getc
75 .out:
76 mov cl,0x20
77 call put_char
78 call put_char
79 call put_char
80 call put_char ;打印四个空格
81
82 pop ecx
83 retf 4

67:从栈中取得属性值

68~74:用于打印以0结尾的字符串。

76~80:用于打印4个空格。

过程put_core_salt的代码相似。这里不再赘述。

看一下运行效果吧:

程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25-LMLPHP

左边黄色的是用户符号。右边红色的是内核符号。我们能够清晰地看到符号的比較过程:

@TerminateProgram比較了2次后匹配上了;

@ReadDiskData比較了2次后匹配上了;

@PrintDwordAsHexString比較了3次才匹配上。

这篇博文就到这里。

下篇博文,会讲NASM的条件编译,Makefile的一些改动。另外还有13章的习题。敬请期待…

04-25 18:17