(*1*):建立DLL工程。在第二步选1。即默认。 //这个dll工程只用来输出两个函数。别无他用。 添加文件dll.cpp: 文件内容如下: #include"stdio.h" void __declspec(dllexport) ExportOne( void ) { printf("I am ExportOne!\n"); } void __declspec(dllexport) ExportTwo( void ) { printf("I am ExportTwo!\n"); } 编译运行产生dll.obj dll.dll. [[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]] 也可这样建立: //文件dll.cpp #include"stdio.h" //void __declspec(dllexport) ExportOne( void ) void ExportOne(void) { printf("I am ExportOne!\n"); } //void __declspec(dllexport) ExportTwo( void ) void ExportTwo(void) { printf("I am ExportTwo!\n"); } //文件dll.def ; dll.def : Declares the module parameters for the DLL.
LIBRARY "dll" DESCRIPTION 'dll Windows Dynamic Link Library'
EXPORTS ; Explicit exports can go here ExportOne @1 ExportTwo @2 [[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]
(*2*):建立LIB工程。 //这个LIB工程只用来测试引入刚才DLL输出的两个函数。 添加文件lib.cpp 文件内容如下:
#include"stdio.h" void ExportOne(void); void ExportTwo(void); void main() { ExportOne(); ExportTwo(); } 编译运行产生lib.obj lib.exe. (*3*)LIB.OBJ分析 (*4*)反编译LIB.OBJ.注意代码节的文件偏移为00000392
:00000000 55 push ebp ...... :00000018 E800000000 call 0000001D //这里就是ExportOne()调用 :0000001D E800000000 call 00000022 //这里就是ExportTwo()调用 ...... :00000032 C3 ret
(*5*)LIB.EXE分析:
:00401000 55 push ebp ...... :00401017 AB stosd
* Reference To: dll.ExportOne, Ord:0001h | :00401018 E81D000000 Call 0040103A
* Reference To: dll.ExportTwo, Ord:0002h | :0040101D E812000000 Call 00401034 ...... :00401032 C3 ret
:00401033 CC int 03
* Referenced by a CALL at Address: |:0040101D |
* Reference To: dll.ExportTwo, Ord:0002h | :00401034 FF25C8C04000 Jmp dword ptr [0040C0C8]
* Referenced by a CALL at Address: |:00401018 |
* Reference To: dll.ExportOne, Ord:0001h | :0040103A FF25C4C04000 Jmp dword ptr [0040C0C4]
(*6*)引入函数与非引入函数的区别。 从上我们可以看出,其实不管是不是引入函数,编译器产生的函数调用代码都是CALL XXXXXXXX形式的。 //from dll.lib Archive member name at 8: / 3E951F55 time/date Thu Apr 10 15:37:57 2003 ... correct header end 7 public symbols
1FE __IMPORT_DESCRIPTOR_dll 4F8 __NULL_IMPORT_DESCRIPTOR 62C dll_NULL_THUNK_DATA 778 ?ExportOne@@YAXXZ 778 __imp_?ExportOne@@YAXXZ 7E2 ?ExportTwo@@YAXXZ 7E2 __imp_?ExportTwo@@YAXXZ 我们可以看到,在LIB文件中有引入函数的信息。 函数符号比如?ExportOne@@YAXXZ能够被解析。并且LIB文件中有很多关于引入函数的信息。比如: Summary BA .debug$S 14 .idata$2 14 .idata$3 4 .idata$4 4 .idata$5 8 .idata$6 所有的.idata节最终会被合并到可执行文件的.IDATA节中。从而形成IAT和其他有关引入表的结构。 SECTION HEADER #2 .idata$5 name ... C0300040 flags ... RAW DATA #2 00000000: 00 00 00 00 如果函数是通过序号引入的。那么在.idata$5节的DWORD的最高位为1。低位是引入(出)序号。 否则.idata$5节的DWORD为0。 如果函数是通过名字引入的。那么在.idata$6节的第一个WORD为引入(出)序号。接下去是一个函数名字。 **通过LIB文件,函数被决议为一个JMP DWORD PTR[XXXXXXXX]形式的指令。 通常称为STUB。当然LIB文件中也有引入函数的真正地址。 010 00000000 SECT3 notype () External | ?ExportOne@@YAXXZ (void __cdecl ExportOne(void)) //以下为函数ExportOne的代码。 SECTION HEADER #3 .text name ... RAW DATA #3 00000000: 55 8B EC 83 EC 40 53 56 57 8D 7D C0 B9 10 00 00 U....@SVW.}..... 00000010: 00 B8 CC CC CC CC F3 AB 68 00 00 00 00 E8 00 00 ........h....... 00000020: 00 00 83 C4 04 5F 5E 5B 83 C4 40 3B EC E8 00 00 ....._^[..@;.... 00000030: 00 00 8B E5 5D C3 ....]. 综上所述,对引入函数。产生的代码大致形式如下: CALL XXXXXXXX ... XXXXXXXX: JMP DWORD PTR[YYYYYYYY] YYYYYYYY地址在引入节部分。 最后调到引入函数的地址去执行。 
|