保护模式运行环境配置(第三章)
注意:
1)保证已将freedos.img(将附件中freedos-img.tar.gz解压后,将其中a.img改名为freedos.img)放到Desktop/OSImpl/bochs-2.4.6/中了
2)这里以第三章chapter3/a/pmtest1b.asm为例——“实模式跳转到保护模式,写显存显示字符”
(chapter3/a/pmtest1b.asm与书上的chapter3/a/pmtest1.asm区别在于:将第8行org 07c00h改为org 0100h;
汇编为 pmtest1b.com而非pmtest1b.bin)
1、制作软盘b(对应镜像文件pm.img) (此镜像也放在bochs-2.4.6下,稍候会将*.com文件复制到其中)
[hadoop@sam1 /]$ cd
[hadoop@sam1 ~]$ cd Desktop/OSImpl/bochs-2.4.6/
[hadoop@sam1 bochs-2.4.6]$ ./bximage
========================================================================
bximage
Disk Image Creation Tool for Bochs
$Id: bximage.c,v 1.34 2009/04/14 09:45:22 sshwarts Exp $
========================================================================
Do you want to create a floppy disk image or a hard disk image?
Please type hd or fd. [hd] fd
Choose the size of floppy disk image to create, in megabytes.
Please type 0.16, 0.18, 0.32, 0.36, 0.72, 1.2, 1.44, 1.68, 1.72, or 2.88.
[1.44]
I will create a floppy image with
cyl=80
heads=2
sectors per track=18
total sectors=2880
total bytes=1474560
What should I name the image?
[a.img] pm.img
Writing: [] Done.
I wrote 1474560 bytes to pm.img.
The following line should appear in your bochsrc:
floppya: image="pm.img", status=inserted
[hadoop@sam1 bochs-2.4.6]$
确保bochs-2.4.6/
.bochsrc中有这样的修改:
floppya: 1_44=freedos.img, status=inserted -->这个镜像为启动盘,启动dos
floppyb: 1_44=pm.img, status=inserted -->这个镜像中有我们要运行的*.com文件
boot:a
-->插入软盘a(freedos.img,为启动盘)、b(pm.img,为com文件载体)
2、启动bochs,格式化新建的b盘(对应镜像文件pm.img)
否则挂载出错(报错:mount: you must specify the filesystem type)
[hadoop@sam1 /]$ cd
[hadoop@sam1 ~]$ cd Desktop/OSImpl/bochs-2.4.6/
[hadoop@sam1 bochs-2.4.6]$ ./bochs
...
1. Restore factory default configuration
2. Read options from...
3. Edit options
4. Save options to...
5. Restore the Bochs state from...
6. Begin simulation
7. Quit now
Please choose one: [6]
...
<bochs:1> c -->一直运行,直到碰到断点
在虚拟机中如下操作:
A:\> format B:
...
关掉虚拟机
3、汇编*.asm得到*.com,将*.com放到软盘b中
[hadoop@sam1 bochs-2.4.6]$ cd bootscript_sam/
[hadoop@sam1 bootscript_sam]$ ls
a.img bochsrc pm.inc pmtest1b.asm
[hadoop@sam1 bootscript_sam]$ nasm -o pmtest1b.com pmtest1b.asm
[hadoop@sam1 bootscript_sam]$ ls
a.img bochsrc pm.inc pmtest1b.asm pmtest1b.com
[hadoop@sam1 bootscript_sam]$ su root
Password:
[root@sam1 bootscript_sam]# mount -o loop ../pm.img /mnt/floppy -->这种方法可以挂载*.iso和*.img等镜像文件
[root@sam1 bootscript_sam]# cp pmtest1b.com /mnt/floppy/
[root@sam1 bootscript_sam]# umount /mnt/floppy/
4、启动freedos,然后执行pmtest1b.com
A:\> B:
B:\> pmtest1b.com
***********************************************************************************************************************************
以上基本上是第一次运行的配置,以后每次运行前工程是这样的结构:
bochs-2.4.6
bootscript_sam/ -->本次需要的所有脚本。删除以前这个文件夹下的所有内容,将诸如chapter3/b/* 拷贝到此文件夹下
... ...
freedos.img
pm.img(这里仍有以前测试的*.com文件,可以不用理会他们)
***********************************************************************************************************************************
DOS下COM文件的加载过程
COM文件格式的历史可以追溯到CP/M(Control Program/Moniter)时代,被沿用至今,所有的Windows系统都支持此格式。
COM文件包含程序的一个绝对映象――就是说,为了运行程序准确的处理器指令和内存中的数据,MS-DOS通过直接把该映象从文件拷贝到内存而加载COM程序,而不作任何改变。
加载过程如下:
1 分配内存
因为COM程序必须位于一个64K的段中,所以COM文件的大小不能超过65,278(65,536减去用于PSP(程序段前缀,Program Segment Prefix)的256字节和用于一个起始堆栈的至少2字节)。如果MS-DOS不能为程序、一个PSP、一个起始堆栈分配足够内存,分配尝试失败。否则,MS-DOS分配尽可能多的内存(直至所有保留内存),即使COM程序本身不能大于64K。在试图运行另一个程序或分配另外的内存之前,大部分COM程序释放任何不需要的内存。
2 设置PSP
分配内存后,MS-DOS在该内存的头256字节建立一个PSP,如果PSP中的第一个FCB(文件控制块,File Control Block)含有一个有效驱动器标识符,则置AL为00h,否则为0FFh。MS-DOS还置AH为00h或0FFh,这依赖于第二个FCB是否含有一个有效驱动器标识符。
3 加载COM程序
建造PSP后,MS-DOS在PSP后立即开始(偏移100h)加载COM文件
4 设置寄存器
它置CS,SS,DS和ES为PSP的段地址,接着创建一个堆栈。为创建一个堆栈,MS-DOS置SP为0000h,若已分配了至少64K内存;否则,它置寄存器为比所分配的字节总数大2的值。最后,它把0000h推进栈(这是为了保证与在早期MS-DOS版本上设计的程序的兼容性)。MS-DOS通过把控制传递给偏移100h处的指令而启动程序。程序设计者必须保证COM文件的第一条指令是程序的入口点。注意,因为程序是在偏移100h处加载,因此所有代码和数据偏移也必须相对于100h。汇编语言程序设计者可通过置程序的初值为100h而保证这一点(例如通过在原程序的开始使用语句org 100h)。
由于COM文件没有任何标志,所以识别COM文件的时候有些难度。
***********************************************************************************************************************************
The Program Segment Prefix (PSP)
When a program is loaded into memory for execution, DOS first builds up a program segment prefix immediately before the program is loaded into memory. This PSP contains lots of information, some of it useful, some of it obsolete. Understanding the layout of the PSP is essential for programmers designing assembly language programs.
The PSP is 256 bytes long and contains the following information:
Offset Length Description
0 2 An INT 20h instruction is stored here
2 2 Program ending address
4 1 Unused, reserved by DOS
5 5 Call to DOS function dispatcher
0Ah 4 Address of program termination code
0Eh 4 Address of break handler routine
12h 4 Address of critical error handler routine
16h 22 Reserved for use by DOS
2Ch 2 Segment address of environment area
2Eh 34 Reserved by DOS
50h 3 INT 21h, RETF instructions
53h 9 Reserved by DOS
5Ch 16 Default FCB #1
6Ch 20 Default FCB #2
80h 1 Length of command line string
81h 127 Command line string
Note: locations 80h..FFh are used for the default DTA.
Most of the information in the PSP is of little use to a modern MS-DOS assembly language program. Buried in the PSP, however, are a couple of gems that are worth knowing about. Just for completeness, however, we'll take a look at all of the fields in the PSP.
The first field in the PSP contains an int 20h instruction. Int 20h is an obsolete mechanism used to terminate program execution. Back in the early days of DOS v1.0, your program would execute a jmp to this location in order to terminate. Nowadays, of course, we have DOS function 4Ch which is much easier (and safer) than jumping to location zero in the PSP. Therefore, this field is obsolete.
Field number two contains a value which points at the last paragraph allocated to your program By subtracting the address of the PSP from this value, you can determine the amount of memory allocated to your program (and quit if there is insufficient memory available).
The third field is the first of many "holes" left in the PSP by Microsoft. Why they're here is anyone's guess.
The fourth field is a call to the DOS function dispatcher. The purpose of this (now obsolete) DOS calling mechanism was to allow some additional compatibility with CP/M-80 programs. For modern DOS programs, there is absolutely no need to worry about this field.
The next three fields are used to store special addresses during the execution of a program. These fields contain the default terminate vector, break vector, and critical error handler vectors. These are the values normally stored in the interrupt vectors for int 22h, int 23h, and int 24h. By storing a copy of the values in the vectors for these interrupts, you can change these vectors so that they point into your own code. When your program terminates, DOS restores those three vectors from these three fields in the PSP. For more details on these interrupt vectors, please consult the DOS technical reference manual.
The eighth field in the PSP record is another reserved field, currently unavailable for use by your programs.
The ninth field is another real gem. It's the address of the environment strings area. This is a two-byte pointer which contains the segment address of the environment storage area. The environment strings always begin with an offset zero within this segment. The environment string area consists of a sequence of zero-terminated strings. It uses the following format:
string1 0 string2 0 string3 0 ... 0 stringn 0 0
That is, the environment area consists of a list of zero terminated strings, the list itself being terminated by a string of length zero (i.e., a zero all by itself, or two zeros in a row, however you want to look at it). Strings are (usually) placed in the environment area via DOS commands like PATH, SET, etc. Generally, a string in the environment area takes the form
name = parameters
For example, the "SET IPATH=C:\ASSEMBLY\INCLUDE" command copies the string "IPATH=C:\ASSEMBLY\INCLUDE" into the environment string storage area.
Many languages scan the environment storage area to find default filename paths and other pieces of default information set up by DOS. Your programs can take advantage of this as well.
The next field in the PSP is another block of reserved storage, currently undefined by DOS.
The 11th field in the PSP is another call to the DOS function dispatcher. Why this call exists (when the one at location 5 in the PSP already exists and nobody really uses either mechanism to call DOS) is an interesting question. In general, this field should be ignored by your programs.
The 12th field is another block of unused bytes in the PSP which should be ignored.
The 13th and 14th fields in the PSP are the default FCBs (File Control Blocks). File control blocks are another archaic data structure carried over from CP/M-80. FCBs are used only with the obsolete DOS v1.0 file handling routines, so they are of little interest to us. We'll ignore these FCBs in the PSP.
Locations 80h through the end of the PSP contain a very important piece of information- the command line parameters typed on the DOS command line along with your program's name. If the following is typed on the DOS command line:
MYPGM parameter1, parameter2
the following is stored into the command line parameter field:
23, " parameter1, parameter2", 0Dh
Location 80h contains 2310, the length of the parameters following the program name. Locations 81h through 97h contain the characters making up the parameter string. Location 98h contains a carriage return. Notice that the carriage return character is not figured into the length of the command line string.
Processing the command line string is such an important facet of assembly language programming that this process will be discussed in detail in the next section.
Locations 80h..FFh in the PSP also comprise the default DTA. Therefore, if you don't use DOS function 1Ah to change the DTA and you execute a FIND FIRST FILE, the filename information will be stored starting at location 80h in the PSP.
One important detail we've omitted until now is exactly how you access data in the PSP. Although the PSP is loaded into memory immediately before your program, that doesn't necessarily mean that it appears 100h bytes before your code. Your data segments may have been loaded into memory before your code segments, thereby invalidating this method of locating the PSP. The segment address of the PSP is passed to your program in the ds register. To store the PSP address away in your data segment, your programs should begin with the following code:
push ds ;Save PSP value
mov ax, seg DSEG ;Point DS and ES at our data
mov ds, ax ; segment.
mov es, ax
pop PSP ;Store PSP value into "PSP"
; variable.
.
.
.
Another way to obtain the PSP address, in DOS 5.0 and later, is to make a DOS call. If you load ah with 51h and execute an int 21h instruction, MS-DOS will return the segment address of the current PSP in the bx register.
There are lots of tricky things you can do with the data in the PSP. Peter Norton's Programmer's Guide to the IBM PC lists all kinds of tricks. Such operations won't be discussed here because they're a little beyond the scope of this manual.