首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第16期->技术专题
作者:Jim Mauro < mail unknown >
整理:小四 < mailto: scz@nsfocus.com >
出处:The dynamic Solaris kernel
主页:http://www.nsfocus.com
日期:2000-12-14
概述:
Solaris提供了一种强大的特性,在不重新编译内核、不重新启动系统的情况下,
允许动态加载新的文件系统、增加系统调用、压入流模块以及加入新的设备驱动
程序。
--------------------------------------------------------------------------
传统Solaris采用单一的二进制内核映象文件,该文件一般位于根文件系统的指
定目录下,比如我的这台测试机上就是/platform/sun4u/kernel/unix,进一步的
细节可以通过man -s 1M kernel、man -s 1M boot了解。后来版本的Solaris开始支
持动态可加载模块(DLKM)机制,一个位于内核中的运行时链接器负责将DLKM动态加入
到当前内核映象。
许多主要的Solaris内核子系统调用一组更底层的服务子例程,这使得Solaris内
核架构看上去类似应用层的客户机/服务器模式。大多数底层服务子例程对于应用层
软件开发不可见(没有提供正规调用接口),调用它们的一般是其它内核子系统,而不
是应用程序。这里有个例外,Solaris为设备驱动程序以及内核流模块编写预留接口,
并提供了相关文档,这就是DDI/DKI规范(Device Driver Interface/Driver Kernel
Interface)。可以man -s 9 intro了解更多细节。内核模块加载演示了这种接口。
动态模块加载主要分以下几步:
. 将模块的静态文件(一个二进制目标文件)读入内存
. 为模块各个段建立内核地址空间映射
. 链接模块各个段进入内核
. 执行相应模块类型的初始化函数
/usr/include/sys/modctl.h文件里定义了可加载模块类型,包括设备驱动程序、系
统调用、文件系统、混杂流模块、可执行模块以及调度类等等。每种模块类型都有其
特殊的进一步加载步骤。
加载模块进入内核类似Solaris操作系统环境下动态链接程序启动后所触发的事件,
执行过程中,程序所用到的共享库动态加载并链接到相应的进程地址空间。
Solaris内核作为一个大的、可执行的、动态链接的二进制映象存在于内存中,
本质上与那些运行中的普通进程并无区别,同样拥有文本段(指令)、数据段(已初始
化、未初始化的各种数据)以及一个堆栈段。下图演示了读取模块静态文件进入内存、
在内核地址空间(和普通应用程序的进程地址空间相对)为模块的文本段、数据段建立
映射关系:
+--------------+
| |
| +--------+<----------------------------------+--------+
|/ | | | modload: | |
/ | Kernel | | . read the object into | static |
/| | .text | | memory | .text | executable binary
/ | | | | | | object segments
| +--------+<----------------------------------+--------+
内 | | | | . create kernel address | |
| | Kernel | | space mappings | static |
核 | | .data | | | .data |
| | | | | |
地 | +--------+<----------------------------------+--------+
| | | | . map the object's segments | |
址 | | Kernel | | into the kernel | static |
| | .stack | | | .stack |
空 | | | | | |
| +--------+<----------------------------------+--------+
间 |/ | . the new module is now \ \
/ | part of "the kernel" +-----\--------\-------------+
/| ... ... | / \ \ \
/ | | /.../kernel/<directory>/<object> \
| ... ... | \ /
| | | \ / |
+--------------+ | +----------------------------+ |
|\ | | /|
物理内存 | \| |/ |
| +----------------------------+ |
| | | |
on-disk kernel object
(kernel module)
图 1. 内核模块加载
faint,不得不承认我并非好的ASCII图像设计者,为了完全采用.txt格式整理这篇文
章,被迫"画"这个图。
系统启动时根据/etc/system里moddir参数设置的模块搜索路径加载那些必需模块,
moddir参数类似PATH环境变量,启动代码依靠它定位相应的内核对象(比如模块)。具
体细节可以man -s 4 system获取,也可以直接查看/etc/system文件中的例子。
内核函数modload()用于加载模块,其中主要涉及到两个数据结构,一个是modctl(模
块控制)结构,一个是module结构,分别在/usr/include/sys/modctl.h文件和
/usr/include/sys/kobj.h文件中定义。内核维护了一个modctl结构链表,所有加载
进入内核的模块都在这个链表上,如图所示:
modules.mod_next (全局变量 extern struct modctl modules;)
~~~~~~~~~~~~~~~~
|
+->+-----------------------+<------+ +------->+-----------------------+
| mod_next ---------------------|------------------------+ | mod_next |
| mod_prev | +---------------------------------- mod_prev |
| mod_id | | mod_id |
| mod_mp ----------------------------------------->+-----------+ | mod_mp |
| mod_inprogress_thread | | ELF info | | mod_inprogress_thread |
| mod_modinfo ------------------->+-------------+ | symsize | | mod_modinfo |
| mod_linkage --------------+ | mod_modinfo | | text_size | | mod_linkage |
| mod_filename | | +-------------+ | data_size | | mod_filename |
| mod_modname | +---->+-------------+ | text | | mod_modname |
| mod_busy | | modlinkage | | data | | mod_busy |
| mod_stub | +-------------+ | symtbl | | mod_stub |
| mod_loaded | | ... ... | | mod_loaded |
| mod_installed | +-----------+ | mod_installed |
| mod_loadflags | module structure | mod_loadflags |
| mod_want | | mod_want |
| mod_requisites | | mod_requisites |
| mod_dependents | | mod_dependents |
| mod_loadcnt | | mod_loadcnt |
+-----------------------+ +-----------------------+
modctl structure modctl structure
图 2. 内核模块控制结构(这是一个双向循环链表)
内核中全局变量modules位于模块控制结构双向循环链表的首部。事实上借助mod_id
成员(模块标识),mod_modinfo结构和modlinkage结构也可以通过这个链表进行遍历。
mod_mp成员指向module结构,该结构在/usr/include/sys/kobj.h中定义,内核运行
时链接器以及模块加载机制将使用module结构。mod_filename指向静态模块文件所在
路径名(包括文件名,不一定是完整绝对路径),mod_modname指向去掉目录信息后的
静态模块文件名,modinfo命令的最后一列信息中的模块名来自这里,模块描述信息
来自类型相关的联接结构(modlxxx)。一个给定内核模块可能依赖其他内核模块的存
在(比如为自己提供服务子例程),也有可能其他内核模块依赖自身的存在。比如,各
个系统5内部进程通信(IPC)模块(共享内存、信号灯、消息队列)都依赖于内核IPC模
块。
[root@ /platform/sun4u/kernel]> modinfo | grep ipc
72 1024c678 43c - 1 ipc (common ipc code)
[root@ /platform/sun4u/kernel]> modinfo | grep sem
80 1025a874 29c8 53 1 semsys (System V semaphore facility)
80 1025a874 29c8 53 1 semsys (32-bit System V semaphore facil)
[root@ /platform/sun4u/kernel]> modinfo | grep share
71 1024a520 28e0 52 1 shmsys (System V shared memory)
71 1024a520 28e0 52 1 shmsys (32-bit System V shared memory)
依赖来自模块代码,依赖关系在modctl结构中由两个struct modctl_list *类型的成
员指定,mod_requisites指定了本模块运行中所依赖的其它模块,mod_dependents指
定了依赖本模块存在的其它模块,这是两个链表,遍历链表就可以获取完整的依赖关
系。
内核模块加载机制是多线程的,在系统启动或者执行modload命令(man -s 1M modload)
的时候,内核modload()函数被调用,它将创建一个内核线程完成整个模块加载。如
果在多处理器系统上,采用线程机制就可以支持多个模块并发加载,此外线程机制加
快了系统启动和初始化。一旦内核线程创建成功,将做如下一系列动作完成模块的加
载和链接:
--------------------------------------------------------------------------
1. 分配内存创建一个模块控制结构(modctl)。首先遍历modctl结构链表,匹配
mod_modname成员,如果找到匹配,内核线程返回已存在的结构地址,否则创建一
个新的modctl结构,并增加到链表中。
2. 进入内核运行时链接器krtld,在内核地址空间创建各个段,绑定后读取静态目标
文件进入内存:
. 如图2所示,分配内存创建一个module结构。
. 在内核的kobj_map资源图中为模块符号分配空间。
. 为.text和.data等建立内核地址空间中的映射关系。
. 将模块各个段链接到内核。
3. 设置modctl结构的mod_loaded成员,对mod_loadcnt增一。
4. 建立一个到模块的modlinkage结构的链接(通过成员mod_linkage)。
5. 寻找模块的_init()函数并调用它,间接执行模块的mod_install()函数。
--------------------------------------------------------------------------
在模块加载流程中涉及到的内核子系统主要是模块机制和内核运行时链接器(krtld),
早在系统启动时krtld就加载了。当mod_loaded成员复位时,modctl结构依旧保持在
链表中,如果一个模块先被加载,又卸载,其对应的modctl结构将保持,依靠
mod_loaded状态标志判断模块是否还在内存中。现在可以理解上述步骤中第一步为什
么遍历modctl结构链表匹配mod_modname成员。内存里modctl结构存在并不意味着对
应模块已经加载到内存中。
Solaris可加载内核模块要求内核符号表做为一个动态实体存在,内核符号表在上述
第二步中建立。所有可执行目标文件都有一张符号表,保存了符号名(比如函数名、
变量名)和虚拟地址之间的对应关系,解析目标文件中符号引用时需要这些信息。
Solaris通过伪设备/dev/ksyms和对应的设备驱动程序/usr/kernel/drv/ksyms维护内
核符号表。
Solaris 2.7中,专门有一个内核线程ksyms_update_thread()负责刷新内核符号表。
模块加载/卸载时,内核运行时链接器krtld唤醒ksyms_update_thread(),内核符号
表被刷新成当前内核对象相关状态。Solaris 2.5/2.6中,刷新机制不同。模块加载
/卸载时更新一个内核变量,设备驱动程序/usr/kernel/drv/ksyms中的ksyms_open()
函数会检查该变量以确定是否需要刷新内核符号表。这存在一个问题,如果一个用户
程序调用了ksyms_open()函数,正在观察内核符号表,此时一个内核模块被加载或者
卸载,当前观察到的内核符号表并不能反映这种变化,为了观察刷新后的内核符号表,
必须先关闭再打开。nm(1)命令用于观察对象的符号表,也可用于通过/dev/ksyms观
察内核符号表。
[root@ /platform/sun4u/kernel]> /usr/ccs/bin/nm -x /dev/ksyms | grep modload
[1785] |0x0000100fbb38|0x00000000009c|FUNC |LOCL |0 |ABS |modctl_modload
[9093] |0x0000100fca7c|0x0000000000a8|FUNC |GLOB |0 |ABS |modload
[1592] |0x0000100fcce0|0x0000000000d0|FUNC |LOCL |0 |ABS |modload_now
[1090] |0x0000100fcb24|0x0000000000c8|FUNC |LOCL |0 |ABS |modload_thread
[9182] |0x0000100fcdb0|0x0000000000bc|FUNC |GLOB |0 |ABS |modloadonly
[root@ /platform/sun4u/kernel]>
上例演示在内核符号表中搜索内核函数modload(),命令返回了多个匹配,其中包括
我们要找的modload()符号名。如果想了解更多关于符号表的信息,参看nm(1)、
a.out(4)和elf(3E) man手册,也可以直接查看ELF文件格式规范(相当晦涩难懂)。下
面引述部分以前翻译的<<ELF文件格式规范解析>>:
--------------------------------------------------------------------------
* st_name
4字节,该值是一个索引值,用于在目标文件的symbol string table中定位一个串,
该串即符号名。如果该值非零,代表一个string table index,否则该符号表表项没
有名字。
注意,C语言的外部符号在目标文件符号表里拥有C源程序中同样的名字
* st_value
4字节,该成员给出了符号值。对于可执行文件或者动态链接库,st_value存放一个
虚拟地址,动态链接器会用到该值。
* st_size
4字节,STT_OBJECT符号的st_size就是数据对象本身的字节大小。如果一个符号没有
大小或者大小未知,该成员为零。
* STT_FUNC
该符号与函数或其他可执行代码相关。这种符号对于动态链接库有特殊意义,当其他
目标文件引用了一个动态链接库中的函数时,链接器自动为被引用的函数符号创建一
个procedure linkage table entry(过程链接表项)。动态链接库中其他非STT_FUNC
符号不会自动通过PLT被引用。
* STB_LOCAL
局部符号对于其他目标文件是不可见的,同名局部符号可以存在于多个目标文件中而
不互相干扰。局部符号总是优先于全局符号、弱符号。
在前面介绍节的时候提到了,一个symbol table section header(此时sh_type等于
SHT_SYMTAB)的sh_info成员指明了第一个非局部符号的符号表索引。注意理解,局部
符号总是先于全局符号、弱符号出现在符号表中的。
* STB_GLOBAL
全局符号对参与链接的所有目标文件都是可见的,此时只要其中一个目标文件中定义
有该符号的值即可,其他目标文件只需简单引用全局符号。
* SHN_ABS
相关符号有一绝对值,并不随重定位操作而改变。
--------------------------------------------------------------------------
上述第五步中我们提到,通过调用模块的_init()函数间接激活模块安装代码。有几
个函数必须出现在可加载内核模块中,比如_init()、_info()、_fini()。设备驱动
程序和流模块必须按照动态加载方式编码。一般来说,很多函数和数据结构对于所有
可加载内核模块都适用,不仅仅适用于设备驱动程序和流模块。然而,还存在一些设
备驱动程序特有的组件,不适用于可加载的系统调用模块、文件系统模块、调度类模
块。这里是三个相关man手册,_init(9E)、_info(9E)和_fini(9E)。模块被加载到内
存中,为了完成最后的加载动作,调用模块的_init()函数。_info()和_fini()函数
也各自激活相应的内核模块管理接口,如表1所示:
+-----------------------+-----------------+-----------------------------+
| Kernel Module Routine | Module Facility | Interface Description |
+-----------------------+-----------------+-----------------------------+
| _init() | mod_install() | Load a kernel module |
| _info() | mod_info() | Retrieve module information |
| _fini() | mod_remove() | Unload a kernel module |
+-----------------------+-----------------+-----------------------------+
表 1. 模块管理接口
模块安装被抽象成一组结构和内核接口。表1中三个模块操作函数指针维护在mod_ops
结构里。每种类型的可加载模块都会扩展该结构,比如可加载系统调用模块有一个
mod_installsys()函数,可加载设备驱动程序有一个mod_installdrv()函数。
mod_ops结构定义在/usr/include/sys/modctl.h文件中。
每种类型的模块都对应有一个模块联接结构(不是struct modlinkage),其中定义了
指向struct mod_ops的指针、一个字符指针(指向的字符串用于描述本模块)、一个模
块类型相关的结构指针。例如,可加载系统调用模块对应的这个模块联接结构:
/* For system calls */
struct modlsys
{
struct mod_ops *sys_modops;
char *sys_linkinfo;
struct sysent *sys_sysent;
};
最后这个指针sys_sysent指向系统调用表,系统调用表里包含所有系统调用入口。每
个可加载内核模块都需要声明并初始化这种模块类型相关的模块联接结构,在
mod_linkage成员指向的struct modlinkage中建立这种关联。仔细研究
/usr/include/sys/modctl.h文件,注意理解上面介绍的种种关系。
--------------------------------------------------------------------------
/*
* Macros to vector to the appropriate module specific routine.
*/
#define MODL_INSTALL(MODL, MODLP) \
(*(MODL)->misc_modops->modm_install)(MODL, MODLP)
#define MODL_REMOVE(MODL, MODLP) \
(*(MODL)->misc_modops->modm_remove)(MODL, MODLP)
#define MODL_INFO(MODL, MODLP, P0) \
(*(MODL)->misc_modops->modm_info)(MODL, MODLP, P0)
--------------------------------------------------------------------------
在模块机制中,mod_install()函数会通过MODL_INSTALL宏调用模块类型相关的安装
函数(比如mod_installsys()函数),于是从模块的_init()函数到最终模块类型相关
的安装函数构成了图3:
+--------------------------------------------+
| |
| modlinkage | +---------------------+
| +--------------+ | /| mod_installdrv() |
| | ml_linkage[]-|---+ | / +---------------------+
| +--------------+ | | / | mod_installsys() |
| +-----------------+ | / +---------------------+
| | modlxxx | / | mod_installfs() |
| +->+--------------+ mod_xxxops | +--> +---------------------+
| | xxx_modops --|-->+----------------+ | | \ | mod_installstrmod() |
| +--------------+ | mod_installxxx | | | \ +---------------------+
| | mod_removexxx | | | \ | mod_installsched() |
| | mod_infoxxx | | | \ +---------------------+
| +----------------+ | | \| mod_installexec() |
| | | +---------------------+
| _init(): | | 模块类型相关安装函数
| mod_install(): ---->-----+ | |
+------------------------------|-------------+ |
一个可加载内核模块 | |
| |
| |
+------|-----------------|------+
| mod_install(): | | 这个宏将被解析成模块
| MODL_INSTALL(&modlinkage) | 类型相关的安装函数
+-------------------------------+
内核模块支持代码
图 3. 内核模块操作函数映射关系
图 3 展示了可加载内核模块中的数据结构:通过modlinkage结构的ml_linkage[]成
员定位类型相关的联接结构(modlxxx),其第一个成员xxx_modops指向类型相关的
mod_xxxops结构(类型都是struct mod_ops),其中定义了类型相关的安装、删除、模
块信息函数。由于modlinkage结构地址做为参数传递给MODL_INSTALL()宏,该宏可以
通过上图所示关系最终调用模块类型相关的安装函数,其他宏类似。模块类型相关的
安装步骤如表2所示:
+--------------+-------------------+---------------------------------------------------+
| 模块类型 | 安装函数 | 简单描述 |
+--------------+-------------------+---------------------------------------------------+
| 设备驱动程序 | mod_installdrv | 封装了ddi_installdrv()函数,在内核devops table中 |
| | | 安装设备驱动程序入口 |
+--------------+-------------------+---------------------------------------------------+
| 系统调用 | mod_installsys | 在内核sysent table中安装系统调用入口 |
+--------------+-------------------+---------------------------------------------------+
| 文件系统 | mod_installfs | 在内核虚拟文件系统表(VFS table)中安装文件系统入口 |
+--------------+-------------------+---------------------------------------------------+
| 流模块 | mod_installstrmod | 在内核fmodsw switch table中安装流入口 |
+--------------+-------------------+---------------------------------------------------+
| 调度类 | mod_installsched | 在内核sclass array中安装调度类 |
+--------------+-------------------+---------------------------------------------------+
| 可执行模块 | mod_installexec | 在内核execsw switch table中安装可执行入口 |
+--------------+-------------------+---------------------------------------------------+
表 2. 模块安装子例程
表2的最后一列简单介绍了各个类型模块安装函数的实际操作。内核在许多子系统中
实现一种switch table机制,用于定位确定的某文件系统、调度类、可执行模块的内
核函数。
可以用modload(1M)命令确定当前系统中加载了哪些模块:
[root@ /platform/sun4u/kernel]> modinfo | more
Id Loadaddr Size Info Rev Module Name
9 101141c0 29660 2 1 ufs (filesystem for ufs)
10 10138738 10d68 226 1 rpcmod (RPC syscall)
11 10146f00 2e478 0 1 ip (IP Streams module)
11 10146f00 2e478 3 1 ip (IP Streams device)
15 1016d7fc 1bf0 12 1 sad (Streams Administrative driver's)
16 1016ed9c 8fb 2 1 pseudo (nexus driver for 'pseudo')
24 1018fb70 1caca 5 1 procfs (filesystem for proc)
29 101c5598 19c34 2 1 tcp (TCP Streams module)
29 101c5598 19c34 42 1 tcp (TCP Streams device)
30 101d8b18 11f5 - 1 md5 (MD5 Message-Digest Algorithm)
31 101d9af8 52f8 3 1 udp (UDP Streams module)
31 101d9af8 52f8 41 1 udp (UDP Streams device)
32 101dd640 46d0 4 1 icmp (ICMP Streams module)
32 101dd640 46d0 5 1 icmp (ICMP Streams device)
33 101e0598 62eb 5 1 arp (ARP Streams module)
33 101e0598 62eb 44 1 arp (ARP Streams driver)
46 101fe744 499e 1 1 elfexec (exec module for elf)
46 101fe744 499e 0 1 elfexec (32-bit exec module for elf)
47 10202740 131d 13 1 mm (memory driver)
52 102164c8 1a70 26 1 ptsl (tty pseudo driver slave 'ptsl')
53 102176c0 2777 25 1 ptc (tty pseudo driver control 'ptc')
58 1022500c 52ab 105 1 tl (TPI Local Transport Driver - tl)
61 10223890 50b 42 1 pipe (pipe(2) syscall)
61 10223890 50b 42 1 pipe (32-bit pipe(2) syscall)
65 1023bd30 1198 12 1 fdfs (filesystem for fd)
66 1023c880 1026 90 1 kstat (kernel statistics driver)
67 1023d460 163d2 11 1 tmpfs (filesystem for tmpfs)
68 10242690 4a85 201 1 doorfs (doors)
68 10242690 4a85 201 1 doorfs (32-bit door syscalls)
69 10246398 19a0 4 1 namefs (filesystem for namefs)
70 10247624 39c2 127 1 pm (power manager driver v1.65)
71 1024a520 28e0 52 1 shmsys (System V shared memory)
71 1024a520 28e0 52 1 shmsys (32-bit System V shared memory)
72 1024c678 43c - 1 ipc (common ipc code)
74 10256ca4 1eca - 1 bootdev (bootdev misc module)
78 1025a188 c8b 134 1 power (power driver v1.4)
80 1025a874 29c8 53 1 semsys (System V semaphore facility)
80 1025a874 29c8 53 1 semsys (32-bit System V semaphore facil)
82 10229934 1fd 2 1 IA (interactive scheduling class)
85 10223a8c 12c3 24 1 pts (Slave Stream Pseudo Terminal dr)
87 1022998c 43d 14 1 redirmod (redirection module)
88 102246c8 c28 72 1 ksyms (kernel symbols driver)
89 1010fcbc 1e12 15 1 telmod (telnet module)
[root@ /platform/sun4u/kernel]>
做为演示,上述列表删减了不少。第一列是模块号,第二列是模块被链接到的内核空
间虚拟地址,第三列是以16进制表示的以字节为单位的模块大小,第四列是模块相关
的信息,第五列是版本修订号,最后一列是模块名以及模块描述。modload(1M)用于
手工加载一个模块进入运行中的内核系统,modunload(1M)用于解除模块与内核之间
的链接关系并从内核中删除模块。系统拒绝卸载一个处在"忙"状态的模块。
正如我们所看到的,动态加载内核模块主要由两个内核子系统----模块管理代码和内
核运行时链接器----支持。这些内核组件利用到其他一些内核服务,比如
kernel memory allocator、kernel locking primitives、kernel ksyms driver等
等。可加载内核模块技术演示了早些时候讨论过的分层模型。
--------------------------------------------------------------------------
作者简介:
Jim Mauro是Sun微系统公司东北区技术主管,主要研究方向是服务系统、集群以
及高可用性,拥有18年的产业经验,目前从事Unix内核、管理方面教育工作。
--------------------------------------------------------------------------
作者头像:
begin 666 author.bmp
M0DWN!````````#X````H````3````&0````!``$``````+`$``!T$@``=!(`
M`````````````````/___P#______[_____P``#______IS____P``#_____
M_@#____P``#______`1____P``#_____\``____P``#_____^``____P``#_
M_W__X``____P``#__O__P``____P``#_____```____P``#____\```____P
M``#_^__X```?___P``#____P```?___P``#_]__P```'___P``#_Y__````!
M___P``#_Y_\`````?__P``#_S_^`````/__P``#_S_]`````#__P``#_S_^`
M````!__P``#_C_X````!@__P``#_C_X````'(__P``#_C_T````',__P``#_
MC_@````'<__P``#_C_`````>R__P``#_C_`````?T?_P``#_S_````!_<?_P
M``#___```@!_(?_P``#___``%>1^^?_P``#___``'_S^.?_P``#___@`#_P_
M^?_P``#___@`'_P`:?_P``#_S]@#__P``?_P``#_AP@/__\``/_P``#_A\@7
M__^``/_P``#_A^@/___EL/_P``#_A_@/___WB/_P``#_#_@'____@?_P``#_
M'_@?____@__P``#^/_D____^`__P``#^/_D____X`?_P``#^?]A____P#__P
M``#\/_C____S___P``#\?OA____7___P``#\_GA____G___P``#X[WA_____
M___P``#Y<W!________P``#Y>'!__^_____P``#Q?/!__^_____P``#Q?_#_
M_^O____P``#A?^!__\_____P``#A-\#__T_X___P``#A,X#_^,__?__P``#@
M!P!__$_____P``#@'``__`Q____P``#`<``_@#`?\/_P``#```!__\0!=/_P
M``#```!___>!Y`WP``#```!___^#X?_P``#```!____A^?_P``#````___^'
M^'_P``#````___X/_'_P``#````___@'_'_P``#````____?_W_P``#````_
M_______P``#````________P``#```!________P``#```!________P``#`
M``!________P``#@``!________P``#P``!________P``#P``!________P
M``#X```________P``#X```________P``#\```________P``#\```_____
M___P``#^```________P``#_```________P``#_@``________P``#_@``_
M_______P``#_P``____^___P``#_\``____^___P``#_^``____R___P``#_
M_``#___@___P``#__@``__\`___P``#__X``/_@!___P``#__^`````#___P
M``#___@````'___P``#___\````____P``#____X``/____P``#______#__
M___P``#____________P``#____________P``#____________P``#_____
M_______P``#____________P``#____________P``#____________P``#_
M___________P``#____________P``#____________P``#____________P
"```
`
end
--------------------------------------------------------------------------
<完>
版权所有,未经许可,不得转载