2005年03月30日

  PE文件中呼叫另一模块中的函数的运行机制

要点:PE文件中,当呼叫另一模块中的函数,编译器产生的CALL指令并不会把控制权直接传给DLL中的函数,而是传给一个JMP
DWORD PTR[xxxxxxxx]
指令。

呼叫外部的DLLs,其实并不是直接呼叫DLL本身,而是跳到一块存放有JMP
DWORD PTR [XXXXXXXX]
指令的存储区域去(可能放在.text或是.icode)。不过若在VC++中使用__declspec(dllimport)进行函数呼叫,编译器不会在模块的其他地方产生JMP指令,而是直接就会产生CALL DWORD PTR[XXXXXXXX]。不论哪种情况,JMPCALL指令中的位址都存放在.idata节的一个DWORD
值中(这个DWORD内含该函数的真正位址,即函数入口点)。JMPCALL指令会把控制权转给该位址。.

一个PE文件呼叫imported function的图示( User32.dll中的GetMessage函数)

上述图示中的偏移地址BFC0847D才是真正指向User32.dll模块中的GetMessage()函数。

   为什么DLL的呼叫需要以此方式实现?

原因是把对同一个DLL函数的所有呼叫都集中到一处,加载器就不再需要修补每一个呼叫DLL的指令。PE加载器要做的,只是把DLL函数的真实位址放到.idata的那个DWORD之中,根本就没有程序码需要修改。(不象NE文件的每一个节段内含一串待修正记录fixup records)。PE文件这种处理方式的缺点:不能够以DLL函数的真正位址初始化一个变量,如:FARPROC pfnGetMessage=GetMessage ;

关于引入表中OrignalFirstThunkFirstThunk两值的作用?

系统在程序初始化时,根据OrignalFirstThunk的值找到函数名,然后调用GetProcAddress函数,根据函数名取得函数的入口地址,然后用函数入口地址取代FirstThunk指向的地址串中对应的值。有的程序OrignalFirstThunk的值为0,则初始化时系统根据FirstThunk的值找到指向函数名的地址串,由地址串找到函数名,再根据函数名得到入口地址,然后用入口地址取代FirstThunk指向的地址串中的原值。

根据这个说明可以看出,在添加新的引入函数的时候,真正重要的是FirstThunk处的值,OrignalFirstThunk填不填无关紧要,要填的话,就得填入一个真正有效的值,不然就使其为0

为什么由两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构呢?第一个Characteristics是单独的一项,而且不可改写,它有时被称为提示名表(Hint Name Table。第二个数组(FirstThunk所指)是由PE装入器重写的。装载程序迭代搜索数组中的每一个指针,找到每一个IMAGE_IMPORT_BY_NAME结构所指的输入函数的地址,然后装载器找到程序的地址改写IMAGE_IMPORT_BY_NAME指针。Jmp dword ptr [xxxxxxxx]中的[xxxxxxxx]是指First Thunk数组中的一个入口。因为它被称为输入地址表(Import Address Table


1DOS MZ header   (64(0×40)个字节,偏移0×00—0×3F)

这是一个常规的实模式下的DOS文件头,为的是保持和DOS的兼容。从编程角度看,DOS MZ header 又定义成结构IMAGE_DOS_HEADER 。查询windows.inc,我们知道
IMAGE_DOS_HEADER 结构的e_magic成员应包含字符“MZ,e_lfanew成员就是指向 PE header 的文件偏移量。

2, DOS
stub  
(一般为112(0×70)个字节,偏移0×40—0xAF,但不是一定的)

这是个DOS的代理程序块,具体内容根据链接参数决定,不过大多数是一个简单的显示语句,如:This
Program is intended to run under Windows System

3PE header  (4+20+224=248字节,其各成员可表示为e_lfanew+偏移量)

PE header PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ header 中找到 PE header 的起始偏移量(一般是0×3C处)。因而跳过了 DOS stub 直接定位到真正的文件头 PE header从编程角度看,PE header 实际就是一个 IMAGE_NT_HEADERS 结构。

定义如下:

          IMAGE_NT_HEADERS STRUCT

Signature
dd ?

FileHeader
IMAGE_FILE_HEADER <>

OptionalHeader
IMAGE_OPTIONAL_HEADER32 <>

        
IMAGE_NT_HEADERS ENDS

A.      
Signature是个DWORD类型的值,值为50h, 45h,
00h, 00h
PE\0\0)。它是PE签名。我们可以据此识别给定文件是否为有效PE文件。

注:如果IMAGE_NT_HEADERSsignature域值等于“PE\0\0″,那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。

             IMAGE_DOS_SIGNATURE
equ 5A4Dh

IMAGE_OS2_SIGNATURE
equ 454Eh

IMAGE_OS2_SIGNATURE_LE
equ 454Ch

IMAGE_VXD_SIGNATURE
equ 454Ch

IMAGE_NT_SIGNATURE
equ 4550h

B.      
FileHeader 该结构域包含了关于PE文件物理分布的信息,比如节数目、文件执行机器等。

               
Typedef struct _IMAGE_FILE_HEADER{

USHORT Machine;

USHORT NumberOfSections;     //这个文件中的节的数目。

ULONG  TimeDateStamp;

ULONG  PointerToSymbolTable;

ULONG  NumberOfSymbols;

USHORT SizeOfOptionalHeader;

USHORT Characteristics;

                      }IMAGE_FILE_HEADER,
*PIMAGE_FILE_HEADER;

C.        
OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有可选字样,但实际上本结构总是存在的。

运用:

如何才能校验指定文件是否为一有效PE文件呢? 这个问题很难回答,完全取决于想要的精准程度。您可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。正如上述:如果IMAGE_NT_HEADERSsignature域值等于“PE\0\0″,那么就是有效的PE文件。

如何定位 PE header? 答案很简单: DOS MZ header 已经包含了指向 PE header 的文件偏移量。DOS MZ header 又定义成结构 IMAGE_DOS_HEADER 。查询windows.inc,我们知道 IMAGE_DOS_HEADER 结构的e_lfanew成员就是指向 PE header 的文件偏移量。

现将所有的步骤简单列出:

1.首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE是则
DOS
MZ header
有效。

2.一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位
PE
header
了。

3.比较 PE header 的第一个字的值是否等于 IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

这几天整理我的电脑,发现几篇旧笔记,差不多快三年多了,都打算删掉了。贴上后我就从电脑上删掉了,可能也确实没用了。
电子计算机简称计算机,俗称电脑.是一种能快速处理信息的电子设备.

                   电子计算机的发展概述

电子管时代(19461957

1946214,电子计算机埃尼阿克(ENIAC)的诞生,揭开了计算机时代的序幕。(研制目的:计算炮弹的轨迹。ENIAC意指“电子数字积分电脑”。它占地面积为170平方米,总重30吨。共用了18000多个电子管,运算速度为每秒5000次。)

1951614,第一台通用型电子计算机(UNIVAC)在美国人口普查署用于制作前一年人口普查图表,其主要元件是电子管。

晶体管时代(19581964

其主存储器采用磁芯,外部存储器开始采用磁带和磁盘。

集成电路时代(19651970

大规模集成电路时代(1971——目前)即微处理器时代

70年代微型计算机的出现,被人们称为电子计算机的第二次革命。这得益于19711115Intel公司对外正式公布了由特德-霍夫领导开发出的世界上第一个第一片微处理器4004型。19724月,Intel宣布微处理器8008制作成功;1974年其改进型微处理器8080正式投放市场。

1981812,这是一个在电脑史上值得大书特书的日子,IBM PC8088处理器)即横空出世,昭示着人类从此进入了个人电脑的新时代。

 

计算机的特点:

运算速度快、计算精确、自动化程度高、通用性强、可靠性高。

计算机的结构:硬件和软件

硬件:输入输出设备、存贮器、运算器、控制器等部件。

存贮器分为内存储器和外存储器。内存也称为主存。

控制器和运算器统称为中央处理器(CPU:Central Processing Unit)

中央处理器和内存储器称为计算机的主机。

软件:系统软件和应用软件

计算机网络的概述

19世纪是火车和铁路的时代,20世纪是汽车与高速公路的时代,21世纪是电脑网络的时代。Internet六十年代起源于美国,经过40年的发展,取得了极大的成功,尤其是近几年的爆发性发展,在全世界范围内刮起了Internet旋风,掀起了新一轮信息化的浪潮。以计算机和网络为代表的信息技术正在以惊人的速度改变着人类的工作、生活方式,甚至包括思维方式和价值观念。



USBUniversal Serial Bus的缩写,中文意思是通用串行总线.它是由CompaqDECIBMIntelNEC、微软以及Northern Telecom(北方电讯)等公司于199411月共同提出的一种新型接口技术。

1.  USB的传输速度

可以分为低速、中速、高速三种传输等级。低速主要就是针对鼠标、键盘等周边设备而言,也就是“交互式设备”,对于这些周边设备速度,通常为10-100kbps,中速是针对音效(Audio)、电话(Phone)、压缩视频(Compressed Video)等用途的传输而言的,速度范围为500k-10Mbps,高速就是指视频(Video)、磁盘(Disk)等用途。速度为25-500Mbps

2.  USB接口的主要特点

速度快:现在USB接口的传输速度为12Mbps(在USB2.0推广后可以高达480Mbps),和串口115200bps的速度相比,相当于串口速度的100多倍(USB2.0则相当于4000)

连接使用简单快捷:即插即用

无需外接电源:

良好的兼容性:

3.  USB的缺陷

从目前USB1.1规格的产品来看,一个USB设备最多可以得到6Mbps的传输频宽,而无法将12Mbps全部供应给一个USB设备,这是为了确保USB至少能同时使用两个装置而设的规定。




 

早先的CPU主频是与周边设备(如硬盘、显卡)同步运行的,但随着CPU主频的大幅度上升,而周边设备因为兼容性和技术上的问题,它们的运行频率和高速提升中的CPU频率相比有了很大的差异。所以,为了不影响CPU频率的提升,就将CPU频率分为外频和倍频,外频乘以倍频就是CPU的主频。而外频则通过一些分频方式再作为其他设备的工作频率。

2005年03月22日

Applet程序是可以内嵌于网页中的小应用程序。使用Applet的好处是用户可以直接从网站下载程序,并直接由浏览器来运行。

所有的Applet程序都必须继承于Applet类或JApplet类,JApplet上还可以使用Swing窗口组件。Applet类存放在java.applet类套件(package)中。Applet类和java.awt类集合有很大的关系。因为在java.awt类套件中所收集的是一组关于编写使用者的接口(Windows UI)、绘制图形(Graphic)和图像(Image)的相关类。AWT类的全名是Abstract  WIndow  Toolkit。简单的说您可以使用AWT中所提供的各种类来编写Java窗口程序。

Applet程序必须通过浏览器来运行,其生命周期自然开始于当它被浏览器加载的那一刻,而结束于浏览器被关闭或转移至其他网页时。从Applet程序开始于运行到结束运行时的过程中,有四个方法(method)会在某些事件发生时被调用,分别是:

  init( )——程序第一次被加载时
  start( )——程序开始运行时
  stop(  )——程序停止运行时
  destory(  )——程序结束时


在正确定安装KDE中文桌面环境的前提下,具体的操作方式请参见上面的相关内容。在/etc/X11/Xsession.d/目录下新建一个文件92fcitx,内容如下:

export LANG=”zh_CN.UTF-8″ #设置中文locales,如果不设,fcitx启动后乱码,不能使用。
export XIM_PROGRAM=fcitx
export XIM=fcitx
export XMODIFIERS=”@im=fcitx”
fcitx&

[注释]
在kdm中好象不能像GDM一样设置locales,所以需手动用export设置。

在/etc/X11/Xsession.d/ 目录下的所有配置文件在X启动时都会被自动执行。所以我就利用了这个特性,在该目录下新建了一个92fcitx文件,用以启动fcitx。类似于 gnome环境下的/etc/X11/Xsession.d/55gnome-session_gnomerc文件的作用。注意,这不是唯一的方法,因为在x启动过程中会自动运行很多个脚本,所以在这些脚本中插入fcitx的启动脚本也是可行的。这就是linux高可配置的体现。

2005年03月10日

以下方法没有试验过,我只是从网上收集的。
过程如下:

1.先用rational.exe,lmgrd.exe覆盖到你的安装目录的Rartional\commen\下

2.然后改license.dat里面的

SERVER yourPC ANY

DAEMON rational “C:\Program Files\Rational\Common\rational.exe”

改成

SERVER 你的机器名 ANY

DAEMON rational “你的安装目录\rational.exe”

3.将Flexlm.cpl拷贝到C:\winnt\system32\下,

在控制面板里运行FlexLm License Manager,

在Setup面板配置文件路径,lmgrd.exe->你的安装目录\Common\lmgrd.exe

License File为你改过的license.dat

4.在Control面板点击Start,如果成功的话点击Status按钮将显示

你的机器名:license server UP (MASTER) 说明成功了

失败的话重启一下FlexLm License Manager就没问题了。

5.如果弹出对话框License Key Administrator Wizard后,

选定Point to a Rational License Server to get my licenses,单击下一步,

Server Name文本框中填写你的机器号(可能已经填好),单击完成。

(成功的话会出现两屏的licenses)

再次运行RationalRose就应该就没问题了。

目录

1. 自增长 primary key

2. 避免用复合主键 (compound primary key)

3. 双主键

4. 以固定的数据库、表应付变化的客户需求

5. 避免一次取数据库大量数据,取大量数据一定要用分页。

详细内容

1. 自增长 primary key

采用自增长 primary key主要是性能。早期的数据库系统,经常采用某种编号,比如身份证号码,公司编号等等作为数据库表的 primary key。然而,很快,大家就发现其中的不利之处。

比如早期的医院管理系统,用身份证号码作为病人表的 primary
key。然而,第一,不是每个人都有身份证;第二,对于国外来的病人,不同国家的病人的证件号码并不见得没有重复。因此,用身份证号码作为病人表的
primary key是一个非常糟糕的设计。考虑到没有医生或者护士会刻意去记这些号码,使用自增长 primary key是更好的设计。

公司编号采用某种特定的编码方法,这也是早期的数据库系统常见的做法。它的缺点也显而易见:很容易出现像千年虫的软件问题,因为当初设计数据库表的
时候设计的位数太短,导致系统使用几年后不能满足要求,只有修改程序才能继续使用。问题在于,任何人设计系统的时候,在预计某某编号多少位可以够用的时
候,都存在预计不准的风险。而采用自增长 primary key 则不存在这种问题。同样的道理,没有人可以去记这些号码。

使用自增长 primary key另外一个原因是性能问题。略有编程常识的人都知道,数字大小比较比字符串大小比较要快得多。使用自增长 primary key可以大大地提高数据查找速度。

2. 避免用复合主键 (compound primary key)

这主要还是因为性能问题。数据检索是要用到大量的 primary key 值比较,只比较一个字段比比较多个字段快很多。使用单个 primary key 从编程的角度也很有好处, sql 语句中 where 条件可以写更少的代码,这意味着出错的机会大大减少。

3. 双主键

双主键是指数据库表有两个字段,这两个字段独立成为主键,但又同时存在。 数据库系统的双主键最早用在用户管理模块。最早的来源可能是参照操作系统的用户管理模块。

操作系统的用户管理有两个独立的主键:操作系统自己自动生成的随机 ID (Linux, windows 的 SID), login
id。这两个 ID 都必须是唯一的,不同的是,删除用户 test 然后增加一个用户 test, SID 不同,login id
相同。采用双主键主要目的是为了防止删除后增加同样的 login id 造成的混乱。比如销售经理 hellen 本机共享文件给总经理
peter, 一年后总经理离开公司,进来一个普通员工 peter ,两个peter 用同样的 login id, 如果只用 login id
作操作系统的用户管理主键,则存在漏洞:普通员工 peter 可以访问原来只有总经理才能看的文件。操作系统自己自动生成的随机 ID
一般情况下面用户是看不到的。

双主键现在已经广泛用在各种数据库系统中,不限于用户管理系统。

4. 以固定的数据库、表应付变化的客户需求

这主要基于以下几个因素的考虑:

4.1 大型 EPR 系统的正常使用、维护需要软件厂商及其众多的合作伙伴共同给客户提供技术服务,包括大量的二次开发。
如果用户在软件正常使用过程中需要增加新的表或者数据库,将给软件厂商及其众多的合作伙伴带来难题。

4.2 软件升级的需要。
没有一个软件能够让客户使用几十上百年不用升级的。软件升级往往涉及数据库表结构的改变。软件厂商会做额外的程序将早期版本软件的数据库数据升级到新的版本,但是对于用户使用过程中生成的表进行处理就比较为难。

4.3 软件开发的需要。
使用固定的数据库库表从开发、二次开发来说,更加容易。对于用户使用过程中生成的表,每次查找数据时都要先查表名,再找数据,比较麻烦。

举例来说,早期的用友财务软件用 Access
作数据库,每年建立一个新的数据库。很快,用户和用友公司都发现,跨年度数据分析很难做。因此这是一个不好的设计。在 ERP
中,很少有不同的年度数据单独分开。一般来说,所有年份的数据都在同一个表中。对于跨国公司甚至整个集团公司都用同一个 ERP
系统的时候,所有公司的数据都在一起。这样的好处是数据分析比较容易做。

现在大多数数据库系统都能做到在常数时间内返回一定量的数据。比如,Oracle 数据库中,根据 primary key 在 100万条数据中取 10 条数据,与在1 亿条数据中取 10 条数据,时间相差并不多。

5. 避免一次取数据库大量数据,取大量数据一定要用分页。

这基本上是现在很多数据库系统设计的基本守则。ERP 系统中超过 100万条数据的表很多,对于很多表中的任何一个,一次取所有的会导致数据库服务器长时间处于停滞状态,并且影响其它在线用户的系统响应速度。

一般来说,日常操作,在分页显示的情况下面,每次取得数据在 1-100
之间,系统响应速度足够快,客户端基本没有特别长的停顿。这是比较理想的设计。这也是大型数据库系统往往用 ODBC, ADO
等等通用的数据库联接组件而不用特定的速度较快的专用数据库联接组件的原因。因为系统瓶颈在于数据库( Database)
方面(数据量大),而不在于客户端(客户端每次只取少量数据)。

在 B/S 数据库系统中,分页非常普遍。早期的数据库系统经常有客户端程序中一次性取大量数据做缓冲。现在已经不是特别需要了,主要原因有:

5.1 数据库本身的缓冲技术大大提高。
大部分数据库都会自动将常用的数据自动放在内存中缓冲,以提高性能。

5.2 数据库联接组件的缓冲技术也在提高。
包括 ADO 在内的一些数据库联接组件都会自动对数据结果集(result set)进行缓冲,并且效果不错。比较新颖的数据库联接组件,比如 Hibernate 也加入了一些数据结果集缓冲功能。
当然,也有一些数据库联接组件没有对数据结果集进行缓冲,比如 JDBC Driver,不过几年之内情况应该有所改观。也有些不太成功的数据缓冲,比如 EJB 中的实体Bean,性能就不尽如人意,实体Bean数据也是放在内存中,可能是因为占用内存过多的缘故。

相对来说,今天的程序员写客户端数据缓冲,能够超过以上两个缓冲效果的,已经比较难了。

一个简单的银行存储小程序
public class test
{
 public static void main(String args[])
 {
  boolean blue=false;
  float money=0.00f;
  
  String xx;
  demo impl=new demo();
  blue=impl.regname(“wenyu1314″,12,8000.0f);
  if(blue)
  {
   System.out.println(“Good luck! you have 8000$\n”);
   System.out.println(“UserName:  ”+impl.username);
   System.out.println(“UserID:    ”+impl.userid);
   System.out.println(“Money:     ”+impl.money);
   System.out.println(“———————————————————–”);
  }
  else
   System.out.println(“Sorry!! this is system’s worng”);
   
  blue=impl.depost(500);
  if(blue)
   System.out.println(“Good luck!”);
  else
   System.out.println(“Sorry!! this is system’s worng1″); 
   
  money=impl.retive(12,20.0f); 
  if(money!=0.0f)
   {
    System.out.println(“Good luck!”);
    System.out.println(“You have money is :  ”+impl.money);
   } 
  else
   System.out.println(“Sorry!! this is system’s worng2″);
   
   
  float money1=0.0f;
  blue=impl.getuserid(12); 
  if(blue)
   System.out.println(“OK! you ID is money :   ”+impl.money);
  else
   System.out.println(“Sorry is not this userid”);
 }
}
interface conner
{
 boolean regname(String username,int userid,float money);
 boolean depost(float money);
 float retive(int userid,float money);
 boolean getuserid(int userid);
}
class demo implements conner
{
 String username;
 int userid;
 float money;
 
 public demo(){};
 public demo(String username,int userid,float money)
 {
  this.username=username;
  this.userid=userid;
  this.money=money;
 }
 
 public boolean regname(String username,int userid,float money)
 {
  this.username=username;
  this.userid=userid;
  this.money=money;
  return true;
 }
 public float retive(int userid,float money)
 {
  if(userid==this.userid)
   return money;
  else
   return 0.00f;
 }
 public boolean depost(float money)
 {
  this.money+=money;
  return true;
 }
 public boolean getuserid(int userid)
 {
  if(userid==this.userid)
   return true;
  else
   return false;
 }
}