程序的载入和运行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25
前面几篇博文最终把代码分析完了。这篇就来说说代码的编译、运行和调试。
1.代码的编译及写入镜像文件
之前我们都是在命令行输入命令进行编译和写入。源文件少的时候还不认为麻烦,当源文件多了,就会认为特别麻烦。有没有简单的方法呢?
当然有,就是用make
工具。
1.1.什么是make工具
make
是一个命令工具,它解释Makefile
中的指令。在Makefile
文件里描写叙述了整个project全部文件的编译顺序、编译规则。
注意:make命令不只用于编译程序。不管何时,当须要通过多个输入文件来生成输出文件时,我们都能够利用它来完毕任务。
1.2.关于Makefile
Makefile
有自己的书写格式、关键字、函数。
像C语言有自己的格式、关键字和函数一样。
并且在Makefile
中能够使用系统shell所提供的不论什么命令来完毕想要的工作。
以上不过非常简略地对make
和Makefile
进行介绍。
关于他们的使用。能够搜索相关关资料来学习。
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.使用说明
- 依据自己的Bochs的配置文件里A盘和C盘的路径改动
A_DIR=
和C_DIR=
后面的路径; - 把改动后的内容保存为文本文件,命名为
Makefile
,放在第13章的文件夹下;例如以下图所看到的:
- 在命令行键入
make
,回车,坐等编译和写入完毕。例如以下图所看到的:
能够看到,我们须要的.bin文件都生成了,对A盘和C盘的写入也完毕了。
2.运行结果
最终能够看结果了,我们启动Bochs,运行结果如图:
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软件中比較改动后和改动前的差异,例如以下图
另外,过程put_char
有两个地方须要改动。第二个地方是一个小BUG.
这样改动后。我们调用put_string
的时候,须要先压栈字符属性。
例如以下图:
改动后的运行效果例如以下图:
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
的值。
运行效果例如以下(浅蓝色第一行):
或许有的朋友会奇怪。push 'eax'
这样的写法能够吗?
对于NASM编译器,这样的写法是同意的。'eax'
属于字符常数。
一个字符常数最多由包括在双引號或单引號中的四个字符组成。一个具有多个字符的字符常数会被序列化成小端序。
mov eax,'abcd'
相当于
mov eax,0x64636261
所以。我们能够把'eax'
这样的字符常数压入栈中(由于在32位模式下,所以默认按4个字节压入,最高位会补零)。作为參数传递给过程。在过程中把这个參数的每一个字符提取出来。显示在屏幕上。
下图显示这个过程的第一处改动:
从标号.p_char
到.ok
之间的代码。就是从栈中依次取出我们要显示的字符(遇到0值为止)。输出到屏幕。 .ok
后面的2行,是为了打印等号=
;
这个过程的第二处改动例如以下图:
3.3.3.本地Label
在源代码中,会发现作者在非常多地方都使用了以.
开头的标号,这样的标号属于本地标号。
下面摘自NASM的官方手冊
http://www.nasm.us/doc/nasmdoc3.html#section-3.9
NASM gives special treatment to symbols beginning with a period. A label beginning with a single period is treated as a local label, which means that it is associated with the previous non-local label. So, for example:
label1 ; some code
.loop
; some more code
jne .loop
ret
label2 ; some code
.loop
; some more code
jne .loop
ret
In the above code fragment, each JNE instruction jumps to the line immediately before it, because the two definitions of .loop are kept separate by virtue of each being associated with the previous non-local label.
我认为这样做能够方便用户。不用为给label起名字而伤脑筋。
3.4.符号表的重定位
我的博文
程序的载入和运行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23
已经指出在重定位符号表的时候,有一个小BUG.
我准备增加调试信息打印,证明这确实是一个BUG,同一时候也证明我的改动是对的。
第575~583行。我增加了一些代码,用于打印将要比較的用户符号和内核符号。
运行完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
的代码相似。这里不再赘述。
看一下运行效果吧:
左边黄色的是用户符号。右边红色的是内核符号。我们能够清晰地看到符号的比較过程: @TerminateProgram
比較了2次后匹配上了; @ReadDiskData
比較了2次后匹配上了; @PrintDwordAsHexString
比較了3次才匹配上。
这篇博文就到这里。
下篇博文,会讲NASM的条件编译,Makefile的一些改动。另外还有13章的习题。敬请期待…