2006年01月06日

Linux下 网卡驱动编写
======================转载自linuxmine.net======================


工作需要写了我们公司一块网卡的linux驱动程序。经历一个从无到有的过程,深感技术交流的重要。Linux作为挑战微软垄断的强有力武器,日益受到大家的喜爱。真希望她能在中国迅速成长。把程序文档贴出来,希望和大家探讨Linux技术和应用,促进Linux在中国的普及。
本文可随意转载,但请不要在盈利性出版物上刊登。
—————— linux操作系统网络驱动程序编写 ——————-
———— Contact the author by mailto:bordi@bordi.dhs.org ——

linux操作系统网络驱动程序编写

一.linux系统设备驱动程序概述
1.1 linux设备驱动程序分类
1.2 编写驱动程序的一些基本概念
二.linux系统网络设备驱动程序
2.1 网络驱动程序的结构
2.2 网络驱动程序的基本方法
2.3 网络驱动程序中用到的数据结构
2.4 常用的系统支持
三.编写linux网络驱动程序中可能遇到的问题
3.1 中断共享
3.2 硬件发送忙时的处理
3.3 流量控制(flow control)
3.4 调试
四.进一步的阅读
五.杂项

[目录]

——————————————————————————–


概述

一.linux系统设备驱动程序概述
1.1 linux设备驱动程序分类
linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。在2.0.xx到2.2.xx的变动里,驱动程序的编写做了一些改变,但是从2.0.xx的驱动到2.2.xx的移植只需做少量的工作。
linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。典型的字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘设备,CD-ROM等。一个文件系统要安装进入操作系统必须在块设备上。

网络设备在linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

1.2 编写驱动程序的一些基本概念
无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动程序的支持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本要求。

1.2.1 发送和接收
这是一个网络设备最基本的功能。一块网卡所做的无非就是收发工作。所以驱动程序里要告诉系统你的发送函数在哪里,系统在有数据要发送时就会调用你的发送程序。还有驱动程序由于是直接操纵硬件的,所以网络硬件有数据收到最先能得到这个数据的也就是驱动程序,它负责把这些原始数据进行必要的处理然后送给系统。这里,操作系统必须要提供两个机制,一个是找到驱动程序的发送函数,一个是驱动程序把收到的数据送给系统。

1.2.2 中断
中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断的能力。一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后调用驱动程序的处理程序。linux支持中断的共享,即多个设备共享一个中断。

1.2.3 时钟
在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时间过了以后回调注册的时钟函数。在网络驱动程序中,如果硬件没有中断功能,定时器可以提供轮询(poll)方式对硬件进行存取。或者是实现某些协议时需要的超时重传等。

[目录]

——————————————————————————–


设备驱动

二.linux系统网络设备驱动程序
2.1 网络驱动程序的结构
所有的linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备就是一个对象(device 结构),它内部有自己的数据和方法。每一个设备的方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身的数据(类似面向对象程序设计时的this引用)。
一个网络设备最基本的方法有初始化、发送和接收。

——————- ———————
|deliver packets | |receive packets queue|
|(dev_queue_xmit()) | |them(netif_rx()) |
——————- ———————
| | /
/ | |
——————————————————-
| methods and variables(initialize,open,close,hard_xmit,|
| interrupt handler,config,resources,status…) |
——————————————————-
| | /
/ | |
—————– ———————-
|send to hardware | |receivce from hardware|
—————– ———————-
| | /
/ | |
—————————————————–
| hardware media |
—————————————————–

初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请。发送程序是在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发送数据进行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通过硬件中断来通知的。在中断处理程序里,把硬件帧信息填入一个skbuff结构中,然后调用netif_rx()传递给上层处理。


2.2 网络驱动程序的基本方法
网络设备做为一个对象,提供一些方法供系统访问。正是这些有统一接口的方法,掩蔽了硬件的具体细节,让系统对各种网络设备的访问都采用统一的形式,做到硬件无关性。
下面解释最基本的方法。

2.2.1 初始化(initialize)
驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初始化程序。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的特征检查硬件是否存在,然后决定是否启动这个驱动程序。配置和初始化硬件。在初始化程序里你可以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时候进行配置(linux内核对PnP功能没有很好的支持,可以在驱动程序里完成这个功能)。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。接下来你要初始化device结构中的变量。最后,你可以让硬件正式开始工作。

2.2.2 打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状态由down–>up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。
open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。

2.2.3 关闭(stop)
close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。
另外close方法必须返回成功(0==success)。

2.2.4 发送(hard_start_xmit)
所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数据。
如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果设备暂时无法处理,比如硬件忙,则返回1。这时如果dev->tbusy置为非0,则系统认为硬件忙,要等到dev->tbusy置0以后才会再次发送。tbusy的置0任务一般由中断完成。硬件在发送结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通知系统可以再次发送。在发送不成功的情况下,也可以不置dev->tbusy为非0,这样系统会不断尝试重发。如果hard_start_xmit发送不成功,则不要释放sk_buff。
传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。

2.2.5 接收(reception)
驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb),从硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中的一些信息。skb->dev = dev,判断收到帧的协议类型,填入skb->protocol(多协议的支持)。把指针skb->mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要设置skb->pkt_type,标明第二层(链路层)数据类型。可以是以下类型:
PACKET_BROADCAST : 链路层广播
PACKET_MULTICAST : 链路层组播
PACKET_SELF : 发给自己的帧
PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧)

最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回,真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后,驱动程序就不能再存取数据缓冲区skb。

2.2.6 硬件帧头(hard_header)
硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网(Ethernet)就有14字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供一个hard_header方法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。
硬件帧头的长度必须填在dev->hard_header_len,这样协议层回在数据之前保留好硬件帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就可以了。
在协议层调用hard_header时,传送的参数包括(2.0.xx):数据的sk_buff,device指针,protocol,目的地址(daddr),源地址(saddr),数据长度(len)。数据长度不要使用sk_buff中的参数,因为调用hard_header时数据可能还没完全组织好。saddr是NULL的话是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目的地址。如果hard_header完全填好了硬件帧头,则返回添加的字节数。如果硬件帧头中的信息还不完全(比如daddr为NULL,但是帧头中需要目的硬件地址。典型的情况是以太网需要地址解析(arp)),则返回负字节数。hard_header返回负数的情况下,协议层会做进一步的build header的工作。目前linux系统里就是做arp(如果hard_header返回正,dev->arp=1,表明不需要做arp,返回负,dev->arp=0,做arp)。
对hard_header的调用在每个协议层的处理程序里。如ip_output。

2.2.7 地址解析(xarp)
有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方法。调用的主要参数包括指向硬件帧头的指针,协议层地址。如果驱动程序能够解析硬件地址,就返回1,如果不能,返回0。
对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。

2.2.8 参数设置和统计数据
在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。设置方法有:
dev->set_mac_address()
当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。
dev->set_config()
当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。
dev->do_ioctl()
如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱动程序的这个方法。一般是设置设备的专用数据。
读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个dev->get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。
ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。


2.3 网络驱动程序中用到的数据结构
最重要的是网络设备的数据结构。定义在include/linux/netdevice.h里。它的注释已经足够详尽。
struct device
{

/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char *name;

/* I/O specific fields – FIXME: Merge these and struct ifmap into one */
unsigned long rmem_end; /* shmem "recv" end */
unsigned long rmem_start; /* shmem "recv" start */
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned char irq; /* device IRQ number */

/* Low-level status flags. */
volatile unsigned char start, /* start an operation */
interrupt; /* interrupt arrived */
/* 在处理中断时interrupt设为1,处理完清0。 */
unsigned long tbusy; /* transmitter busy must be longg for
bitops */

struct device *next;

/* The device initialization function. Called only once. */
/* 指向驱动程序的初始化方法。 */
int (*init)(struct device *dev);

/* Some hardware also needs these fields, but they are not part of the
usual set specified in Space.c. */
/* 一些硬件可以在一块板上支持多个接口,可能用到if_port。 */
unsigned char if_port; /* Selectable AUI, TP,..*/
unsigned char dma; /* DMA channel */

struct enet_statistics* (*get_stats)(struct device *dev);

/*
* This marks the end of the "visible" part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will).
*/

/* These may be needed for future network-power-down code. */
/* trans_start记录最后一次成功发送的时间。可以用来确定硬件是否工作正常。*/
unsigned long trans_start; /* Time (in jiffies) of last Tx */
unsigned long last_rx; /* Time of last Rx */

/* flags里面有很多内容,定义在include/linux/if.h里。*/
unsigned short flags; /* interface flags (a la BSD) */
unsigned short family; /* address family ID (AF_INET) */
unsigned short metric; /* routing metric (not used) */
unsigned short mtu; /* interface MTU value */

/* type标明物理硬件的类型。主要说明硬件是否需要arp。定义在
include/linux/if_arp.h里。 */
unsigned short type; /* interface hardware type */

/* 上层协议层根据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/
unsigned short hard_header_len; /* hardware hdr length */

/* priv指向驱动程序自己定义的一些参数。*/
void *priv; /* pointer to private data */

/* Interface address info. */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */
unsigned char pad; /* make dev_addr alignedd to 8
bytes */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */
unsigned char addr_len; /* hardware address length */
unsigned long pa_addr; /* protocol address */
unsigned long pa_brdaddr; /* protocol broadcast addr */
unsigned long pa_dstaddr; /* protocol P-P other side addr */
unsigned long pa_mask; /* protocol netmask */
unsigned short pa_alen; /* protocol address length */

struct dev_mc_list *mc_list; /* Multicast mac addresses */
int mc_count; /* Number of installed mcasts */

struct ip_mc_list *ip_mc_list; /* IP multicast filter chain */
__u32 tx_queue_len; /* Max frames per queue allowed */

/* For load balancing driver pair support */

unsigned long pkt_queue; /* Packets queued */
struct device *slave; /* Slave device */
struct net_alias_info *alias_info; /* main dev alias info */
struct net_alias *my_alias; /* alias devs */

/* Pointer to the interface buffers. */
struct sk_buff_head buffs[DEV_NUMBUFFS];

/* Pointers to interface service routines. */
int (*open)(struct device *dev);
int (*stop)(struct device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,
struct device *dev);
int (*hard_header) (struct sk_buff *skb,
struct device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
int (*rebuild_header)(void *eth, struct device *dev,
unsigned long raddr, struct sk_buff *skb);
#define HAVE_MULTICAST
void (*set_multicast_list)(struct device *dev);
#define HAVE_SET_MAC_ADDR
int (*set_mac_address)(struct device *dev, void *addr);
#define HAVE_PRIVATE_IOCTL
int (*do_ioctl)(struct device *dev, struct ifreq *ifr, intt cmd);
#define HAVE_SET_CONFIG
int (*set_config)(struct device *dev, struct ifmap *map);
#define HAVE_HEADER_CACHE
void (*header_cache_bind)(struct hh_cache **hhp, struct devvice
*dev, unsigned short htype, __u32 daddr);
void (*header_cache_update)(struct hh_cache *hh, struct devvice
*dev, unsigned char * haddr);
#define HAVE_CHANGE_MTU
int (*change_mtu)(struct device *dev, int new_mtu);

struct iw_statistics* (*get_wireless_stats)(struct device *dev);
};


2.4 常用的系统支持

2.4.1 内存申请和释放
include/linux/kernel.h里声明了kmalloc()和kfree()。用于在内核模式下申请和释放内存。
void *kmalloc(unsigned int len,int priority);
void kfree(void *__ptr);
与用户模式下的malloc()不同,kmalloc()申请空间有大小限制。长度是2的整次方。可以申请的最大长度也有限制。另外kmalloc()有priority参数,通常使用时可以为GFP_KERNEL,如果在中断里调用用GFP_ATOMIC参数,因为使用GFP_KERNEL则调用者可能进入sleep状态,在处理中断时是不允许的。
kfree()释放的内存必须是kmalloc()申请的。如果知道内存的大小,也可以用kfree_s()释放。

2.4.2 request_irq()、free_irq()
这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。

request_irq()调用的定义:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq是要申请的硬件中断号。在Intel平台,范围0–15。handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map找到中断对应的设备。
void free_irq(unsigned int irq,void *dev_id);

2.4.3 时钟
时钟的处理类似中断,也是登记一个时间处理函数,在预定的时间过后,系统会调用这个函数。在include/linux/timer.h里声明。
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
void init_timer(struct timer_list * timer);
使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。time_list结构里expires是标明这个时钟的周期,单位采用jiffies的单位。jiffies是linux一个全局变量,代表时间。它的单位随硬件平台的不同而不同。系统里定义了一个常数HZ,代表每秒种最小时间间隔的数目。这样jiffies的单位就是1/HZ。Intel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间间隔了。所以expires/HZ就是以秒为单位的这个时钟的周期。
function就是时间到了以后的回调函数,它的参数就是timer_list中的data。data这个参数在初始化时钟的时候赋值,一般赋给它设备的device结构指针。
在预置时间到系统调用function,同时系统把这个time_list从定时队列里清除。所以如果需要一直使用定时函数,要在function里再次调用add_timer()把这个timer_list加进定时队列。

2.4.4 I/O
I/O端口的存取使用:
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);
在include/adm/io.h里定义。
inb_p()、outb_p()与inb()、outb_p()的不同在于前者在存取I/O时有等待(pause)一适应慢速的I/O设备。
为了防止存取I/O时发生冲突,linux提供对端口使用情况的控制。在使用端口之前,可以检查需要的I/O是否正在被使用,如果没有,则把端口标记为正在使用,使用完后再释放。系统提供以下几个函数做这些工作。
int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent,const char *name)
void release_region(unsigned int from, unsigned int extent);
其中的参数from表示用到的I/O端口的起始地址,extent标明从from开始的端口数目。name为设备名称。

2.4.5 中断打开关闭
系统提供给驱动程序开放和关闭响应中断的能力。是在include/asm/system.h中的两个定义。
#define cli() __asm__ __volatile__ ("cli":icon_smile.gif
#define sti() __asm__ __volatile__ ("sti":icon_smile.gif

2.4.6 打印信息
类似普通程序里的printf(),驱动程序要输出信息使用printk()。在include/linux/kernel.h里声明。
int printk(const char* fmt, …);
其中fmt是格式化字符串。…是参数。都是和printf()格式一样的。

2.4.7 注册驱动程序
如果使用模块(module)方式加载驱动程序,需要在模块初始化时把设备注册到系统设备表里去。不再使用时,把设备从系统中卸除。定义在drivers/net/net_init.h里的两个函数完成这个工作。
int register_netdev(struct device *dev);
void unregister_netdev(struct device *dev);
dev就是要注册进系统的设备结构指针。在register_netdev()时,dev结构一般填写前面11项,即到init,后面的暂时可以不用初始化。最重要的是name指针和init方法。name指针空(NULL)或者内容为”或者name[0]为空格(space),则系统把你的设备做为以太网设备处理。以太网设备有统一的命名格式,ethX。对以太网这么特别对待大概和linux的历史有关。
init方法一定要提供,register_netdev()会调用这个方法让你对硬件检测和设置。
register_netdev()返回0表示成功,非0不成功。

2.4.8 sk_buff
linux网络各层之间的数据传送都是通过sk_buff。sk_buff提供一套管理缓冲区的方法,是Linux系统网络高效运行的关键。每个sk_buff包括一些控制方法和一块数据缓冲区。控制方法按功能分为两种类型。一种是控制整个buffer链的方法,另一种是控制数据缓冲区的方法。sk_buff组织成双向链表的形式,根据网络应用的特点,对链表的操作主要是删除链表头的元素和添加到链表尾。sk_buff的控制方法都很短小以尽量减少系统负荷。(translated from article written by AlanCox)
常用的方法包括:
.alloc_skb() 申请一个sk_buff并对它初始化。返回就是申请到的sk_buff。
.dev_alloc_skb()类似alloc_skb,在申请好缓冲区后,保留16字节的帧头空
间。主要用在Ethernet驱动程序。
.kfree_skb() 释放一个sk_buff。
.skb_clone() 复制一个sk_buff,但不复制数据部分。
.skb_copy()完全复制一个sk_buff。
.skb_dequeue() 从一个sk_buff链表里取出第一个元素。返回取出的sk_buff,
如果链表空则返回NULL。这是常用的一个操作。
.skb_queue_head() 在一个sk_buff链表头放入一个元素。
.skb_queue_tail() 在一个sk_buff链表尾放入一个元素。这也是常用的一个
操作。网络数据的处理主要是对一个先进先出队列的管理,skb_queue_tail()
和skb_dequeue()完成这个工作。
.skb_insert() 在链表的某个元素前插入一个元素。
.skb_append() 在链表的某个元素后插入一个元素。一些协议(如TCP)对没按
顺序到达的数据进行重组时用到skb_insert()和skb_append()。

.skb_reserve() 在一个申请好的sk_buff的缓冲区里保留一块空间。这个空间
一般是用做下一层协议的头空间的。
.skb_put() 在一个申请好的sk_buff的缓冲区里为数据保留一块空间。在
alloc_skb以后,申请到的sk_buff的缓冲区都是处于空(free)状态,有一个
tail指针指向free空间,实际上开始时tail就指向缓冲区头。skb_reserve()
在free空间里申请协议头空间,skb_put()申请数据空间。见下面的图。
.skb_push() 把sk_buff缓冲区里数据空间往前移。即把Head room中的空间移
一部分到Data area。
.skb_pull() 把sk_buff缓冲区里Data area中的空间移一部分到Head room中。

————————————————–
| Tail room(free) |
————————————————–
After alloc_skb()

————————————————–
| Head room | Tail room(free) |
————————————————–
After skb_reserve()

————————————————–
| Head room | Data area | Tail room(free) |
————————————————–
After skb_put()

————————————————–
|Head| skb_ | Data | Tail room(free) |
|room| push | | |
| | Data area | |
————————————————–
After skb_push()

————————————————–
| Head | skb_ | Data area | Tail room(free) |
| | pull | | |
| Head room | | |
————————————————–
After skb_pull()

[目录]

——————————————————————————–


需要注意

三.编写linux网络驱动程序中需要注意的问题
3.1 中断共享
linux系统运行几个设备共享同一个中断。需要共享的话,在申请的时候指明共享方式。系统提供的request_irq()调用的定义:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
如果共享中断,irqflags设置SA_SHIRQ属性,这样就允许别的设备申请同一个中断。需要注意所有用到这个中断的设备在调用request_irq()都必须设置这个属性。系统在回调每个中断处理程序时,可以用dev_id这个参数找到相应的设备。一般dev_id就设为device结构本身。系统处理共享中断是用各自的dev_id参数依次调用每一个中断处理程序。

3.2 硬件发送忙时的处理
主CPU的处理能力一般比网络发送要快,所以经常会遇到系统有数据要发,但上一包数据网络设备还没发送完。因为在linux里网络设备驱动程序一般不做数据缓存,不能发送的数据都是通知系统发送不成功,所以必须要有一个机制在硬件不忙时及时通知系统接着发送下面的数据。
一般对发送忙的处理在前面设备的发送方法(hard_start_xmit)里已经描述过,即如果发送忙,置tbusy为1。处理完发送数据后,在发送结束中断里清tbusy,同时用mark_bh()调用通知系统继续发送。
但在具体实现我的驱动程序时发现,这样的处理系统好象并不能及时地知道硬件已经空闲了,即在mark_bh()以后,系统要等一段时间才会接着发送。造成发送效率很低。2M线路只有10%不到的使用率。内核版本为2.0.35。
我最后的实现是不把tbusy置1,让系统始终认为硬件空闲,但是报告发送不成功。系统会一直尝试重发。这样处理就运行正常了。但是遍循内核源码中的网络驱动程序,似乎没有这样处理的。不知道症结在哪里。

3.3 流量控制(flow control)
网络数据的发送和接收都需要流量控制。这些控制是在系统里实现的,不需要驱动程序做工作。每个设备数据结构里都有一个参数dev->tx_queue_len,这个参数标明发送时最多缓存的数据包。在linux系统里以太网设备(10/100Mbps)tx_queue_len一般设置为100,串行线路(异步串口)为10。实际上如果看源码可以知道,设置了dev->tx_queue_len并不是为缓存这些数据申请了空间。这个参数只是在收到协议层的数据包时判断发送队列里的数据是不是到了tx_queue_len的限度,以决定这一包数据加不加进发送队列。发送时另一个方面的流控是更高层协议的发送窗口(TCP协议里就有发送窗口)。达到了窗口大小,高层协议就不会再发送数据。
接收流控也分两个层次。netif_rx()缓存的数据包有限制。另外高层协议也会有一个最大的等待处理的数据量。

发送和接收流控处理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()中。

3.4 调试
很多linux的驱动程序都是编译进内核的,形成一个大的内核文件。但对调试来说,这是相当麻烦的。调试驱动程序可以用module方式加载。支持模块方式的驱动程序必须提供两个函数:int init_module(void)和void cleanup_module(void)。init_module()在加载此模块时调用,在这个函数里可以register_netdev()注册设备。init_module()返回0表示成功,返回负表示失败。cleanup_module()在驱动程序被卸载时调用,清除占用的资源,调用unregister_netdev()。
模块可以动态地加载、卸载。在2.0.xx版本里,还有kerneld自动加载模块,但是2.2.xx中已经取消了kerneld。手工加载使用insmod命令,卸载用rmmod命令,看内核中的模块用lsmod命令。
编译驱动程序用gcc,主要命令行参数-DKERNEL -DMODULE。并且作为模块加载的驱动程序,只编译成obj形式(加-c参数)。编译好的目标文件放在/lib/modules/2.x.xx../misc下,在启动文件里用insmod加载。

[目录]

——————————————————————————–


参考

四.进一步的阅读
linux程序设计资料可以从网上获得。这就是开放源代码的好处。并且没有什么“未公开的秘密”。我编写驱动程序时参阅的主要资料包括:
linux内核源代码
<> by Michael K. Johnson
<> by Ori Pomerantz
<> by olly in BBS水木清华站
可以选择一个模板作为开始,内核源代码里有一个网络驱动程序的模板,drivers/net/skeleton.c。里面包含了驱动程序的基本内容。但这个模板是以以太网设备为对象的,以太网的处理在linux系统里有特殊“待遇”,所以如果不是以太网设备,有些细节上要注意,主要在初始化程序里。
最后,多参照别人写的程序,听听其他开发者的经验之谈大概是最有效的帮助了。(linux知识宝库)

2005年03月11日


                           APIHOOK实例剖析

 关于APIHOOK的基础知识有很多,如dll的相关知识、Hook的相关知识、系统进程与线程之间的联系等。具体可以看我的另两篇文章:”我的Dll(动态链接库)学习笔记” 和 “我的Hook学习笔记“。:)下面进入这篇文章的重点,根据APIHook源码进行APIHook的剖析。
 
一、APIHOOK之dll部分
 
//////////////////////////////// APIHook_Dll.cpp ////////////////////////////////////////
//                             rivershan写于2002.9.23                                  //
/////////////////////////////////////////////////////////////////////////////////////////

#include “stdafx.h”
#include “APIHook_Dll.h”

#include <ImageHlp.h>
#include <tlhelp32.h>

#pragma comment(lib,”ImageHlp”) //定义全局共享数据段

#pragma data_seg(“Shared”)
HMODULE hmodDll=NULL;
HHOOK hHook=NULL;

#pragma data_seg()

#pragma comment(linker,”/Section:Shared,rws”) //设置全局共享数据段的属性

///////////////////////////////////// DllMain 函数 /////////////////////////////////////////
//dll的入口点
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
 switch(ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
  //if(sHook)  
  
 case DLL_PROCESS_DETACH:
  UnInstallHook();
  break;
 }
 hmodDll=hModule;
    return TRUE;
}

///////////////////////////////////// HookOneAPI 函数 /////////////////////////////////////////
//进行IAT转换的关键函数,其参数含义:
//pszCalleeModuleName:需要hook的模块名
//pfnOriginApiAddress:要替换的自己API函数的地址
//pfnDummyFuncAddress:需要hook的模块名的地址
//hModCallerModule:我们要查找的模块名称,如果没有被赋值,
//     将会被赋值为枚举的程序所有调用的模块

void WINAPI HookOneAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule)
{
 ULONG size;

 //获取指向PE文件中的Import中IMAGE_DIRECTORY_DESCRIPTOR数组的指针

 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
  ImageDirectoryEntryToData(hModCallerModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);

 if (pImportDesc == NULL)
  return;

 //查找记录,看看有没有我们想要的DLL

 for (;pImportDesc->Name;pImportDesc++)
 {
  LPSTR pszDllName = (LPSTR)((PBYTE)hModCallerModule+pImportDesc->Name);
  if (lstrcmpiA(pszDllName,pszCalleeModuleName) == 0)
   break;
 }

 if (pImportDesc->Name == NULL)
 {
  return;
 }

 //寻找我们想要的函数

 PIMAGE_THUNK_DATA pThunk =
  (PIMAGE_THUNK_DATA)((PBYTE)hModCallerModule+pImportDesc->FirstThunk);//IAT
 for (;pThunk->u1.Function;pThunk++)
 {
  //ppfn记录了与IAT表项相应的函数的地址

  PROC * ppfn= (PROC *)&pThunk->u1.Function;  
  if (*ppfn == pfnOriginApiAddress)
  {
   //如果地址相同,也就是找到了我们想要的函数,进行改写,将其指向我们所定义的函数

   WriteProcessMemory(GetCurrentProcess(),ppfn,&(pfnDummyFuncAddress),
    sizeof(pfnDummyFuncAddress),NULL);
   return;
  }
 }
}

//查找所挂钩的进程所应用的dll模块的

BOOL WINAPI HookAllAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule)
{
 if (pszCalleeModuleName == NULL)
 {
  return FALSE;
 }
 if (pfnOriginApiAddress == NULL)
 {
  return FALSE;
 }
 //如果没传进来要挂钩的模块名称,枚举被挂钩进程的所有引用的模块,
 //并对这些模块进行传进来的相应函数名称的查找
 
 if (hModCallerModule == NULL)
 {
  MEMORY_BASIC_INFORMATION mInfo;
  HMODULE hModHookDLL;
  HANDLE hSnapshot;
  MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
  //MODULEENTRY32:描述了一个被指定进程所应用的模块的struct

  VirtualQuery(HookOneAPI,&mInfo,sizeof(mInfo));
  hModHookDLL=(HMODULE)mInfo.AllocationBase;
  
  hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,0);
  BOOL bOk = Module32First(hSnapshot,&me);
  while (bOk)
  {
   if (me.hModule != hModHookDLL)
   {
    hModCallerModule = me.hModule;//赋值
    //me.hModule:指向当前被挂钩进程的每一个模块
    HookOneAPI(pszCalleeModuleName,pfnOriginApiAddress,
     pfnDummyFuncAddress,hModCallerModule);
   }
   bOk = Module32Next(hSnapshot,&me);
  }
  return TRUE;  
 }
 //如果传进来了,进行查找
 else
 {
  HookOneAPI(pszCalleeModuleName,pfnOriginApiAddress,
    pfnDummyFuncAddress,hModCallerModule);
  return TRUE;
 }
 return FALSE;
}

//////////////////////////////////// UnhookAllAPIHooks 函数 /////////////////////////////////////
//通过使pfnDummyFuncAddress与pfnOriginApiAddress相等的方法,取消对IAT的修改
BOOL WINAPI UnhookAllAPIHooks(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
         PROC pfnDummyFuncAddress,HMODULE hModCallerModule)
{
 PROC temp;
 temp = pfnOriginApiAddress;
 pfnOriginApiAddress = pfnDummyFuncAddress;
 pfnDummyFuncAddress = temp;
 return HookAllAPI(pszCalleeModuleName,pfnOriginApiAddress,
  pfnDummyFuncAddress,hModCallerModule);
}

////////////////////////////////// GetMsgProc 函数 ////////////////////////////////////////
//钩子子程。与其它钩子子程不大相同,没做什么有意义的事情,继续调用下一个钩子子程,形成循环
LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam)
{
 return CallNextHookEx(hHook,code,wParam,lParam);
}

//////////////////////////////////// InstallHook 函数 /////////////////////////////////////
//安装或卸载钩子,BOOL IsHook参数是标志位
//对要钩哪个API函数进行初始化
//我们这里装的钩子类型是WH_GETMESSAGE
void __declspec(dllexport) WINAPI InstallHook(BOOL IsHook,DWORD dwThreadId)
{
 if(IsHook)
 {
 hHook=SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)GetMsgProc,hmodDll,dwThreadId);
 
 //GetProcAddress(GetModuleHandle(“GDI32.dll”),”ExtTextOutA”):取得要钩的函数在所在dll中的地址
 
 HookAllAPI(“GDI32.dll”,GetProcAddress(GetModuleHandle(“GDI32.dll”),
  ”TextOutW”),(PROC)&H_TextOutW,NULL);
 HookAllAPI(“GDI32.dll”,GetProcAddress(GetModuleHandle(“GDI32.dll”),
  ”TextOutA”),(PROC)&H_TextOutA,NULL);
 }
 else
 {
  UnInstallHook();
  UnhookAllAPIHooks(“GDI32.dll”,GetProcAddress(GetModuleHandle(“GDI32.dll”),
   ”TextOutW”),(PROC)&H_TextOutW,NULL);
  UnhookAllAPIHooks(“GDI32.dll”,GetProcAddress(GetModuleHandle(“GDI32.dll”),
   ”TextOutA”),(PROC)&H_TextOutA,NULL);
 }
}

///////////////////////////////////// UnInstallHook 函数 ////////////////////////////////////
//卸载钩子
BOOL WINAPI UnInstallHook()
{
 UnhookWindowsHookEx(hHook);
 return TRUE;
}

///////////////////////////////////// H_TextOutA 函数 /////////////////////////////////////////
//我们的替换函数,可以在里面实现我们所要做的功能
//这里我做的是显示一个对话框,指明是替换了哪个函数
BOOL WINAPI H_TextOutA(HDC hdc,int nXStart,int nYStart,LPCSTR lpString,int cbString)
{
 MessageBox(NULL,”TextOutA”,”APIHook_Dll —rivershan”,MB_OK);
 TextOutA(hdc,nXStart,nYStart,lpString,cbString);//返回原来的函数,以显示字符
 return TRUE;
}

///////////////////////////////////// H_TextOutW 函数 /////////////////////////////////////////
//同上
BOOL WINAPI H_TextOutW(HDC hdc,int nXStart,int nYStart,LPCWSTR lpString,int cbString)
{
 MessageBox(NULL,”TextOutW”,”APIHook_Dll —rivershan”,MB_OK);
 TextOutW(hdc,nXStart,nYStart,lpString,cbString);//返回原来的函数,以显示字符
 return TRUE;
}

**********************************************************************************************
**********************************************************************************************

//////////////////////////////// APIHook_Dll.h ////////////////////////////////////////
//                             rivershan写于2002.9.23                                  //
/////////////////////////////////////////////////////////////////////////////////////////

//dll头文件,用于声明函数

void __declspec(dllexport) WINAPI InstallHook(BOOL,DWORD);
BOOL WINAPI UnInstallHook();
LRESULT CALLBACK GetMsgProC(int code,WPARAM wParam,LPARAM lParam);

void WINAPI HookOneAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule);
BOOL WINAPI HookAllAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule);
BOOL WINAPI UnhookAllAPIHooks(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
         PROC pfnDummyFuncAddress,HMODULE hModCallerModule);

BOOL WINAPI H_TextOutA(HDC, int, int, LPCSTR, int);
BOOL WINAPI H_TextOutW(HDC, int, int, LPCWSTR, int);
BOOL WINAPI H_ExtTextOutA(HDC, int, int, UINT, CONST RECT *,LPCSTR, UINT, CONST INT *);
BOOL WINAPI H_ExtTextOutW(HDC, int, int, UINT, CONST RECT *,LPCWSTR, UINT, CONST INT *);

**********************************************************************************************
**********************************************************************************************

;APIHook_Dll之def文件
LIBRARY APIHook_Dll.dll
EXPORT
 InstallHook
 
二、APIHOOK之exe部分

//////////////////////////// APIHook_EXEDlg.cpp /////////////////////////////////////////
//                             rivershan写于2002.9.23                                  //
/////////////////////////////////////////////////////////////////////////////////////////


#include “stdafx.h”
#include “APIHook_EXE.h”
#include “APIHook_EXEDlg.h”
#include “APIHook_Dll.h”

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAPIHook_EXEDlg dialog

CAPIHook_EXEDlg::CAPIHook_EXEDlg(CWnd* pParent /*=NULL*/)
: CDialog(CAPIHook_EXEDlg::IDD, pParent)
{
 //{{AFX_DATA_INIT(CAPIHook_EXEDlg)
 // NOTE: the ClassWizard will add member initialization here
 //}}AFX_DATA_INIT
 // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CAPIHook_EXEDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CAPIHook_EXEDlg)
 // DDX_Control(pDX, IDC_EDIT1, m_Edit);
 //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAPIHook_EXEDlg, CDialog)
//{{AFX_MSG_MAP(CAPIHook_EXEDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
 ON_BN_CLICKED(IDC_BUTTON_OUT, OnButtonOut)
 ON_BN_CLICKED(IDC_BUTTON_BEGIN, OnButtonBegin)
 ON_BN_CLICKED(IDC_BUTTON_STOP, OnButtonStop)
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CAPIHook_EXEDlg message handlers

BOOL CAPIHook_EXEDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 
 // Set the icon for this dialog.  The framework does this automatically
 // when the application’s main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 // TODO: Add extra initialization here
 
 return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CAPIHook_EXEDlg::OnPaint()
{
 if (IsIconic())
 {
  CPaintDC dc(this); // device context for painting
  
  SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
  
  // Center icon in client rectangle
  int cxIcon = GetSystemMetrics(SM_CXICON);
  int cyIcon = GetSystemMetrics(SM_CYICON);
  CRect rect;
  GetClientRect(&rect);
  int x = (rect.Width() – cxIcon + 1) / 2;
  int y = (rect.Height() – cyIcon + 1) / 2;
  
  // Draw the icon
  dc.DrawIcon(x, y, m_hIcon);
 }
 else
 {
  CDialog::OnPaint();
 }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CAPIHook_EXEDlg::OnQueryDragIcon()
{
 return (HCURSOR) m_hIcon;
}
///////////////////////////////////// OnButtonOut 函数 //////////////////////////////////////
//使用TextOut函数
void CAPIHook_EXEDlg::OnButtonOut()
{
 // TODO: Add your control notification handler code here
 HDC hdc = ::GetDC(GetSafeHwnd());
 ::TextOutA(hdc,0,0,”APIHOOK_EXE —rivershan”,30);
 UpdateWindow();
}

///////////////////////////////////// OnButtonBegin 函数 ////////////////////////////////////
//开始挂钩,这里我们挂的是自身这个APIHook_EXE这个程序
void CAPIHook_EXEDlg::OnButtonBegin()
{
 DWORD dwThreadId = GetWindowThreadProcessId(m_hWnd,NULL);//获得自身进程ID
 InstallHook(TRUE,dwThreadId);
}

///////////////////////////////////// OnButtonStop 函数 ////////////////////////////////////
//取消挂钩
void CAPIHook_EXEDlg::OnButtonStop()
{
 InstallHook(FALSE,0);
}

三、APIHOOK之集成

1. 用 VC++新建一个 Win32 Dynamic-Link Library 程序,命名为 APIHook_Dll。接下来选择第二项 A Simple DLL Project;
2. 新建一头文件,命名为 APIHook_Dll.h。删除工程中 APIHook_Dll.cpp文件中原来的内容,然后把上面的 APIHook_Dll.cpp 和 APIHook_Dll.h文件的内容全部复制到新建的这个工程的 .cpp及 .h文件中来;
3. 新建一 Text文件,命名为 APIHook_Dll.def。复制上面的def文件内容。
4. 编译;
5. 新建一 MFC APPWizard(exe)程序,命名为 APIHook_EXE。接着选择第三项,基于对话框的程序,其它默认;
6. 删除原来对话框上的控件,然后新建三个按钮ID分别为:IDC_BUTTON_BEGIN、IDC_BUTTON_STOP、IDC_BUTTON_OUT,Caption分别为:Bigin Hook、Stop Hook、Text Out。不要让这三个按钮出于对话框客户区的最上面就行;
7. 拷贝 APIHook_Dll.h文件到 APIHook_EXE程序目录下,然后加到 APIHook_EXE的头文件夹中。
8. 删除工程中 APIHook_EXEDlg.cpp文件中原来的内容,然后把上面的 APIHook_EXEDlg.cpp文件的内容全部复制到新建的这个工程的 .cpp文件中来;
9. 打开 Project->Setting菜单,选择第四项link,在 Object/library moduls里添加我们的dll的lib文件的路径:..APIHook_DllDebugAPIHook_Dll.lib;
10. 编译;
11. 把 APIHook_Dll.dll文件放在 APIHook_Dll.exe程序的同一个文件夹内;
12. 运行程序,点击 Bigin Hook按钮,开始挂钩。再点击 Text Out按钮会跳出对话框并且会在程序中显示所要显示的字。点击 Stop Hook然后在点击 Text Out按钮就没有对话框出现了。

四、一些说明

1、我这个 HookAPI是使用了 Jeffrey Richter的改写程序的 IAT来实现的,也可以用跳转函数入口点的方法来实现,这个我没做研究。:)

2、我的一些心得:

 所谓 HookAPI,就是改写程序的 IAT,再调用我自己写的用于替换原API函数的函数。在我们自己写的API函数中,我们可以进行我们想要的工作。之后呢,可以把原来的函数传回去,也可以不传回去,只要你设计好了就行。

 而所谓调用自己的函数,就是把原函数参数都传给我的替换函数。我们就可以利用这些参数去干我们想做的事。而系统呢,我想由于微软设置的这个钩子的目的(我这么认为的),所以不会去检查替换函数是否就是原函数,只要参数、返回值符合条件就行,要不会出错。替换函数的返回值最好是原函数,否则有可能会出错

 HookAPI时,exe程序起到的作用就是进行Hook,把dll注入到要Hook的程序,并且传回要挂接的进程的ID或者全局钩子,以便查询所要挂接的模块的IAT。如果不注入进去,系统不会让你去查询IAT的。DLL做的事情是确定要挂接哪个函数和这个函数在哪个DLL中等。

                                                                                rivershan 原创于 2002-9-23

2005年01月15日

分形几何与分形艺术

本文章出自http://www.fractal.net.cn/同时向看到这篇文章的朋友推荐!

  我们人类生活的世界是一个极其复杂的世界,例如,喧闹的都市生活、变幻莫测的股市变化、复杂的生命现象、蜿蜒曲折的海岸线、坑坑洼洼的地面等等,都表现了客观世界特别丰富的现象。基于传统欧几里得几何学的各门自然科学总是把研究对象想象成一个个规则的形体,而我们生活的世界竟如此不规则和支离破碎,与欧几里得几何图形相比,拥有完全不同层次的复杂性。分形几何则提供了一种描述这种不规则复杂现象中的秩序和结构的新方法。

一、分形几何与分形艺术

什么是分形几何?通俗一点说就是研究无限复杂但具有一定意义下的自相似图形和结构的几何学。什么是自相似呢?例如一棵苍天大树与它自身上的树枝及树枝上的枝杈,在形状上没什么大的区别,大树与树枝这种关系在几何形状上称之为自相似关系;我们再拿来一片树叶,仔细观察一下叶脉,它们也具备这种性质;动物也不例外,一头牛身体中的一个细胞中的基因记录着这头牛的全部生长信息;还有高山的表面,您无论怎样放大其局部,它都如此粗糙不平等等。这些例子在我们的身边到处可见。分形几何揭示了世界的本质,分形几何是真正描述大自然的几何学。

“分形”一词译于英文Fractal,系分形几何的创始人曼德尔布罗特(B.B.Mandelbrot)于1975年由拉丁语Frangere一词创造而成,词本身具有”破碎”、”不规则”等含义。Mandelbrot研究中最精彩的部分是1980年他发现的并以他的名字命名的集合,他发现整个宇宙以一种出人意料的方式构成自相似的结构(见图1)。Mandelbrot 集合图形的边界处,具有无限复杂和精细的结构。如果计算机的精度是不受限制的话,您可以无限地放大她的边界。图2、图3 就是将图1中两个矩形框区域放大后的图形。当你放大某个区域,它的结构就在变化,展现出新的结构元素。这正如前面提到的”蜿蜒曲折的一段海岸线”,无论您怎样放大它的局部,它总是曲折而不光滑,即连续不可微。微积分中抽象出来的光滑曲线在我们的生活中是不存在的。所以说,Mandelbrot集合是向传统几何学的挑战。


图 1 Mandelbrot集合


图 2 Mandelbrot集合局部放大


图 3 Mandelbrot集合局部放大

用数学方法对放大区域进行着色处理,这些区域就变成一幅幅精美的艺术图案,这些艺术图案人们称之为”分形艺术”。”分形艺术”以一种全新的艺术风格展示给人们,使人们认识到该艺术和传统艺术一样具有和谐、对称等特征的美学标准。这里值得一提的是对称特征,分形的对称性即表现了传统几何的上下、左右及中心对称。同时她的自相似性又揭示了一种新的对称性,即画面的局部与更大范围的局部的对称,或说局部与整体的对称。这种对称不同于欧几里德几何的对称,而是大小比例的对称,即系统中的每一元素都反映和含有整个系统的性质和信息。这一点与上面所讲的例子:”一头牛身体中的一个细胞中的基因记录着这头牛的全部生长信息”,完全吻合。不管你是从科学的观点看还是从美学的观点看,她都是那么富有哲理,她是科学上的美和美学上的美的有机结合。

二、复平面中的神奇迭代

Mandelbrot集合是Mandelbrot在复平面中对简单的式子 Z <- Z^2 + C 进行迭代产生的图形。虽然式子和迭代运算都很简单,但是产生的图形出现那么丰富多样的形态及精细结构简直令人难以置信以至于不可思议。在传统几何学中难以找到如此简单的规律隐藏着如此复杂而生动的例子。Mandelbrot集合告诉我们自然界中简单的行为可以导致复杂的结果。例如,大型团体操中每个人穿的衣服只有几种颜色中的一种,每个人的动作也只是导演规定的几种之一。但是整体上可以显示出多种多样的复杂形态。

Julia 集合

在复平面上,水平的轴线代表实数,垂直的轴线代表虚数。每个Julia集合(有无限多个点)都决定一个常数C,它是一个复数。现在您在复平面上任意取一个点,其值是复数Z。将其代入下面方程中进行反复迭代运算:

Zn+1=Zn2+C

就是说,用旧的Z自乘再加上C后的结果作为新的Z。再把新的Z作为旧的Z,重复运算。 当你不停地做,你将最后得到的Z值有3种可能性:

1、Z值没有界限增加(趋向无穷)
2、Z值衰减(趋向于零)
3、Z值是变化的,即非1或非2

趋向无穷和趋向于零的点叫定常吸引子,很多点在定常吸引子处结束,被定常吸引子所吸引。非趋向无穷和趋向于零的点是”Julia集合”部分,也叫混沌吸引子。

问题是我们怎样才能让计算机知道哪一个点是定常吸引子还是”Julia集合”。一般按下述算法近似计算:

n=0;
while ((n++ < Nmax) && (( Real(Z)^2 + Imag(Z)^2) < Rmax))
{
Z=Z*Z+C;
}

其中:Nmax为最大迭代次数
Rmax为逃离界限

退出while循环有两种情况,第一种情况是:

(Real(Z)^2 + Imag(Z)^2) >= Rmax

属于这种情况的点相当于”1、Z值没有界限增加(趋向无穷)”,为定常吸引子,我们把这些区域着成白色。第二种情况是:

n >= Nmax

属于这种情况的点相当于”2、Z 值衰减(趋向于零)”或”3、Z 值是变化的”,我们把这些区域着成黑色。黑色区域图形的边界处即为”Julia集合”。”Julia集合”有着极其复杂的形态和精细的结构。

黑白两色的图形艺术感染力不强。要想得到彩色图形,最简单的方法是用迭代返回值n来着颜色。要想获得较好的艺术效果,一般对n做如下处理:

Red = n*Ar+Br;
Grn = n*Ag+Bg;
Blu = n*Ab+Bb;
if ((Red & 0×1FF) > 0xFF) Red = Red ^ 0xFF;
if ((Grn & 0×1FF) > 0xFF) Grn = Grn ^ 0xFF;
if ((Blu & 0×1FF) > 0xFF) Blu = Blu ^ 0xFF;
其中:Ar、Ag、Ab及Br、Bg、Bb为修正量
获得的Red、Grn、Blu为RGB三基色,着色效果为周期变化,具有较强的艺术感染力,而且等位线也蕴藏在周期变化的色彩之中。
你可以想象得出,在屏幕上顺序的试用每个像素点来反复迭代方程要花费很长的时间。一幅 1024×768 屏幕尺寸的画面有786432个点。其中一些点在计算机上要反复迭代方程次数达1000次(取决于Nmax的取值)或更多次才放弃运算。 运算产生一幅Julia集合需要花费很长的时间,有时需要产生一幅做海报用的大图像时,如 10240×7680,要花几天的时间。当然,你使用高速计算机会缩短这个时间。图 4、5、6是三幅Julia集合:

 

图 4 象尘埃一样的结构 
 

图 5 稳定的固态型


图 6 象树枝状

Mandelbrot 集合

将Mandelbrot集合和Julia集合联系在一起,Julia集合有若干类型,都包含在Mandelbrot集合之中。Julia集合中的C是一个常量,而Mandelbrot集合的C是由进入迭代前的Z值而定。迭代结果,Z值同样有3种可能性,即:

1、Z值没有界限增加(趋向无穷)
2、Z值衰减(趋向于零)
3、Z值是变化的,即非1或非2

Mandelbrot集合是所有的朱莉娅集合的合并,Mandelbrot集合的某个区域放大后就是这个点的Julia集合。 Mandelbrot集合有着一些很异国情调并且古怪的形状(见图1)。你能不停地永远放大Mandelbrot集合,但是受到计算机精度的限制。

Newton/Nova 分形

Newton奠定了经典力学、光学和微积分学的基础。但是除了创造这些自然科学的基础学科外,他还建立了一些方法,这些方法虽然比不上整个学科那么有名,但已被证明直到今天还是非常有价值的。例如,牛顿建议用一个逼近方法求解一个方程的根。你猜测一个初始点,然后使用函数的一阶导数,用切线逐渐逼近方程的根。如方程 Z^6 + 1 = 0有六个根,用牛顿的方法”猜测”复平面上各点最后趋向方程的那一个根,你就可以得到一个怪异的分形图形。和平常的Julia分形一样,你能永远放大下去,并有自相似性。 牛顿分形图形中的颜色显示每个答案的种类及性质,即迭代到目的地花费的时间,如图7所示:

 
 
7 Newton分形

Paul Derbyshire研究牛顿分形图形时,他把Julia集合的常值C加入进去改变了一下算法,并用同样的方法去估算Z,逼近答案,产生奇特的并称之为”Nova”的分形图形。”Nova”类型分形图形如图8所示:

 
 

图 8 Nova分形

三、关于分形艺术的争论

把计算机产生的图形看成是艺术,有人可能要提出一些疑问。这些图形可以利用高品质的打印机产生任意多幅同样质量的”原作”,从而在商业化的艺术市场上造成混乱,因此她没有收藏价值,没有收藏价值的作品还能算得上是艺术吗?

这是一个十分敏感的问题。早在六十年代初有些数学家和程序设计人员就开始利用计算机及绘图设备从事这方面的工作。但他们大部分人避免将自己的工作与”艺术”一词挂起钩来,以免与艺术界的人们发生冲突。但是有一些人还是挺着腰杆去面对批评,承认计算机是视觉艺术的一种新工具,称他们自己的方法为”计算机艺术”。在批评面前,他们没有受到影响。他们不顾理论界的反对而继续自己的探索。他们积累了大量令人难忘的成果。正因为他们的努力才出现了今天的PhotoShop、Corel DRAW等等著名的软件, 以及各种计算机艺术团体组织。PhotoShop也成了某些美术专业学生的必修课。
当今时代出现的充满科技含量的”分形艺术”又不同于运用PhotoShop从事的计算机艺术创作。 “分形艺术”是纯数学产物,是否能算得上艺术必然会引起新的争论。争论最活跃的问题是:分形图形是纯数学产物能算得上艺术吗?既然学习数学和程序设计就可以从事艺术创作了,学习美术专业还有什么用处呢?
这个问题提的好。从事分形艺术创作的人要研究产生这些图形的数学算法,这些算法产生的图形是无限的。他们没有结束,你永远不能看见它的全部。你不断放大她们的局部,也许你可能正在发现前人没曾见到过的图案。这些图案可能是非常精彩的。她们与现实世界相符合,从浩瀚广阔的宇宙空间到极精致的细节,是完全可以用数学结构来描述的。另一个的问题是颜色,好的颜色选择,就可以得到一幅奇妙的图形。糟糕的选择,你得到的就是垃圾。所以说,创造分形艺术,最好再学一点绘画基础、色彩学等,那将是大有益处。
分形几何冲击着不同的学术领域,她在艺术领域显示出非凡的作用。创作精美的分形艺术是国内外分形艺术家们的人生追求,总有一天分形艺术会登上大雅艺术殿堂。 

Paul Derbyshire研究牛顿分形图形时,他把Julia集合的常值C加入进去改变了一下算法,并用同样的方法去估算Z,逼近答案,产生奇特的并称之为”Nova”的分形图形。”Nova”类型分形图形如图8所示:

 
 

图 8 Nova分形

三、关于分形艺术的争论

把计算机产生的图形看成是艺术,有人可能要提出一些疑问。这些图形可以利用高品质的打印机产生任意多幅同样质量的”原作”,从而在商业化的艺术市场上造成混乱,因此她没有收藏价值,没有收藏价值的作品还能算得上是艺术吗?

这是一个十分敏感的问题。早在六十年代初有些数学家和程序设计人员就开始利用计算机及绘图设备从事这方面的工作。但他们大部分人避免将自己的工作与”艺术”一词挂起钩来,以免与艺术界的人们发生冲突。但是有一些人还是挺着腰杆去面对批评,承认计算机是视觉艺术的一种新工具,称他们自己的方法为”计算机艺术”。在批评面前,他们没有受到影响。他们不顾理论界的反对而继续自己的探索。他们积累了大量令人难忘的成果。正因为他们的努力才出现了今天的PhotoShop、Corel DRAW等等著名的软件, 以及各种计算机艺术团体组织。PhotoShop也成了某些美术专业学生的必修课。
当今时代出现的充满科技含量的”分形艺术”又不同于运用PhotoShop从事的计算机艺术创作。 “分形艺术”是纯数学产物,是否能算得上艺术必然会引起新的争论。争论最活跃的问题是:分形图形是纯数学产物能算得上艺术吗?既然学习数学和程序设计就可以从事艺术创作了,学习美术专业还有什么用处呢?
这个问题提的好。从事分形艺术创作的人要研究产生这些图形的数学算法,这些算法产生的图形是无限的。他们没有结束,你永远不能看见它的全部。你不断放大她们的局部,也许你可能正在发现前人没曾见到过的图案。这些图案可能是非常精彩的。她们与现实世界相符合,从浩瀚广阔的宇宙空间到极精致的细节,是完全可以用数学结构来描述的。另一个的问题是颜色,好的颜色选择,就可以得到一幅奇妙的图形。糟糕的选择,你得到的就是垃圾。所以说,创造分形艺术,最好再学一点绘画基础、色彩学等,那将是大有益处。
分形几何冲击着不同的学术领域,她在艺术领域显示出非凡的作用。创作精美的分形艺术是国内外分形艺术家们的人生追求,总有一天分形艺术会登上大雅艺术殿堂。

 
文章中图片出自http://www.fractal.net.cn/

2005年01月03日

效果的确不错啊,

先将下面的内容保存为1.txt

 e100 33 f6 bf 0 20 b5 10 f3 a5 8c c8 5 0 2 50 68 13 1 cb e 1f be a1 1 bf 0 1
e11b 6 57 b8 11 1 bb 21 13 89 7 4b 4b 48 79 f9 ad 86 e0 8b c8 bd ff ff e8 20
e134 0 3d 0 1 74 1a 7f 3 aa eb f3 2d ff 0 50 e8 f 0 5a f7 d8 8b d8 26 8a 1 aa
e14f 4a 75 f9 eb de cb 57 bb 21 13 8b c1 40 f7 27 f7 f5 8b fb ba 11 1 4f 4f 4a
e168 39 5 7f f9 52 8b c5 f7 25 f7 37 2b c8 95 f7 65 2 f7 37 95 2b e8 fe e fe
e181 10 79 6 c6 6 fe 10 7 46 d0 14 d1 d1 d1 e5 79 ec 5a b8 11 1 ff 7 4b 4b 48
e19b 3b d0 75 f7 5f c3 83 f7 83 a6 5d 59 82 cd b2 8 42 46 9 57 a9 c5 ca aa 1b
e1b4 4f 52 b4 92 3f ab 6e 9e a8 1d c6 3 fc e 6a e7 ae bb 5f 7b 10 b8 b4 f7 8
e1cd e2 bf 36 4e 39 9d 79 29 3f a f9 36 52 16 fb 5 e8 e5 a6 c2 e9 b0 43 d3 a3
e1e6 cf d3 fd fd cb d1 4c 5e e0 63 58 86 bb 3e 9 c1 20 bc cc 91 a3 47 81 70 b3
e1ff d6 1a 9e c2 c9 12 e7 4e ad f4 5f e3 30 e9 9 39 d7 e8 f9 f4 d2 44 e8 d7 22
e218 be e2 ce 88 25 cf 30 4a a8 29 ae 3f 47 c6 2d 85 e9 73 54 13 b e6 e0 34 65
e231 e2 50 8a 89 18 5f ce 70 99 3 5f 42 bf eb 7 ae d0 ca 5 22 8d 22 a5 b7 f0
e24a 90 81 bc 7a bc dc 5 db c0 6a 2 e5 57 38 be 60 cb ac ba a5 3b 9d f1 77 38
e263 a6 84 d1 3c af 49 d8 6a 45 a2 76 60 21 12 c0 c2 44 f2 5e bb e5 37 a9 2b
e27b ec 4a 8c 4c f2 f7 a9 58 71 2b ba 6d d6 6a e5 60 46 e0 da e5 b9 90 e5 a3
e293 f7 7f 31 60 58 f0 c4 88 10 4e 3c a3 ee 4e 11 55 8f a 92 eb db ad 7a 9c f
e2ac db 5a 28 96 da 87 ae 91 91 2d e3 5e ea df 6 95 71 67 71 40 ce d1 2e 31 6d
e2c5 c1 9c d8 6a 76 9b 4a e8 36 44 d6 76 d 30 5 ff d4 1b ac 1f 32 65 31 bf 55
e2de 26 b a4 55 e1 5d 5e 16 ed 97 48 6c 77 fb 81 86 e f9 18 bd d4 f4 8b de 1d
e2f7 ba d 47 75 3 89 4b 3e dc 27 86 1c d0 17 89 48 d1 a6 8d d4 2b 54 4e 8f b0
e310 2 e1 6b 1a 75 78 ea 21 91 13 c0 cf 78 a0 ab f3 35 c6 b4 c8 90 8d d7 45 e7
e329 c 5b a4 ba 52 10 64 f5 4a 50 b7 ec 46 22 15 23 84 30 81 5c df 61 5a 8f 67
e342 c4 63 57 6d f7 26 92 a3 1f e5 3 a5 0 54 41 8 48 7c 26 90 33 82 9c 91 b0
e35b ab 78 5d df 99 e0 b9 fc 5 36 ac d9 49 91 ab 20 a2 63 48 89 ce 5c 60 64 f0
e374 63 d9 a8 38 3b d3 e6 4c 8c 23 34 4e 20 51 93 5e 6d b4 7a 22 9b 4c f2 d3
e38c c4 f8 3 6f 47 40 f4 f8 45 9b 83 f3 83 6 31 d0 0 17 82 83 dc 67 f9 62 77
e3a5 90 3b d9 ec f3 55 96 b8 d9 db 79 55 f1 e5 8c 5e f2 e5 2e b0 b 6e e2 81 25
e3be 93 8e b5 dd 5b 46 f9 af ed 6 12 cf c9 1d f0 f7 3b 16 2d c6 58 73 8d e9 5f
e3d7 fd 5a b6 a1 94 4d 1a 8 ff eb b7 6 80 c7 86 83 b6 b9 fd 1c e0 c c3 2e a0
e3f0 2f b 3e 3 6b 29 e1 27 85 1c ea 6d df b3 a3 ed 65 4a 9a 59 3b 54 e 4b ae
e409 9e 27 f0 4d 3b c 4c 46 b7 e5 57 1b 1f 1f bb 80 86 f5 b7 ef 73 52 bf 2c c7
e422 ed a b7 81 2 f3 90 3e ee cc 6c eb f 38 1 6c 68 b1 d 45 78 b2 f f6 83 b0
e43c c4 33 df b1 d1 91 98 1e 81 a5 e2 59 9f f4 8c b6 72 8 a7 8c f6 e a3 b2 1f
e455 d9 d3 23 f0 7c 5e 5f 68 61 8b 45 da 1d 91 ec 8d 4e ea 1a 38 85 94 aa ac
e46d f2 4 f6 c4 e5 92 8e 9a 4e 83 e1 73 e8 cf 2a 5c 2b 7e f1 30 2 8a e6 28 1a
e486 3b ce bc 96 aa 7f eb 87 cd 8b 96 2d 9 59 7a a0 1a 43 62 9a 9e 4f ff 8e d9
e49f ce d6 a4 70 79 cd 65 fa 2e 92 14 29 f7 6c 74 4b 49 60 80 bb ff 41 bb 2d
e4b7 60 33 3f 98 77 9a 1 ee a6 a3 da bc ba e9 f3 72 f4 7c c3 59 2 a6 44 a4 c8
e4d0 c8 54 93 ce bd 69 bb b9 43 21 2c c4 ea 4a 5c 3f 75 60 f2 b4 91 ca 9 82 e3
e4e9 a e9 a6 20 b9 76 50 ed 47 e9 fe 6d 41 34 13 2f 28 2f 4e f4 da e 3c 78 6c
e502 b1 79 87 45 98 a4 d4 c3 b3 29 c2 4a 8b ed a6 54 e2 1b 31 62 60 ff 2c 1d
e51a 21 0 15 b2 4e 5c c 2 d 83 fa a2 f3 8a 5 12 72 4a c7 44 7c 91 d4 be b a f2
e535 70 52 fb b4 a2 df 89 de ff c4 96 73 c9 c ed d3 c9 8e 5c dc 8e d1 3b de 8c
e54e 53 a2 8b f9 e9 91 dd d6 df 6e 74 d1 dd 34 60 8f 9e 32 7f 3b ec 79 a3 83
e566 45 78 b4 2f 1c 50 7b 7a 97 b0 9d 2d c dd 8a 26 cd 7d 8c 4c 5a 8a 4c f9 a4
e57f 11 f9 2c 6c 92 e9 b5 cb 56 89 8c be f6 64 fa 25 43 fa 6f e2 c8 3a 18 a8
e597 f0 e9 f4 c2 86 e6 2b 44 67 4a b9 34 9 ed 5f 33 42 62 d4 8a 1e 5b 31 67 cd
e5b0 3d 71 6d 83 fd 36 20 69 ea 1 c3 e6 e6 de 99 aa 7 11 5b 59 8a 1f 43 83 52
e5c9 ea 5d 8c 6a 69 c7 3 eb 4e 3b 88 a5 5f b1 6e 27 5f 3 5c 28 c 9b 6c c3 f8
e5e2 e5 b9 d6 11 d6 8b fa 5c 8 c7 1 eb 45 db f3 6c 9f 16 46 61 51 ed df f bb
e5fb c0 c4 1e 64 68 98 4 79 30 94 72 df d4 cd 1f 7f 72 c6 82 2e 79 47 4e 8c 4b
e614 a2 c7 e2 36 df 76 fd a4 b6 4e db 96 40 3b 8b b5 d4 85 64 c6 0 2c ad 9d 27
e62d 14 99 82 4b bc 9 fa 94 b5 db 7c 98 eb b 13 a7 b0 79 1d 7e c5 45 aa 20 49
e646 be ff 9d 64 0 5d c ec 6 5 ad f2 38 6b ed 7a d6 b2 c7 2e 6a a6 12 4b ff 55
e660 20 3b a 77 f b9 0 9d 57 4a ad ce a4 d3 ff 1 4f fb 53 54 88 f 1 ed 4b 56
e67a 15 c8 dc 28 bf f2 72 d4 10 1f 99 42 69 9e 78 e2 47 82 93 31 d0 2d be 9f
e692 93 93 9a 1b 80 c0 10 c 53 78 a0 26 2a 96 4f 74 4b 16 c7 9c 8d ad ac fb 16
e6ab 15 c6 fd c9 a4 14 48 62 47 20 c9 41 ed 61 f8 9b f8 ff ba 39 50 65 87 ee
e6c3 bd ce 95 c0 fb a5 7e d8 cd 27 fd 2c 74 3 c1 1b 89 b9 51 d5 e3 da ef 9e 6
e6dc f0 aa a9 a7 fb 87 4c 5d cd ff 65 36 8c 73 6f 9 c6 78 9a b6 77 db df 81 68
e6f5 3b b8 ae 5d e1 af d4 e6 66 8c d6 a4 83 9f 37 3c 1 dc a2 a6 57 c2 20 1b 90
e70e 75 df cd a5 62 a5 36 79 fb 35 8a 9b b0 a0 a5 c3 37 6f 80 72 bc 52 30 8d
e726 9f 7a 64 d3 7 41 45 d8 68 97 f2 aa 1c a1 6c 7c 9d 32 7d ad 15 b1 53 e3 33
e73f 8a ed e9 49 d4 cf dc 96 22 37 36 11 9d 7f f0 4d e0 62 31 b1 c7 69 c4 79
e757 ac 20 1 e8 3c 6a 8c 32 cb 52 63 36 68 f4 10 2b 9c 21 4f df 5d 60 92 39 91
e770 e2 f9 c9 7d ca 48 3 3f 21 dd 6c f 23 2e 61 3a 9f ba c3 f9 4e 7 ea ed ef
e789 71 4a 72 3a ed 23 3d 77 b5 ed d5 1d f6 a4 99 fa ef 98 dd 2 98 80 b6 7c a3
e7a2 62 96 7b 8e bf 7b 81 9f 9a ce 3f 12 40 2e 25 db 84 16 dd 2e 86 f f4 b2 7e
e7bb 5e b4 14 6a f3 29 b1 a4 57 d5 a8 17 6f 87 a4 74 5b 9b 17 79 f1 ec 33 c8
e7d3 f0 1d b2 7e a8 4d 95 7f 5f 9 d5 1a 5a 45 f4 41 c6 d 3f eb 66 2a c0 e8 5b
e7ec 3c bd 50 ad f1 53 9d 2e 45 9a d8 7d 2c 17 a8 6e 15 48 13 39 53 ed 3d 78
e804 ad f 3a 65 a3 3e 2e fa ca 7 94 4a 1f b4 d8 7e 47 8a 8e de e7 7e 34 c1 69
e81d 7f 6a aa 66 58 18 31 24 72 13 22 34 8a 56 36 87 df c2 d 8e 3f 71 a2 5f 25
e836 8b 8d 4 78 fd c9 45 d1 55 79 c1 9f 13 84 1b c8 5 db 95 d0 7c 64 96 20 51
e84f c4 e0 5e ee 47 8a 11 ac fb 9 e0 bb 40 db 86 84 12 93 b9 c9 f2 9c 63 47 c9
e868 eb ad 1 3e fa 6d 3f a 64 5b 58 56 27 f ca 5d e0 30 bc 3e 10 5d ec 17 28
e881 85 5 51 8e 95 a3 94 3a a8 f1 96 f2 f 29 5c 97 dc 47 db 9d 6c 63 e8 e7 f0
e89a e4 a 70 f8 f1 47 54 d3 2d 32 7c ef bb 9a b4 1b 0 2b d6 dd e7 30 b a2 75
e8b3 c7 f5 d0 31 d7 d2 8a b0 ac 1c 6d 60 3a f7 c2 db 1e 6d 7 f6 8f 35 88 e5 7f
e8cc 3c 26 81 34 a0 32 a3 25 18 6e 73 b2 a0 f1 cb 86 61 e7 65 8b 76 98 19 6f
e8e4 c0 62 9b a3 cc 18 5e 40 12 97 2b d0 15 79 de 19 ea df 7a 59 2f b5 d7 39
e8fc 52 e2 6 f1 3 a0 a5 d9 1b 88 93 4d 30 c8 2d f5 db 55 ea 85 6f a 3f dc bd
e915 57 15 6a a3 a3 3e 8e ad 2d da a0 ca 75 7c 57 8b c5 cb b 1d 2c 8e c6 96 2e
e92e 6d 59 83 7d 64 72 ca 80 2e 6 a4 ff f6 f2 d5 1e 7 4 ba 34 6e 9 86 25 aa 4e
e948 e0 7f f5 32 47 3e 7c 43 d8 28 c4 1c 11 1d bd 33 3 b5 ca 13 43 34 2 b1 a0
e961 57 ed 9d 3c 23 d4 45 b2 6e 81 6e af 3e 67 90 be 59 a5 45 34 53 46 85 d1
e979 25 ee 7d cb a4 db 12 c3 aa 17 61 9a fb 66 40 76 fe 3a 69 96 c0 91 14 a7
e991 5d cc 9f f6 73 59 ee b8 55 97 20 26 ff 99 ec 72 41 b5 27 21 6e ae 8a d0
e9a9 e4 d3 da 6f c4 53 c5 f8 b3 a7 a1 5d 66 93 d8 b1 89 40 23 92 c0 90 fb cb
e9c1 e7 6b 4e 51 0 5d 57 f7 cd 1 e2 88 bf 44 9f ef c4 33 ce fa 46 46 a1 86 b
e9da 7a 84 66 66 b9 2 ec 10 c6 a1 d4 c1 18 33 b1 d1 2 18 ad 2f 53 e4 b9 33 59
e9f3 be 3c af 80 4c 8a d5 76 c 3b a7 e2 97 94 15 75 4d 17 d5 97 cf f9 4a d0 6e
ea0c bb 27 20 fc f1 f5 9 a8 df 4d b6 5d f0 1d 69 3b 76 35 82 a4 f3 56 64 39 5b
ea25 6b b3 7 e7 5 8e 82 11 22 a8 1a db c8 3e 67 4a 3 7e 72 51 d6 3d 1a 1c f6
ea3e b8 da 4b 18 8a 15 9d d0 a4 84 96 3e cd 3 f9 3a 30 f3 fb 8f 6e 2 73 eb 52
ea57 93 95 cf dc 6f 48 fb ab d2 a9 70 b4 e2 23 8d 72 86 a8 fa 78 98 1d c5 fe
ea6f 8a 51 88 2b b7 58 b0 ca ae 40 8a 33 32 75 1 6 c0 d4 b7 da 2a a7 bb ad f7
ea88 48 98 5a bc d3 d1 e6 16 97 c3 80 ab 73 ac 32 11 41 1f d 5d aa 0 dc d9 6e
eaa1 fc 30 6 ef 11 60 27 a2 5f eb 5f b9 35 8 23 4 be 10 c0 85 3e 55 b3 82 fd
eaba f7 c3 24 9f 2d 83 94 32 36 de ff 7c 87 7f 4a 80 7 2 23 cf a4 52 eb 3e 19
ead3 a0 b4 a 94 1a 40 58 d9 16 6d c0 64 c4 69 ed 60 46 65 cb df 58 38 0 51 c3
eaec ad a0 37 e4 cf ab f7 6c 24 7d 9 48 65 4a 9f 91 ad 1c 79 a4 a1 78 55 c e8
eb05 44 5b d ef 51 bd ea 2d a7 42 57 ab 3a 4f 2 b 3 19 6a 4d 72 76 5c 97 0 6c
eb1f c5 5d bc dd e7 81 cf 8d 34 38 50 3c 98 58 cc 41 aa 99 90 af fe 4e 96 77
eb37 ed 54 18 ce 2c d1 5d 34 cb 79 50 ff 28 96 44 e0 51 64 6 a8 b7 6e 8c 62 c4
eb50 66 95 81 4f 8c f6 26 ba ea 5d d2 79 b1 e4 e9 29 fc a fd b3 85 8c e6 52 dd
eb69 33 bd 5d c7 39 ef 6 ef 9e a6 6a 61 9c 9f d5 54 b4 fa a1 d4 10 9b ff 7e 33
eb82 11 52 99 c7 26 6e a1 36 8a ad ee 48 7a 2c 7f d5 b7 27 8a 6b 37 c 71 39 85
eb9b 9c ba a8 a 17 b9 d0 51 56 95 c2 3b 5 a7 31 c5 8b 5c 95 6e 4c 89 6f 17 ef
ebb4 d4 5a a 77 65 e1 49 b2 e8 72 ac 3c f0 6b 71 fa 3 c7 ca fc ad f9 55 22 ec
ebcd 58 2f 1c fa 29 cf 73 b4 ad 51 5c f8 66 70 59 5d 70 3e d1 3f c4 eb ec f1
ebe5 7 78 6a 93 67 9f 44 fc cb 5b 95 ff 74 c0 b7 42 77 26 c9 aa 8c ed 39 a2 db
ebfe 9c b3 eb 3d 4a 1e 9b 89 e4 d8 a8 27 74 ef a3 ed a5 24 5d bb ab d0 fe a1
ec16 29 ab df 75 a a6 23 0 cc f1 14 72 9b 1a 55 7e e5 d1 da 98 dc c4 cf ab 34
ec2f ba 8d de 4a 59 6 13 dd d8 44 3c e bb 56 95 ae 97 e2 3b 49 e5 9a 6b a2 53
ec48 c1 33 35 24 1b 33 17 c3 8a 8c 12 3d 3d 4e 5b 75 22 30 67 4f a0 5d 3a 78
ec60 88 a 11 35 7 b1 77 42 32 a8 c3 bb 20 fb 98 5 d6 ac e7 3a 63 35 90 93 9e
ec79 44 24 2e 1b d7 8c aa 29 53 4d d9 ab eb e6 1 56 c4 fd 54 a3 bd 14 5b b0 8f
ec92 ce be 23 24 93 c4 48 18 a3 e7 4 5 4b 78 cc 79 dd 3 56 a4 ed dd 5f 98 41
ecab 1b 68 4c c1 bb 41 c2 1e 3e 94 8e ef 28 1e b 76 e 4f 36 b1 c 6e e2 18 17
ecc4 20 fc 35 40 1f e4 6d a4 18 bb bc d5 9e ea 85 86 af af 63 d4 13 66 92 c4
ecdc 2b 69 84 ca 23 2b d3 66 81 6b 81 73 26 4 85 36 21 4c 49 44 75 64 39 16 3c
ecf5 ed e0 6d 44 75 45 30 43 68 c0 78 fc d0 17 b eb 81 3e c3 ba 1b f 4d ae c5
ed0e 55 1f c 39 12 5d 8 65 f1 34 59 de dd 98 56 17 43 38 66 49 9a eb db c1 87
ed27 51 38 cc b7 5f 98 fd 43 be 2d bb 74 f3 f8 f2 36 3d a4 34 a5 7e d2 26 cc
ed3f 84 1f ea 56 f0 80 18 69 4d 88 41 fc 56 fd 41 3b 1e e 9 27 4f f6 3b 62 4e
ed58 5a 1b 2a 4e 85 8c b2 4f 79 ef 59 4e e 73 3d bd c4 ca 60 e7 4a 47 90 b5 8
ed71 2a f0 4e dc ba 66 ae 48 2b 31 73 a2 11 c 32 ff 54 14 77 6b d6 58 4b bf ee
ed8a f6 6a bc dd 1 88 d da a9 f 81 24 c5 f8 72 9a db d5 c8 2a 80 a9 16 d7 c6
eda3 b1 91 c0 a9 95 40 b5 b3 a8 2a 28 c6 92 16 ab 54 7d f8 93 5f 3a 17 c8 45
edbb a9 f0 e0 71 23 76 53 38 a5 a1 cc d4 f1 f2 3c 2b 46 43 a1 d5 ba e d7 19 7a
edd4 c2 e1 8f 67 1d d 98 9d a1 79 9d 1b 20 7f 4d e7 bf f9 ff fe aa 28 ab 8f c
eded 4d 50 33 e3 26 fc 3c 3 3a 2b 26 12 f7 1 8f ee 97 4c e6 6 2b d9 1f a1 4a
ee06 77 44 d4 8b b7 3e 5e 2d 18 c3 54 68 99 a8 8d 92 96 9e 9d ab 33 38 ff b8
ee1e ee 78 c6 7b b5 84 95 d3 6 27 ae 5d 27 38 a 38 8e f0 1 a5 96 4b d7 9b 42
ee37 e5 6f 57 75 4c e9 78 2d 5b ec b6 d2 29 e2 a8 92 95 9c 65 2a 3e bf 8d e0
ee4f bf b3 ac c8 e 7e 13 af 88 26 7d 48 5a c7 39 29 36 d2 90 e8 3b 3 d0 61 1a
ee68 d2 e8 a8 f ba 8e a1 9f df 12 ab 54 7 23 98 de 62 af 4c 7e d4 fb 6b 2 6e
ee81 40 40 37 b7 73 f2 d8 81 be 29 d2 99 c0 73 25 1a 3c 92 75 6e bd d7 79 79
ee99 4 14 c0 4e 99 57 66 93 74 ec b0 29 7c df 61 b0 3 3a d1 c3 fa a4 f7 f 9f
eeb2 d3 f 0 b9 2a 5a 3a c5 88 25 b8 b9 cc 82 3 57 3a e1 7b 51 75 70 a6 74 1a
eecb ca cb 3 18 68 ca 77 fe 1b ad cd 68 7f 36 85 fc b7 4f a0 11 da 69 fa 79 87
eee4 d6 b9 21 dd 3e 70 db dc 84 d4 6e d1 20 4 af f6 32 a2 8e d 54 25 fe 7 54
eefd e 7a 74 4b a0 4b f7 f4 e8 74 22 e9 98 70 fb 25 2e f4 64 57 75 28 85 45 53
ef16 3a 2e e2 3c 54 36 e9 29 6 67 59 43 10 7e c1 49 cd 5e f9 97 a 58 5f 8a 11
ef2f 4f 3d 9a e2 2b 22 58 fa be fc 69 91 7a 8c 3f 77 9f c9 3b 54 26 23 93 b3
ef47 85 de ae f5 bd c5 47 4c c4 cd 5e ad bc 8f ba 31 f6 e4 70 fb 6e a7 96 d5
ef5f ad 10 80 39 43 97 4f 10 cc 1b 8f 8d cd 4c 63 4 d8 1e 85 70 41 6c a8 eb df
ef78 7f 36 c5 60 a7 12 9 16 73 fe 75 3a 2d 40 29 7d aa a 5c 2 29 23 0 a6 e5 6b
ef92 24 6d 9b 20 e5 7 cb 40 b0 38 59 9c a7 69 6a 70 d3 38 ef e2 b2 11 3e ea 2a
efab f9 2b 2e 43 1d 65 cf d6 1b ef 83 5a 5f e6 c5 62 16 ca 5e 4c a6 39 e4 53
efc3 2d 23 d2 5e 7e 15 54 8a 8 b7 3d bb 88 59 b9 9e a2 7c 42 1f a2 77 3c 5b 9
efdc 6d fa 8f 21 46 1a 3e ed ce 49 56 1d 29 2d 70 3 a7 6f 75 ac 1 87 ff 27 86
eff5 73 49 28 85 2d 97 7a 84 e 37 3d 86 10 21 4c e2 74 62 6b 51 70 8f 15 72 f3
e100e 81 b2 a9 9d 8a 63 ad 1b d5 aa 8a dc 96 3c e7 47 16 51 fc 87 50 9 b7 60
e1026 29 33 52 fb b0 df 70 c5 65 4a 60 3b c d7 a8 29 47 51 f7 8a 77 f3 99 3f
e103e 38 16 60 de 68 27 b2 24 7 62 a2 fd 40 86 b2 75 c3 3c 2f 3d fa 9 d9 a9 9a
e1057 71 3c ce 46 94 0 f9 bc 46 7f b8 2e 85 7f 7d d3 8d ea b4 63 81 59 10 bb
e106f 57 d0 b6 ab e1 83 74 1e 25 d5 73 78 18 b1 60 62 c f4 76 8d 17 d5 ed 23
e1087 23 e4 f6 32 64 5a 61 9 63 f6 92 57 d5 29 40 d6 3b ba 63 72 18 0 25 1b 7
e10a0 ee 7f 25 4a fa 6 74 19 46 e3 e8 89 7a c6 56 54 a7 43 13 4e bf 97 a5 6f
e10b8 99 2f ac 33 4d fa 58 3a 5a a a4 1a 74 62 c8 4f 3b 78 9 d7 ee 7e ee 2d 69
e10d1 30 40 ea 47 82 3b 85 8e 3 23 8f 74 4e 8 35 ab 74 4 1 57 d5 85 b1 6b 1e
e10ea f4 7d 1e d2 1e b3 fe f3 12 10 32 39 51 48 2d 6f e5 d3 a3 8c 8 8
 
rcx
1100
n1.com
w
g
q

然后呢,在DOS窗口下输入: c:\> debug<1.txt (假设1.txt保存在c:\下)
结果,也许你不觉得不可思议,呵呵,一个天旋地转的世界出现了!

这其实是一个多年以前的世界什么什么比赛的获奖程序,至于他是怎么实现的,我们以后再说哦!

    浮点运算简介
                                       Hume/冷雨飘心

对于习惯于C的灵活多变的数据类型和方便的计算那些人而言,了解底层的浮点运算似乎没有什么意义,现在Visual盛行的时代还有多少人关心那些所谓的底层呢?
对了AfOs来说,浮点运算是编程中很重要的一部分,因为我们可能会面临一些稍微复杂的运算,如果你和我一样是Die-hard的asm拥护者,不想轻易用C来解决问题,你肯定能想像在asm下用整数运算求sin(2.3)的痛苦,实际上,微机早就为我们准备了解决之道:那就是浮点运算.但现在关于浮点运算的资料较少,相信很多人和我一样还不掌握这种强有力的技术,那好,我们一起来学习学习.
                    


                              (一)浮点数
                                  (This Part mainly Froe Bill’s Article)
在这之前,先来看几个术语:
FPU->Floating Point Unit,浮点运算部件
BCD->Binary Coded Decimal 压缩的二十进制数,是用4个位来表示数字0~9,一个byte表示两个十进制数,比如01111001表示89
科学计数法:这是科学的~~~~具体含义查查初中还是小学的数学课本 D:)

浮点运算使用三种不同的数据:
      1)整数(Integer),又分为字,短整数(Short Integer)和长整数(Long Integer)
      2)实数(Real)分单精度(Single Real)和双精度(Double Real)
      3)压缩的二十进制数(BCD)
  
  下面是其位数(bits)和能表示的大致范围和


      Type            Length          Range
      ———————————————–
      Word Integer    16 bit          -32768 to 32768
      Short Integer   32 bit          -2.14e9 to 2.14e9
      Long Integer    64 bit          -9.22e18 to 9.22e18
      Single Real     32 bit          1.18e-38 to 3.40e38
      Double Real     64 bit          2.23e-308 to 1.79e308
      extended Real   80 bit          3.37e-1932 to 1.18e4932
      Packed BCD      80 bit          -1e18 to 1e18

双精度数和扩展精度数表示范围对一般应用来说已经足够大了!

1)整数,以补码形式存储,正数的补码是其本身,负数补码是其绝对值的各位变反后加1,下面是实际存储的例子:
      0024            var1 dw 24
      FFFE            var2 dw -2
      000004D2        var3 dd 1234
      FFFFFF85        var4 dd -123
      0000000000002694var5 dq 9876
      FFFFFFFFFFFFFEBFvar6 dq -321

2)BCD数
      在FPU中用80位表示正好是浮点堆寄存器的宽度,在其格式如下存储:
  Bit
      79___72_71________________________________________0
      符号             —18个二十进制数——–
看下面的例子:
      00000000000000012345         var1   dt    12345
      80000000000000000100         var2   dt    -100

3)浮点数,这个复杂点,有三种格式

      单精度:_31_30________23_22___________0
            符号    指数         有效数

      双精度:_63_62__________52_51__________________0
            符号     指数             有效数

      扩展精度数:
            _79_78____________64_63___________________0
             符号     指数              有效数
例子:
      C377999A                     var1   dd      -247.6
      40000000                     var2   dd      2.0
      486F4200                     var3   real4   2.45e+5
      4059100000000000             var4   dq      100.25
      3F543BF727136A40             var5   real8   0.00123

      C377999A                     var1   dd      -247.6
      40000000                     var2   dd      2.0
      486F4200                     var3   real4   2.45e+5
      4059100000000000             var4   dq      100.25
      3F543BF727136A40             var5   real8   0.001235
      400487F34D6A161E4F76         var6   real10  33.9876

DD和real4都可以在asm中来定义单精度浮点数,4 bytes
DQ和real8都可以在asm中来定义双精度浮点数,8 bytes
DT和real10都可以在asm中来定义扩展精度浮点数,10 bytes
                      
                      (二)浮点部件

FPU从功能上分为两个部分:控制单元和运算单元,控制单元主要面向CPU,而算数单元负责具体算数运算.
FPU即浮点部件包括8个通用寄存器,5个错误指针寄存器和三个控制寄存器.

1)8个通用寄存器每个80 bit,形成一个寄存器堆栈,所有的计算结果都保存在寄存器堆栈中,其中数据全部是80位的扩展精度格式,即使是BCD,整数,单精度和双精度等在装入寄存器的时候都要被FPU自动转化为80位的扩展精度格式,注意栈顶通常表示为ST(0),然后是ST(1)…ST(i),ST(i)是相对于栈顶而言的.

和堆栈很相似,只不过宽度为80bit,映像如下:

 st(0)
 st(1)
 ……
 st(i)

2)控制寄存器,FPU有三个控制寄存器:状态寄存器,控制寄存器和标记寄存器

状态寄存器->SW
      _M_____D________10___9____8___7_________5_________________________0__
     |  B |  C3| TOP| C2 | C1 | C0 | ES |    | PE | UE | OE | ZE | DE | IE |
     |____|____|____|____|____|____|____|____|____|____|____|____|____|____|

B:     浮点部件正忙
C0-C3  指示浮点运算的结果,不同指令有不同含义
TOP    指示栈顶,通常是0
ES 以下任何位置位 (pe, ue, oe, ze, de, or ie) 则置位
PE 精度故障  
UE 数字太小无法表示溢出  
OE 现有精度无法表示,数字太大溢出  
ZE 除0错  
DE 指示至少有一个操作数未规格化  
IE 无效错误,指示堆栈上溢或下溢,无效操作数等


控制寄存器:
      _15____________10___9____8___7_________5______________________0__
     |              |IC | RC | PC  |    | PM | UM | OM | ZM | DM | IM |
     |____|____|____|___|__|_|__|__|____|____|____|____|____|____|____|

IC 无穷大控制,对486,已经无效
RC 舍入控制
      00 = 朝最接近或者偶数舍入
      01 = 朝负无穷大方向舍入
      10 = 朝正无穷大方向舍入
      11 = 超0方向截断
PC 精度控制
      00 = 单精度
      01 = 保留
      10 = 双精度
      11 = 扩展精度
PM~IM 屏蔽状态寄存器低5位指示的错误.为1则屏蔽.


标记寄存器:
      每2 bit表示一个对应堆栈寄存器的状态,具体含义如下:
    15________________________________________3_____0
     |Tag7 |……………………………..|tag1|
     |_____|___________________________________|____|
      
含义:
      00 = 有效
      01 = 零
      10 = 无效或无穷大
      11 = 为空

                              (三)浮点指令系统


                     (三)浮点指令系统及MASM下浮点程序设计
事实上最重要和比较难于找到资料在(一)和(二)部分中已经介绍,下面是为了完整性的考虑,如果你是第一次接触浮点指令,看看下面的摘要也无妨.另外本文未涉及到的一个方面是关于浮点处理异常的情况,因为涉及到保护模式和中断、任务切换以及SEH等较多内容,我相信介绍之后只会令人更加迷惑,况且我现在似乎也无法把这几个问题完全说清除,一般我们几乎不需要知道这些.让我们先来看主要内容.

关于浮点程序设计是一个大的话题,我只是提纲挈领地简述Masm32V7(/V6)中的设计方法,因为486以上的CPU内建了浮点部件所以可以在程序里直接使用浮点指令.下面是一个小例子:

__MASMSTD  equ    1
.386p
.model flat, stdcall
option casemap :none  ; case sensitive
include c:\hd\hd.h
include c:\hd\mac.h

;;————–
  .DATA
num1    dq      12345
num2    dq      98765
res    dd      0
      .DATA?
buf    db 200 dup(?)

;;—————————————–
  .CODE
__Start:
      finit                  ;初始化浮点部件
      fild    num1            ;装入num1
      fild    num2            ;装入num2
      fmul                    ;执行乘法
      fist    res            ;存储
      invoke    wsprintf,addr buf,CTEXT(“the result is: %ld”),res        
      invoke    StdOut,addr buf ;显示,注意是控制台显示,编译用/SUBSYSTEM:CONSOLE
      
  invoke    StdIn,addr buf,20
      invoke    ExitProcess,0  
END    __Start

具体你要怎样运用指令,那就得看你自己所要进行的操作和要执行的算法了.注意在fpu内部寄存器总是以扩展精度数来表示数值的,因此进行整数运算最后要用fist来存储,这样才能得到正确的结果,这些转换是由fpu自动完成的.

浮点指令系统分为五类:数据传送类、算术运算类、超越函数类、比较类、环境及系统控制类.
我并不想列出所有函数的参数以及用法,因为这会是劳动力的浪费.我打字用拼音的!:D)具体参考资料见文章最后,别的我就帮不上你了.  

1)数据传送类,主要包括
这类指令主要是从内存装入浮点寄存器堆数据,一般目的地址总是栈顶ST(0),用调试器你可以清除的看到这一点.注意带P结尾的操作,是在前面操作完成之后出栈,也就是原来ST(1)的内容现在成了ST(0)的内容,注意到这一点,你可以方便地设计出灵活多变的程序.
装入:    
      FLD    Push real onto stack
      FILD    Convert two’s complement integer to real and push
      FBLD    Convert BCD to real and push to stack
存储:    
      FST    Store floating-point number from stack
      FSTP    Convert top of stack to integer
      FIST    
      FISTP    Convert top of stack to integer
      FBSTP    Store BCD to integer and pop stack
交换:    
      FXCH    Exchange top two stack elements
常数装载:    
      FLD1    装入常数1.0
      FLDZ    装入常数0.0
      FLDPI    装入常数pi (=3.1415926….精度足够,放心使用)
      FLDL2E    装入常数log(2)e
      FLDL2T    装入常数log(2)10
      FLDLG2    装入常数log(10)2
      FLDLN2    装入常数Log(e)2

我逼并不想列出所有的浮点指令的详细格式,因为没有必要!很多资料都有这些指令格式的介绍,浮点指令均以F开头,LD表示Load,ILD表示整数的Load,BLD是二十进制数的Load,这样记起来就很容易了,很多指令功能都可以根据指令一眼看出来.


2)算术运算类
加法:    
      FADD/FADDP    Add/add and pop
      FIADD    Integer add
减法:    
      FSUB/FSUBP    Subtract/subtract and pop
      FSUBR/FSUBRP    Subtract/subtract and pop with reversed operands
      FISUB    Integer subtract
      FISUBR    Integer subtract/subtract with reversed operands
乘法:    
      FMUL/FMULP    Multiply/multiply and pop
      FIMUL    Integer multiply
除法:    
      FDIV/FDIVP    Divide/divide and pop
      FIDIV    Integer divide
      FDIVR/FDIVRP    Divide/divide and pop with reversed operands
      FIDIVR    integer divide with reversed operands
其他:    
      FABS    Calculate absolute value
      FCHS    Change sign
      FRNDINT    Round to integer
      FSQRT    Calculate square root
      FSCALE    Scale top of stack by power of 2
      FXTRACT    Separate exponent and mantissa
      FPREM    Calculate partial remainder
      FPREM1    Calculate partial remainder in IEEE format

如果指令后面未带操作数,其默认的操作数为ST(0)和ST(1),关于带R后缀的指令是正常操作数的顺序变反,比如fsub执行的是x-y,fsubr执行的就是y-x.

3)超越函数类
三角函数    
      FSIN    Calculate sine
      FCOS    Calculate cosine
      FSINCOS    Calculate quick sine and cosine
      FPTAN    Calculate partial tangent
      FPATAN    Calculate partial arctangent
Log类    
      FYL2X    Calculate y times log base 2 of x
      FYL2XP1    Calculate y times log base 2 of (x+1)
      F2XM1    Calculate (2^x)-1

4)比较类
      FCOM    Compare
      FCOMP    Compare and pop
      FICOM    Integer compare
      FTST    Integer compare and pop
      FUCOM    Unordered compare
      FUCOMP    Unordered compare and pop
      FXAM    Set condition code bits for value at top of stack
      FSTSW    Store status word

会根据结果设置,C0~C3,在上面并未就C0~C3进行具体介绍,C1是用来判断上溢或者下溢的,C0相当于EFLAGS里面的CF,作用也基本一致,C2相当于PF,C3相当于ZF,你可能会看到如下指令
      FSTSW  ax
      SAHF
      JZ      label
为什么如此呢,因为用如上指令将状态字存入EFLAGS,C0正好置于CF位,C3正好置于ZF位.

5)环境及系统控制类
      FLDCW    Load control word
      FSTCW    Store control word
      FSTSW    Store status word
      FLDENV    Load environment block
      FSTENV    Store environment block
      FSAVE    Save coprocessor state
      FRSTOR    Restore coprocessor state

      FINIT    Initialize coprocessor
      FCLEX    Clear exception flags
      FINCSTP    Increment stack pointer
      FDECSTP    Decrement stack pointer
      FFREE    Mark element as free
      FNOP    No operation
      FWAIT    Wait until floating-point instruction complete

我实在不想罗嗦了,因为这些指令的格式以及用法在Masm32V7的help目录下面的fphelp.hlp文件中有详细的说明,当然也还有很多其他的指令格式列表,我之所以列出来是为了完整性.这里还有一个比较困难的问题就是浮点数的显示,windows没有现成的函数调用,wsprintf只能显示整数,但有好多库支持,比如LYB主页上的浮点开发包,当然等你搞熟了这些东西,也可以自己写.
关于浮点程序的调试,建议使用Softice,因为Trw不支持浮点堆栈的显示,现在网上有一个fpu插件,可以部分解决问题,不过不够好用.一切看你自己的选择了.
_________________________
对下面的指令先做一些说明:
st(i):代表浮点寄存器,所说的出栈、入栈操作都是对st(i)的影响
src,dst,dest,op等都是指指令的操作数,src表示源操作数,dst/dest表示目的操作数
mem8,mem16,mem32,mem64,mem80等表示是内存操作数,后面的数值表示该操作数的内存位数(8位为一字节)
x <- y 表示将y的值放入x,例st(0) <- st(0) – st(1)表示将st(0)-st(1)的值放入浮点寄存器st(0)
1.  数据传递和对常量的操作指令

指令格式
指令含义
执行的操作

FLD src
装入实数到st(0)
st(0) <- src (mem32/mem64/mem80)

FILD src
装入整数到st(0)
st(0) <- src (mem16/mem32/mem64)

FBLD src 
装入BCD数到st(0)
st(0) <- src (mem80)

 

FLDZ
将0.0装入st(0)
st(0) <- 0.0

FLD1
将1.0装入st(0)
st(0) <- 1.0

FLDPI
将pi装入st(0)
st(0) <- ?(ie, pi)

FLDL2T
将log2(10)装入st(0)
st(0) <- log2(10)

FLDL2E
将log2(e)装入st(0)
st(0) <- log2(e)

FLDLG2
将log10(2)装入st(0)
st(0) <- log10(2)

FLDLN2
将loge(2)装入st(0)
st(0) <- loge(2)

 

FST dest
保存实数st(0)到dest
dest <- st(0) (mem32/mem64)

FSTP dest
 
dest <- st(0) (mem32/mem64/mem80);然后再执行一次出栈操作

FIST dest
将st(0)以整数保存到dest
dest <- st(0) (mem32/mem64)

FISTP dest
 
dest <- st(0) (mem16/mem32/mem64);然后再执行一次出栈操作

FBST dest
将st(0)以BCD保存到dest
dest <- st(0) (mem80)

FBSTP dest 
 
dest<- st(0) (mem80);然后再执行一次出栈操作


2.比较指令

指令格式
指令含义
执行的操作

FCOM
实数比较
将标志位设置为 st(0) – st(1) 的结果标志位

FCOM op
实数比较
将标志位设置为 st(0) – op (mem32/mem64)的结果标志位

 

FICOM op
和整数比较
将Flags值设置为st(0)-op 的结果op (mem16/mem32)

FICOMP op
和整数比较
将st(0)和op比较 op(mem16/mem32)后;再执行一次出栈操作

 

FTST 
零检测 
将st(0)和0.0比较

FUCOM st(i) 
 
比较st(0) 和st(i)                  [486]

FUCOMP st(i)      
 
比较st(0) 和st(i),并且执行一次出栈操作

FUCOMPP st(i)    
 
比较st(0) 和st(i),并且执行两次出栈操作

FXAM  
 
Examine: Eyeball st(0) (set condition codes)


3.运算指令

指令格式
指令含义
执行的操作

加法

FADD
加实数
st(0) <-st(0) + st(1)

FADD src
 
st(0) <-st(0) + src (mem32/mem64)

FADD st(i),st
 
st(i) <- st(i) + st(0)

FADDP st(i),st 
 
st(i) <- st(i) + st(0);然后执行一次出栈操作

FIADD src  
加上一个整数
st(0) <-st(0) + src (mem16/mem32)

减法

FSUB
减去一个实数
st(0) <- st(0) – st(1)

FSUB src
 
st(0) <-st(0) – src (reg/mem)

FSUB st(i),st
 
st(i) <-st(i) – st(0)

FSUBP st(i),st
 
st(i) <-st(i) – st(0),然后执行一次出栈操作

FSUBR st(i),st
用一个实数来减
st(0) <- st(i) – st(0)

FSUBRP st(i),st
 
st(0) <- st(i) – st(0),然后执行一次出栈操作

FISUB src
减去一个整数
st(0) <- st(0) – src (mem16/mem32)

FISUBR src
用一个整数来减
st(0) <- src – st(0) (mem16/mem32)

乘法

FMUL
乘上一个实数
st(0) <- st(0) * st(1)

FMUL st(i)
 
st(0) <- st(0) * st(i)

FMUL st(i),st
 
st(i) <- st(0) * st(i)

FMULP st(i),st
 
st(i) <- st(0) * st(i),然后执行一次出栈操作

FIMUL src
乘上一个整数
st(0) <- st(0) * src (mem16/mem32)

除法

FDIV 
除以一个实数
st(0) <-st(0) /st(1)

FDIV st(i)
 
st(0) <- st(0) /t(i)

FDIV st(i),st
 
st(i) <-st(0) /st(i)

FDIVP st(i),st
 
st(i) <-st(0) /st(i),然后执行一次出栈操作

FIDIV src 
除以一个整数
st(0) <- st(0) /src (mem16/mem32)

FDIVR st(i),st
用实数除
st(0) <- st(i) /st(0)

FDIVRP st(i),st
 
FDIVRP st(i),st

FIDIVR src 
用整数除
st(0) <- src /st(0) (mem16/mem32)

 

FSQRT
平方根
st(0) <- sqrt st(0)

 

FSCALE
2的st(0)次方
st(0) <- 2 ^ st(0)

FXTRACT
Extract exponent:
st(0) <-exponent of st(0); and gets pushed

st(0) <-significand of st(0)

 

FPREM 
取余数
st(0) <-st(0) MOD st(1)

FPREM1
取余数(IEEE),同FPREM,但是使用IEEE标准[486]

 
 
 

FRNDINT 
取整(四舍五入)
st(0) <- INT( st(0) ); depends on RC flag

 

FABS
求绝对值
st(0) <- ABS( st(0) ); removes sign

FCHS
改变符号位(求负数)
st(0) <-st(0)

 

F2XM1
计算(2 ^ x)-1
 st(0) <- (2 ^ st(0)) – 1

FYL2X  
计算Y * log2(X)
st(0)为Y;st(1)为X;将st(0)和st(1)变为st(0) * log2( st(1) )的值

 

FCOS
余弦函数Cos
st(0) <- COS( st(0) )

FPTAN
正切函数tan
st(0) <- TAN( st(0) )

FPATAN
反正切函数arctan
st(0) <- ATAN( st(0) )

FSIN
正弦函数sin
st(0) <- SIN( st(0) )

FSINCOS
sincos函数
st(0) <-SIN( st(0) ),并且压入st(1)

st(0) <- COS( st(0) )

 
 
 

FYL2XP1 
计算Y * log2(X+1)
st(0)为Y; st(1)为X; 将st(0)和st(1)变为st(0) * log2( st(1)+1 )的值

处理器控制指令

FINIT
初始化FPU
 

FSTSW AX
保存状态字的值到AX
AX<- MSW

FSTSW dest
保存状态字的值到dest
dest<-MSW (mem16)

 
 
 

FLDCW src
从src装入FPU的控制字
FPU CW <-src (mem16)

FSTCW dest
将FPU的控制字保存到dest
dest<- FPU CW

 
 
 

FCLEX 
清除异常
 

 
 
 

FSTENV dest
保存环境到内存地址dest处 保存状态字、控制字、标志字和异常指针的值

FLDENV src
从内存地址src处装入保存的环境
 

FSAVE dest
保存FPU的状态到dest处 94字节
 

FRSTOR src
从src处装入由FSAVE保存的FPU状态
 

 
 
 

FINCSTP
增加FPU的栈指针值
st(6) <-st(5); st(5) <-st(4),…,st(0) <-?

FDECSTP
减少FPU的栈指针值
st(0) <-st(1); st(1) <-st(2),…,st(7) <-?

 
 
 

FFREE st(i)
标志寄存器st(i)未被使用
 

 
 
 

FNOP 
空操作,等同CPU的nop
st(0) <-st(0)

WAIT/FWAIT
同步FPU与CPU:停止CPU的运行,直到FPU完成当前操作码

 

FXCH
交换指令,交换st(0)和st(1)的值
st(0) <-st(1)

st(1) <- st(0)

2004年11月19日





















用Debug函数实现API函数的跟踪


发布者: soarlove (进入soarlove个人专栏)
文章类型:转载
发布日期:2003.04.27  
升级次数:0
今日浏览:1
总浏览:7254



评价等级:







代码下载
2位用户为此文章评分,平均分为5.0





作者:彭春华 来自:赛迪网

如果我们能自己编写一个类似调试器的功能,这个调试器需要实现我们对于跟踪监视工具的要求,即自动记录输入输出参数,自动让目标进程继续运行。下面我们就来介绍在不知道函数原型的情况下也可以简单输出监视结果的方案——用Debug函数实现API函数的监视。

 



用Debug函数实现API函数的监视




大家知道,VC可以用来调试程序,除了调试Debug程序,当然也可以调试Release程序(调试Release程序时为汇编代码)。如果知道函数的入口地址,只需在函数入口上设置断点,当程序调用了设置断点的函数时,VC就会暂停目标程序的运行,你就可以得到目标程序内存的所有你希望得到的东西了。一般来说,只要你有足够的耐心和毅力,以及一些汇编知识,对于监视API函数的输入输出参数还是可以完成的。

不过,由于VC的调试器会在每次断点时暂停目标程序的运行,对目标程序的过多的暂停对于监视任务而言实在不能忍受。所以,不会有太多的人真的会用VC的调试器作为一个良好的API函数监视器的。

如果VC调试器能够在你设置好断点后,在运行时自动输出断点时的堆栈值(也就是函数的输入参数),在函数运行结束时也自动输出堆栈值(也就是函数的输出参数)和CPU寄存器的值(就是函数返回值),并且不会暂停目标程序。所有一切都是自动的无需我们干预。你会用它来作为监视器吗?我会的。

我不知道如何让VC这样作(或许VC真的可以这样,但我不知道。有人知道的话请通知我一声,谢谢),但我知道显然VC也是通过调用Windows API函数完成调试器的任务,而且,这些函数显然可以实现我的要求。我需要作的事情就是自己利用这些API函数,写一个简单的调试器,在目标程序断点发生时自动输出监视结果并且自动恢复目标程序的运行。

显然,用VC调试器作为监视器的话无需知道目标函数的原型就可以得到简单的输入输出参数和函数运行结果,而且,由于监视代码没有注入目标程序中,就不会出现监视目标函数和监视代码的冲突。VC调试器显然可以跟踪递归函数,也可以跟踪DLL模块调用DLL本身的函数,以及EXE内部调用自身的函数。只要你知道目标函数的入口地址,就可以跟踪了(监视Exe自身的函数可以通过生成Exe模块时选择输出Map文件,就可以参考Map文件得到Exe内部函数的地址)。没有听说VC不能调试多线程的,最多是说调试多线程比较麻烦—-证明多线程是可以调试的。显然,VC也可以调试DllMain中的代码。这些,已经可以证明通过调试函数可以实现我们的目标了。

 



如何编写实现我们目标的程序?需要哪些调试函数?




首先,让目标程序进入被调试状态:

对于一个已经启动的进程而言,利用DebugActiveProcess函数就可以捕获目标进程,将目标进程进入被调试状态。

 





BOOL DebugActiveProcess(DWORD dwProcessId);



参数dwProcessId是目标进程的进程ID。如何通过ToolHelp系列函数或Psapi库函数获得一个运行程序的进程ID在很多文章中介绍过,这里就不再重复。对于服务器程序而言,由于没有权限无法捕获目标进程,可以通过提升监视程序的权限得到调试权限进行捕获目标进程(用户必须拥有调试权限)。

对于启动一个新的程序而言,通过CreateProcess函数,设置必要的参数就可以将目标程序进入被调试状态。

 





BOOL CreateProcess(LPCTSTR lpApplicationName, LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES
lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID
lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation );



该函数的具体说明请参考MSDN,在这里我仅介绍我们感兴趣的参数。这里和一般的用法不同,作为被调试程序dwCreationFlags必须设置为DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS。这样启动的目标程序就会进入被调试状态。这里说明一下DEBUG_PROCESS和DEBUG_ONLY_THIS_PROCESS。DEBUG_ONLY_THIS_PROCESS就是只调试目标进程,而DEBUG_PROCESS参数则不仅调试目标进程,而且调试由目标进程启动的所有子进程。比如:在A.exe中启动B.exe,如果用DEBUG_ONLY_THIS_PROCESS启动,监视进程只调试A.exe不会调试B.exe,如果是DEBUG_PROCESS就会调试A.exe和B.exe。为简单起见,本文只讨论启动参数为DEBUG_ONLY_THIS_PROCESS的情况。

使用方法:

 





STARTUPINFO st = {0};
PROCESS_INFORMATION pro = {0};
st.cb = sizeof(st);
CreateProcess(NULL, pszCmd, NULL, NULL, FALSE,
DEBUG_ONLY_THIS_PROCESS,
NULL, szPath, &st, &pro));
// 关闭句柄—这些句柄在调试程序中不再使用,所以可以关闭
CloseHandle(pro.hThread);
CloseHandle(pro.hProcess);



其次,对进入被调试状态的程序进行监视:

目标进程进入了被调试状态,调试程序(这里调试程序就是我们的监视程序,以后不再说明)就负责对被调试的程序进行调试操作的调度。调试程序通过WaitForDebugEvent函数获得来自被调试程序的调试消息,调试程序根据得到的调试消息进行处理,被调试进程将暂停操作,直到调试程序通过ContinueDebugEvent函数通知被调试程序继续运行。

 





BOOL WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent, // debug event information
DWORD dwMilliseconds // time-out value
);



在参数lpDebugEvent中可以获得调试消息,需要注意的是该函数必须和让目标程序进入调试状态的线程是同一线程。也就是说和通过DebugActiveProcess或CreateProcess调用的线程是一个线程。另外,我又喜欢将dwMilliseconds设置为-1(无限等待)。所以我通常都会将CreateProcess和WaitForDebugEvent函数在一个新的线程中使用。

 





typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;



在这个调试消息结构体中,dwDebugEventCode记录了产生调试中断的消息代码。消息代码的详细说明可以参考MSDN。其中,我们感兴趣的消息代码为:

 





EXCEPTION_DEBUG_EVENT:产生调试例外
CRATE_THREAD_DEBUG_EVENT:新的线程产生
CREATE_PROCESS_DEBUG_EVENT:新的进程产生。注:在DEBUG_ONLY_THIS_PROCESS时只有一次,
在DEBUG_PROCESS时如果该程序启动了子进程就可能有多次。
EXIT_THREAD_DEBUG_EVENT:一个线程运行中止
EXIT_PROCESS_DEBUG_EVENT:一个进程中止。注:在DEBUG_ONLY_THIS_PROCESS时只有一次,
在DEBUG_PROCESS可能有多次。
LOAD_DLL_DEBUG_EVENT:一个DLL模块被载入。
UNLOAD_DLL_DEBUG_EVENT:一个DLL模块被卸载。



在得到目标程序的调试消息后,调试程序根据这些消息代码进行不同的处理,最后通知被调试程序继续运行。

 





BOOL ContinueDebugEvent(
DWORD dwProcessId, // process to continue
DWORD dwThreadId, // thread to continue
DWORD dwContinueStatus // continuation status
);



该函数通知被调试程序继续运行。

使用例:

 





DEBUG_EVENT dbe;
BOOL rc;
CreateProcess(NULL, pszCmd, NULL, NULL, FALSE,
DEBUG_ONLY_THIS_PROCESS,
NULL, szPath, &st, &pro));
while(WaitForDebugEvent(&dbe, INFINITE))
{
// 如果是退出消息,调试监视结束
if(dbe. dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
// 进入调试监视处理
rc = OnDebugEvent(&dbe);
if(rc)
ContinueDebugEvent(dbe.dwProcessId , dbe.dwThreadId , DBG_CONTINUE );
else
ContinueDebugEvent(dbe.dwProcessId , dbe.dwThreadId ,
DBG_ DBG_EXCEPTION_NOT_HANDLED);
}
// 调试消息处理程序
BOOL WINAPI OnDebugEvent(DEBUG_EVENT* pEvent)
{
// 我们还没有对目标进程进行操作,所以,先返回TRUE。
return TRUE;
}



上面这些程序就是一个最简单的调试程序了。不过,它基本上没有什么用途。你还没有在目标进程中设置断点,你就不能完成对API函数监视的任务。


对目标进程设置断点:

我们的目标是监视API函数的输入输出,那么,首先应该知道DLL模块中提供了哪些API函以及这些API的入口地址。在前面将过,广义的API还包括未导出的内部函数。如果你有DLL模块的调试版本和调试连接文件(pdb文件),也可以根据调试信息得到内部函数的信息。

· 得到函数名及函数入口地址

通过程序得到函数的入口地址有很多种方法。对于用VC编译出来的DLL,如果是Debug版本,可以通过ImageHlp库函数得到调试信息,分析出函数的入口地址。如果没有Debug版本,也可以通过分析导出函数表得到函数的入口地址。

1.用Imagehlp库函数得到Debug版本的函数名和函数入口地址。

可以利用Imagehlp库函数分析Debug信息,关联的函数为SymInitialize、SymEnumerateSymbols和UnDecorateSymbolName。详细可以参考MSDN中关于这些函数的说明和用法。不过,用Imagehlp只能分析出用VC编译的程序,对C++Builder编译的程序不能用这种方法分析。

2.DLL的导出表得到函数导出函数名和函数的入口地址。

在大多数情况下,我们还是希望监视的是Release版本的输入输出参数,毕竟Debug版本不是我们最终提供给用户的产品。Debug和Release的编译条件不同导致产生的结果不同,在很多BBS中都讨论过。所以,我认为跟踪监视Release版本更加有实用价值。

通过分析DLL导出表得到导出函数名在MSDN上就有源代码。关于导出表的说明大家可以参考关于PE结构的文章。

3.通过OLE函数取得COM接口

你也可以通过OLE函数分析DLL提供的接口函数。接口函数不是通过DLL导出表导出的。你可以通过LoadTypeLib函数来分析COM接口,得到COM记录接口的入口地址,这样,你就可以监视COM接口的调用了。这是API HOOK没法实现的。在这里我不打算分析分析COM接口的方式了。在MSDN上通过搜索LoadTypeLib sample关键词你就可以找到相关的源代码进行修改实现你的目标。

这里是通过计算机自动分析目标模块得到DLL导出函数的方案,作为我们监视的目的而言,这些工作只是为了得到一系列的函数名和函数地址而已。函数名只是一个让我们容易识别函数的名称而已,该函数入口地址才是我们真正关心的目标。换句话说,如果你能够确保某一个地址一定是一个函数(包括内部函数)的入口地址,你就完全可以给这个函数定义自己的名称,将它加入你的函数管理表中,同样可以实现监视该函数的输入输出参数的功能。这也是实现Exe内部函数的监视功能的原因。如果你有Exe编译时生成的Map文件(你可以在编译时选择生成Map文件),你就可以通过分析Map文件,得到内部函数的入口地址,将内部函数加入到你的函数管理表中。(一个函数的名称对于监视功能来讲究竟是FunA还是FunB并没有什么意义,但名称是FunA还是FunB的名称对于监视者分析监视结果是有意义的,你完全可以将MessageBox的函数在输出监视结果是以FunA的名称输出,所以在监视一些内部无名称的函数时,你完全可以定义你自己的名字)。

· 在函数入口地址处设置断点

设置断点非常简单,只要将0xCC(int 3)写入指定的地址就可以了。这样程序运行到指定地址时,将产生调试中断信息通知调试程序。修改指定进程的内存数据可以通过WriteProcessMemory函数来完成。由于一般情况下作为程序代码段都被保护起来了,所以还有一个函数也会用到。VirtualProtectEx。在实际情况下,当调试断点发生时,调试程序还应该将原来的代码写回被调试程序。

 





unsigned char SetBreakPoint(DWORD pAdd, unsigned char code)
{
unsigned char b;
BOOL rc;
DWORD dwRead, dwOldFlg;
// 0×80000000以上的地址为系统共有区域,不可以修改
if( pAdd >= 0×80000000 || pAdd == 0)
return code;
// 取得原来的代码
rc = ReadProcessMemory(_ghDebug, pAdd, &b, sizeof(BYTE), &dwRead);
// 原来的代码和准备修改的代码相同,没有必要再修改
if(rc == 0 || b == code)
return code;
// 修改页码保护属性
VirtualProtectEx(_ghDebug, pAdd, sizeof(unsigned char), PAGE_READWRITE,
&dwOldFlg);
// 修改目标代码
WriteProcessMemory(_ghDebug, pAdd, &code, sizeof(unsigned char), &dwRead);
// 恢复页码保护属性
VirtualProtectEx(_ghDebug, pAdd, sizeof(unsigned char), dwOldFlg, &dwOldFlg);
return b;
}



在设置断点时你必须将原来的代码保存起来,这样在恢复断点时就可以将代码还原了。一般用法为:设置断点m_code = SetBreakPoint( pFunAdd, 0xCC); 恢复断点:SetBreakPoint( pFunAdd, m_code); 记住,每个函数入口地址的代码都可能不同,你应该为每个断点地址保存一个原来的代码,在恢复时就不会发生错误了。

好了,现在目标程序中已经设置好了断点,当目标程序调用设置了断点的函数时,将产生一个调试中断信息通知调试程序。我们就要在调试程序中编写我们的调试中断程序了。

 



编写调试中断处理程序




被调试程序产生中断时,将产生一个EXCEPTION_DEBUG_EVENT信息通知调试程序进行处理。同时将填充EXCEPTION_DEBUG_INFO结构。

 





typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;



在该结构中,我们比较感兴趣的是产生中断的地址ExceptionAddress和产生中断的信息代码ExceptionCode。在信息代码中与我们任务相关的信息代码为:

 





EXCEPTION_BREAKPOINT:断点中断信息代码
EXCEPTION_SINGLE_STEP:单步中断信息代码



断点中断是由于我们在前面设置断点0xCC代码运行时产生的。由于产生中断后,我们必须将原来的代码写回被调试程序中继续运行。但是,代码一旦被写回目标程序,这样,当目标程序再次调用该函数时将不会产生中断,我们就只能实现一次监视了。所以,我们必须在将原代码写回被调试程序后,应该让被调试程序已单步的方式运行,再次产生一个单步中断的调试信息。在单步中断处理中,我们再次将0xCC代码写入函数的入口地址,这样就可以保证再次调用时产生中断。

首先,在进行中断处理前我们必须作些准备工作,管理起线程ID和线程句柄。为了管理单步中断处理,我们还必须维护一个基于线程的单步地址的管理,这样就可以允许被调试程序拥有多线程的功能。–我们不能保证单步运行时不被该进程的其他线程所打断。

 





// 我们利用一个map进行管理线程ID和线程句柄之间的关系
// 同时也用一个map管理函数地址和断点的关系
typedef map > THREAD_MAP;
typedef map > THREAD_SINGLESTEP_MAP;
THREAD_MAP _gthreads;
FUN_BREAK_MAP _gFunBreaks;
// 并且假设设置断点时采用了如下方案进行原来代码的管理
BYTE code = SetBreakPoint(pFunAdd, 0xCC);
if(code != 0xCC)
_gFunBreaks[pFunAdd] = code;

// 调试处理程序
BOOL WINAPI OnDebugEvent(DEBUG_EVENT* pEvent)
{
BOOL rc = TRUE;
switch(pEvent->dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
// 记录线程ID和线程句柄的关系
_gthreads[pEvent->dwThreadId] = pEvent->u.CreateProcessInfo.hThread;

break;
case CREATE_THREAD_DEBUG_EVENT:
// 记录线程ID和线程句柄的关系
_gthreads [pEvent->dwThreadId] = pEvent->u.CreateThread.hThread;

break;
case EXIT_THREAD_DEBUG_EVENT:
// 线程退出时清除线程ID
_gthreads.erase (pEvent->dwThreadId);

break;
case EXCEPTION_DEBUG_EVENT:
// 中断处理程序
rc = OnDebugException(pEvent);
break;

}
return rc;
}



下面进行中断处理程序。同样,我们只考虑我们关心的中断信息代码。在发生中断时,我们通过GetThreadContext(&context)得到中断线程的上下文信息。此时,context.esp就是函数的返回地址,context.esp+4位置的值就是函数的第一个参数,context.esp+8就是第二个参数,依次类推可以得到你想要的任何参数。需要注意的是因为参数是在被调试进程中的内容,所以你必须通过ReadProcessMemory函数才能得到:

 





DWORD buf[4]; // 取4个参数
ReadProcessMemory(_ghDebug, (void*)(context.esp + 4), &buf, sizeof(buf),
&dwRead);



那么buf[0]就是第一个参数,buf[1]就是第二个参数。。。注意,在FunA(int a, char* p, OPENFILENAME* pof)函数调用时,buf[0] = a, buf[1] = p这里buf[1]是p的指针而不是p的内容,如果你希望访问p的内容,必须同样通过ReadProcessMemory函数再次取得p的内容。对于结构体指针也必须如此:

 





// 取得p的内容:
char pBuf[256];
ReadProcessMemory(_ghDebug, (void*)(buf[1]), &pBuf, sizeof(pBuf), &dwRead);
//取得pof的内容:
OPENFILENAME of
ReadProcessMemory(_ghDebug, (void*)(buf[2]), &of, sizeof(of), &dwRead);



如果结构体中还有指针,要取得该指针的内容,也必须和取得p的内容一样的方式读取被调试程序的内存。总的来说,你必须意识到监视目标程序的所有内容都是对目标进程的内存读取操作,这些指针都是目标进程的内存地址,而不是调试进程的地址。









 

很明显,当被调试进程在函数入口产生中断调试信息时,调试程序只能得到函数的输入参数,而不能得到我们希望的输出参数及返回值!为了实现我们的目标,我们必须在函数调用结束时,再次产生中断,取得函数的输出参数和返回值。在处理函数入口中断时,就必须设置好函数的返回地址的断点。这样,在函数返回时,就可以得到函数的输出参数和返回值了。关于这里的实现说明请参考附录的源代码。

你完全可以参照附录的源代码写出你自己的简单的调试监视程序。当然,有几个问题因为比较复杂,我没有在这里进行说明。一个就是函数返回断点的处理,比如TRY、CATCH的处理,就必须重新设计好RETURN_FUN_STACK的结构,考虑一些除错处理还是可以解决这个问题的。另外一个问题就是函数的入口断点和返回断点没有任何关系。这个问题更好解决,只需重新设计RETURN_FUN,FUN_BREAK_MAP等结构体就可以将它们关联起来。由于我在这里只要是分析如何实现中断调试处理的过程,这些完善程序的工作就由读者自行跟踪改造了。

 



关于Win9X系统




细心的读者在上面可以发现一个问题,那就是在SetBreakPoint函数中有一个限制,就是函数的入口地址不能大于0×80000000。确实如此,我们知道0×80000000以上的空间是系统共有的空间,我们一般不能修改这些空间的程序,否则将影响系统的工作。在NT环境下,所有的DLL都被加载在0×80000000下,修改0×80000000以下空间的代码不会对其它进程产生影响。所以在NT下可以用上面的方案监视所有的DLL函数。然而,在Win9X下,kernel32.dll,user32.dll,gdi32.dll等系统DLL都被加载到0×80000000以上的空间,修改这些空间的代码将破坏系统工作。那么,在9X下就不能监视这些DLL模块的函数吗?

的确,在Win9X平台下不能利用在函数入口处设置断点的方法实现监视。我们必须采用另外的方法实现该功能。在前面讨论中知道,通过API HOOK修改模块导入表的方法可以实现将API的入口修改为自己监视程序的入口,也可以实现监视功能。如果采用API HOOK的方法有限制,即必须知道函数原型,对每一个函数都必须编写相应的监视代码,灵活性受到限制。而我们的目标是不管有多少个DLL,不管DLL有多少个导出函数,在不修改我们的程序前提下都可以实现我们的监视功能。所以,API HOOK是不可以完成我们的目标,但我们可以利用修改导入表的方案实现目标。首先,修改导入表,将函数的调用地址指向我们的监视代码,在监视代码中,我们无需对函数编程,只是简单调用jmp XXXX就可以了。然后,设置断点时,不是设置在函数的入口点,而是设置在我们的监视代码上。这样,当我们的模块调用系统API函数时,就可以实现监视功能了。修改原理如图:

 






如图所示,假设我们的监视代码在目标进程的的0×20000000空间,我们在分析DLL导出表的同时,将导出表函数的地址经过计算,在监视代码中设置为jmp xxxx的代码。这样我们在修改EXE模块的导入表时写入的地址为监视代码的地址。当目标程序调用MessageBox函数是,程序将首先跳转到监视代码中执行jmp指令到user32.dll的MessageBox入口地址中。经过这样处理后,我们希望监视MessageBox函数的调用时,只需在监视代码的0×20000000处设置断点,就达到了监视的目的。限于篇幅原因,这里不再讨论。

 



扩展应用




你可以很轻松的在此基础上进行扩展你的监视跟踪功能。只需要修改一下记录输入输出函数结果的程序,就得到一个新的功能:

1.在记录输入输出参数的地方加入取得当前时刻的功能,就实现了监视函数调用性能的功能。(相当于Numega的TrueTime功能)由于采用了Debug技术,得到的时间将包括调试函数导致产生进程的切换时间。等到的时间只是一个参考价值,但对分析性能而言一般足够。

2.在记录输入输出参数的地方加入函数调用的计数器,就实现了Numega的TrueCoverage功能。

3.监视malloc, free, realloc函数的输入输出值,并进行统计,就实现了简单的内存泄漏检查功能。关键的是你可以通过Map文件得到Release版本的malloc等函数的地址,实现对Release版的跟踪。

4.在记录输入参数处理中加入StackWalk函数可以实现call stack功能,分析是由哪个函数调用了自己。在jmp方案中也可以实现这个功能,但是你必须确保StackWalk关联的函数没有调用被你监视的函数。在Hook API(IAT)的方案中到是不用保证,但得出的调用列表中有可能包含你的监视代码。

有一点需要注意的是,我们的目标是监视程序的运行路径,并不是改变参数和修改结果,所以,在jmp和Hook Api(IAT)中可以实现的修改参数和运行路径的做法在这里不能实现。

其他:

本文附录的代码TestDebug.zip就是实现了一个简单的调试监视器,自动输出监视函数的4个输入参数的地址内容和函数调用返回值。该代码只是表明通过监视函数可以实现对API的跟踪,所以没有实现9X下对系统DLL的监视。

DebugApi.zip是一个利用这个方案编写的应用程序DebugApiSpy.exe,它实现了这个方案中的最基本的跟踪监视函数的输入输出参数功能,也实现了9X下对系统DLL的监视支持。该程序支持Win9X/NT/W2K/XP上的运用。


参考资料:

1.《Windows核心编程》, Jeffrey Richter,机械工业出版社

2.微软的MSDN

3.detours 可以在http://research.microsoft.com/sn/detours/ 上得到源代码。detours功能在WinNT和W2K下有效,对9X不支持。

2004年11月13日

1102CC00:处理跑动动作的代码部分
1102CC00   83EC 1C          SUB ESP,1C
1102CC03   A1 909E1811      MOV EAX,DWORD PTR DS:[11189E90]
1102CC08   56               PUSH ESI
1102CC09   894424 1C        MOV DWORD PTR SS:[ESP+1C],EAX
1102CC0D   8BF1             MOV ESI,ECX
1102CC0F   C74424 04 2C0100>MOV DWORD PTR SS:[ESP+4],12C
1102CC17   E8 345A0B00      CALL MHMAIN.110E2650
1102CC1C   84C0             TEST AL,AL
1102CC1E   0F85 26040000    JNZ MHMAIN.1102D04A
1102CC24   53               PUSH EBX
1102CC25   55               PUSH EBP
1102CC26   57               PUSH EDI
1102CC27   EB 07            JMP SHORT MHMAIN.1102CC30
1102CC29   8DA424 00000000  LEA ESP,DWORD PTR SS:[ESP]
1102CC30   8B4E 20          MOV ECX,DWORD PTR DS:[ESI+20]
1102CC33   85C9             TEST ECX,ECX
1102CC35   B8 67666666      MOV EAX,66666667
1102CC3A   7D 11            JGE SHORT MHMAIN.1102CC4D
1102CC3C   41               INC ECX
1102CC3D   F7E9             IMUL ECX
1102CC3F   C1FA 03          SAR EDX,3
1102CC42   8BC2             MOV EAX,EDX
1102CC44   C1E8 1F          SHR EAX,1F
1102CC47   8D7C02 FF        LEA EDI,DWORD PTR DS:[EDX+EAX-1]
1102CC4B   EB 0C            JMP SHORT MHMAIN.1102CC59
1102CC4D   F7E9             IMUL ECX
1102CC4F   C1FA 03          SAR EDX,3
1102CC52   8BFA             MOV EDI,EDX
1102CC54   C1EF 1F          SHR EDI,1F
1102CC57   03FA             ADD EDI,EDX
1102CC59   8B0D 28011911    MOV ECX,DWORD PTR DS:[11190128]
1102CC5F   E8 6C1CFEFF      CALL MHMAIN.1100E8D0
1102CC64   8BC8             MOV ECX,EAX
1102CC66   B8 67666666      MOV EAX,66666667
1102CC6B   F7E9             IMUL ECX
1102CC6D   C1FA 03          SAR EDX,3
1102CC70   8BCA             MOV ECX,EDX
1102CC72   C1E9 1F          SHR ECX,1F
1102CC75   03CA             ADD ECX,EDX
1102CC77   8BE9             MOV EBP,ECX
1102CC79   8B4E 24          MOV ECX,DWORD PTR DS:[ESI+24]
1102CC7C   B8 67666666      MOV EAX,66666667
1102CC81   F7E9             IMUL ECX
1102CC83   C1FA 03          SAR EDX,3
1102CC86   8BC2             MOV EAX,EDX
1102CC88   C1E8 1F          SHR EAX,1F
1102CC8B   03C2             ADD EAX,EDX
1102CC8D   2BE8             SUB EBP,EAX
1102CC8F   8B46 60          MOV EAX,DWORD PTR DS:[ESI+60]
1102CC92   4D               DEC EBP
1102CC93   83F8 04          CMP EAX,4
1102CC96   896C24 18        MOV DWORD PTR SS:[ESP+18],EBP
1102CC9A   75 07            JNZ SHORT MHMAIN.1102CCA3
1102CC9C   837C24 10 14     CMP DWORD PTR SS:[ESP+10],14
1102CCA1   7D 0D            JGE SHORT MHMAIN.1102CCB0
1102CCA3   6A 00            PUSH 0
1102CCA5   8D8E A8000000    LEA ECX,DWORD PTR DS:[ESI+A8]
1102CCAB   E8 20290A00      CALL MHMAIN.110CF5D0   //这附近
1102CCB0   3BBE 98000000    CMP EDI,DWORD PTR DS:[ESI+98]
1102CCB6   75 08            JNZ SHORT MHMAIN.1102CCC0
1102CCB8   3BAE 9C000000    CMP EBP,DWORD PTR DS:[ESI+9C]
1102CCBE   74 79            JE SHORT MHMAIN.1102CD39
1102CCC0   8B8E 9C000000    MOV ECX,DWORD PTR DS:[ESI+9C]
1102CCC6   8B96 98000000    MOV EDX,DWORD PTR DS:[ESI+98]
1102CCCC   55               PUSH EBP
1102CCCD   57               PUSH EDI
1102CCCE   51               PUSH ECX
1102CCCF   52               PUSH EDX
1102CCD0   E8 DBFCFFFF      CALL MHMAIN.1102C9B0
1102CCD5   55               PUSH EBP
1102CCD6   57               PUSH EDI
1102CCD7   68 B4C71111      PUSH MHMAIN.1111C7B4                     ; ASCII “(ii)”
1102CCDC   6A 00            PUSH 0
1102CCDE   68 B0C71111      PUSH MHMAIN.1111C7B0
1102CCE3   68 B4F21111      PUSH MHMAIN.1111F2B4                     ; ASCII “hero_position”
1102CCE8   68 E0CA1111      PUSH MHMAIN.1111CAE0                     ; ASCII “map”
1102CCED   8BD8             MOV EBX,EAX
1102CCEF   E8 AC0EFEFF      CALL MHMAIN.1100DBA0
1102CCF4   83C4 2C          ADD ESP,2C
1102CCF7   85DB             TEST EBX,EBX
1102CCF9   7F 17            JG SHORT MHMAIN.1102CD12
1102CCFB   0F8D 37030000    JGE MHMAIN.1102D038
1102CD01   89BE 98000000    MOV DWORD PTR DS:[ESI+98],EDI
1102CD07   89AE 9C000000    MOV DWORD PTR DS:[ESI+9C],EBP
1102CD0D   E9 26030000      JMP MHMAIN.1102D038
1102CD12   8B86 90000000    MOV EAX,DWORD PTR DS:[ESI+90]
1102CD18   885C04 1C        MOV BYTE PTR SS:[ESP+EAX+1C],BL
1102CD1C   40               INC EAX
1102CD1D   8986 90000000    MOV DWORD PTR DS:[ESI+90],EAX
1102CD23   89BE 98000000    MOV DWORD PTR DS:[ESI+98],EDI
1102CD29   89AE 9C000000    MOV DWORD PTR DS:[ESI+9C],EBP
1102CD2F   C705 60201D11 00>MOV DWORD PTR DS:[111D2060],0
1102CD39   8B4E 24          MOV ECX,DWORD PTR DS:[ESI+24]
1102CD3C   B8 67666666      MOV EAX,66666667
1102CD41   F7E9             IMUL ECX
1102CD43   8B0D 28011911    MOV ECX,DWORD PTR DS:[11190128]
1102CD49   C1FA 03          SAR EDX,3
1102CD4C   8BC2             MOV EAX,EDX
1102CD4E   C1E8 1F          SHR EAX,1F
1102CD51   03C2             ADD EAX,EDX
1102CD53   50               PUSH EAX
1102CD54   57               PUSH EDI
1102CD55   E8 861BFEFF      CALL MHMAIN.1100E8E0
1102CD5A   0FB6D8           MOVZX EBX,AL
1102CD5D   83FB 02          CMP EBX,2
1102CD60   75 0A            JNZ SHORT MHMAIN.1102CD6C
1102CD62   C786 94000000 C8>MOV DWORD PTR DS:[ESI+94],0C8
1102CD6C   8D8E A8000000    LEA ECX,DWORD PTR DS:[ESI+A8]
1102CD72   E8 891F0A00      CALL MHMAIN.110CED00
1102CD77   84C0             TEST AL,AL
1102CD79   75 0A            JNZ SHORT MHMAIN.1102CD85
1102CD7B   C786 94000000 C8>MOV DWORD PTR DS:[ESI+94],0C8
1102CD85   8B86 94000000    MOV EAX,DWORD PTR DS:[ESI+94]
1102CD8B   8B5424 10        MOV EDX,DWORD PTR SS:[ESP+10]
1102CD8F   40               INC EAX
1102CD90   42               INC EDX
1102CD91   83FB 02          CMP EBX,2
1102CD94   8986 94000000    MOV DWORD PTR DS:[ESI+94],EAX
1102CD9A   8BC8             MOV ECX,EAX
1102CD9C   895424 10        MOV DWORD PTR SS:[ESP+10],EDX
1102CDA0   0F84 5D010000    JE MHMAIN.1102CF03
1102CDA6   81F9 C8000000    CMP ECX,0C8
1102CDAC   0F8E 51010000    JLE MHMAIN.1102CF03
1102CDB2   8B86 90000000    MOV EAX,DWORD PTR DS:[ESI+90]
1102CDB8   85C0             TEST EAX,EAX
1102CDBA   0F85 43010000    JNZ MHMAIN.1102CF03
1102CDC0   81F9 E02E0000    CMP ECX,2EE0
1102CDC6   0F8F AF000000    JG MHMAIN.1102CE7B
1102CDCC   83FB 01          CMP EBX,1
1102CDCF   0F84 A6000000    JE MHMAIN.1102CE7B
1102CDD5   8B4E 24          MOV ECX,DWORD PTR DS:[ESI+24]
1102CDD8   B8 67666666      MOV EAX,66666667
1102CDDD   F7E9             IMUL ECX
1102CDDF   C1FA 03          SAR EDX,3
1102CDE2   8BCA             MOV ECX,EDX
1102CDE4   C1E9 1F          SHR ECX,1F
1102CDE7   03CA             ADD ECX,EDX
1102CDE9   51               PUSH ECX
1102CDEA   8B0D 28011911    MOV ECX,DWORD PTR DS:[11190128]
1102CDF0   8D57 01          LEA EDX,DWORD PTR DS:[EDI+1]
1102CDF3   52               PUSH EDX
1102CDF4   E8 E71AFEFF      CALL MHMAIN.1100E8E0
1102CDF9   3C 01            CMP AL,1
1102CDFB   0F85 CF000000    JNZ MHMAIN.1102CED0
1102CE01   8B4E 24          MOV ECX,DWORD PTR DS:[ESI+24]
1102CE04   B8 67666666      MOV EAX,66666667
1102CE09   F7E9             IMUL ECX
1102CE0B   C1FA 03          SAR EDX,3
1102CE0E   8BC2             MOV EAX,EDX
1102CE10   C1E8 1F          SHR EAX,1F
1102CE13   03C2             ADD EAX,EDX
1102CE15   50               PUSH EAX
1102CE16   8D4F FF          LEA ECX,DWORD PTR DS:[EDI-1]
1102CE19   51               PUSH ECX
1102CE1A   8B0D 28011911    MOV ECX,DWORD PTR DS:[11190128]
1102CE20   E8 BB1AFEFF      CALL MHMAIN.1100E8E0
1102CE25   3C 01            CMP AL,1
1102CE27   0F85 A3000000    JNZ MHMAIN.1102CED0
1102CE2D   8B4E 24          MOV ECX,DWORD PTR DS:[ESI+24]
1102CE30   B8 67666666      MOV EAX,66666667
1102CE35   F7E9             IMUL ECX
1102CE37   C1FA 03          SAR EDX,3
1102CE3A   8BC2             MOV EAX,EDX
1102CE3C   C1E8 1F          SHR EAX,1F
1102CE3F   8D4C02 FF        LEA ECX,DWORD PTR DS:[EDX+EAX-1]
1102CE43   51               PUSH ECX
1102CE44   8B0D 28011911    MOV ECX,DWORD PTR DS:[11190128]
1102CE4A   57               PUSH EDI
1102CE4B   E8 901AFEFF      CALL MHMAIN.1100E8E0
1102CE50   3C 01            CMP AL,1
1102CE52   75 7C            JNZ SHORT MHMAIN.1102CED0
1102CE54   8B4E 24          MOV ECX,DWORD PTR DS:[ESI+24]
1102CE57   B8 67666666      MOV EAX,66666667
1102CE5C   F7E9             IMUL ECX
1102CE5E   C1FA 03          SAR EDX,3
1102CE61   8BC2             MOV EAX,EDX
1102CE63   C1E8 1F          SHR EAX,1F
1102CE66   8D4C02 01        LEA ECX,DWORD PTR DS:[EDX+EAX+1]
1102CE6A   51               PUSH ECX
1102CE6B   8B0D 28011911    MOV ECX,DWORD PTR DS:[11190128]
1102CE71   57               PUSH EDI
1102CE72   E8 691AFEFF      CALL MHMAIN.1100E8E0
1102CE77   3C 01            CMP AL,1
1102CE79   75 55            JNZ SHORT MHMAIN.1102CED0
1102CE7B   6A 01            PUSH 1
1102CE7D   E8 BED40300      CALL MHMAIN.1106A340
1102CE82   6A 01            PUSH 1
1102CE84   8BD8             MOV EBX,EAX
1102CE86   E8 C5F60000      CALL MHMAIN.1103C550
1102CE8B   50               PUSH EAX
1102CE8C   6A 00            PUSH 0
1102CE8E   53               PUSH EBX
1102CE8F   E8 6CD60300      CALL MHMAIN.1106A500
1102CE94   8B96 88000000    MOV EDX,DWORD PTR DS:[ESI+88]
1102CE9A   53               PUSH EBX
1102CE9B   52               PUSH EDX
1102CE9C   6A 00            PUSH 0
1102CE9E   6A 00            PUSH 0
1102CEA0   68 ACF21111      PUSH MHMAIN.1111F2AC                     ; ASCII “(iiiO)”
1102CEA5   6A 00            PUSH 0
1102CEA7   68 B0C71111      PUSH MHMAIN.1111C7B0
1102CEAC   68 A8F21111      PUSH MHMAIN.1111F2A8                     ; ASCII “cmm”
1102CEB1   68 A0F21111      PUSH MHMAIN.1111F2A0                     ; ASCII “netmap”
1102CEB6   E8 E50CFEFF      CALL MHMAIN.1100DBA0
1102CEBB   83C4 38          ADD ESP,38
1102CEBE   85DB             TEST EBX,EBX
1102CEC0   74 0E            JE SHORT MHMAIN.1102CED0
1102CEC2   FF0B             DEC DWORD PTR DS:[EBX]
1102CEC4   75 0A            JNZ SHORT MHMAIN.1102CED0
1102CEC6   8B43 04          MOV EAX,DWORD PTR DS:[EBX+4]
1102CEC9   53               PUSH EBX
1102CECA   FF50 18          CALL DWORD PTR DS:[EAX+18]
1102CECD   83C4 04          ADD ESP,4
1102CED0   8B86 90000000    MOV EAX,DWORD PTR DS:[ESI+90]
1102CED6   85C0             TEST EAX,EAX
1102CED8   75 10            JNZ SHORT MHMAIN.1102CEEA
1102CEDA   81BE 94000000 E0>CMP DWORD PTR DS:[ESI+94],2EE0
1102CEE4   0F8E E0000000    JLE MHMAIN.1102CFCA
1102CEEA   33C0             XOR EAX,EAX
1102CEEC   8986 90000000    MOV DWORD PTR DS:[ESI+90],EAX
1102CEF2   8986 94000000    MOV DWORD PTR DS:[ESI+94],EAX
1102CEF8   89AE A4000000    MOV DWORD PTR DS:[ESI+A4],EBP
1102CEFE   E9 C1000000      JMP MHMAIN.1102CFC4
1102CF03   8B86 90000000    MOV EAX,DWORD PTR DS:[ESI+90]
1102CF09   85C0             TEST EAX,EAX
1102CF0B   0F8E B9000000    JLE MHMAIN.1102CFCA
1102CF11   81F9 C8000000    CMP ECX,0C8
1102CF17   7F 14            JG SHORT MHMAIN.1102CF2D
1102CF19   837E 60 02       CMP DWORD PTR DS:[ESI+60],2
1102CF1D   75 05            JNZ SHORT MHMAIN.1102CF24
1102CF1F   83F8 04          CMP EAX,4
1102CF22   74 09            JE SHORT MHMAIN.1102CF2D
1102CF24   83F8 08          CMP EAX,8
1102CF27   0F8C 9D000000    JL MHMAIN.1102CFCA
1102CF2D   50               PUSH EAX
1102CF2E   E8 0DD40300      CALL MHMAIN.1106A340
1102CF33   8BE8             MOV EBP,EAX
1102CF35   8B86 90000000    MOV EAX,DWORD PTR DS:[ESI+90]
1102CF3B   83C4 04          ADD ESP,4
1102CF3E   33DB             XOR EBX,EBX
1102CF40   85C0             TEST EAX,EAX
1102CF42   7E 21            JLE SHORT MHMAIN.1102CF65
1102CF44   0FBE4C1C 1C      MOVSX ECX,BYTE PTR SS:[ESP+EBX+1C]
1102CF49   51               PUSH ECX
1102CF4A   E8 01F60000      CALL MHMAIN.1103C550
1102CF4F   50               PUSH EAX
1102CF50   53               PUSH EBX
1102CF51   55               PUSH EBP
1102CF52   E8 A9D50300      CALL MHMAIN.1106A500
1102CF57   8B86 90000000    MOV EAX,DWORD PTR DS:[ESI+90]
1102CF5D   83C4 10          ADD ESP,10
1102CF60   43               INC EBX
1102CF61   3BD8             CMP EBX,EAX
1102CF63  ^7C DF            JL SHORT MHMAIN.1102CF44
1102CF65   8B96 88000000    MOV EDX,DWORD PTR DS:[ESI+88]
1102CF6B   8B86 A4000000    MOV EAX,DWORD PTR DS:[ESI+A4]
1102CF71   8B8E A0000000    MOV ECX,DWORD PTR DS:[ESI+A0]
1102CF77   55               PUSH EBP
1102CF78   52               PUSH EDX
1102CF79   50               PUSH EAX
1102CF7A   51               PUSH ECX
1102CF7B   68 ACF21111      PUSH MHMAIN.1111F2AC                     ; ASCII “(iiiO)”
1102CF80   6A 00            PUSH 0
1102CF82   68 B0C71111      PUSH MHMAIN.1111C7B0
1102CF87   68 A8F21111      PUSH MHMAIN.1111F2A8                     ; ASCII “cmm”
1102CF8C   68 A0F21111      PUSH MHMAIN.1111F2A0                     ; ASCII “netmap”
1102CF91   E8 0A0CFEFF      CALL MHMAIN.1100DBA0
1102CF96   33DB             XOR EBX,EBX
1102CF98   83C4 24          ADD ESP,24
1102CF9B   3BEB             CMP EBP,EBX
1102CF9D   74 0F            JE SHORT MHMAIN.1102CFAE
1102CF9F   FF4D 00          DEC DWORD PTR SS:[EBP]
1102CFA2   75 0A            JNZ SHORT MHMAIN.1102CFAE
1102CFA4   8B55 04          MOV EDX,DWORD PTR SS:[EBP+4]
1102CFA7   55               PUSH EBP
1102CFA8   FF52 18          CALL DWORD PTR DS:[EDX+18]
1102CFAB   83C4 04          ADD ESP,4
1102CFAE   8B4424 18        MOV EAX,DWORD PTR SS:[ESP+18]
1102CFB2   899E 90000000    MOV DWORD PTR DS:[ESI+90],EBX
1102CFB8   899E 94000000    MOV DWORD PTR DS:[ESI+94],EBX
1102CFBE   8986 A4000000    MOV DWORD PTR DS:[ESI+A4],EAX
1102CFC4   89BE A0000000    MOV DWORD PTR DS:[ESI+A0],EDI
1102CFCA   8DBE A8000000    LEA EDI,DWORD PTR DS:[ESI+A8]
1102CFD0   8BCF             MOV ECX,EDI
1102CFD2   E8 291D0A00      CALL MHMAIN.110CED00
1102CFD7   84C0             TEST AL,AL
1102CFD9   75 5D            JNZ SHORT MHMAIN.1102D038
1102CFDB   57               PUSH EDI
1102CFDC   8D4E 78          LEA ECX,DWORD PTR DS:[ESI+78]
1102CFDF   C74424 14 000000>MOV DWORD PTR SS:[ESP+14],0
1102CFE7   E8 F4210A00      CALL MHMAIN.110CF1E0
1102CFEC   8B8E B4000000    MOV ECX,DWORD PTR DS:[ESI+B4]
1102CFF2   8B96 B0000000    MOV EDX,DWORD PTR DS:[ESI+B0]
1102CFF8   894E 74          MOV DWORD PTR DS:[ESI+74],ECX
1102CFFB   8BCF             MOV ECX,EDI
1102CFFD   8956 70          MOV DWORD PTR DS:[ESI+70],EDX
1102D000   E8 DB200A00      CALL MHMAIN.110CF0E0
1102D005   50               PUSH EAX
1102D006   8B46 74          MOV EAX,DWORD PTR DS:[ESI+74]
1102D009   50               PUSH EAX
1102D00A   68 98F21111      PUSH MHMAIN.1111F298                     ; ASCII “(is)”
1102D00F   6A 00            PUSH 0
1102D011   68 B0C71111      PUSH MHMAIN.1111C7B0
1102D016   68 8CF21111      PUSH MHMAIN.1111F28C                     ; ASCII “c_map_emote”
1102D01B   68 A0F21111      PUSH MHMAIN.1111F2A0                     ; ASCII “netmap”
1102D020   E8 7B0BFEFF      CALL MHMAIN.1100DBA0
1102D025   83C4 1C          ADD ESP,1C
1102D028   8BCE             MOV ECX,ESI
1102D02A   E8 B1470000      CALL MHMAIN.110317E0
1102D02F   6A 00            PUSH 0
1102D031   8BCF             MOV ECX,EDI
1102D033   E8 98250A00      CALL MHMAIN.110CF5D0
1102D038   8BCE             MOV ECX,ESI
1102D03A   E8 11560B00      CALL MHMAIN.110E2650
1102D03F   84C0             TEST AL,AL
1102D041  ^0F84 E9FBFFFF    JE MHMAIN.1102CC30
1102D047   5F               POP EDI
1102D048   5D               POP EBP
1102D049   5B               POP EBX
1102D04A   8B4C24 1C        MOV ECX,DWORD PTR SS:[ESP+1C]
1102D04E   5E               POP ESI
1102D04F   E8 4AF50C00      CALL MHMAIN.110FC59E
1102D054   83C4 1C          ADD ESP,1C
1102D057   C2 0400          RETN 4

2004年09月22日
CSDN文档中心Visual C++  
标题   利用HOOK拦截封包原理     选择自 raphyer 的 Blog
关键字   HOOK+外挂
出处   http://www.kucn.com/bbs
截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。
首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下:
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure
HINSTANCE hMod, // handle to application instance
DWORD dwThreadId // thread identifier
);
具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。
这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。

之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。

DWORD dwCurrentPID = 0;
HHOOK hOldHook = NULL;
DWORD pSend = 0;
DWORD pRecv = 0;
GETMESSAGE pGetMessage = NULL;

BYTE btNewBytes[8] = { 0×0B8, 0×0, 0×0, 0×40, 0×0, 0×0FF, 0×0E0, 0 };
DWORD dwOldBytes[3][2];

HANDLE hDebug = INVALID_HANDLE_value;

LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam )
{
DWORD dwSize;
DWORD dwPIDWatched;
HMODULE hLib;

if( dwCurrentPID == 0 )
{
dwCurrentPID = GetCurrentProcessId();
HWND hwndMainHook;
hwndMainHook = ::FindWindow( 0, “MainHook” );
dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 );
hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 );

if( dwCurrentPID == dwPIDWatched )
{
hLib = LoadLibrary( “ws2_32.dll” );
pSend = (DWORD)GetProcAddress( hLib, “send” );
pRecv = (DWORD)GetProcAddress( hLib, “recv” );

::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

hLib = LoadLibrary( “user32.dll” );
pGetMessage = (GETMESSAGE)GetProcAddress( hLib, “GetMessageA” );
::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

hDebug = ::CreateFile( “C:\\Trace.log”, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
}
}

if( hOldHook != NULL )
{
return CallNextHookEx( hOldHook, nCode, wParam, lParam );
}

return 0;
}

上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是
mov eax, 0×400000
jmp eax
这里的0×400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例:

BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax )
{
DWORD dwSize;
char szTemp[256];
BOOL r = false;

//Watch here before it’s executed.
sprintf( szTemp, “Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x \r\n”, hWnd, wMsgFilterMin, wMsgFilterMax );
::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 );
//Watch over

// restore it at first
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );

// execute it
r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax );

// hook it again
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

//Watch here after it’s executed
sprintf( szTemp, “Result of GetMessage is %d.\r\n”, r );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
if( r )
{
sprintf( szTemp, “Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X\r\nTime 0x%8.8X, X %d, Y %d\r\n”,
lpMsg->hwnd, lpMsg->message,
lpMsg->wParam, lpMsg->lParam, lpMsg->time,
lpMsg->pt.x, lpMsg->pt.y );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
}
strcpy( szTemp, “\r\n” );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );

//Watch over

return r;
}

先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。
整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,但这个我没有试验过。

2004年09月19日

标 题:keymaker原理-INT 3的插入 发信人:softdim
时 间:2002-9-2 22:01:06
详细信息:


记得上次问树袋熊大哥,大哥回答得挺详细,但我一直想试一试INT 3断点是如何插入的。经过两天的折腾,终于弄明白了,不敢独享,大哥见笑啦。下面是分析:
目标:英语会话精灵2.0(topbar.exe)注册机
使用工具:IDA4.15,Softice
程序使用upx加壳,有改动!部分资源经过XOR加密(文件尾部)废话少说,切入正题。
为节省篇幅,只分析其原理:(假设已经知道我们要插入断点的地址–即已经跟踪出注册码地址)
<1>使用CreateProcessA加载要调试的程序topbar.exe,获得句柄hThread
循环使用ReadProcessMemory读取topbar.exe进程地址addr1(47a057),如果此处指令为我们预设指令,则
使用子程序(sub1)修改此处指令为INT 3(CCH),并保存原指令(5byte)后等待中断发生。
sub1 流程
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
dwLength=1Ch
lpBuffer-->PMEMORY_BASIC_INFORMATION(dwLength=1C byte)
lpAddress–>addr1
hProcess–>
invoke  VirtualQueryEx,hProcess,lpAddress,lpBuffer,dwLength(Kernel32.lib)

lpflOldProtect–>
flNewProtect=4;
dwSize–>1000h
lpAddress–>47a000
hProcess–>
invoke  VirtualProtectEx,hProcess,lpAddress,dwSize,flNewProtect,lpflOldProtect
lpNumberOfBytesWritten=0
nSize=5
lpBuffer–>读取缓冲
lpBaseAddress–>addr1
hProcess–>
invoke ReadProcessMemory,hProcess,lpBaseAddress,lpBuffer,nSize,lpNumberOfBytesWritten
lpflOldProtect–>
flNewProtect=flOldProtect;
dwSize–>1000h
lpAddress–>47a000
hProcess–>
invoke  VirtualProtectEx,hProcess,lpAddress,dwSize,flNewProtect,lpflOldProtect
<2>
&&&&&&&&&&&&&&&&&&&&&&&&&&&
中断发生后,由调试程序(注册机)接管,使用GetThreadContext取得topbar.exe的CONTEXT上下文
读取CONTEXT STRUCT的offset B8h即regEip的值,比较是否程序在此中断,此时regEip的值应为addr1+1,将regEip
减1,(即恢复IP指针)使用SetThreadContext保存修改了的CONTEXT结构
指针是恢复了但被修改的指令并没有恢复,因此下一步就是恢复原来的指令:调用sub2
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
sub2 流程
dwLength=1Ch
lpBuffer-->PMEMORY_BASIC_INFORMATION(dwLength=1C byte)
lpAddress–>addr1
hProcess–>
invoke  VirtualQueryEx,hProcess,lpAddress,lpBuffer,dwLength(Kernel32.lib)
lpflOldProtect–>
flNewProtect=4;
dwSize–>1000h
lpAddress–>47a000
hProcess–>
invoke  VirtualProtectEx,hProcess,lpAddress,dwSize,flNewProtect,lpflOldProtect
lpNumberOfBytesWritten=0
nSize=5
lpBuffer–>写缓冲指向保存好的指令
lpBaseAddress–>addr1
hProcess–>
invoke WriteProcessMemory,hProcess,lpBaseAddress,lpBuffer,nSize,lpNumberOfBytesWritten
lpflOldProtect–>
flNewProtect=flOldProtect;
dwSize–>1000h
lpAddress–>47a000
hProcess–>
invoke  VirtualProtectEx,hProcess,lpAddress,dwSize,flNewProtect,lpflOldProtect
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
<3>
现在指令也恢复了,被调试程序可以正常运行了,完了吗,不对,忘了我们要干什么啦,好的,
下一步就是读取注册码(假设我们已经知道注册码的位置,在此程序中我们修改的指令的上一条指令POP EAX的EAX中存放
有注册码,见附源程序片段):
使用GetThreadContext取得topbar.exe的CONTEXT 结构上下文,
lpContext->CONTEXT 结构
ContextFlags=10007h ; CONTEXT_FULL=CONTEXT_CONTROL OR CONTEXT_INTEGER OR CONTEXT_SEGMENTS
invoke GetThreadContext,hThread,lpContext
此时CONTEXT ->B0h即regEax即为注册码的地址,将其存入CONTEXT–>B8h,然后使用ReadProcessMemory读出注册码.
CONTEXT STRUCT
  ContextFlags  DWORD      ?
  iDr0          DWORD      ?
  iDr1          DWORD      ?
  iDr2          DWORD      ?
  。。。
  。。。
  regEdx        DWORD      ?
  regEcx        DWORD      ?
  regEax        DWORD      ?  ;offset B0
  regEbp        DWORD      ?
  regEip        DWORD      ?  ;offset B8
  regCs        DWORD      ?
  regFlag      DWORD      ?
  regEsp        DWORD      ?
  regSs        DWORD      ?
  ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
××××××××××××××××××
topbar.exe源程序片段:
:0047A048 8B45F4                  mov eax, dword ptr [ebp-0C]
:0047A04B 8D55F8                  lea edx, dword ptr [ebp-08]
:0047A04E E89DE7F8FF              call 004087F0
:0047A053 8B55F8                  mov edx, dword ptr [ebp-08]
:0047A056 58                      pop eax---》》》注册码
:0047A057 E8489EF8FF              call 00403EA4
:0047A05C 0F853D010000            jne 0047A19F
:0047A062 6840000400              push 00040040
:0047A067 B908A24700              mov ecx, 0047A208

* Possible StringData Ref from Code Obj ->”successs!”
                                  |
:0047A06C BA18A24700              mov edx, 0047A218
结论:通过上面的分析,现在我就可以写出自己的注册机,实际上我已经写了。使用的就是看雪学院下载的win32asm教程第28-29篇的debug篇的一个小程序(只需稍加改动,加入我的代码即可)当然,要想向树袋熊大哥一样写出那么漂亮的界面,那得下一翻大功夫.
如有想试牛刀者,Eamil to me softdim@vip.sina.com,英语会话精灵2.0(topbar.exe)及其注册机文件都不大,是一个很好的学习例子。

2004年09月03日

.386
  .model flat,stdcall
  option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .const
szMsg  db ‘异常发生位置:%08X,异常代码:%08X,标志:%08X’,0
szSafe  db ‘回到了安全的地方!’,0
szCaption db ‘SEH例子’,0

  .code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 错误 Handler
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
 
_Handler proc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
  local @szBuffer[256]:byte

  pushad
  mov esi,_lpExceptionRecord
  mov edi,_lpContext
  assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
  invoke wsprintf,addr @szBuffer,addr szMsg,\
   [edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
  invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK

;********************************************************************
; 将 EIP 指向安全的位置并恢复堆栈
;********************************************************************
  mov eax,_lpSEH
  push [eax + 8]
  pop [edi].regEip
  push [eax + 0ch]
  pop [edi].regEbp
  push eax
  pop [edi].regEsp
  assume esi:nothing,edi:nothing
  popad
  mov eax,ExceptionContinueExecution
  ret
 
_Handler endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test  proc

;********************************************************************
; 在堆栈中构造一个 EXCEPTION_REGISTRATION 结构
;********************************************************************
  assume fs:nothing
  push ebp
  push offset _SafePlace
  push offset _Handler
  push fs:[0]
  mov fs:[0],esp

 
;********************************************************************
; 会引发异常的指令
;********************************************************************
  pushad
  xor ebp,ebp
  xor eax,eax
  mov dword ptr [eax],0
  popad  ;这一句将无法被执行
_SafePlace:
  invoke MessageBox,NULL,addr szSafe,addr szCaption,MB_OK
 
;********************************************************************
; 恢复原来的 SEH 链
;********************************************************************
  pop fs:[0]
  add esp,0ch
  ret

_Test  endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
  invoke _Test
  invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start