今天是: Sunday, 6th July 2008
登录
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy.
欢迎来到知识库,这里是收集整理的地方。希望能够给你点帮助
SEAL加密算法源代码(C)
/* seal.c - SEAL encryption algorithm */
#undef SEAL_DEBUG
#define ALG_OK 0
#define ALG_NOTOK 1
struct SealKey {
unsigned long t[520]; /* 512 rounded up to a multiple of 5 + 5*/
unsigned long s[265]; /* 256 rounded up to a multiple of 5 + 5*/
unsigned long r[20]; /* 16 rounded up to multiple of 5 */
};
#define ROT2(x) (((x) >> 2) | ((x) << 30))
#define ROT9(x) (((x) >> 9) | ((x) << 23))
#define ROT8(x) (((x) >>
| ((x) << 24))
#define ROT16(x) (((x) >> 16) | ((x) << 16))
#define ROT24(x) (((x) >> 24) | ((x) << 8))
#define ROT27(x) (((x) >> 27) | ((x) << 5))
#define WORD(cp) ((cp[0] << 24)|(cp[1] << 16)|(cp[2] << 8)|(cp[3]))
#define F1(x, y, z) (((x) & (y)) | ((~(x)) & (z)))
#define F2(x, y, z) ((x)^(y)^(z))
#define F3(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
#define F4(x, y, z) ((x)^(y)^(z))
int g(in, i, h)
unsigned char *in;
int i;
unsigned long *h;
{
unsigned long h0;
unsigned long h1;
unsigned long h2;
unsigned long h3;
unsigned long h4;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned long e;
unsigned char *kp;
unsigned long w[80];
unsigned long temp;
kp = in;
h0 = WORD(kp); kp += 4;
h1 = WORD(kp); kp += 4;
h2 = WORD(kp); kp += 4;
h3 = WORD(kp); kp += 4;
h4 = WORD(kp); kp += 4;
w[0] = i;
for (i=1;i<16;i++)
w[i] = 0;
for (i=16;i<80;i++)
w[i] = w[i-3]^w[i-8]^w[i-14]^w[i-16];
a = h0;
b = h1;
c = h2;
d = h3;
e = h4;
for (i=0;i<20;i++)
{
temp = ROT27(a) + F1(b, c, d) + e + w[i] + 0×5a827999;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=20;i<40;i++)
{
temp = ROT27(a) + F2(b, c, d) + e + w[i] + 0×6ed9eba1;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=40;i<60;i++)
{
temp = ROT27(a) + F3(b, c, d) + e + w[i] + 0×8f1bbcdc;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=60;i<80;i++)
{
temp = ROT27(a) + F4(b, c, d) + e + w[i] + 0xca62c1d6;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
h[0] = h0+a;
h[1] = h1+b;
h[2] = h2+c;
h[3] = h3+d;
h[4] = h4+e;
return (ALG_OK);
}
unsigned long gamma(a, i)
unsigned char *a;
int i;
{
unsigned long h[5];
(void) g(a, i/5, h);
return h[i % 5];
}
int seal_init(key, ks)
unsigned char *key;
struct SealKey **ks;
{
struct SealKey *result;
int i;
unsigned long h[5];
result = (struct SealKey *) calloc(1, sizeof(*result));
if (result == (struct SealKey *) 0)
return (ALG_NOTOK);
for (i=0;i<510;i+=5)
g(key, i/5, &(result->t[i]));
/* horrible special case for the end */
g(key, 510/5, h);
for (i=510;i<512;i++)
result->t[i] = h[i-510];
/* 0×1000 mod 5 is +1, so have horrible special case for the start */
g(key, (-1+0×1000)/5, h);
for (i=0;i<4;i++)
result->s[i] = h[i+1];
for (i=4;i<254;i+=5)
g(key, (i+0×1000)/5, &(result->s[i]));
/* horrible special case for the end */
g(key, (254+0×1000)/5, h);
for (i=254;i<256;i++)
result->s[i] = h[i-254];
/* 0×2000 mod 5 is +2, so have horrible special case at the start */
g(key, (-2+0×2000)/5, h);
for (i=0;i<3;i++)
result->r[i] = h[i+2];
for (i=3;i<13;i+=5)
g(key, (i+0×2000)/5, &(result->r[i]));
/* horrible special case for the end */
g(key, (13+0×2000)/5, h);
for (i=13;i<16;i++)
result->r[i] = h[i-13];
#ifdef SEAL_DEBUG
printf(”T:\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->t[i]);
printf(”\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->t[i+8]);
printf(”\n”);
printf(”…\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->t[i+504]);
printf(”\n”);
printf(”S:\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->s[i]);
printf(”\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->s[i+8]);
printf(”\n”);
printf(”…\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->s[i+248]);
printf(”\n”);
printf(”R:\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->r[i]);
printf(”\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->r[i+8]);
printf(”\n”);
#endif /* SEAL_DEBUG */
*ks = result;
return (ALG_OK);
}
int seal(key, in, out)
struct SealKey *key;
unsigned long in;
unsigned long *out;
{
int i;
int j;
int l;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned short p;
unsigned short q;
unsigned long n1;
unsigned long n2;
unsigned long n3;
unsigned long n4;
unsigned long *wp;
wp = out;
for (l=0;l<4;l++)
{
a = in ^ key->r[4*l];
b = ROT8(in) ^ key->r[4*l+1];
c = ROT16(in) ^ key->r[4*l+2];
d = ROT24(in) ^ key->r[4*l+3];
for (j=0;j<2;j++)
{
p = a & 0×7fc;
b += key->t[p/4];
a = ROT9(a);
p = b & 0×7fc;
c += key->t[p/4];
b = ROT9(b);
p = c & 0×7fc;
d += key->t[p/4];
c = ROT9(c);
p = d & 0×7fc;
a += key->t[p/4];
d = ROT9(d);
}
n1 = d;
n2 = b;
n3 = a;
n4 = c;
p = a & 0×7fc;
b += key->t[p/4];
a = ROT9(a);
p = b & 0×7fc;
c += key->t[p/4];
b = ROT9(b);
p = c & 0×7fc;
d += key->t[p/4];
c = ROT9(c);
p = d & 0×7fc;
a += key->t[p/4];
d = ROT9(d);
for (i=0;i<64;i++)
{
p = a & 0×7fc;
b += key->t[p/4];
a = ROT9(a);
b ^= a;
q = b & 0×7fc;
c ^= key->t[q/4];
b = ROT9(b);
c += b;
p = (p+c) & 0×7fc;
d += key->t[p/4];
c = ROT9(c);
d ^= c;
q = (q+d) & 0×7fc;
a ^= key->t[q/4];
d = ROT9(d);
a += d;
p = (p+a) & 0×7fc;
b ^= key->t[p/4];
a = ROT9(a);
q = (q+b) & 0×7fc;
c += key->t[q/4];
b = ROT9(b);
p = (p+c) & 0×7fc;
d ^= key->t[p/4];
c = ROT9(c);
q = (q+d) & 0×7fc;
a += key->t[q/4];
d = ROT9(d);
*wp = b + key->s[4*i];
wp++;
*wp = c ^ key->s[4*i+1];
wp++;
*wp = d + key->s[4*i+2];
wp++;
*wp = a ^ key->s[4*i+3];
wp++;
if (i & 1)
{
a += n3;
c += n4;
}
else
{
a += n1;
c += n2;
}
}
}
return (ALG_OK);
}
/* end of file */
Tags: seal
列出本机安装的所有硬件设备
以下代码可以把你机器上已安装的硬件设备全部列出来。
#include#include #include #include #include #pragma comment(lib, “Setupapi.lib”) int main( int argc, char *argv[ ], char *envp[ ] ) { HDEVINFO hDevInfo; SP_DEVINFO_DATA DeviceInfoData; DWORD i; // Create a HDEVINFO with all present devices. hDevInfo = SetupDiGetClassDevs(NULL, 0, // Enumerator 0, DIGCF_PRESENT | DIGCF_ALLCLASSES ); if (hDevInfo == INVALID_HANDLE_VALUE) { // Insert error handling here. return 1; } // Enumerate through all devices in Set. DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i, &DeviceInfoData);i++) { DWORD DataT; LPTSTR buffer = NULL; DWORD buffersize = 0; // // Call function with null to begin with, // then use the returned buffer size // to Alloc the buffer. Keep calling until // success or an unknown failure. // while (!SetupDiGetDeviceRegistryProperty( hDevInfo, &DeviceInfoData, SPDRP_DEVICEDESC, &DataT, (PBYTE)buffer, buffersize, &buffersize)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { // Change the buffer size. if (buffer) LocalFree(buffer); buffer = (char *)LocalAlloc(LPTR,buffersize); } else { // Insert error handling here. break; } } printf(”Result:[%s]\n”,buffer); if (buffer) LocalFree(buffer); } if ( GetLastError()!=NO_ERROR && GetLastError()!=ERROR_NO_MORE_ITEMS ) { // Insert error handling here. return 1; } // Cleanup SetupDiDestroyDeviceInfoList(hDevInfo); return 0; }
彻底弄明白IRP
微软的东东,多数是把聪明人搞晕,把晕的人搞残,把残的人搞死!
一直以来,想弄明白驱动和IRP到底是怎么回事,在驱网上也查了无数的资料,到头来,手脚抽筋也没弄明白。
从网上查到一段说明,可以说是迄今最后的解释,在家里看了N天,似同天书。
引用:
1. 子系统调用NT的IO系统服务打开命名文件。
2. NT的IO管理器调用对象管理器,查询命名文件,并且帮助解决文件对象的符号连接。同时调用安全参照监视器,检查子系统是否具备打开文件句柄的正确权限。
3. 如果NT文件系统不认识文件对象,IO管理器挂起请求。调用多个文件系统直到识别出文件对象才继续请求。
4. IO管理器负责为打开的请求分配内存和初始化IRP。对于NT驱动,打开请求等同于创建请求。
5. IO管理器调用文件系统驱动,将IRP传递给它们。文件系统存取它们的IRP中本地IO栈,决定必须进行哪一种操作。检查参数,确定请求文件是否在缓存中。如果不是,设置下一个IRP中驱动的IO栈。
6. 无论是驱动处理IRP还是完成IO请求操作。都调用IO管理器和其它NT元素提供的核心态例程。
7. 驱动设置返回给IO管理器的IRP中的IO状态块表示请求操作是成功还是失败。
8. IO管理器通过获取IRP中的IO状态,将信息同过保护子系统返回给原始调用者。
9. IO管理器释放已完成的IRP。
10. 如果打开操作成功,IO管理器返回文件句柄给子系统。反之返回错误状态。
晕晕乎乎中,似乎有所顿悟,何不用一个具体的类比,也解释一下IRP到底是怎么回事,所以喝了二两二锅头,斗胆把上面的10段话作如下的翻译,有翻译不妥当之处,望大大们指正:
引用:
1. 客人(IRP)来到大厦(驱动程序)外,该大厦有一个按人名造册的接待系统(IO管理器)。
2. 客人(IRP)先到大厅,查花名册,按人名(设备对象DeviceObject,符号连接SymbolicLink)查。同时查安全薄,看这个人是不是恐怖分子、台独分子(STATUS_INVALID_DEVICE_REQUEST, SECURITY_CLIENT_CONTEXT),不是则被允许进入。如是,则不允许则禁入。
3. 如果查不到客人名,则拒绝进入。调用多本花名册(文件系统,FSD),直到识别出来人,才继续。
4. 接待系统(IO管理器)为客人(IRP)分配房间、会议室,餐饮,一应俱全(RtlZeroMemory ,RtlCopyMemory),并打扫一遍(初始化, KeSetEvent, KeWaitForSingleObject, IoMarkIrpPending, InitializeObjectAttributes)。
5. 接待系统(IO管理器)调用管理人员,将客人的信息传递给下面各部门。各部门管理人员根据他们的子系统情况,分配资源队列(本地IO栈),并决定必须进行哪一种操作(IoGetCurrentIrpStackLocation, Irp->AssociatedIrp.SystemBuffer)。客人是否在不同级别的客人清单中。如果没有这个级别,则新建一个级别 (DeviceObject->DeviceExtension),并进行登记。比如是国宾级,则一定要准备一个总统套间。
6. 不论是对于客人(Irp),还是客人的级别档次要求(IO栈),都使用模块化的规范流程。
7. 向大厦总经理报告人员进入、级别招待的请求是成功还是失败。
8. 获取人员、招待的状态,将是不是超过招待能力的信息(过保护状态)返回给新来的客人。如果超出接待能力,则挂牌“客满,恕不接待”。
9. 人员离开,则释放客人占有的所有资源(分配房间、会议室等)。IoCompleteRequest, DriverObject->DriverUnload,IoDeleteDevice, ZwClose
10. 招待成功,返回OK或返回错误(Irp->IoStatus.Status = STATUS_SUCCESS,return ntStatus)
Tags: irp
深入了解文件系统Cache管理器
译者序
本文仅用于学习交流,不负版权责任。翻译者楚狂人,有问题可与我联系,qq16191935。MSN walled_river@hotmail.com.
我以前不大明白Windows的文件系统和缓冲管理器之间的关系。仅仅知道Cc开头的系列调用是缓冲管理器提供的,文件系统中可以调用。也知道缓冲对于文件系统的意义,在于缓冲读写操作,使磁盘得到高效的利用。
为了翻译了这些资料,我也大致明白了文件系统和缓冲管理器之间的互动。windows的文件系统和缓冲管理器之间是相互调用的关系,双向沟通。总的来说,是缓冲管理器提供一些调用给文件系统,文件系统注册一些回调函数给缓冲管理器,而且缓冲管理器会发IRP给文件系统来处理。
首先文件系统如果想要实现缓冲读写,那么肯定要调用缓冲管理器提供的调用。(如对于缓冲读,不直接自己读磁盘,而是调用CcCopyRead),但是缓冲管理器自然还是要通过文件系统来实际读磁盘。因此缓冲管理器会发出IRP来给文件系统进行处理。这样,文件系统就要分别缓冲管理器发来的请求和用户进程发来的请求分别处理了。
把不同的请求放在同一个例程中处理,这正是设计上最令人恼火的地方。不过理解了这一点之后,你也就大致明白缓冲管理器和文件系统是如何交互的了。
NT 文件系统缓冲管理器
本文中,我们的同缓冲管理器的运行例程的一个基础描述.此外,给出了一些例程的使用例子.也参考了MS IFS 中能找到的代码.
缓冲管理器概述
缓冲管理器是一个纯软件组件.和Windows NT的内存管理器紧密的结合为一个整体.同时使文件系统数据的缓冲和虚拟内存管理系统也结合在一起.一些操作系统单独实现他们的文件系统,所以他们有不同的数据缓冲方式.但由于物理内存的有限性,这样的缓冲一样必须合理的管理.而且内存被这样的缓冲用了,就不能在系统中再做其他的用途了.
所以Window NT 缓冲管理器的一个关键优点,就是允许我们在文件系统缓冲和程序运行使用物理内存之间保持一个平衡.当一个应用正在消耗内存,用来进行文件数据缓冲的内存可能被缩减到接近0.结果系统让物理内存得到了更好的使用,最终提供了更好的性能.
文件系统还有一个使用Cache管理器的关键原因.一个文件可能被标准的文件系统接口访问.比如读和写,也有可能通过内存管理器被做为内存映射文件访问.有时两种访问方式被用在同一个文件上.这是Cache管理器提供一个机制,建立这两种访问方式之间的桥梁,以确保数据的可靠性.
Cache管理器数据结构
文件系统和Cache管理器之间的接口是一种过程上的接口.所有的Cache管理器用到的数据结构,有必要通过一个文件联系起来.但是实际上这些内部接口对文件系统是透明的.在这里我们描述这些文件系统和Cahce管理器共享的关键的数据结构.
缓冲控制块(BCB,Buffer Control Block)
如果一个文件的一部分被映射到一个系统内存地址,Cahce管理器内部使用缓冲控制块来记录.有时文件系统进行某些临界操作的时候,需要在内存中锁定一些数据.所以这个结构必须暴露给文件系统使用:
缓冲控制块的大部分是部可见的.开头的一部分暴露给文件系统:
typedef struct _PUBLIC_BCB {
CSHORT NodeTypeCode;
CSHORT NodeByteSize;
ULONG MappedLength;
LARGE_INTEGER MappedFileOffset;
} PUBLIC_BCB,*PPUBLIC_BCB;
开始的两个域是Windows数据结构的标准域,说明数据结构的类型和长度.后边的两个域是文件系统感兴趣的,说明了这个文件被这个特殊的缓冲控制块所管理的内容范围.
文件大小信息
文件系统和内存管理器都维护文件大小的信息.当文件系统建立了一个文件的映射,它记录了文件的当前大小.以后的任何改动同样被提交给Cache管理器.
Cache管理器用三个值用来表示文件的大小:
typedef struct _CC_FILE_SIZES {
LARGE_INTEGER AllocationSize;
LARGE_INTEGER FileSize;
LARGE_INTEGER ValidDataLength;
} CC_FILE_SIZES,*PCC_FILE_SIZES;
这些域的名字容易混淆.比如AllocationSize不是说给这个文件分配实际的物理空间,而是当前已经分配的空间中能容纳的数据总量.对一般的文件系统来说,上边两个数据当然应该是一样的.但是,对一个支持数据压缩或者扩展的文件系统而言,这个值表示当前控件中所能容纳的数据量(而不是空间大小).
AllocationSize被内存管理器用来得到section object.section object被用来觉得一个文件是如何映射到内存的,所以AllocationSize总是至少和文件一样大.缓冲管理器和内存管理起没有考虑当文件系统把AllocationSize设置得比file size小的情况.系统会因数据结构的混乱而崩溃.
FileSize表示了文件数据中最后一个有效的字节的位置,一般是文件结束标记.
缓冲管理器回调
文件系统和内存管理器之间的互动是通过一系列的回调实现的.这些回调函数被注册给Cache管理器之后被Cahce管理器用来确保数据结构在一个文件系统操作之前被\”锁定\”.
Windows NT假设资源如何被文件系统,Cahce管理器和内存管理器使用有一个严格的顺序.这个顺序确保死缩不会发生.反之则可能死锁.一般来说,文件系统首先得到资源.然后是Cache管理器,最后是内存管理器.
因此,这些回调被内存管理器用来维护它的层级:这些回调有:
typedef BOOLEAN (*PACQUIRE_FOR_LAZY_WRITE) ( IN PVOID Context, IN BOOLEAN Wait );
typedef VOID (*PRELEASE_FROM_LAZY_WRITE) ( IN PVOID Context );
typedef BOOLEAN (*PACQUIRE_FOR_READ_AHEAD) ( IN PVOID Context, IN BOOLEAN Wait, );
typedef VOID (*PRELEASE_FROM_READ_AHEAD ( IN PVOID Context );
typedef struct _CAHHE_MANAGER_CLALLBACKES {
PACQUIRE_FOR_LAZY_WRITE AcquireForLazyWrite;
PRELEASE_FROM_LAZY_WRITE ReleaseFromLazyWrite;
PACQUIRE_FOR_READ_AHEAD AcquireForReadAhead;
PRELEASE_FROM_READ_AHEAD ReleaseFromReadAhead;
} CACHE_MANAGER_CALLBACKS,*PCACHE_MANAGER_CLALLBACKS;
注意这些回调分别被缓冲管理器的两个部分使用.首先是延迟写,负责把脏的(修改过的)缓冲数据写回文件系统.另一种是预读,预先的读取文件,给用户调用的读返回信息.
首先,在设计上很重要的是要理解你的文件系统应该避免那些情况,而哪些是不需要避免的.比如说,没有理由把用户应用的缓冲读操作和缓冲管理器的延迟写序列化.它们互无影响.但是,你需要避免用户调用的非缓冲写操作改变文件的长度和缓冲管理器延迟写冲突.
NT的文件系统使用两个资源结构来实现这个.所有这些资源可以被不同的操作系统组件来使用,通过使用共同的头,铁别是头中的Resource域和PageingIoResource域.缓冲管理器并不直接去获取这些资源,它通过调用文件系统中的回调函数来获取这些必要的资源.
请注意这些回调例程必须由你的文件系统提供,他们不是可选的.如果你不能提供,那么系统会崩溃.
微软的IFSKit中每个文件系统的例子都含有这些例程.比如延迟写的位置入下:
| 文件系统 | 文件名 | 例程 |
| FAT | resrcsup.c | FatAcquireFcbForLazyWrite |
| CDFS | resrcsup.c | CdAcquireForCache |
| RDR2 | rxce\\resrcsup.c | RxAcquireFcbForLazyWrite |
其他的例程基本上可以在同一个文件中找到.
下面的代码是一个回调例程的例子:
static BOOLEAN OwAcquireForLazyWrite(PVOID Conetext,BOOLEAN Wait)
{
POW_FCB fcb = (POW_FCB)Context;
BOOLEAN result;
// 打开文件上的锁
result = OwAcquireResourceExclusiveExp(&fcb->Resource,Wait);
if(!result) {
// 我们没有能获得资源
return result;
}
// 我们得到了资源,我们必须:
// (1)保存当前线程的线程id(用于释放)
// (2)设置顶级irp一个假的值.
// 无论如何,当前线程id在设置之前应该为0(必须是没有使用的)
OwAssert(!fcb->ResourceThread);
fcb->ResourceThread = OwGetCurrentResourceThread();
return (TRUE);
}
CcCanIWrite
因为一个应用程序可能修改内存中的数据的速度超过写磁盘到数据上的能力,导致虚拟内存系统可能被数据所\”饱和\”.这可能导致虚拟系统发生内存枯竭的情况.为了避免这点,文件系统必须和虚拟系统合作探测这种条件.缓冲管理器提供的一个关键操作之一就是CcCanIWrite,原型如下:
NTKERNELAPI BOOLEAN CcCanIWrite ( IN PFILE_OBJECT FileObject, IN ULONG BytesToWrite, IN BOOLEAN Wait, IN BOOLEAN Retrying );
如果返回了FALSE,那么文件系统必须延迟脏页面的写入来避免内存枯竭.典型的内存枯竭症状是一些调用返回NO_PAGES_AVAILABLE.
文件系统必须排列这些写操作的重试并发送.文件系统可以通过内部的发送机制来发送,也可以使用这个例程.
CcDeferWrite
你的文件系统可以调用FsRtlCopyWirte,这样就不必直接调缓冲管理器了.这样内部的延迟写实际上是用这个例程实现延迟写的.
CcCopyRead
只要一个文件系统建立了一个缓冲(通过CcInitializeCacheMap调用),它就可以使用FsRtl例程(比如FsRtlCopyRead)或者这个例程.一般FsRtlCopyRead用来实现Fast IO的读,而这个例程一般用来实现IRP_MJ_READ,原型如下:
NTKERNELAPI BOOLEAN CcCopyRead ( IN PFILE_OBJECT FileObject, IN PLARGE_INTERGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus );
FileObject含有一个指向SectionObjectPointer的指针.当数据从缓冲拷贝到用户缓冲区间(也就是这里的Buffer所指的区域),SectionObjectPointer是被缓冲管理器所使用.当然这里假设缓冲已经实现初始化过了.
Wait说明调用这是否希望阻塞一段不定长的时间.比如可能要等待获取一个锁.这个参数应该被看成一个\”提示\”而不是一个\”确保\”.比如说,如果一个磁盘io有必要结束这个操作,这个操作可能继续运行.即使Wait的值是FALSE.
Buffer是调用者提供的缓冲.它不一定是一个有效的缓冲.失败的情况下,这个调用会抛出一个异常.一个文件系统驱动应该捕获这个异常并返回一个错误给应用程序.
IoStatus用来收集操作的完成状态,以及有多少个字节被成功的读取了.
请注意缓冲管理器的这个调用可能导致分页交换.这可能导致文件系统驱动处理实际的分页交换读写的操作的例程重入.
(译者注:假设文件系统中的读处理例程中调用了CcCopyRead从缓冲中读,这可能导致缓冲进行页面交换来读取硬盘.而读取硬盘的操作又是文件系统的读处理例程进行的,因此这个读处理例程是可能重入的.).
CcCopyWrite
当一个文件系统已经初始化了一个缓冲(使用CcInitializeCachedMap调用),它可以使用FsRtl例程(FsRtlCopyWrite)或者这个例程.一般的,FsRtlCopyWrite用来实现FastIo写.而这个例程用来实现普通的IRP_MJ_WRITE,原型如下:
NTKERNELAPI BOOLEAN CcCopyWrite ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN PVOID Buffer );
参数同上一函数.
请注意这个操作可能导致一个缓冲页面的部分内容被改写.这种情况下,这个页面的内容首先从磁盘上读出,然后被修改.因此,文件系统用来处理文件被修改导致的页面失败的例程有可能被重入.
CcDeferWrite
你的文件系统必须限制数据写入的最大流量.为了简化这个实现,缓冲管理器提供一个简单的机制来把这些写操作排队,直到虚拟内存系统能容纳它们.当CcCanIWrite调用返回失败的时候,这些由你的文件系统所注册的回调函数来完成.
这个回调函数的原型为:
Typedef VOID (*PCC_POST_DEFERRED_WRITE) ( IN PVOID Context1, IN PVOID Context2 );
这些上下文指针由你的文件系统使用,座位建立延迟写过程的一部分.CcDeferWrite的原型如下:
NTKERNELAPI VOID CcDeferWrite ( IN PFILE_OBJECT FileObject, IN PCC_POST_DEFERRED_WRITE PostRoutine, IN PVOID Context1, IN PVOID Context2, IN ULONG BytesToWrite, IN BOOLEAN Retrying);
FileObject标明要写的文件.
PostRoutine是文件系统驱动提供的回调函数,当虚拟内存系统已经发生了改变,附加的写操作可以被允许执行了,缓冲管理器会调用这个回调.
Context1和Context2指正是文件系统驱动所定义的.当写文件被允许的时候传给上面的回调函数.
BytesToWrite参数说明要写的字节数.虚拟内存系统用这个信息判断这个操作是否安全的(基于可用的页).
Retrying参数表示这是第一次尝试(Retrying是FALSE)还是一次延后的尝试(Retrying是TRUE).
CcGetDirtyPages
这个例程列出来仅仅为了完整性.这个用于文件系统利用windows nt的内部日志机制.文件系统中一般不使用.原型如下:
NTKERNELAPI LARGE_INTEGER CcGetDirtyPages (
IN PVOID LogHandle,
IN PDIRTY_PAGE_ROUTINE DirtyPageRoutine,
IN PVOID Context1,
IN PVOID Context2
);
CcGetFileObjectFromBcb
一个私有的缓存控制块包含一个FileObject的指针.虚拟内存系统用此来跟踪文件缓冲的信息.FileObject因此可以从一个BCB中获得.这是一个必要的调用.原型如下:
NTKERNELAPI PFILE_OBJECT CcGetFileObjectFromBcb ( IN PVOID Bcb );
CcGetFileObjectFromSectionPtrs
一个文件建立缓冲时,缓冲管理器使用FileObject作为参数调用CcInitializeCacheMap来生成一个新的section object.这个对象用于缓冲文件数据.所以当缓冲管理器得到了这个文件的缓冲数据,原来的FileObject被虚拟内存系统用于各种不同的IO操作.
给定的任意一个FileObject的SectionObjectPointer,这个例程可以告诉文件系统实际被虚拟内存系统所使用的实际file object.原型如下:
NTKERNELAPI PFILE_OBJECT CcGetFileObjectFromSectionPtrs ( IN PSECTION_OBJECT_POINTERS SectionObjectPointer );
文件过滤系统驱动开发Filemon学习笔记
WINDOWS文件过滤系统驱动开发,可用于硬盘还原,防病毒,文件安全防护,文件加密等诸多领域。而掌握核心层的理论及实践,对于成为一名优秀的开发人员不可或缺。
WINDOWS文件过滤系统驱动开发的两个经典例子,Filemon与SFilter,初学者在经过一定的理论积累后,对此两个例子代码的研究分析,会是步入驱动开发殿堂的重要一步,相信一定的理论积累以及贯穿剖析理解此两个例程后,就有能力开始进行文件过滤系统驱动开发的实际工作了。
对于SFilter例子的讲解,楚狂人的教程已经比较流行,而Filemon例子也许因框架结构相对明晰,易于剖析理解,无人贴出教程,本人在剖析Filemon的过程中积累的一些笔记资料,陆续贴出希望对初学者有所帮助,并通过和大家的交流而互相提高。
Filemon学习笔记 第一篇:
Filemon的大致架构为,在此驱动程序中,创建了两类设备对象。
一类设备对象用于和Filemon对应的exe程序通信,以接收用户输入信息,比如挂接或监控哪个分区,是否要挂接,是否要监控,监控何种操作等。此设备对象只创建了一个,在驱动程序的入口函数DriverEntry中。此类设备对象一般称为控制设备对象,并有名字,以方便应用层与其通信操作。
第二类设备对象用于挂接到所须监控的分区,比如c:,d:或e:,f:,以便拦截到引应用层对该分区所执行的读,写等操作。此类设备对象为安全起见,一般不予命名,可根据须监控多少分区而创建一个或多个。
驱动入口函数大致如下:
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS ntStatus;
PDEVICE_OBJECT guiDevice;
WCHAR deviceNameBuffer[] = L”\\Device\\Filemon”;
UNICODE_STRING deviceNameUnicodeString;
WCHAR deviceLinkBuffer[] = L”\\DosDevices\\Filemon”;
UNICODE_STRING deviceLinkUnicodeString;
ULONG i;
DbgPrint ((”Filemon.SYS: entering DriverEntry\n”));
FilemonDriver = DriverObject;
//
// Setup the device name
//
RtlInitUnicodeString (&deviceNameUnicodeString,
deviceNameBuffer );
//
// Create the device used for GUI communications
//此设备对象用来和用户交互信息
ntStatus = IoCreateDevice ( DriverObject,
sizeof(HOOK_EXTENSION),
&deviceNameUnicodeString,
FILE_DEVICE_FILEMON,
0,
TRUE,
&guiDevice );
//
// If successful, make a symbolic link that allows for the device
// object’s access from Win32 programs
//
if(NT_SUCCESS(ntStatus)) {
//
// Mark this as our GUI device
//
((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;
//
// Create a symbolic link that the GUI can specify to gain access
// to this driver/device
//
RtlInitUnicodeString (&deviceLinkUnicodeString,
deviceLinkBuffer );
ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
&deviceNameUnicodeString );
if(!NT_SUCCESS(ntStatus)) {
DbgPrint ((”Filemon.SYS: IoCreateSymbolicLink failed\n”));
IoDeleteDevice( guiDevice );
return ntStatus;
}
//
// Create dispatch points for all routines that must be handled.
// All entry points are registered since we might filter a
// file system that processes all of them.
//
for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {
DriverObject->MajorFunction[i] = FilemonDispatch;
}
#if DBG
//
// Driver unload is only set if we are debugging Filemon. This is
// because unloading a filter is not really safe - threads could
// be in our fastio routines (or about to enter them), for example,
// and there is no way to tell. When debugging, we can risk the
// occasional unload crash as a trade-off for not having to
// reboot as often.
//
// DriverObject->DriverUnload = FilemonUnload;
#endif // DBG
//
// Set up the Fast I/O dispatch table
//
DriverObject->FastIoDispatch = &FastIOHook;
} else {
//
// If something went wrong, cleanup the device object and don’t load
//
DbgPrint((”Filemon: Failed to create our device!\n”));
return ntStatus;
}
//
// Initialize the name hash table
//
for(i = 0; i < NUMHASH; i++ ) HashTable[i] = NULL;
//
// Find the process name offset
//
ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字
//
// Initialize the synchronization objects
//
#if DBG
KeInitializeSpinLock( &CountMutex );
#endif
ExInitializeFastMutex( &LogMutex );
ExInitializeResourceLite( &FilterResource );
ExInitializeResourceLite( &HashResource );
//
// Initialize a lookaside for file names
//
ExInitializeNPagedLookasideList( &FullPathLookaside, NULL, NULL,
0, MAXPATHLEN, ‘mliF’, 256 );
//
// Allocate the first output buffer
//
CurrentLog = ExAllocatePool( NonPagedPool, sizeof(*CurrentLog) );
if( !CurrentLog ) {
//
// Oops - we can’t do anything without at least one buffer
//
IoDeleteSymbolicLink( &deviceLinkUnicodeString );
IoDeleteDevice( guiDevice );
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Set the buffer pointer to the start of the buffer just allocated
//
CurrentLog->Len = 0;
CurrentLog->Next = NULL;
NumLog = 1;
return STATUS_SUCCESS;
}
在此驱动入口点函数中,主要做了生成新的设备对象,此设备对象用来和应用层信息交互,比如应用层向驱动传递需要挂接或者监控的分区盘符,或者是否挂接盘符,是否监控操作等。
上面创建设备对象的代码为:
ntStatus = IoCreateDevice ( DriverObject,
sizeof(HOOK_EXTENSION),
&deviceNameUnicodeString,
FILE_DEVICE_FILEMON,
0,
TRUE,
&guiDevice );
//
// If successful, make a symbolic link that allows for the device
// object's access from Win32 programs
//
if(NT_SUCCESS(ntStatus)) {
//
// Mark this as our GUI device
//
((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;
//
// Create a symbolic link that the GUI can specify to gain access
// to this driver/device
//
RtlInitUnicodeString (&deviceLinkUnicodeString,
deviceLinkBuffer );
ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
&deviceNameUnicodeString );
if(!NT_SUCCESS(ntStatus)) {
DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n"));
IoDeleteDevice( guiDevice );
return ntStatus;
}
上面代码完成的功能为创建了用于与应用层交互的控制设备对象,名字在参数&deviceNameUnicodeString,中。设备对象创建成功后又调用IoCreateSymbolicLink创建了一个符号连接,以便于应用层交互。 在入口点函数DriverEntry代码中,还有一处代码: ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字。此函数体如下:
ULONG
FilemonGetProcessNameOffset(
VOID
)
{
PEPROCESS curproc;
int i;
curproc = PsGetCurrentProcess();//调用PsGetCurrentProcess取得KPEB基址
//然后搜索KPEB,得到ProcessName相对KPEB的偏移量
// Scan for 12KB, hoping the KPEB never grows that big!
//
for( i = 0; i < 3*PAGE_SIZE; i++ ) {
if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) {
return i;
}
}
//
// Name not found - oh, well
//
return 0;
这个函数通过查找KPEB (Kernel Process Environment Block),取得进程名,GetProcessNameOffset主要是调用PsGetCurrentProcess取得KPEB基址,然后搜索KPEB,得到ProcessName相对KPEB的偏移量,存放在全局变量ProcessNameOffset中,得到此偏移量的作用是:无论当前进程为哪个,其名字在KPEB中的偏移量不变,所以都可以通过此偏移量得到。而在入口点函数DriverEntry执行时,当前进程必为系统进程,所以在此函数中方便地根据系统进程名SYSNAME(#define SYSNAME “System”)得到此偏移量。
分发函数剖析:
在入口点函数中,通过代码:
for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {
DriverObject->MajorFunction[i] = FilemonDispatch;
}
简单地把各个分发例程设置成了FilemonDispatch; 然后我们追踪其函数体:
NTSTATUS
FilemonDispatch(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//
// Determine if its a request from the GUI to us, or one that is
// directed at a file system driver that we've hooked
//
if( ((PHOOK_EXTENSION) DeviceObject->DeviceExtension)->Type == GUIINTERFACE ) {
return FilemonDeviceRoutine( DeviceObject, Irp );
} else {
return FilemonHookRoutine( DeviceObject, Irp );
}
}
函数体先判断需要处理IRP包的设备对象的类型,看是属于控制设备对象,还是属于用于挂接并监控文件读写操作的过滤设备对象。如果是属于后者 则进入:FilemonHookRoutine( DeviceObject, Irp )
此函数是拦截文件操作的中心,在其中获得了被操作的文件名字,并且根据操作类型,在
switch( currentIrpStack->MajorFunction ) {
}
中针对不同的MajorFunction,打印出相关操作信息。
因此函数体太长 不再全部列出。
其函数体总体框架为:得到被操作的文件名字,打印相关操作信息,然后下发IRP到底层驱动。
在下发IRP到底层驱动处理前,本层驱动必须负责设置下层IO堆栈的内容。这样下一层驱动调用IoGetCurrentIrpStackLocation()时能得到相应的数据。
设置下层IO堆栈的内容,一般用两个函数来实现:
IoCopyCurrentIrpStackLocationToNext( Irp )
此函数一般用在本驱动设置了完成例程时调用,把本层IO _STACK_LOCATION 中的参数copy到下层,但与完成例程相关的参数信息例外。因为本驱动设置的完成例程只对本层驱动有效。
IoSkipCurrentIrpStackLocationToNext(Irp)
此函数的作用是:直接把本层驱动IO堆栈的内容设置为下层驱动IO堆栈指针的指向。因两层驱动IO堆栈的内容完全一致,省却copy过程。
而在Filemon的处理中,它用了一个特别的办法,没有调用此两个函数,FilemonHookRoutine函数体里面有三句代码:
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
*nextIrpStack = *currentIrpStack;//此步设置了下层驱动的IO_STACK_LOCATION
直接设置了下层驱动IO堆栈的值。
在FilemonHookRoutine函数里,用一个宏实现了复杂的获得拦截到的被操作文件的名字:
if( FilterOn && hookExt->Hooked ) {
GETPATHNAME( createPath );
}
GETPATHNAME( createPath )宏展开为:
#define GETPATHNAME(_IsCreate) \
fullPathName = ExAllocateFromNPagedLookasideList( &FullPathLookaside ); \
if( fullPathName ) { \
FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName ); \
} else { \
fullPathName = InsufficientResources; \
}
在函数:FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName )中实现了获得被操作的文件名字,此函数代码较多,判断条件复杂,理解起来比较麻烦,下面重点讲解。
对函数FilemonGetFullPath的理解关键在于理顺结构,
此函数的功能就是获得文件名字,获得文件名字一般在三种状态下:
一:在打开文件请求中,但在打开文件前。
二:在打开文件请求中,但在打开文件后,通过在本层驱动中设置完成例程。在完成例程中获得。
三:在过滤到读写等操作时。
而在此函数中,它包含了第一种和第三种方法,通过一些烦琐的条件判断,先判断出目前是处于什么状态中,然后根据不同状态采取不同方法。
先分析当在第一种条件下,此函数的处理方法,可以精炼为如下:
VOID
FilemonGetFullPath(
BOOLEAN createPath,
PFILE_OBJECT fileObject,
PHOOK_EXTENSION hookExt,
PCHAR fullPathName
)
{
ULONG pathLen, prefixLen, slashes;
PCHAR pathOffset, ptr;
BOOLEAN gotPath;
PFILE_OBJECT relatedFileObject;
ANSI_STRING fileName;
ANSI_STRING relatedName;
UNICODE_STRING fullUniName;
prefixLen = 2; // "C:"
if( !fileObject ) {
sprintf( fullPathName, "%C:", hookExt->LogicalDrive );
return;
}
//
// Initialize variables
//
fileName.Buffer = NULL;
relatedName.Buffer = NULL;
gotPath = FALSE;
if( !fileObject->FileName.Buffer)
{
sprintf( fullPathName, "%C:", hookExt->LogicalDrive);
return;
}else
DbgPrint("fileOjec->FileName:%s",fileObject->FileName);
if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fileObject->FileName, TRUE ))) {
sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive );
return;
}
pathLen = fileName.Length + prefixLen;
relatedFileObject = fileObject->RelatedFileObject;
//
// Only look at related file object if this is a relative name
//
if( fileObject->FileName.Buffer[0] != L’\\’ &&
relatedFileObject && relatedFileObject->FileName.Length ) {
DbgPrint(”relatedFileObject filename : %s”,relatedFileObject->FileName);
if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &relatedName, &relatedFileObject->FileName, TRUE ))) {
sprintf( fullPathName, “%C: <Out of Memory>”, hookExt->LogicalDrive );
RtlFreeAnsiString( &fileName );
return;
}
pathLen += relatedName.Length+1;
}
if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {
sprintf( fullPathName, “%C:”, hookExt->LogicalDrive );
}
if( pathLen >= MAXPATHLEN ) {
strcat( fullPathName, ” <Name Too Long>” );
} else {
//
// Now we can build the path name
//
fullPathName[ pathLen ] = 0;
pathOffset = fullPathName + pathLen - fileName.Length;
memcpy( pathOffset, fileName.Buffer, fileName.Length + 1 );
if( fileObject->FileName.Buffer[0] != L’\\’ &&
relatedFileObject && relatedFileObject->FileName.Length ) {
//
// Copy the component, adding a slash separator
//
*(pathOffset - 1) = ‘\\’;
pathOffset -= relatedName.Length + 1;
memcpy( pathOffset, relatedName.Buffer, relatedName.Length );
//
// If we’ve got to slashes at the front zap one
//
if( pathLen > 3 && fullPathName[2] == ‘\\’ && fullPathName[3] == ‘\\’ ) {
strcpy( fullPathName + 2, fullPathName + 3 );
}
}
}
}
上面的精简后的函数代码为只考虑目前处于第一种情况,即打开文件请求中,但文件尚未打开时。
在此时,文件的名字信息存储在文件对象fileObject->FileName,与fileObject->RelatedFileObject->FileName, FileObject->FileName 是RelatedObject 的相对路径,通过对两者的解析组合出文件名字。
而在FilemonGetFullPath 函数体中的另一段代码:
FilemonGetFullPath
{
…………………..
…………………..
…………………..
if( !gotPath && !createPath ) {
fileNameInfo = (PFILE_NAME_INFORMATION) ExAllocatePool( NonPagedPool,
MAXPATHLEN*sizeof(WCHAR) );
if( fileNameInfo &&
FilemonQueryFile(hookExt->FileSystem, fileObject, FileNameInformation,
fileNameInfo, (MAXPATHLEN - prefixLen - 1)*sizeof(WCHAR) )) {
fullUniName.Length = (SHORT) fileNameInfo->FileNameLength;
fullUniName.Buffer = fileNameInfo->FileName;
if( NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fullUniName, TRUE ))) {
fullPathName[ fileName.Length + prefixLen ] = 0;
if( hookExt->Type == NPFS ) {
strcpy( fullPathName, NAMED_PIPE_PREFIX );
} else if( hookExt->Type == MSFS ) {
strcpy( fullPathName, MAIL_SLOT_PREFIX );
} else if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {
sprintf( fullPathName, “%C:”, hookExt->LogicalDrive );
} else {
//
// No prefix for network devices
//
}
memcpy( &fullPathName[prefixLen], fileName.Buffer, fileName.Length );
gotPath = TRUE;
RtlFreeAnsiString( &fileName );
fileName.Buffer = NULL;
}
}
if( fileNameInfo ) ExFreePool( fileNameInfo );
}
…………………
…………………
…………………
}
上面这段代码是处理另外一种情况,即是在其他读写请求中,自己根据拦截到的fileObject构建IRP,下发到底层,以此来查询文件名信息。整个过程还是易于理解的。
在理清这两种脉络后,再剖析此整个函数,就很容易理解整个函数代码了。
代码中对 MajorFunction == IRP_MJ_CREATE_NAMED_PIPE
MajorFunction == IRP_MJ_CREATE_MAILSLOT 的判断是为了辨别对拦截到的进程间的两种通信方式:命名管道与邮槽的处理。
周末来了,祝大家周末愉快。余下的整理后再帖,希望和大家多交流。
连城碧 QQ 276265852 MSN:haochao0000@hotmail.com
Int 13 中断功能
INT 13H,AH=00H 软、硬盘控制器复位
说明:
此功能复位磁盘(软盘和硬盘)控制器板和磁盘驱动器,它在磁盘控制器芯片上完成复位操场作并在磁盘进行所需的操作之前做一系列用于磁盘校准的磁盘操作。
当磁盘I/O功能调用出现错误时,需要调用此功能,此刻复位功能将使BIOS象该磁盘重新插入一样检查驱动器中磁盘状态,并将磁头校准使之在应该在的位置上。
此功能调用不影响软盘或硬盘上的数据。
入口参数:
AH=00H 指明调用复位磁盘功能。
DL 需要复位的驱动器号。
返回参数:
若产生错误,进位标志CF=1,错误码在AH寄存器。详情请见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: MOV AH, 00H
MOV DL, 80H
INT 13H
;复位硬盘 C
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START
INT 13H,AH=02H 读扇区说明:
调用此功能将从磁盘上把一个或更多的扇区内容读进存贮器。因为这是一个低级功能,在一个操作中读取的全部扇区必须在同一条磁道上(磁头号和磁道号相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道开始,因此用户必须
把跨多条磁道的读操作分为若干条单磁道读操作。
入口参数:
AH=02H 指明调用读扇区功能。
AL 置要读的扇区数目,不允许使用读磁道末端以外的数值,也不允许使该寄存器为0。
DL 需要进行读操作的驱动器号。
DH 所读磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 读出数据的缓冲区地址。
返回参数:
如果CF=1,AX中存放出错状态。读出后的数据在ES:BX区域依次排列。详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: JMP READ
BUFFER DB 512 DUP(0)
READ: PUSH CS
POP ES
MOV BX, OFFSET BUFFER
MOV AX, 0201H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;读软盘A, 0面0道1扇区
;读出后数据在BUFFER中
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START
INT 13H,AH=03H 写扇区
说明:
调用此功能将从磁盘上把一个或更多的扇区内容写入驱动器。因为这是一个低级功能,在一个写入操作中的全部扇区必须在同一条磁道上(磁头号和磁道号相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道开始,因此用户必须把跨多条磁道的写操作分为若干条单磁道写操作。
入口参数:
AH=03H 指明调用写扇区功能。
AL 置要写的扇区数目,不允许使用超出磁道末端以外的数值,也不允许使该寄存器为0。
DL 需要进行写操作的驱动器号。
DH 所写磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 放置写入数据的存贮区地址。
返回参数:
如果CF=1,AX中存放出错状态。详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: JMP WRITE
BUFFER DB 512 DUP(0FFH)
WRITE: PUSH CS
POP ES
MOV BX, OFFSET BUFFER
MOV AX, 0301H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;写入软盘A, 0面0道1扇区
;把此扇区数据全部置为0FFH
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START
INT 13H,AH=04H 检测扇区
说明:
这个功能检测磁盘上1个或更多的扇区。这个验证测试不是把磁盘上的数据和内存中的数据进行比较,而只是简单地确定读出的数据有无CRC错误。
这个功能可用来验证驱动器中的软盘版。如果盘片的格式正确,CF=0。
入口参数:
AH=03H 指明调用检测扇区功能。
AL 置要检测的连续扇区数目,不允许使用超出磁道末端以外的数值,也不允许使该寄存器为0。
DL 需要进行检测的驱动器号。
DH 磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入起始扇区号,位7-6表示磁道号的高2位。
返回参数:
如果CF=1,AX中存放出错状态。CF=0,检测正确。详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: MOV AX, 0401H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;检测软盘A, 0面0道1扇区
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START
磁盘错误状态返回码:
磁盘错误状态
AH=
00H 未出错
01H 非法功能调用命令区。
02H 地址标记损坏,扇区标识(ID)无效或未找到。
03H 企图对有写保护的软盘执行写操作。
04H 所寻找的扇区没找到。
05H 复位操作失败。
06H 无介质。
07H 初始化错误,数据未存在DMA的64K缓冲区内。
08H DMA故障
09H DMA边界错误,数据未存在DMA的64K缓冲区内。
0AH 检测出错误码率的扇区标志。
0BH 所寻找的磁道没找到。
0CH 介质类型没发现。
0DH 扇区号有问题。
0EH 发现控制数据地址标记。
0FH 超出DMA边界
10H 读磁盘时奇偶校验错,且纠错码(EDC)不能纠正。
11H 读磁盘时奇偶校验错,但纠错码(EDC)已纠正错误。
20H 控制器错。
40H 查找操作无效。
80H 超时错误,驱动器不响应。
AAH 驱动器未准备好。
BBH 不明错误。
CCH 被选驱动器出现写故障。
E0H 错误寄存器是零
FFH 非法操作。
备注:
控制器的最后状态将会在磁盘操作完成后写入相应的BIOS数据区(40:41)
80386ASM程序设计基础
80386 ASM程序设计基础,呵呵,这是最近一段时间我的业余爱好。本期将连续推出若干篇有关80386ASM程序设计的基础,主要介绍80386ASM指令的详细用法及如何在80386实模式下,保护模式下及虚拟8086模式编程以及我会详细介绍80386下的段页管理机制,我会将80386下的指令与8086下的相同指令进行比较。在你去看罗云彬的ASM编程之前,不妨先看看我的基础篇,希望有志于从事汇编语言的朋友,多提意见。
80386处理器是Intel公司80×86发展史上的里程碑,它不但兼容先前的8086/8088,80186,80286处理器,而且也为后来的486,Pentium(586),Pentium Pro(686)的发展打下了坚实的基础,对于我们程序员来讲更重要的是:我们关心80386在指令上到底有哪些扩展呢?80386有哪些寻址方式呢?毫无疑问,它不但兼容了8086的所有指令,而且还对它们进行增强.
呵呵,我知道有很多人问我CPU已经发展到PentiumIIII,没有必要学习80386的汇编。其实不然,80386处理器中的保护模式,虚拟8086模式以及地址的段页管理机制,虚拟内存这些都是以后处理器的核心。所以说80386是后续发展处理器的基础,比如说80486实质上80386+80387协处理,这块协处理器主要用于处理浮点运算,Pentium处理器在80386指令的基础上增加了57条指令,8个数据类型,8个64位的寄存器来处理多媒体。从这一点来看,完全有必要了解80386ASM,这就好像学习80386,必须先要熟练掌握8086。
1.80386的的寄存器:
80386的寄存器可以分为8组:通用寄存器,段寄存器,指令指针寄存器,标志寄存器,系统地址寄存器,控制寄存器,调试寄存器,测试寄存器,它们的宽度都是32位的。本篇主要介绍80386的寄存器。
A1.General Register(通用寄存器)
EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP,它们的低16位就是8086的AX,BX,CX,DX,SI,DI,SP,BP,它们的含义如下:
EAX:累加器
EBX:基址寄存器
ECX:计数器
EDX:数据寄存器
ESI:源地址指针寄存器
EDI:目的地址指针寄存器
EBP:基址指针寄存器
ESP:堆栈指针寄存器
这些寄存器可以将低16位单独存取,也就是8086的AX,BX,CX,DX,SI,DI,SP,BP,在存取这些寄存器的低16位(AX,BX,CX,DX,SI,DI,SP,BP),它们的高16位不受影响,同时和8086一样对于AX,BX,CX,DX这四个寄存器来讲,可以单独存取它们的高8位和低8位(AH,AL,BH,BL,CH,CL,DH,DL)
A2:Segment Register(段寄存器)
除了8086的4个段外(CS,DS,ES,SS),80386还增加了两个段FS,GS,这些段寄存器都是16位的,它们的含义如下:
CS:代码段(Code Segment)
DS:数据段(Data Segment)
ES:附加数据段(Extra Segment)
SS:堆栈段(Stack Segment)
FS:附加段
GS 附加段
A3:Instruction Pointer(指令指针寄存器)
EIP,它的低16位就是8086的IP,它存储的是下一条要执行指令的地址。
A4:Flag Register(标志寄存器)
EFLAGS,和8086的16位标志寄存器相比,增加了4个控制位,不过这4个控制位它们在实模下不起作,这四个控制位分别是:
a.IOPL(I/O Privilege Level),I/O特权级字段,它的宽度为2bit,它指定了I/O指令的特权级。如果当前的特权级别在数值上小于或等于IOPL,那么I/O指令可执行。否则,将发生一个保护性异常。
b.NT(Nested Task):控制中断返回指令IRET,它宽度为1位。NT=0,用堆栈中保存的值恢复EFLAGS,CS和EIP从而实现中断返回;NT=1,则通过任务切换实现中断返回。
c.RF(Restart Flag):重启标志,它的宽度是1位。它主要控制是否接受调试故障。RF=0接受,RF=1忽略。如果你的程序每一条指令都被成功执行,那么RF会被清0。而当接受到一个非调试故障时,处理器置RF=1。
d.VM(Virtual Machine):虚拟8086模式(用软件来模拟8086的模式,所以也称虚拟机)。VM=0,处理器工作在一般的保护模式下;VM=1,工作在V8086模式下。
其它16个标志位的含义和8086一样,在这里也重温一遍:
e.CF(Carry Flag):进位标志位,由CLC,STC两标志位来控制
f.PF(Parity Flag):奇偶标志位
g.AF(Assistant Flag):辅助进位标志位
h.ZF(Zero Flag):零标志位
i.SF(Singal Flag):符号标志位
j.IF(Interrupt Flag):中断允许标志位,由CLI,STI两条指令来控制
k.DF(Direction Flag):向量标志位,由CLD,STD两条指令来控制
l.OF(Overflow Flag):溢出标志位。
控制寄存器,系统地址的寄存器,调试寄存器,测试寄存器将在介绍完80386分段,分页管理机制后介绍,请继续关注第二篇“80386存储器的寻址方式”。
2.80386处理器的寻址方式
在实式模式下,80386处理器的最大寻址空间仍然为1M,和8086/8088相似。即段地址*10H+段内偏移地址,从而形成20位地址。此种模式下,段基址是16的倍数,长度最大不超过64K。
在保护模式下,80386处理器可以使用所有的物理内存。段基址可以是32位,也可以不是16的倍数,同时它的最大长度为4G,这与8086完全不同,在形成逻辑地址时用段基址直接加上段内偏移地址,而并不将段基址左移4位(乘以16)。通常情况下,除了访问堆栈外,默认的段都为DS,有跨段前缀就另当别论了。在以BP,EBP,ESP作为基址寄存器时,这时默认的段寄存器应该是SS,举几个简单的例子:
MOV EAX,[SI];这里的段寄存器是DS
MOV EAX,FS:[ESI];这里的段寄存器是FS,因为指令中使用跨段前缀显示指定了
MOV EAX,[BP];这里的段寄存器是SS,因为指令中使用了BP作为基址寄存器
MOV EAX,GS:[BP];这里段寄存器是GS,因为指令中使用跨段前缀显示指定了
80386中32位数的操作的顺序是“高高低低”,即是说高16-》高16,高8-》高8,低16-》低16,低8-》低8,这和8086相似。同时80386微处理器兼容所有8086的寻址方式,而且对8086的寻址方式有很大的改进和扩展。在8086下,只允许BP,BX,SI,DI作为寻址寄存器,但在80386下,8个通用寄存器都可以作为寻址寄存器。不过有一点要注意的是在基址变址寄存器寻址方式或相对基址变址寻址方式中,段寄存器由基址寄存器来确定,而不是由变址寄存器来确定,同时除ESP外其它的7个通用寄存器都可以作为变址寄存器,用代码来表示就是:
MOV EAX,[EBP+ESP+2];这条指令是错误的,因为不可以用ESP作为变址寄存器
MOV EAX,[EBP+ESI+10H];这里的段寄存器应该有基址寄存器来决定。基址寄存器是BP,那么这里的段寄存就是SS
MOV EAX,GS:[EBP+EDI+100H];不用看了,这里的段寄存器应该是GS,因为指令通过跨段前缀显示指定了
80386支持的基地址+变址+位移量寻址进一步满足了高级语言支持的数据类型。对于C语言来讲,普通变量,数组,结构体,结构体的数组,数组的构体我们既可存放在栈中(静态定义-static definition),也可以存放在堆中(动态定义-dynamic definition),用ASM也一样可以实现。基址变址寄存器提供了两个可以改变的部分,而位移量则是静态的。看下面的例子:
//Variables in C Programming-Language,the corresponding ASM will list below
void main()
{
int a;//普通的变量,用ASM寻址时直接用DS:[一位移量],如DS:[2000],属于直接寻址方式
int array[24];//数组,用ASM寻址时用DS:[BX+SI*4],4表示整型的长度,属于基址变址寻址方式
struct abc
{
int a,b,c;
float d;
};
struct abc aa;//结构体,用ASM寻址时DS:[BX+Shift],Shift代表位移量,属于寄存器相对寻址方式
struct abc aa[100];//结构体数组,用ASM寻址时用DS:[BX+SI*sizeof(abc)+Shift],属于相对基址变址寻址方式
struct cde
{
int array[100];
float e,f,g;
};
struct cde ccc;//数组结构体,用ASM寻址时用DS:[BX+SI*4+Shift],属于相对基址变址寻址方式
}
80386与8086的寻址方式差不多完全一样,只不过80386的寻址方式更灵活,它的操作数有32位,16位,8位。
让我们再重温一下8086的寻址方式:
a.立即寻址,所谓立即寻址就是操作数就在指令中,比如说:MOV AX,5678H
b.直接寻址,即直接包含操作数的有效地址EA,比如说MOV AX,[1234]
c.寄存器间址寻址,用寄存器的内容来作为操作数的有效地址,比如说SI=1234,MOV AX,[SI],8086下可用的寄存器只有4个:BX,BP,SI,DI,80386下8个通用的寄存器都可以使用。
d.寄存器相对寻址,即在寄存器间址寻址方式的基础上再加一个位移量,位移量可以是8位也可以是16位,比如说MOV AX,[BX+90H]。
e.基址变址寻址,即操作数的有效地址由一基址寄存器和一变址寄存器产生,如MOV AX,[BX+SI]。那么在8086下,只有SI,DI可以作为变址寄存器,在80386下除ESP外的其它7个通用寄存器都可以作为变址寄存器,比如说MOV AX,[BX+SI]。
f.相对基址变址寻址,在e寻址方式的基础上加上一位移量,比如说MOV AX,[BX+SI+100H]。
在8086下,我们如进行字节或字操作,往往要加上伪指令WORD PTR或BYTE PTR。在80386下不用显示指定,处理器会自动处理,当发现目的操作为8位时,处理器就会进行8位操作,同理当发现目的操作为16位,处理器就会进行16位操作,80386下以目的操作数的长度为准,以下几条简单的传送指令:
MOV AL,CS:[EAX];8位操作,段寄存器是CS,寻址方式是寄存器间址寻址
MOV AL,ES:[BX];8位操作,段寄存器是ES,寻址方式是寄存器间址寻址
MOV EDX,[EDX+EBX+1234H];32位操作,段寄存器是DS,寻址方式是相对基址变址寻址
MOV AX,[EBX+ESI*4];16位操作,段寄存器是DS,寻址方式是基址变址寻址
MOV BH,ES:[EBX+EDI+900H];8位操作,段寄存器是ES,寻址方式是相对基址变址寻址
MOV DL,[EBP+ESI+1900H];8位操作,段寄存是SS,因为用了EBP作为基址寄存器。寻址方式是相对基址变址寻址
80386的指令集包含了8086/8088,80186,80286的指令集,可以分为几个大类:数据传送指令,算术运算/逻辑运算指令,移位指令,控制转移指令,串操作指令,高级语言支持的指令,条件字节设置指令,位操作指令,处理器控制指令和保护方式指令。高级语言支持指令始于80186,保护方式指令始于80286,条件字节设置指令和位操作指令是80386新增的。
本篇主要介绍数据传送指令,数据传送指令可以分为:通用数据传送,累加器专用传送,地址传送,标志传送,分别介绍如下:
A.数值传送指令MOV,MOVZX,MOVSX,XCHG,PUSH,PUSHA,PUSHAD,POPA,POPAD,
a.MOV,指令和8086相似,不过它支持32位操作。
b.MOVZX,零扩展传送,格式–MOVZX DST,SRC,表示将源操作送给目的操作数,目的操作数空出的部分用0填补。
c.MOVSX,符号扩展传送,格式–MOVSX DST,SRC,表示将源操作送给目的操作数,目的操作数空出的部分用SRC的符号位来填补,举个简单的例子来演示:
MOV DL,90H;
MOVSX AX,DL;AX=FF90H
MOVZX AX,DL;AX=0090H
MOVSX ESI,DL;ESI=FFFFFF90H
MOVZX ESI,DL;ESI=00000090H
事实上在8086中也有两条指令CBW,CWD可以对操作数进行扩展。MOVSX可以对有符号数进行扩展,MOVZX可以对无符号数进行扩展,看看CBW,CWD的用法:
CBW将字节数据扩展成字,符号位扩展到AH中
CWD将字数据扩展成双字,符号位放到DX中
MOV AL,70H;
CBW;//AX=0070
CWD;//DX=0000,AX=0070
d.XCHG,功能和8080相同,不过它支持8位,16位,32位操作,下面的语句均是合法的。
XCHG AH,AL
XCHG AX,AL
XCHG ESI,EDI
XCHG ESI,[EBX+EDI+1000H]
e.PUSH,和8086不同的是,它支持立即数入栈,8位入栈,当然还有32位入栈,下面的语句均是合法的。
PUSH AL
PUSH BH
PUSH 100H
PUSH EAX
PUSH EBX
PUSH DWORD PTR [EAX]
f.POP,功能和用法和8086一样。
g.PUSHA,将8个通用寄存器全部进栈,进栈顺序为:AX,CX,DX,BX,SP,BP,SI,DI,然后SP指针寄存减16,不过SP入栈的内容是PUSHA指令执行前的内容。
h.POPA,8个通用寄存器全部出栈,堆栈指针寄存器不是堆栈中弹出的内容,而是加16而得到的,虽然这样得到的值和从堆栈中弹出来的内容一样,但物理意义不一样。
i.PUSHAD,将8个32位通用寄存器全部入栈,入栈顺序EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI,ESP的内容是执行指令PUSHAD之前的内容
j.POPAD,8个32位通寄存器全部出栈,ESP的内容参见h
B.地址传送指令LEA,LDS,LES,LFS,LGS,LSS
a.LEA,取有效地址,功能,用法与8086相同,不过它支持32位操作。规则:目的操作必须是16位或32位通用寄存器,当目的操作数是16位时,那么只装入有效地址的低16位。事实上LEA指令相当于伪指令OFFSET,看例子:
MOV EAX,12345678H
MOV EBX,56784321H
LEA ECX,[EAX+EBX];ECX=99999999H
b.LDS,装入指针,功能,用法与8086相同,不过它支持32位操作。格式:LDS REG,OPRD。规则,目的寄存器必须是16位或32位的通用寄存器,OPRD必须是内存单元,不可以是立即数。如果目的寄存器是16位,那么源操作数OPRD含32位指针;如果目的寄存器是32位,那么源操作数有48位指针。该指令将目的操作数OPRD所指向的内存单存的4个或6个连续字节的内容送给助记符指令中指定的DS段寄存器和指令中目的寄存器。比如:
LDS EAX,[1000H];这表明将偏移地址为1000,1001H这两个字节单元的内容送给段寄存器DS,将偏移地址1002,1003,1004,1005四个字节单元的内容送往EAX。
LDS AX,[1000H];这表明将偏移地址为1000,1001H这两个字节单元的内容送给段寄存器DS,将偏移地址1002,1003H两个字节单元的内容送往EAX。
c.LES,同LDS,不过段寄存器是ES。
d.LFS,同LDS,不过段寄存器是FS。
e.LGS,同LDS,不过段寄存器是GS。
h.LSS,同LDS,不过段寄存器是SS。
C.标志传送指令LAHF,SAHF,PUSHF,PUSHFD,POPF,POPFD
a.LAHF,将标志寄存器的低8位送至AH中,包括SF,ZF,ZF,PF,CF。
b.SAHF,与i的过程恰好相反
c.PUSHF,将标志寄存器的EFLAGS低16位内容入栈,和8086相同
d.PUSHFD,将标志寄存器EFLAGS的内容入栈
e.POPF,将栈顶的一个字弹出,并将它送到标志寄存器EFLAGS的低16位
f.POPFD,将栈顶的两个字弹出,并将它送到标志寄存器EFLAGS
D.累加器传送指令IN,OUT,XLAT
a.IN,和8086相同,但可以输入一个双字节,同样如果端口的范围位于00H-FFH,可以直接用,如果超出这个范围,则先要将端口号送至DX,下面的语句是合法的:
IN AL,20H;从20H端口读入一个字节
IN AX,20H;从20H端口读入一个字
MOV DX,0378H
IN EAX,DX;从20H端口读两个字节
b.OUT,和8086相同,但可以输出一个双字节,同样如果端口的范围位于00H-FFH,可以直接用,如果超出这个范围,则先要将端口号送至DX,下面的语句是合法的:
OUT 20H,AL;从20H端口输出一个字节
IN 20H,AX;从20H端口输出一个字
MOV DX,0378H
IN EAX,DX;从20H端口输出两个字
c.XLAT,查表指令,功能和用法与8086相同,不过基址寄存器用的是EBX,来看看XLAT的实现过程:XLAT以BX作为基址寄存器,以AL作为变址寄存进器对指定的缓冲区进行查表,将AL指定位置的内容送往AL,比如说我们在MS-DOS方式写一个小程序:
C:\>Debug
-A100
MOV BX,0120
SUB AL,AL
MOV DL,AL
MOV AH,2
INT 21
MOV AH,4C
INT 21
INT 20
-E120 ‘ABCDEFGHIJKLLMMDDKDJDK’
=G100
屏幕上会显示A,如果AL=3,那么屏幕会显示D
以上所有的指令均不影响EFLAGS的各标志位。
算术运算指令,逻辑运算指令,移位指令
AA.算术运算指令
A.加减法运算ADD,ADC,INC,SUB,SBB,DEC,CMP,NEG
a.ADD,和8086功能,用法相同,不过支持32位操作,下面的语句都是合法的。
ADD ESI,EDI
ADD EAX,DWORD PTR [1000H]
b.ADC,带进位的加法指令,即OPRDS+OPRDD+CF,其中OPRDS代表源操作数,OPRDD代表目的操作,CF代表进位标志位,功能和用法与8086相同,支持32位操作。
c.SUB,和8086相同,支持32位操作。
d.SBB,带进位的减法指令,即OPRDD-OPRDS-CF,其中OPRDS代表源操作数,OPRDD代表目的操作数,CF代表进位标志位,功能和用法与8086相同,支持32位操作。
e.DEC,减1操作,功能和用法与8086相同,支持32位操作。
f.CMP,比较操作,功能和用法与8086相同,支持32位操作。
g.NEG,求补操作,功能和用法与8086相同,支持32位操作。
h.INC 加1操作,功能和用法与8086相同,支持32位操作。
B.乘除法指令MUL,DIV,IMUL,IDIV
a.MUL,无符号数乘法指令,和8086功能用法一样,即指令中只给出一个操作,被乘数已默认,如果指令给出的操作数是32位的话,被乘数默认为EAX,那么乘积将存放在EDX:EAX中,其中EDX存放高32位,EAX存放低32位,如果此时EDX=0,即高32位为0的话,那么OF=0,CF=0,否则被置1。如果指令给出的操数作是16位的话,被乘数默认为AX那么乘积将放在DX:AX中,其中DX中将存放高16位,AX中存放低16位。如果指令给出的操作数是8位的话,被乘数默认为AL,那么乘积将放在AX,AH中存放高8位,AL中存放低8位。
b.DIV,无符号数的除法指令,和8086一样,指令给出一个操作数,被除数已默认。如果指令中给出的操作数为32,那么被除数将是EDX:EAX, 最终的商将存放在EAX, 余数将存放在EDX中。如果指令给出操作数为16位,那么被除数为EAX,最终得到的商放在AX,余数放在EAX的高16位。如果指令中给出的操作数为8位,那么被除数是16位,最终得到的商将放在AL中,余数放在AH中。
c.IMUL,有符号数的乘法指令,除了具有8086的用法外,有新的形式:
c1.IMUL DST,SRC;将源操作数SRC与目的操作DST相乘,并将结果送往DST。
c2.IMUL DST,SRC1,SRC2;将源操作数SRC1与源操作数SRC2相乘,并将结果送往DST。
使用这种形式必须遵守的规则,形式c1指令中目的操作数必须是16位或32位通寄存器,源操作数的长度必须与目的操作的长度一样(8位立即数除外,即00H-FFH或80H-7FH),源操作数可以是通用寄存器,也可以是存储单元或立即数。形式c2指令中的源操作数SRC1可以是通用寄存器也可以是存储单元,源操作数SRC2必须是立即数,DST必须是16位或32位通用寄存器。呵呵,对于这些规则无需去问为什么,这是硬件的特性决定的,如果一定要问为什么,那只能问INTEL公司的硬件工程师了:)。同时,有一点要注意的是:这两种形式的指令,目的寄存器的长度与源操作数长度一样(8位立即数除外),这样的话,该指令事实上对有符号数和无符号数是一样的,因为乘积的低位部分均存储在目的寄存器中,而高位部分在这两种形式的指令中不予以存储。
d.IDIV,有符号数的除法指令,用法和8086相同,不过支持32位操作。
C.符号扩展指令CBW,CWD,CWDE,CDQ
a.CBW,前面已介绍,在第三篇。
b.CWD,前面已介绍,在第三篇。
c.CWDE,是80386新增的指令。格式:CWDE。功能:将AX的符号位扩展到EAX的高16位中。
d.CDQ,是80386新增的指令。格式:CDQ。功能,将EAX的符号位扩展到EDX中。
e.以上四条指令均不影响标志位。
f.举例说明:
;If AX=1234H,EAX=99991234H
CBW;After processing the instruction,AX=1234,DX=0000H
CDQ;After processing the instruction,EAX=99991234H,EDX=FFFFFFFFH
BB.逻辑运算指令和移位指令NOT,AND,OR,XOR,TEST,SAL,SAR,SHL,SHR,ROL,ROR,RCL,RCR,SHLD,SHRD
a.NOT,AND,OR,XOR,TEST这些指令的功能和用法与8086完全相同,不过它们支持32位操作。
b.TEST,测试指令,该指令测试的结果并不回送到目的操作数和源操数。之所以要使用这条的指令,主要是因为根据TEST指令得到的结果,进行程序的条件转移。
c.SAL,算术左移,功能和8086一样,但在8086中,如果在移位的位数超过1位,那么一定要移位的位数放在CX寄存器中。在80386中,可以不用这样做,其它的移位指令也一样。除了这一点以外,用法和8086一样,当然也支持32位操作。以下的语句均是合法的。
SHL AL,5;这在8086中是非法,但在80386中是合法的
SHL WORD PTR [SI],3
d.SAR,算术右移,将操作数右移指定的位数,但左边的符号位保持不变,移出的最低位进入CF标志位。
e.SHL,逻辑左移,用法和功能与SAL一样。
f.SHR,逻辑右移,将操作右移指定的位数,同时每移一位,左边用0补充,移出的最低位进入CF标志位。
g.说明:在80386中,实际移位的位数是指令中移位位数的低5位,也就是说移位位数的范围在0-31或0-1FH,CF标志位总是保留着目的操作数最后被移出位的值。当移位位数大于操作数的长度时,CF被置0。如果移位位数为1,移位前后的结果的符号位都是一样,那么很明显的是该操作数经移位后没有移出,此时OF=0。这四条指令的移位示意图(我画的是16位操作数的移位示意图,8位和32依此类推),SAL,SHL相当于乘法;SAR,SHR相当于除法。
SAL:
|——————————————————————————————-|
|CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|
|– —————————————————————————————-|
SHL:
|——————————————————————————————-|
|CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|
|— —————————————————————————————|
SAR:
|——————————————————————————————–|
|-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|CF||
| |—|—————————————————————————————-|
| ^
|—–|最高位保持不变
SHR:
|——————————————————————————————–|
0->|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|CF||
|——————————————————————————————–|
h.ROL,循环左移,支持32位操作数,用法和8086一样。
i.ROR,循环右移,支持32位操作数,用法和8086一样。
j.RCL,带进位的循环左移,支持32位操作数,用法和8086一样。
k.RCR,带进位的循环右移,支持32位操作数,用法和8086一样。
l.ROL,ROR,RCL,RCR的移位示意图(仍然以16位操作数来画,8位/32位依次类推):
ROL:
|————————————————————————————————–|
|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|<——–|
|————————————————————————————————–|
|————————————————————————————————–|
ROR:
|——————————————————————————————-|
|->|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|
|——————————————————————————————-|
|——————————————————————————————-|
RCL:
|————————————————————————————————-|
|<-|CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|<-|
|————————————————————————————————-|
|————————————————————————————————-|
RCR:
|————————————————————————————————-|
|->|CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|
|—- ——————————————————————————————–|
|————————————————————————————————-|
m.SHLD,80386新增的双精度左位指令,指令格式:SHLD OPRD1,OPRD2,M
n.SHRD,80386新增的双精度右移指令,指令格式:SHRD,OPRD1,OPRD2,M
o.m,n这两条指令的使用规则是:源操作数OPRD1可以是16位或32位通用寄存器或者16位存储单元或者32位存储单元,源操作数OPRD2必须是16位或32位通寄存器,M表示移位次数,可以是CL寄存器,也可以是8位立即数。功能:SHLD是将源操作数OPRD1移M位,空出的位用OPRD2高端的M位来填补,源操作数OPRD2的内容不变,最后移出的位放在CF中;SHRD将源操作数OPRD1移M位,空出的位用OPRD2低端M位来填补,源操作数OPRD2保持不变,最后移出的位放在CF中,对于这两条指令,当移位位数仅为1的话,移出和移后的符号位不变的话,那么OF=0,如果符号位不一样的话,那OF=1。
p.这两条指令是80386新增的指令,举两个简单的例子加以说明:
p1.SHLD:
MOV AX,8321H
MOV DX,5678H
SHLD AX,DX,1
SHLD AX,DX,2
分析一下该指令的详细执行过程(用示意图, 第一个图画的就是AX的内容):
AX=8321h
|——————————-|
|1|0|0|0|0|0|1|1|0|0|1|0|0|0|0|1|
|——————————-|
根据指令SHLD AX,DX,1,先左移一位,得到AX=0642H:
|——————————-|
|0|0|0|0|0|1|1|0|0|1|0|0|0|0|1|0| CF=1
|——————————-|
经过上一步的移位后,AX的最后一位(即bit0)空出来,其值为0;根据指令的用法将用DX的第15位填充,填充后AX的内容为:
|——————————-|
|0|0|0|0|0|1|1|0|0|1|0|0|0|0|1|0|
|——————————-|
同时由于移位后AX的符号位与移位前AX的符号位不同,所以在移位过程中产生了溢出,OF=1,最后结果AX=0642H。
同理,SHLD AX,DX,2,执行完这条指令后,最后结果为AX=0644H
p2.SHRD:
MOV EAX,12345678H
MOV EDX,99994599H
SHRD AX,DX,1
SHRD AX,DX,2
分析一下该指令的详细执行过程(用示意图,第一个图画的是EAX的内容):
EAX=12345678H
|—————————————————————|
|0|0|0|1|0|0|1|0|0|0|1|1|0|1|0|0|0|1|0|1|0|1|1|0|0|1|1|1|1|0|0|0|
|—————————————————————|
根据指令SHRD AX,DX,1,将AX右移一位得到EAX=091A2B3EH:
|—————————————————————|
|0|0|0|0|1|0|0|1|0|0|0|1|1|0|1|0|0|0|1|0|1|0|1|1|0|0|1|1|1|1|1|0|
|—————————————————————|
经过上一步的移位后,EAX的最高位(第31位)空出来用0填充,根据指令的用法,最EDX的第0位来填充,填充后EAX的内容为:
|—————————————————————|
|1|0|0|0|1|0|0|1|0|0|0|1|1|0|1|0|0|0|1|0|0|1|1|0|0|1|1|1|1|0|0|0|
|—————————————————————|
即EAX=891A2B3EH,CF=0,OF=0
同理,指令SHRD AX,DX,2,执行完这条件指令后,最后结果为EAX=048D159C,CF=0,OF=0
控制转移指令,串操作指令
80386控制转移指令包括:转移指令,循环指令,过程调用和返回指令。
A.转移指令包括无条件转移指令JMP和条件转移指令,无条件转移指令分为段内直接转移,段内间接转移,段间直接转移,段间间接转移。由于80386有保护模式和实模式,在实模式下,段内转移的范围在-128~127,段间转移最大范围为64K。在保护模式需要用48位指针,即CS:EIP(16位+32位)。条件转移指令有很多包括JCXZ,JECXZ,JBE,JAE,JA,JB等,其用法和8086相似。
B.循环指令LOOP,LOOPZ,LOO0PE,LOOPNZ,LOOPNE,TASM支持助记符LOOP,LOOPWE,LOOPWZ,LOOPWNZ,LOOPWNE,LOOPD,LOOPWD,LOOPDE,LOOPDNE,LOOPDNZ。以CX作为计数器时,就可用LOOP,LOOPWE,LOOPWZ,LOOPWNZ,LOOPWNE;在以ECX作为计数器时,以LOOPD,LOOPDE,LOOPDZ,LOOPDNZ,LOOPDNE,下面的一段例子可以说明问题:
ABC PROC
MOV CX,100H
AA:
;ADD YOUR CODES HERE
LOOP AA
ABC END
C.过程调用和返回调用CALL,RET
这两个指令与8086的用法相同,但由于80386下有实模式和保护模式下。在实模式下,无论是段内调用还是段间调用均采用32位指针,即CS:IP,它们的用法与8086下相同。在保护模式下,段间调用和段内调用均用48位指针,即ECS:IP。RET用于返回,具体实现过程会比较复杂,在介绍完80386的地址的管理机制后会作介绍,先介绍一下以下CALL指令在8086中的用法:
a.段内直接转移,具体格式:CALL 过程名。此时CS不入栈,IP的内栈入栈,入栈后再将加上目的地址与CALL指令的下一条指令的偏移地址之差值就可以转移到目的地址,详细过程:
SP-2=>SP;将堆栈指针SP减2
(SP)<=IP;将IP进栈
IP+偏移地址之差;转到目的地址
b.段内间接转移,具体格式:CALL OPRD,那么在这里OPRD可以寄存器或内存单元,它的具体实现过程:
SP-2=>SP;将堆栈指针SP减2
(SP)<=IP;将IP进栈
IP<=(OPRD);转到目的地址
同a一样,CS不入栈
c.段间直接转移,具体格式:CALL 过程名 [FAR],此时CS,IP均要入栈,详细的实现过程:
SP-2=>SP;将堆栈指针减2
(SP)<=CS;将CS入栈
SP-2=>SP;将堆栈指针再减2
(SP)<=IP;将IP入栈
;装入新的CS,IP
IP<=过程入口的偏移地址
CS<=过程入口的段地址
d.段间间接转移,具体格式:CALL OPRD [FAR],此时CS,IP均要入栈,OPRD是32位,你知道在8086中没有32位寄存器。因此,这里的OPRD一定是存储单元,高16位是CS的值,低16位是IP值,详细的实现过程:
SP-2=>SP;将堆栈指针减2
(SP)<=CS;将CS入栈
SP-2=>SP;将堆栈指针再减2
(SP)<=IP;将IP入栈
;装入新的CS,IP
IP<=(OPRD+2,OPRD+3)
CS<=(OPRD,OPRD1)
e.段内返回
格式:RET。实际上它的实现过程:
(SP)=>IP;从当前栈顶弹出一个字,将它送给IP指令计数器
SP+2=>SP;SP
f.段间返回
格式:RET,实际上它的实现过程:
(SP)=>IP;IP出栈
SP+2=>SP;
(SP)=>CS;CS出栈
SP+2=>SP;
D.中断返回指令IRET
功能和用法与8086相同,这里顺便介绍一下8086的中断返回指令
IRET,具体的实现过程:
IP<=(SP);IP出栈
SP+2=>SP;
CS<=(SP);CS出栈
SP+2=>SP;
FLAGS<=(SP);标志寄存器出栈
SP+2=>SP;
E.串操作指令
80386在串操作指令方面增加了双字操作,在8086五条指令的基础上增加了INS,OUTS。
a.LOADSD,和8086的用法和功能相同,不过是对32位操作数操作。
b.STOSD,和8086的用法和功能相同,不过是对32位操作数操作。
c.CMPSD,和8086的用法和功能相同,不过是对32位操作数操作。
d.SCANSD,和8086的用法和功能相同,不过是对32位操作数操作。
e.MOVSD,和8086的用法和功能相同,不过是对32位操作数操作。
f.重复前缀REP,和8086的功能与用法相同,仍以CX为计数器,看下面的一小程序:
ROR ECX,2
REP MOVSD;以CX为计数器,每次传送双字
ROL ECX,1
REP MOVSW;以CX为计数器,每次传送一字
ROL ECX,1
REP MOVSB;以CX为计数器,每个传送一个字节
g.INSB,INSW,INSD,OUTSB,OUTSW,OUTSD
g1.INSB,串输入指令,以字节单位,该指令的功能是从DX指定的端口读入一个字节到ES:DI指定的内存单元中。
g2.INSW,串输入指令,以字单位,该指令的功能是从DX指定的端口读入一个字节到ES:DI指定的内存单元中。
g3.INSD,串输入指令,以双字单位,该指令的功能是从DX指定的端口读入一个字节到ES:DI指定的内存单元中。
g4.OUTSB, 串输出指令,以字节为单位,将DS:SI内存单元的内容送往DX指定的端口。
g5.OUTSW, 串输出指令,以字为单位,将DS:SI内存单元的内容送往DX指定的端口。
g6.OUTSD, 串输出指令,以双字为单位,将DS:SI内存单元的内容送往DX指定的端口。
g7.串输入和串输出指令不影响标志寄存器中的各标志位,串操作指令可以与REP一起使用
高级语言支持,条件字节设置指令
AA.高级语言支持指令,开始于80186,主要是用来简化高级语言的某些特征,总共有3条指令:ENTER,LEAVE,BOUND
a.ENTER,LEAVE,建立与释放堆栈框架命令。在C语言中,栈不仅用来向函数传递入口参数,而且在函数内部的局部变量也存放在栈中。为了准确地存取这些这些局变量和准确地获得入口参数,就需要建立堆栈框架,先看一个小程序:
//C Programming-Language
int sum(int x,int y)
{
int sum;
sum=x+y;
return sum;
}
//The corresponding ASM codes lists below
_sum proc near;注意C语言中函数参数的入栈方式是从右向左,即先是参数y入栈,再是x入栈,再是函数的返回地址入栈
push bp
mov bp,sp;建立堆栈框架
sub sp,2
mov ax,word ptr [bp+4];取参数x
add ax,word ptr [bp+6];加参数y
mov word ptr [bp-2],ax
mov ax,word ptr [bp-2]
mov sp,bp;释放栈框架
pop bp
ret
_sum endp
此时栈顶的示意图是:
|———————-|
| BP |<====SP
|———————-|
| 函数返回地址 |<====BP+2
|———————-|
| 参数x |<====BP+4
|———————-|
| 参数y |<====BP+6
|———————-|
| …… |<====BP+8
|———————-|
| …….. |<====BP+n,n是一能被2整除的数
|———————-|
如果用建立和释放堆栈框架指令,那么对应的汇编程序应该是:
_sum proc near
enter 2,0;建立栈框架
mov ax,word ptr [bp+4];取参数x
add ax,word ptr [bp+6];加参数y
mov word ptr [bp-2],ax
mov ax,word ptr [bp-2]
leave;释放栈框架
ret
_sum endp
b.建立栈框架指令ENTER,格式如下:ENTER CNT1,CNT2。其中CNT1表示框架的大小,即子程序中需要放在栈中局部变量的字节数;CNT2是立即数,表示子程序嵌套级别,即从调用框架复制到当前框架的指针数。在立即数CNT2为0时,ENTER指令的实过程是:
PUSH BP
SP=>BP
SP<=SP-CNT1
c.释放栈框架指令LEAVE,其具体实现过程:
8086:
BP=>SP
POP BP
80386:
EBP=>ESP
POP EBP
d.ENTER和LEAVE指令均不影响标志寄存器中的各标志位,同时LEAVE指令只负责释放栈框架,并不负责函数返回。因此,要在LEAVE指令后安排一条返回指令。
BB.条件字节设置指令
这是80386新增的一组指令集,将会在后面全部列表出来。条件字节设置指令的格式:
SETxx OPRD
xx是助记符的一部分,OPRD只能是8位的寄存器或存储单元。
eg:
SETO AL;表示当溢出标志位为1时,即OF=1,将AL置1,否则AL清0
SETNC CH;表示当CF=0时,将CH置1,否则将CH清0
SETNA BYTE PTR [100];表示当AF=0,将DS:[100]这一个字置1,否则将它清0
a.SETZ OPRD;等于0时(ZF=1),置OPRD为1,否则清0
b.SETE OPRD;同a
c.SETNZ OPRD;不等于0时(ZF=0),置OPRD为1,否则清0
d.SETNE OPRD;同c
e.SETS OPRD;为负数时(SF=1)置OPRD为1,否则清0
f.SETNS OPRD;同e正好相反(SF=0)
g.SETO OPRD;OF=1,置OPRD为1,否则清0
h.SETNO OPRD;同g正好相反
i.SETP OPRD;偶(PF=1)置1
j.SETPE OPRD;同i
k.SETNP OPRD;奇(PF=0)置1
l.SETPO OPRD;同k
m.SETB OPRD;低于置OPRD为1,否则清0,这是针对无符号数的
n.SETNAE OPRD;不高于即低于或等于时置OPRD为1,否则清0,这是针对无符号数的
o.SETC OPRD;CF=1,置OPRD为1,否则清0
p.SETNB OPRD;高于或等于时,置OPRD为1,否则清0,这是针对无符号数的
q.SETAE OPRD;高于时置OPRD为1,否则清0,这是针对无符号数的
r.SETNC OPRD;CF=0时,置OPRD为1,否则清0,这是针对无符号数的
s.SETBE OPRD;低于或等于时,置OPRD为1,否则清0,这是针对无符号数的,CF|ZF=1
t.SETNA OPRD;同s,这是针对无符号数的,CF|ZF=1
u.SETNBE OPRD;高于时置OPRD为1,否则清0,这是针对无符号数的,CF OR ZF=0
v.SETA OPRD;同u,这是针对无符号数的,CF OR ZF=0
w.SETL OPRD;小于时,置OPRD为1,否则清0,这是针对有符号数的
x.SETNGE OPRD;同w,这是针对有符号数的
y.SETNL OPRD;大于或等于时,置OPR为1,否则清0,这是针对有符号数的
z.SETGE OPRD;同y,这是针对有符号数的
a1.SETLE OPRD;小于或等于时,置OPRD为1,否则清0,这是针对有符号数的
a2.SETNG OPRD;同a1,这是针对有符号数的
a3.SETNLE;大于时,置OPRD为1,否则清0,这是针对有符号数的
a4.SETG;同a3,这是针对有符号数的
位操作指令,处理器控制指令
AA.位操作指令,8086新增的一组指令,包括位测试,位扫描。BT,BTC,BTR,BTS,BSF,BSR
a.BT(Bit Test),位测试指令,指令格式:
BT OPRD1,OPRD2,规则:操作作OPRD1可以是16位或32位的通用寄存器或者存储单元。操作数OPRD2必须是8位立即数或者是与OPRD1操作数长度相等的通用寄存器。如果用OPRD2除以OPRD1,假设商存放在Divd中,余数存放在Mod中,那么对OPRD1操作数要进行测试的位号就是Mod,它的主要功能就是把要测试位的值送往CF,看几个简单的例子:
b.BTC(Bit Test And Complement),测试并取反用法和规则与BT是一样,但在功能有些不同,它不但将要测试位的值送往CF,并且还将该位取反。
c.BTR(Bit Test And Reset),测试并复位,用法和规则与BT是一样,但在功能有些不同,它不但将要测试位的值送往CF,并且还将该位复位(即清0)。
d.BTS(Bit Test And Set),测试并置位,用法和规则与BT是一样,但在功能有些不同,它不但将要测试位的值送往CF,并且还将该位置位(即置1)。
e.BSF(Bit Scan Forward),顺向位扫描,指令格式:BSF OPRD1,OPRD2,功能:将从右向左(从最低位到最高位)对OPRD2操作数进行扫描,并将第一个为1的位号送给操作数OPRD1。操作数OPRD1,OPRD2可以是16位或32位通用寄存器或者存储单元,但OPRD1和OPRD2操作数的长度必须相等。
f.BSR(Bit Scan Reverse),逆向位扫描,指令格式:BSR OPRD1,OPRD2,功能:将从左向右(从最高位到最低位)对OPRD2操作数进行扫描,并将第一个为1的位号送给操作数OPRD1。操作数OPRD1,OPRD2可以是16位或32位通用寄存器或存储单元,但OPRD1和OPRD2操作数的长度必须相等。
g.举个简单的例子来说明这6条指令:
AA DW 1234H,5678H
BB DW 9999H,7777H
MOV EAX,12345678H
MOV BX,9999H
BT EAX,8;CF=0,EAX保持不变
BTC EAX,8;CF=0,EAX=12345778H
BTR EAX,8;CF=0,EAX=12345678H
BTS EAX,8;CF=0,EAX=12345778H
BSF AX,BX;AX=0
BSR AX,BX;AX=15
BT WORD PTR [AA],4;CF=1,[AA]的内容不变
BTC WORD PTR [AA],4;CF=1,[AA]=1223H
BTR WORD PTR [AA],4;CF=1,[AA]=1223H
BTS WORD PTR [AA],4;CF=1,[AA]=1234H
BSF WORD PTR [AA],BX;[AA]=0;
BSR WORD PTR [AA],BX;[AA]=15(十进制)
BT DWORD PTR [BB],12;CF=1,[BB]的内容保持不变
BTC DWORD PTR [BB],12;CF=1,[BB]=76779999H
BTR DWORD PTR [BB],12;CF=1,[BB]=76779999H
BTS DWORD PTR [BB],12;CF=1,[BB]=77779999H
BSF DWORD PTR [BB],12;[BB]=0
BSR DWORD PTR [BB],12;[BB]=31(十进制)
BB.处理器控制指令
处理器控制指令主要是用来设置/清除标志,空操作以及与外部事件同步等。
a.CLC,将CF标志位清0。
b.STC,将CF标志位置1。
c.CLI,关中断。
d.STI,开中断。
e.CLD,清DF=0。
f.STD,置DF=1。
g.NOP,空操作,填补程序中的空白区,空操作本身不执行任何操作,主要是为了保持程序的连续性。
h.WAIT,等待BUSY引脚为高。
i.LOCK,封锁前缀可以锁定其后指令的操作数的存储单元,该指令在指令执行期间一直有效。在多任务环境中,可以用它来保证独占其享内存,只有以下指令才可以用LOCK前缀:
XCHG,ADD,ADC,INC,SUB,SBB,DEC,NEG,OR,AND,XOR,NOT,BT,BTS,BTR,BTC
j.说明处理器类型的伪指令
.8086,只支持对8086指令的汇编
.186,只支持对80186指令的汇编
.286,支持对非特权的80286指令的汇编
.286C,支持对非特权的80286指令的汇编
.286P,支持对80286所有指令的汇编
.386,支持对80386非特权指令的汇编
.386C,支持对80386非特权指令的汇编
.386P,支持对80386所有指令的汇编
只有用伪指令说明了处理器类型,汇编程序才知道如何更好去编译,连接程序,更好地去检错。
在后续的几篇里将详细介绍80386的段页管理机制及控制寄存器,调试寄存器,以及如何在386实模下和保护模式下编程。
80386实模式下编程
80386在实模式下是一个更快的8086,它不但可以进行32位操作,而且还可以进32位寻址,并且还可以使用80386的扩展指令。不过,由于是在实模下,寻址的最大空间为1M。在一个段内,段的最大长度不超过64K,否则就会发生异常。
在8086下定义一个段的完整格式是:
段名 [定位类型] [组合类型] [‘类别’]
80386下定义一个段的完整格式是:
段名 [定位类型] [组合类型] [‘类别’] [属性类型]
说明:属性类型有两种:USE32和USE16,USE32表示32位段,USE16表示16位段。如果你在程序中用到伪指令.386,那么默认的属性类型就是USE32(32位段),如果没有用伪指令指定CPU的类型,那么默认的属性类型就是USE16,在实方式下只能使用16位段,即用USE16。
eg:
CSEG PARA PUBLIC USE32;定义一个32位的段
AA DW ?
BB DD ?
CC DB ?
DD DW ?
EE DW 0,0,0…..
CSEG ENDS
由于在80386中用到了66H操作前缀和67H地址前缀,因此尽管在实式模式下,只要设定的CPU类型是80386,仍然可以进行32位操作,可以进行32位寻址,66H,67H这两个前缀无需程序员在程序中书写,汇编程序会自动加上的。只要在程序中对32位操作数进行访问,或进行32位寻址,那么就会加上操作数前缀66H和地址前缀67H。相反,如果在32位段中对16位或8位的访问,汇编程序中也会加上这两个前缀。
下面将给出一个例子程序,演示一下在80386的实模式下编程的方法与技巧(这是从网上down的一个程序,不是我写的,但我会作详细的解剖,并与8086下的程序设计作出比较):
用十进制,十六进制,二进制三种形式显示双字存储单元F000:1234中的内容
Using the Windows Headers
From: MSDN
Windows API 头文件允许您创建32- 和64-位应用程序。它们包含了Unicode 和 ANSI 版本的 API声明。更多的信息可参见 Unicode in the Windows API。They use data types that allow you to build both 32- and 64-bit versions of your application from a single source code base. For more information, see Getting Ready for 64-bit Windows. Additional features include Header Annotations and STRICT Type Checking.
Microsoft Visual C++ includes copies of the Windows header files that were current at the time Visual C++ was released. Therefore, if you install updated header files from an SDK, you may end up with multiple versions of the Windows header files on your computer. If you do not ensure that you are using the latest version of the SDK header files, you will receive the following error code when compiling code that uses features that were introduced after Visual C++ was released: error C2065: undeclared identifier.
条件申明
某些函数在部分Windows版本才具备,使用条件代码来声明它们。您就可以使用编译器来检测这些函数是否被目标Windows版本支持。必须定义适当的宏来编译使用到这些函数的应用程序。否则,你会收到C2065的出错信息。
Windows头文件使用宏来决定
The Windows header files use macros to indicate which versions of Windows support many programming elements. Therefore, you must define these macros to use new functionality introduced in each major operating system release. (Individual header files may use different macros; therefore, if compilation problems occur, check the header file that contains the definition for conditional definitions.) For more information, see SdkDdkver.h.
下表描述了Windows 头文件中的首选宏。
| 最小系统需求 | NTDDI_VERSION |
|---|---|
| Windows Server 2008 | NTDDI_WS08 |
| Windows Vista SP1 | NTDDI_VISTASP1 |
| Windows Vista | NTDDI_VISTA |
| Windows Server 2003 SP1 | NTDDI_WS03SP1 |
| Windows Server 2003 | NTDDI_WS03 |
| Windows XP SP2 | NTDDI_WINXPSP2 |
| Windows XP SP1 | NTDDI_WINXPSP1 |
| Windows XP | NTDDI_WINXP |
| Windows 2000 SP4 | NTDDI_WIN2KSP4 |
| Windows 2000 SP3 | NTDDI_WIN2KSP3 |
| Windows 2000 SP2 | NTDDI_WIN2KSP2 |
| Windows 2000 SP1 | NTDDI_WIN2KSP1 |
| Windows 2000 | NTDDI_WIN2K |
下表描述了在Windows头文件中使用的宏。
| 最小系统需求 | _WIN32_WINNT 和 WINVER的最小值 |
|---|---|
| Windows Server 2008 | 0×0600 |
| Windows Vista | 0×0600 |
| Windows Server 2003 SP1, Windows XP SP2 | 0×0502 |
| Windows Server 2003, Windows XP | 0×0501 |
| Windows 2000 | 0×0500 |
| 最小版本需求 | _WIN32_IE的最小值 |
|---|---|
| Internet Explorer 7.0 | 0×0700 |
| Internet Explorer 6.0 SP2 | 0×0603 |
| Internet Explorer 6.0 SP1 | 0×0601 |
| Internet Explorer 6.0 | 0×0600 |
| Internet Explorer 5.5 | 0×0550 |
| Internet Explorer 5.01 | 0×0501 |
| Internet Explorer 5.0, 5.0a, 5.0b | 0×0500 |
Note that some features introduced in the latest version of Windows may be added to a service pack for a previous version of Windows. Therefore, to target a service pack, you may need to define _WIN32_WINNT with the value for the next major operating system release. For example, the GetDllDirectory function was introduced in Windows Server 2003 and is conditionally defined if _WIN32_WINNT is 0×0502 or greater. This function was also added to Windows XP SP1. Therefore, if you were to define _WIN32_WINNT 0×0501 to target Windows XP, you would miss features that are defined in Windows XP SP1.
You can define these symbols by using the #define statement in each source file, or by specifying the /D compiler option supported by Visual C++. To specify compiler options, go to the Projects menu and click Properties. Go to Configuration Properties, then C++, then Command Line. Enter the option under Additional Options.
控制结构包
Projects should be compiled to use the default structure packing, which is currently 8 bytes because the largest integral type is 8 bytes. Doing so ensures that all structure types within the header files are compiled into the application with the same alignment the Windows API expects. It also ensures that structures with 8-byte values are properly aligned and will not cause alignment faults on processors that enforce data alignment.
With Microsoft Visual C++ 2005, structure packing is controlled by the /Zp switch. When this switch is omitted from a project build, the compiler automatically uses the default structure packing size.
If you are using a packing setting other than the default, be sure to use a pragma pack statement as shown in the following example to ensure that the Windows header files are packed correctly.
用最少的头文件来快速编译
You can reduce the size of the Windows header files by excluding some of the less common API declarations as follows:
- Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
- Define one or more of the NOapi symbols to exclude the API. For example, NOCOMM excludes the serial communication API. For a list of support NOapi symbols, see Windows.h.
在中间层驱动中处理IRP
Higher-level drivers have a different set of standard routines than lowest-level device drivers, with an overlapping subset of standard routines common to both types of drivers.
The set of routines for intermediate and highest-level drivers also varies according to the following criteria:
- The nature of the underlying physical device
- Whether an underlying device driver sets up device objects for direct or buffered I/O
- The design of the individual higher-level driver
The following figure illustrates the path an IRP might take through the standard routines of an intermediate
下图展示的驱动有下面的特性:
- The driver is layered over more than one physical device and possibly over more than one device driver.
- The driver sometimes allocates additional IRPs for lower-level drivers, depending on the requested operation in the input IRP.
- The driver has at least one file system driver layered above it, and that file system driver might be layered over other intermediate drivers at a higher level than this one.
IRP Path through Intermediate Driver Routines
As the figure shows, the I/O manager creates an IRP and sends it to the driver’s dispatch routine for the given major function code. Assuming the function code is IRP_MJ_WRITE, the dispatch routine is DDDispatchWrite. The intermediate driver’s I/O stack location is shown in the middle, with an indefinite number of I/O stack locations for higher- and lower-level drivers shown shaded.
分派IRPs
The mirror driver’s purpose is to send write requests to several physical devices, and to send read requests alternately to the drivers of these devices. For write requests, the driver creates duplicate IRPs for each device on which the data is to be written, assuming the parameters in the input IRP are valid.
The previous figure shows a call to
When the dispatch routine calls IoAllocateIrp, it specifies the number of I/O stack locations needed for the IRP. The driver must specify a stack location for each lower driver in the chain, getting the appropriate value from the device objects of each driver just below the mirror driver. Optionally, the driver can add one to this value when it calls IoAllocate
27 Jun 08 | 