程序的载入和运行(五)——《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
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,同一时候也证明我的改动是对的。
第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章的习题。敬请期待…