在上篇:PE手工分析-PE头 中我们了解了PE文件头内容,在此基础上我们来分析一下导入表和导入函数地址表的内容.
还是使用上篇使用的PE文件来分析,上一篇中我们基本上已经定位出PE头的位置以及相应内容.
在PE扩展头中包含 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
通过该数据目录表我们可以进一步对导入表和导入函数地址表进行详细的分析(其数据目录表分析也雷同)
详细信息可以查看MSDN定义.
数据目录表结构定义:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]数据目录表分析如下:
|
索引 |
数据目录表 |
文件偏移地址 |
偏移 |
大小 |
说明 |
|
0 |
导出表 |
00000170h |
00 00 00 00 |
00 00 00 00 |
没有导出 |
|
1 |
导入表 |
00000178h |
00 E0 01 00 |
64 00 00 00 |
|
|
|
... |
|
|
|
|
|
12 |
导入地址表 |
000001d0h |
A0 E2 01 00 |
3C 02 00 00 |
|
|
|
… |
|
|
|
|
|
|
... |
|
|
|
|
|
|
... |
|
|
|
|
导入表RVA=01E000
IAT RVA=01E2A0
RVA的概念请参考:RVA,另外可以参考《加密解密ii》中的第二章介绍

了解了RVA之后我们就明白了在获取导入表的RVA之后需要根据节地址转换成文件相对地址FOA
所以为了获取导入表的文件相对地址需要对PE的节表进行分析
首先需要知道节表所在的位置(紧跟数据目录表结尾)
总共16个数据目录,导入地址函数表在第12(0开始)个位置
节表起始位置=1d0+8+3×8=1F0
节表结构如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];// #define IMAGE_SIZEOF_SHORT_NAME 8
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
从上面镜像头分析可以之后该PE中包含有7个节
每个节包含40个字节
总共占有空间:40×7=280(0x118)->01F0-20E

可以看到每个节对应的内容如下:
|
段名称 |
虚拟地址 |
虚拟大小 |
物理地址 |
物理大小 |
标志 |
PointerToRawData |
|
.textbss |
1000 |
0 |
10000 |
0 |
2EE0 |
|
|
.text |
11000 |
|
897D |
00008A00 |
60000020 |
00000400 |
|
.rdata |
1A000 |
|
24B5 |
2600 |
40000040 |
8E00 |
|
.data |
01D000 |
|
05F4 |
2000 |
C0000040 |
B400 |
|
.idata |
0001E000 |
|
10C8 |
1200 |
C0000040 |
B600 |
|
.rsrc |
00020000 |
|
0459 |
0600 |
40000040 |
C800 |
|
.reloc |
00021000 |
|
06DA |
0800 |
42000040 |
CE00 |
节表的分析完毕,然后我们需要针对导入表的RVA获取相应的FOA
RVA:01E000=>PointerToRawData:B600
指向导入表描述符
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; //
DWORD OriginalFirstThunk; //
} DUMMYUNIONNAME; //0001e270=>B600+270=b870->01E4DC->_foo@4
DWORD TimeDateStamp; // 0
DWORD ForwarderChain; //0
DWORD Name; //01E4E6=>B600+4E6=BAE6->dllExport.dll
DWORD FirstThunk; //01E4AC=>B600+4AC=BAAC->01E4DC->_foo@4
} IMAGE_IMPORT_DESCRIPTOR;
其文件内容如下:


OriginalFirstThunk指向地址(RVA)01E4DC->(FOA)BADC
FirstThunk指向地址(RVA)01E4AC->(FOA)BAAC对应结构为
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
相应IMAGE_THUNK_DATA指向的内容为
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //00
BYTE Name[1]; //_foo@4
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
这里对应的导入表分析基本完成了
- 相应的IAT分析如下:
RVA:01E2A0=>PointerToRawData:B600+(2A0)=B8A0在找到了IAT表的地址后让我们来看一下其中的内容

可以看到这里的BAAC正好处理B8A0的区域中.
所以很显然,FirstThunk属于IAT中的某一个地址区域
相应PE文件内容如下
Dll加载之前导入表结构

在DLL加载之后导入表中内容将被操作系统填充为函数的VA

到这里基本上已经把IAT和INT绑定起来了,而在函数调用过程中将如何实现调用IAT的函数地址呢?
程序中每个调用 API 函数的 CALL 指令所使用的地址都是相应函数登记在 IAT 表的地址
源文档 <http://blog.csdn.net/misterliwei/article/details/840983>
那么,IAT导入函数地址表,和导入表有什么联系呢??
其实,所有的DLL的IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk指向的是一片连续的内存空间,第一个DLL的IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk的值,就是IAT表的起始地址!
也就是说,导入表中的首个IMAGE_IMPORT_DESCRIPTOR的FirstThunk字段,在内存中,等同于IMAGE_NT_HEADER.OptionHeader.DataDirectory[12].VirtualAddress
数据目录表第13项,索引值为12,就是IAT了。
在RING3的API劫持中,很多人都会选择使用IAT劫持,也就是基于这个理论的。
源文档 <http://tieba.baidu.com/f?kz=726947835>
对于IAT和导入表之间的关系用如下图片作为结尾

导入表中的FirstThunk指向IAT表中的某一项
之前的理解有问题所以后面加以修改
要分析PE文件我们首先要对PE结构有一个大致的了解,大体上PE结构可以看成是一个平面空间里面包含有如下内容

相应的MSDOS头结构定义如下,Windows加载器在加载的过程中会判断dos头是否合法
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
通过以上结构我们可以看到e_lfanew(60=0x3c个字节偏移)字段代表exe头在文件中的位置(00F8)

所以可以断定00F8位置指向的内容为PE头结构定义如下
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE头签名PE\0\0
IMAGE_FILE_HEADER FileHeader; //PE文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //PE扩展头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

0xF9=50 45 00 00=PE\0\0->Signature签字段
000000fch=镜像头结构开发位置占20个字节
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //014C-IMAGE_FILE_MACHINE_I386
WORD NumberOfSections; //PE节数量-0007个节
DWORD TimeDateStamp; //时间戳E72B4FA9
DWORD PointerToSymbolTable; //指向符号表0000
DWORD NumberOfSymbols; //符号表数量0000
WORD SizeOfOptionalHeader; //扩展PE头大小00E0
WORD Characteristics; //文件属性0102-IMAGE_FILE_32BIT_MACHINE|IMAGE_FILE_EXECUTABLE_IMAGE
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
00000110h=扩展文件头起始位置占224个字节(E0)
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; //010B-IMAGE_NT_OPTIONAL_HDR32_MAGIC
BYTE MajorLinkerVersion; //0A-连接器主版本号
BYTE MinorLinkerVersion; //00-连接器小版本号
DWORD SizeOfCode; //0000008A(138)-代码节大小
DWORD SizeOfInitializedData; //0000004C(76)-已初始化数据大小
DWORD SizeOfUninitializedData; //00000000(0)-为初始化数据大小
DWORD AddressOfEntryPoint; //000110AA程序入口地址
DWORD BaseOfCode; //00001000程序段基地址
DWORD BaseOfData; //00001000数据段基地址
//
// NT additional fields.
//
DWORD ImageBase; //镜像加载基地址00400000
DWORD SectionAlignment; //节对其0001000(4096)
DWORD FileAlignment; //文件对齐0000200(512)
WORD MajorOperatingSystemVersion; //操作系统主版本号0005
WORD MinorOperatingSystemVersion; //操作系统小版本号0001
WORD MajorImageVersion; //镜像主版本号0000
WORD MinorImageVersion; //镜像小版本号0000
WORD MajorSubsystemVersion; //子系统主版本号0005
WORD MinorSubsystemVersion; //子系统小版本号0001
DWORD Win32VersionValue; //0
DWORD SizeOfImage; //镜像大小00022000
DWORD SizeOfHeaders; //头大小0400
DWORD CheckSum; //0
WORD Subsystem; //03-IMAGE_SUBSYSTEM_WINDOWS_CUI
WORD DllCharacteristics; //8140IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
DWORD SizeOfStackReserve; //栈初始化大小010000
DWORD SizeOfStackCommit; //栈提交大小01000
DWORD SizeOfHeapReserve; //堆初始化大小010000
DWORD SizeOfHeapCommit; //堆提交大小01000
DWORD LoaderFlags; //0
DWORD NumberOfRvaAndSizes; //10(16)
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
到这里我们基本上已经将PE文件头信息给分析完成了.
下一篇我们来了解一下《导入表和函数地址导入表》
RVA是相对虚拟地址(Relative Virtual Address)的缩写,顾名思义,它是一个“相对”地址,也可以说是“偏移量”,PE文件的各种数据结构中涉及到地址的字段大部分都是以RVA表示的。
准 确地说,RVA就是当PE文件被装载到内存中后,某个数据的位置相对于文件头的偏移量。举个例子,如果Windows装载器将一个PE文件装入 00400000h处的内存中,而某个节中的某个数据被装入0040xxxxh处,那么这个数据的RVA就是(0040xxxxh- 00400000h)=xxxxh,反过来说,将RVA的值加上文件被装载的基地址,就可以找到数据在内存中的实际地址。
PE文件中出现RVA的 概念是因为PE的内存映像和磁盘文件映像是不同的,同一数据相对于文件头的偏移量在内存中和在磁盘文件中可能是不同的,为了提高效率,PE文件头中使用的 都是内存映像中的偏移量,也就是RVA。从图17.3中也可以得到另一个结论,那就是RVA仅仅是对于处于节中的数据而言的,对于文件头和节表来说无所谓 RVA和文件偏移,因为它们在被映射到内存中后不管是大小还是偏移都不会有任何改变。
2、汇编中虚拟地址(VRA)与文件偏移地址(FileOffset)的相互转换:
+---------+---------+---------+---------+---------+---------+
| 段名称 虚拟地址 虚拟大小 物理地址 物理大小 标志 |
+---------+---------+---------+---------+---------+---------+
| Name VOffset VSize ROffset RSize Flags |
+---------+---------+---------+---------+---------+---------+
| .text 00001000 00000092 00000400 00000200 60000020|
| .rdata 00002000 000000F6 00000600 00000200 40000040|
| .data 00003000 0000018E 00000800 00000200 C0000040|
| .rsrc 00004000 000003A0 00000A00 00000400 C0000040|
+---------+---------+---------+---------+---------+---------+
文件虚拟偏移地址和文件物理偏移地址的计算公式如下:
>>>>>>>VaToFileOffset( 虚拟地址转文件偏移地址)
如VA = 00401000 (虚拟地址)
ImageBase = 00400000 (基地址)
VRk = VOffset - ROffset = 00001000 - 00000400 = C00 (得出文件虚拟地址和文件物理址之间的VRk值)
FileOffset = VA - ImageBase - VRk = 00401000 - 00400000 - C00 = 400(文件物理地址的偏移地址)
如VA = 00401325,则:
FileOffset = VA - ImageBase - VRk = 00401325 - 00400000 - C00 = 725
>>>>>>FileOffsetToVa( 文件偏移地址转虚拟地址)
如FileOffset = 435(文件偏移地址)
VA = FileOffset + ImageBase + VRk = 435 + 00400000 + C00 = 00401035(虚拟地址)
源文档 <http://blog.csdn.net/xuexi1028/article/details/6948591>
char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char* str5 = "abc"; const char* str6 = "abc"; cout << boolalpha << ( str1==str2 ) << endl; // 输出什么? cout << boolalpha << ( str3==str4 ) << endl; // 输出什么? cout << boolalpha << ( str5==str6 ) << endl; // 输出什么?
输出结果是什么?
false false true
why?反汇编看看
char str1[] = "abc"; 00CD163E mov eax,dword ptr [string "abc" (0CD9A40h)] //将abc字符串(静态地址) 00CD1643 mov dword ptr [str1],eax //str1地址 0x0018fd00 char str2[] = "abc"; 00CD1646 mov eax,dword ptr [string "abc" (0CD9A40h)] 00CD164B mov dword ptr [str2],eax //str2地址 0x0018fcf4 const char str3[] = "abc"; 00CD164E mov eax,dword ptr [string "abc" (0CD9A40h)] 00CD1653 mov dword ptr [str3],eax //str3地址 0x0018fce8 const char str4[] = "abc"; 00CD1656 mov eax,dword ptr [string "abc" (0CD9A40h)] 00CD165B mov dword ptr [str4],eax //str4地址 0x0018fcdc const char* str5 = "abc"; 00CD165E mov dword ptr [str5],offset string "abc" (0CD9A40h) //str5指向0CD9A40h const char* str6 = "abc"; 00CD1665 mov dword ptr [str6],offset string "abc" (0CD9A40h) //str6指向0CD9A40h cout << boolalpha << ( str1==str2 ) << endl; // 输出什么? 00CD166C mov esi,esp 00CD166E mov eax,dword ptr [__imp_std::endl (0CDD31Ch)] 00CD1673 push eax 00CD1674 lea ecx,[str1] 00CD1677 lea edx,[str2] 00CD167A cmp ecx,edx 00CD167C sete al 00CD167F mov edi,esp 00CD1681 movzx ecx,al 00CD1684 push ecx 00CD1685 mov ebx,esp 00CD1687 push offset std::boolalpha (0CD1113h) 00CD168C mov ecx,dword ptr [__imp_std::cout (0CDD318h)] 00CD1692 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD30Ch)] 00CD1698 cmp ebx,esp 00CD169A call @ILT+505(__RTC_CheckEsp) (0CD11FEh) 00CD169F mov ecx,eax 00CD16A1 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD308h)] 00CD16A7 cmp edi,esp 00CD16A9 call @ILT+505(__RTC_CheckEsp) (0CD11FEh) 00CD16AE mov ecx,eax 00CD16B0 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD310h)] 00CD16B6 cmp esi,esp 00CD16B8 call @ILT+505(__RTC_CheckEsp) (0CD11FEh) cout << boolalpha << ( str3==str4 ) << endl; // 输出什么? 00CD16BD mov esi,esp 00CD16BF mov eax,dword ptr [__imp_std::endl (0CDD31Ch)] 00CD16C4 push eax 00CD16C5 lea ecx,[str3] 00CD16C8 lea edx,[str4] 00CD16CB cmp ecx,edx 00CD16CD sete al 00CD16D0 mov edi,esp 00CD16D2 movzx ecx,al 00CD16D5 push ecx 00CD16D6 mov ebx,esp 00CD16D8 push offset std::boolalpha (0CD1113h) 00CD16DD mov ecx,dword ptr [__imp_std::cout (0CDD318h)] 00CD16E3 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD30Ch)] 00CD16E9 cmp ebx,esp 00CD16EB call @ILT+505(__RTC_CheckEsp) (0CD11FEh) 00CD16F0 mov ecx,eax 00CD16F2 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD308h)] 00CD16F8 cmp edi,esp 00CD16FA call @ILT+505(__RTC_CheckEsp) (0CD11FEh) 00CD16FF mov ecx,eax 00CD1701 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD310h)] 00CD1707 cmp esi,esp 00CD1709 call @ILT+505(__RTC_CheckEsp) (0CD11FEh) cout << boolalpha << ( str5==str6 ) << endl; // 输出什么? 00CD170E mov esi,esp 00CD1710 mov eax,dword ptr [__imp_std::endl (0CDD31Ch)] 00CD1715 push eax 00CD1716 mov ecx,dword ptr [str5] 00CD1719 cmp ecx,dword ptr [str6] 00CD171C sete dl 00CD171F mov edi,esp 00CD1721 movzx eax,dl 00CD1724 push eax 00CD1725 mov ebx,esp 00CD1727 push offset std::boolalpha (0CD1113h) 00CD172C mov ecx,dword ptr [__imp_std::cout (0CDD318h)] 00CD1732 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD30Ch)] 00CD1738 cmp ebx,esp 00CD173A call @ILT+505(__RTC_CheckEsp) (0CD11FEh) 00CD173F mov ecx,eax 00CD1741 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD308h)] 00CD1747 cmp edi,esp 00CD1749 call @ILT+505(__RTC_CheckEsp) (0CD11FEh) 00CD174E mov ecx,eax 00CD1750 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0CDD310h)] 00CD1756 cmp esi,esp 00CD1758 call @ILT+505(__RTC_CheckEsp) (0CD11FEh)
所以这里
str1(0x0018fd00)!=str2(0x0018fcf4)
str3(0x0018fce8)!=str4(0x0018fcdc)
str5(0x0CD9A40h)==str6(0x0CD9A40h)
结果为
false
false
true
直接上代码
float a = 1.0f; cout << (int)a << endl; cout << (int&)a << endl; cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么? float b = 0.0f; cout << (int)b << endl; cout << (int&)b << endl; cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么?
请问输出结果如何?
1 1065353216 false 0 0 true
为什么0.0f和1.0f有这么大的差别呢?让我们反汇编看看代码如何?
00931620 push ebp 00931621 mov ebp,esp 00931623 sub esp,0D8h 00931629 push ebx 0093162A push esi 0093162B push edi 0093162C lea edi,[ebp-0D8h] 00931632 mov ecx,36h 00931637 mov eax,0CCCCCCCCh 0093163C rep stos dword ptr es:[edi] float a = 1.0f; 0093163E fld1 //将1.0f装载到st(0) 00931640 fstp dword ptr [a] cout << (int)a << endl; 00931643 mov esi,esp 00931645 mov eax,dword ptr [__imp_std::endl (93D31Ch)] 0093164A push eax 0093164B fld dword ptr [a] //st0 = a 0093164E call @ILT+340(__ftol2_sse) (931159h) //具体做什么不太清楚好像望城了从float到long类型的转换,得到的值为1 00931653 mov edi,esp 00931655 push eax 00931656 mov ecx,dword ptr [__imp_std::cout (93D318h)] 0093165C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D314h)] 00931662 cmp edi,esp 00931664 call @ILT+505(__RTC_CheckEsp) (9311FEh) 00931669 mov ecx,eax 0093166B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D310h)] 00931671 cmp esi,esp 00931673 call @ILT+505(__RTC_CheckEsp) (9311FEh) cout << (int&)a << endl; 00931678 mov esi,esp 0093167A mov eax,dword ptr [__imp_std::endl (93D31Ch)] 0093167F push eax 00931680 mov edi,esp 00931682 mov ecx,dword ptr [a] //输出a地址内容(强制将内容转换为int类型),a内存中的内容为3f800000(float的编码方式),强制转换为int类型得到了3f800000的10进制值 00931685 push ecx 00931686 mov ecx,dword ptr [__imp_std::cout (93D318h)] 0093168C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D314h)] 00931692 cmp edi,esp 00931694 call @ILT+505(__RTC_CheckEsp) (9311FEh) 00931699 mov ecx,eax 0093169B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D310h)] 009316A1 cmp esi,esp 009316A3 call @ILT+505(__RTC_CheckEsp) (9311FEh) cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么? 009316A8 mov esi,esp 009316AA mov eax,dword ptr [__imp_std::endl (93D31Ch)] 009316AF push eax 009316B0 fld dword ptr [a] 009316B3 call @ILT+340(__ftol2_sse) (931159h) 009316B8 cmp eax,dword ptr [a] //(1==0x3f800000)肯定输出false 009316BB sete cl 009316BE mov edi,esp 009316C0 movzx edx,cl 009316C3 push edx 009316C4 mov ebx,esp 009316C6 push offset std::boolalpha (931113h) 009316CB mov ecx,dword ptr [__imp_std::cout (93D318h)] 009316D1 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D30Ch)] 009316D7 cmp ebx,esp 009316D9 call @ILT+505(__RTC_CheckEsp) (9311FEh) 009316DE mov ecx,eax 009316E0 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D308h)] 009316E6 cmp edi,esp 009316E8 call @ILT+505(__RTC_CheckEsp) (9311FEh) 009316ED mov ecx,eax 009316EF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D310h)] 009316F5 cmp esi,esp 009316F7 call @ILT+505(__RTC_CheckEsp) (9311FEh) float b = 0.0f; 009316FC fldz 009316FE fstp dword ptr [b] cout << (int)b << endl; 00931701 mov esi,esp 00931703 mov eax,dword ptr [__imp_std::endl (93D31Ch)] 00931708 push eax 00931709 fld dword ptr [b] 0093170C call @ILT+340(__ftol2_sse) (931159h) 00931711 mov edi,esp 00931713 push eax 00931714 mov ecx,dword ptr [__imp_std::cout (93D318h)] 0093171A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D314h)] 00931720 cmp edi,esp 00931722 call @ILT+505(__RTC_CheckEsp) (9311FEh) 00931727 mov ecx,eax 00931729 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D310h)] 0093172F cmp esi,esp 00931731 call @ILT+505(__RTC_CheckEsp) (9311FEh) cout << (int&)b << endl; 00931736 mov esi,esp 00931738 mov eax,dword ptr [__imp_std::endl (93D31Ch)] 0093173D push eax 0093173E mov edi,esp 00931740 mov ecx,dword ptr [b] 00931743 push ecx 00931744 mov ecx,dword ptr [__imp_std::cout (93D318h)] 0093174A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D314h)] 00931750 cmp edi,esp 00931752 call @ILT+505(__RTC_CheckEsp) (9311FEh) 00931757 mov ecx,eax 00931759 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D310h)] 0093175F cmp esi,esp 00931761 call @ILT+505(__RTC_CheckEsp) (9311FEh) cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么? 00931766 mov esi,esp 00931768 mov eax,dword ptr [__imp_std::endl (93D31Ch)] 0093176D push eax 0093176E fld dword ptr [b] 00931771 call @ILT+340(__ftol2_sse) (931159h) 00931776 cmp eax,dword ptr [b] //b对应内容为0x00000000,转换之后依然为0所以相等 00931779 sete cl 0093177C mov edi,esp 0093177E movzx edx,cl 00931781 push edx 00931782 mov ebx,esp 00931784 push offset std::boolalpha (931113h) 00931789 mov ecx,dword ptr [__imp_std::cout (93D318h)] 0093178F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D30Ch)] 00931795 cmp ebx,esp 00931797 call @ILT+505(__RTC_CheckEsp) (9311FEh) 0093179C mov ecx,eax 0093179E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D308h)] 009317A4 cmp edi,esp 009317A6 call @ILT+505(__RTC_CheckEsp) (9311FEh) 009317AB mov ecx,eax 009317AD call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (93D310h)] 009317B3 cmp esi,esp 009317B5 call @ILT+505(__RTC_CheckEsp) (9311FEh)
如此就可以推导出结果对应为
1
0x3f800000
false
0
0
true
近期公司希望能够开始对项目流程进行整顿,所以希望我能够对团队成员提供一些相关培训.
今天是第一天,也是第一次对团队成员进行培训.这里准备了一点ppt作为自己培训的资料.
作为第一天开始,个人思路是总结历史,展望未来.如果历史可以给我们再来一次的机会我们会如何做?
在会上发出纸请与会同事针对以往的项目经验提出自己的一些相关意见.
收集的意见还真不少,主要集中:
沟通不顺畅
时间紧张
版本发布混乱
没有文档
代码不规范
随意更改需求
没有统一的缺陷管理平台
这几点在PPT中我也有提到,那么可能大家都是深有感触,针对这些问题我们分类过了一遍
问题1:需求不明确,随意更改需求
针对这个问题大家一致认为,在接受到需求之后需要形成一个规范的处理流程
2:开发,测试分别针对整理的结果进行开发设计,测试用例设计(形成开发文档和测试用例)
3:在设计完成之后,增加一个复查环节,确保大家对于需求的理解和设计没有偏差,如果有偏差或者需要修改需求则重复1,2
4:大家一致确认后开始编码实现
5:编码完成添加代码审查环节
6:提交版本交予测试开发
7:发现缺陷,提交缺陷报告,开发进行分析修改.
8:形成测试报告
9:正式发布
但是在这个过程中可能存在缺陷等问题,如何报告缺陷/问题?沟通确实是一大障碍.
问题2:如何沟通
软件开发中人是关键,所以人和人之间自然需要不断的沟通交流,产生统一意识.如何有效沟通也是大家比较关注的问题.
目前初步形成意识,需要有一个统一的管理平台来支撑起从软件开发的整个生命周期.初步这里定位为TFS.
开发:根据需求进行分析设计实现
测试:根据需求进行测试用例设计
将需求-设计实现-测试用例关联起来,形成一个统一的整体.
只有如此才可以针对部分需求(变更)进行有效的跟踪和分析,同时也作为彼此之间的交互桥梁.
相应的针对缺陷平台也可以将缺陷提交到TFS中,然后分配给相关开发人员进行后续的修复
其他的问题在本次讨论中没有得出过多的结果,只是在PPT的后几页中本人对相关问题进行一个小结.
当然本次讨论主要目的:
1,回顾历史,总结经验
2,抛砖引玉,为后续的培训和会议做准备
当然,目前来说还是问题阶段,但是看到这些问题又该如何解决才是最大的问题!
希望大家看到后能够帮忙提供相关意见.
谢谢!
也是最近被问的一个问题,全局变量在哪个阶段初始化?
这个问题到没被问倒,全局变量在mainCRTStartup之后main调用之前,在该阶段应用会完成堆内存的申请(记得哪里还看到如果改了EntryPoint需要自己进行堆内存的申请和管理).
而全局变量也正是在该阶段完成的初始化.
然后又被问,那么全局变量在哪里被释放?回答是在应用退出之后main函数退出之后,这个回答也没问题.基本上算是正确的.
但是回头自己仔细想想,那么全局变量又是怎么样被初始化的呢?还真的有点不太清楚,所以出于好奇,今晚开始细细研究研究!
首先写了一段代码如下:
//头文件
class ClassSizeRes
{
public:
ClassSizeRes(void);
~ClassSizeRes(void);
};
//cpp文件
ClassSizeRes::ClassSizeRes(void)
{
}
ClassSizeRes::~ClassSizeRes(void)
{
}
//main函数处理
ClassSizeRes staticObj;
int _tmain(int argc, _TCHAR* argv[])
{
//...
}
代码大致如此,然后在构造函数处下断点来调试,发现中断之后的调用堆栈如下:

很显然全局变量的初始化确实是在mainCRTStartup之后main调用之前,与之前所理解的确实没有差别,但是编译器又是如何处理的呢?
根据调用堆栈我们可以发现在函数_initterm的定义如下:
#ifdef CRTDLL
void __cdecl _initterm (
#else /* CRTDLL */
static void __cdecl _initterm (
#endif /* CRTDLL */
_PVFV * pfbegin,
_PVFV * pfend
)
{
/*
* walk the table of function pointers from the bottom up, until
* the end is encountered. Do not skip the first entry. The initial
* value of pfbegin points to the first valid entry. Do not try to
* execute what pfend points to. Only entries before pfend are valid.
*/
while ( pfbegin < pfend )
{
/*
* if current table entry is non-NULL, call thru it.
*/
if ( *pfbegin != NULL )
(**pfbegin)();//这里是关键,该函数就是遍历调用无参的函数指针数组
++pfbegin;
}
}
接下来这里的函数指向的地址内容是关键:
这里Pfbegin=0x00f5b30c
查看0x00f5b30c对应内存的内容
0x00F5B30C 00f57f60 00f57fc0 00f58020 (后面内容为00000000)
很显然这里是一个包含3个元素的数组,那么关键就在于这3个元素指向的是什么内容
00f57f60 地址的反汇编内容如下:
ClassSizeRes staticObj;
@0
00F57F60 push ebp
00F57F61 mov ebp,esp
00F57F63 sub esp,0C0h
00F57F69 push ebx
00F57F6A push esi
00F57F6B push edi
00F57F6C lea edi,[ebp-0C0h]
00F57F72 mov ecx,30h
00F57F77 mov eax,0CCCCCCCCh
00F57F7C rep stos dword ptr es:[edi]
00F57F7E mov ecx,offset staticObj (0F5E1E4h)
00F57F83 call ClassSizeRes::ClassSizeRes (0F51190h) //调用构造函数@1
00F57F88 push offset `dynamic atexit destructor for 'staticObj'' (0F590A0h)
00F57F8D call @ILT+190(_atexit) (0F510C3h)
00F57F92 add esp,4
00F57F95 pop edi
00F57F96 pop esi
00F57F97 pop ebx
00F57F98 add esp,0C0h
00F57F9E cmp ebp,esp
00F57FA0 call @ILT+625(__RTC_CheckEsp) (0F51276h)
00F57FA5 mov esp,ebp
00F57FA7 pop ebp
00F57FA8 ret
ClassSizeRes::ClassSizeRes:
@2
00F51190 jmp ClassSizeRes::ClassSizeRes (0F516E0h) @3
ClassSizeRes的构造函数
ClassSizeRes::ClassSizeRes(void)
{
@3
00F516E0 push ebp
00F516E1 mov ebp,esp
00F516E3 sub esp,0CCh
00F516E9 push ebx
00F516EA push esi
00F516EB push edi
00F516EC push ecx
00F516ED lea edi,[ebp-0CCh]
00F516F3 mov ecx,33h
00F516F8 mov eax,0CCCCCCCCh
00F516FD rep stos dword ptr es:[edi]
00F516FF pop ecx
00F51700 mov dword ptr [ebp-8],ecx
}
00F51703 mov eax,dword ptr [this]
00F51706 pop edi
00F51707 pop esi
00F51708 pop ebx
00F51709 mov esp,ebp
00F5170B pop ebp
00F5170C ret
如此看来全局变量的初始化过程如下
Step1:编译器编译之后会根据全局变量声明来生成一些无参函数如上的@0
Step2:程序运行之后,__tmainCRTStartup会调用_initterm函数来调用编译器生成的无参函数
(**pfbegin)();//函数指针,指向编译器自动生成无参函数地址@
这里的pfbegin-pfend都是指向的编译器生成的全局变量初始化函数pfbegin
Step3:无参全局变量初始化函数pfbegin会调用各个类的构造函数完成对象初始化@1
Step3:@1会调用各类的构造函数存根地址(IAT存根地址)
Step4:@2 跳转到构造函数实际实现地址完成对象的初始化
如此到了这一步基本上已经完成了一个全局变量的初始化.
那么相应的释放又是如何实现呢?在析构函数中下断点!发现调用堆栈如下:

很显然实在doexit中调用了相应的析构函数来完成全局变量的析构
static void __cdecl doexit (
int code,
int quick,
int retcaller
)
{
#ifdef _DEBUG
static int fExit = 0;
#endif /* _DEBUG */
#ifdef CRTDLL
if (!retcaller && check_managed_app())
{
/*
Only if the EXE is managed then we call CorExitProcess.
Native cleanup is done in .cctor of the EXE
If the Exe is Native then native clean up should be done
before calling (Cor)ExitProcess.
*/
__crtCorExitProcess(code);
}
#endif /* CRTDLL */
_lockexit(); /* assure only 1 thread in exit path */
__TRY
if (_C_Exit_Done != TRUE) {
_C_Termination_Done = TRUE;
/* save callable exit flag (for use by terminators) */
_exitflag = (char) retcaller; /* 0 = term, !0 = callable exit */
if (!quick) {
/*
* do _onexit/atexit() terminators
* (if there are any)
*
* These terminators MUST be executed in reverse order (LIFO)!
*
* NOTE:
* This code assumes that __onexitbegin points
* to the first valid onexit() entry and that
* __onexitend points past the last valid entry.
* If __onexitbegin == __onexitend, the table
* is empty and there are no routines to call.
*/
_PVFV * onexitbegin = (_PVFV *) DecodePointer(__onexitbegin);
if (onexitbegin) {
_PVFV * onexitend = (_PVFV *) DecodePointer(__onexitend);
_PVFV function_to_call = NULL;
/* save the start and end for later comparison */
_PVFV * onexitbegin_saved = onexitbegin;
_PVFV * onexitend_saved = onexitend;
while (1)
{
_PVFV * onexitbegin_new = NULL;
_PVFV * onexitend_new = NULL;
/* find the last valid function pointer to call. */
while (--onexitend >= onexitbegin && *onexitend == _encoded_null())
{
/* keep going backwards. */
}
if (onexitend < onexitbegin)
{
/* there are no more valid entries in the list, we are done. */
break;
}
/* cache the function to call. */
function_to_call = (_PVFV) DecodePointer(*onexitend);;//Decode之后指向编译器生成的资源释放处理代码
/* mark the function pointer as visited. */
*onexitend = (_PVFV)_encoded_null();
/* call the function, which can eventually change __onexitbegin and __onexitend */
(*function_to_call)();//又是一个无参函数值得关注,调用编译器生成资源释放代码,然后调用析构函数完成析构,与构造类似
onexitbegin_new = (_PVFV *) DecodePointer(__onexitbegin);
onexitend_new = (_PVFV *) DecodePointer(__onexitend);
if ( ( onexitbegin_saved != onexitbegin_new ) || ( onexitend_saved != onexitend_new ) )
{
/* reset only if either start or end has changed */
onexitbegin = onexitbegin_saved = onexitbegin_new;
onexitend = onexitend_saved = onexitend_new;
}
}
}
#ifndef CRTDLL
/*
* do pre-terminators
*/
_initterm(__xp_a, __xp_z);
#endif /* CRTDLL */
}
#ifndef CRTDLL
/*
* do terminators
*/
_initterm(__xt_a, __xt_z);
#endif /* CRTDLL */
#ifdef _DEBUG
/* Dump all memory leaks */
if (!fExit && _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_LEAK_CHECK_DF)
{
fExit = 1;
#ifndef CRTDLL
__freeCrtMemory();
_CrtDumpMemoryLeaks();
#endif /* CRTDLL */
}
#endif /* _DEBUG */
}
/* return to OS or to caller */
__FINALLY
if (retcaller)
_unlockexit(); /* unlock the exit code path */
__END_TRY_FINALLY
if (retcaller)
return;
_C_Exit_Done = TRUE;
_unlockexit(); /* unlock the exit code path */
__crtExitProcess(code);
}
详细的内容就不多做重复,与构造类似,从
(*function_to_call)()->编译器生成资源失败处理代码->调用到析构函数存根函数->跳转到实际的析构函数地址执行资源释放
如此基本上已经完成了全局变量资源的申请释放.
许久没有回头研究c++的一些概念了,突然被一道题考住了大体上如下
class a
{}
//...
a aobj;
size_t aobjSize = sizeof(aobj);
//问aobjSize等于多少?
个人以为aobjSize=4;
毕竟需要有地址空间来储存,而已一般来说每一个对象都有一个this指针所以虽然没有任何成员变量但是仍然应该为4
事实并非如此,写了一下代码发现为1
而且反汇编之后代码如下
...
00031758 mov dword ptr [ebp-4],0
size_t dwSize = sizeof(clsObj);
0003175F mov dword ptr [ebp-24h],1//sizeof(aobj)为什么直接就是1呢?
...
沿着这个问题网上google了一下发现如下:
一、简单对象的存储
1、 基本类型对齐原则:
Char 1
Short 2
Int 4
Long 4
Float 4
Double 8
2、 结构体类型对齐原则:(参见《结构体对齐》一文)
以最大成员类型的对齐方式为准,即当需要增长时,增长最大成员类型所占用的字节数。
3、 静态成员变量不占用类对象的存储空间原则:static
静态成员变量所有的类对象共享一份,在静态区域中,并不占用类对象的空间。
4、 没有任何成员变量的类对象占用一个字节的空间
对于没有任何成员变量的类(空类),其实它并不是空的,它隐含着被编译器添加了一个char。因为实例化的原因(空类同样可以被实例化),每个实例在内存中都必须有一个独一无二的地址。因此,编译器会给一个空类隐含的加一个字节,使空类在实例化后在内存得到独一无二的地址。
原来如此,编译器后台判断出来为空对象之后直接赋予一个char大小的空间,如此sizeof(EmptyClassObject)自然为1
汗那个汗啊。。。
参考网址如下:
http://blog.163.com/niwei_258/blog/static/1062848820109284914229/