当前位置: 首页 > 编程日记 > 正文

PE文件和COFF文件格式分析——导出表

在之前的《PE可选文件头》相关博文中我们介绍了可选文件头中很多重要的属性,而其中一个非常重要的属性是(转载请指明来源于breaksoftware的CSDN博客)

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 

该数组保存了如下节(不一定全包括,要以IMAGE_OPTIONAL_HEADER32(64)::NumberOfRvaAndSizes来确定)的信息

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

我们在之后会介绍各个节的结构和相关应用,本文我将介绍该数组中第一个元素(DataDirectory[0])的信息——导出表信息。
        首先我介绍下导出表。我们做程序时,新手一般喜欢做的是copy+paste。这个方法在代码结构不是很复杂的时候还能过的去。如果像微软这样的系统也是这么写,我想我们的黑客和漏洞挖掘者会非常高兴了——因为这样必定会产生更多的漏洞和bug。因为这样写的代码非常难维护。打个比方,我们有个函数实现了对XML的解析,有ABCDE这么多业务方去copy了这段代码。若干年后某天XML规则发生了改变,我们要修正XML解析算法,这个时候可能由于原来引入该段代码的员工离职了或者时间久远等原因,ABCDE各方都不知道自己的逻辑中用了XML,更不知道要去修正为新的算法。于是如何解决呢?ABCDE方应该让XML解析算法编写者提供一个.h和.cpp文件,里面包含了我们可能会调用的XML算法,然后在各自的代码中include这个XML算法编写者维护的目录下的这个.h文件,并调用.h中的方法。这样,以后XML算法即使改了,各业务方也可以保证我们使用的算法是最新的。但是还别高兴的太早,还有个问题放在我们面前。如果我们的程序是一个独立的Exe发布的话,在后续升级时会带来些麻烦。比如我们发布的Exe文件是1G,可是发布后我们发现一行代码写错了,于是我改了这行代码,却要让用户升级一个1G的文件!!流量啊!怎么办?为了便于升级,我们还是把1G文件合理分割成若干个文件,并保证它们可以协同工作。DLL就是这样被拆分出来的文件中一个非常重要的组成部分,它里面的导出函数就如同供其他方调用的XML解析的各种方法。导出表就是用于保存这些方法的名称和地址等信息的地方。

现在我们来说下导出表节的结构。在DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]中保存了导出表节的相对虚拟偏移RVA和大小,在之后的章节中我们会发现除了DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]保存的是RA(相对文件头的偏移),其他都是RVA。通过该RVA,我们算出RA,从而得到一个描述导出表头的结构体信息,该结构体是

typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD   Characteristics;DWORD   TimeDateStamp;WORD    MajorVersion;WORD    MinorVersion;DWORD   Name;DWORD   Base;DWORD   NumberOfFunctions;DWORD   NumberOfNames;DWORD   AddressOfFunctions;     // RVA from base of imageDWORD   AddressOfNames;         // RVA from base of imageDWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

Characteristics是保留字段,要求为0。

TimeDataStamp保存的生成导出信息的时间。

MajorVersion和MinorVersion分别是主版本号和此版本号。这些信息是我们可以决定的。

Name字段保存的该导出文件的名称的偏移。这儿要注意一点,这个地址是系统不关心的,我们可以将其指向的地址设置为违法的地址,这样会干扰部分PE分析工具的分析结果。

Base是导出函数的起始序数值,该值一般为1。如我们用View dependencies打开一个文件,红色部分就是Base字段相关的

NumberOfFunctions标志导出函数的函数地址数。该数据是非常重要的,我们要知道该文件导出了多少个函数就是要依据这个信息。我们之后会详细说的。

NumberOfNames标志导出函数的函数名数量。

AddressOfFunctions标志导出函数的函数地址表的RVA。

AddressOfNames标志导出函数的函数名表的RVA。

AddressOfNameOrdinals标志导出函数的导出序数表的RVA。

以我电脑上desktmon.dll为例,我们看一下该文件中该结构的布局

我们再用一个图来描述一下PE导出表在View dependencies中显示的相关关系

初次研究这个结构的同学可能会注意一个问题,该结构中有三个表的RVA(AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals),而只给出了其中前两个表的元素个数(NumberOfFunctions,NumberOfNames)。那第三个表——导出序数表的个数是多少?是按导出函数地址表(AddressOfFunctions)中元素个数(NumberOfFunctions)还是按导出函数名称表(AddressOfNames)中元素个数(NumberOfNames)?还有个问题:为什么要设置Base属性?这些问题我们先Mark下。我们先来详细介绍这三个表。

导出地址表。顾名思义,该表中保存了函数入口RVA。但是如果仅仅是如此简单就好了,这个地方保存的还可能是一个指向字符串的RVA!其结构是以下结构体的一个集合。

// 导出表信息
typedef struct _IMAGE_Export_Address_Table_
{union {DWORD dwExportRVA;DWORD dwForwarderRVA;};
}IMAGE_Export_Address_Table, *pIMAGE_Export_Address_Table;

如果它保存的是导出函数入口地址,那没什么好说的。我们说下它保存的是指向一个字符串的偏移的情况。在我的XP系统下Kernel32.dll中AddVectoredExceptionHandler函数的导出函数地址指向的字符串是NTDLL.RtlAddVectoredExceptionHandler。看到这样的名字组合,我想你大概能猜出个眉目。AddVectoredExceptionHandler函数,在Kernel32.dll文件内部是没有实现的。但是如果有程序需要加载Kernel32.dll并需要调用这个函数,则这样的写法会告诉加载器在加载Kernel32.dll时,要将AddVectoredExceptionHandler函数的地址直接改成Ntdll.dll中的RtlAddVectoredExceptionHandler函数地址(即自动加载Ntdll.dll)。这个特性非常有趣吧!我想做加壳的朋友应该对这个场景很熟悉。我之后会介绍利用这个特性去隐性自动加载DLL。最后说一下,我们如何辨别这个字段保存的是函数的入口地址的RVA还是字符串呢?只要判断该偏移不在导出表节中即可:指向的地址在节中就是字符串的RVA;在节外是函数入口的RVA。

导出名称表。计算机做出来是给人用的,如果给人一堆010101这样的数据,我想没谁会有太多兴趣去看的。于是出于人性化考虑,人们发明了别名,比如发明了汇编映射二进制指令,从而帮助理解程序逻辑。导出名称表就是出于这样的考虑而设计的。其结构是以下结构体的一个集合。

typedef struct _IMAGE_Export_Name_Pointer_Table_ {DWORD dwPointer;
}IMAGE_Export_Name_Pointer_Table,*pIMAGE_Export_Name_Pointer_Table;

它是指向字符串的RVA,该字符串是以\0结尾的。

说到这儿,我觉得我们可以停下思考一个问题,是不是只要有这两个表就够了?如果对于我们自己编写的且非常标准的DLL,只要有这两个表的确是够了。你想,当我们调用GetProcAddress时,我们在导入名称表中找到该名称对应的index,然后再返回导出函数地址表中该index的数据即可。

lpFunc = ExportAddressTable[ExportNameTable.find(FuncName)]

但是,PE文件设计的远没有这么简单。如果如此简单,那很多事都好办了。举一个特殊的例子来推翻这种简单的场景: 函数入口地址和函数名之间的关系是1对N(0~n)。我们程序运行起来后,很多时候是要调用其他逻辑,即函数入口。可以说一个函数入口可以唯一标注一个逻辑。而我们经常说的某某API,其实只是某个函数过程的一个名字。比如我们一个实现XML解析的函数,我们可以叫做ParseXML,也可以叫XMLParse。不管是叫哪个名字,该函数的功能是不变的,它的入口地址是不变的。如果入口地址变了,那就是另外一个函数了。这就是为什么说函数入口地址和函数名之间是1对N的关系。

针对以上问题,可能有人会想到,有多少个导出函数名(以导出函数名的数量为标准)就设置多少个导出地址,导出地址表中数据可以重复,比如上图中ParseXML和XMLParse函数名对应的导出地址都设置成0xXXXXXXXX就行了嘛。如

但是还有个场景:windows平台可以通过序数导入一个函数地址(GerProcAddress的第二个参数传序数),那么这就意味着函数可以没有函数名!!因为序数也可以看成一个函数的编号嘛,虽然这样非常不友好,但是仍然是一种可行的方法。那么如果在这种场景下,我们还能以导出函数名的数量为标准么?不可以了吧,因为函数名表元素数量可能是0!其实这类文件挺多,如mfc40u.dll,见下图

通过以上分析,我们可以得出,我们还是要一个能在导出函数地址表和导出函数名称表建立纽带的结构体。这个我们期待的辅助结构体就是我们下面介绍的导出序数表。

  导出序数表。该表保存的是导出地址表的序数偏移!切记这个重要的概念。那这个偏移是相对什么偏移的呢?是针对IMAGE_EXPORT_DIRECTORY::Base属性的。即这个表中保存的值加上Base,就是导出地址表的序数。其结构是以下结构体的一个集合。

typedef struct _IMAGE_Export_Ordinal_Table_ {WORD dwOrdinal;
}IMAGE_Export_Ordinal_Table,*pIMAGE_Export_Ordinal_Table;

从这个表的命名(AddressOfNameOrdinals )看,应该可以发现这个表应该和导出名称表存在一定的关系!是的,它的元素的数量和导出名称表的元素数量是一样的。可能有人会疑问,什么这个表元素的个数不是和导出地址表元素个数一致呢?因为如上面所说,一个函数过程可以对应多个函数名,如果导出序数表元素个数和导出函数地址表元素个数一样,则无法让地址与函数名对应上。比如我们导出地址表有1个函数入口,而我们有2个函数名都指向这个地址,那么导出序数表个数如果是1,则如何表示这两个名称与函数入口的对应呢?如果导出序数表格式是2个,则我们可以让这两个元素都“指向”同一个导出函数入口即可。OK,这儿我就解答了上面我们Mark过的那个问题:导出序数表个数和导出名称表个数一致。

那么这三个表之间具体什么关系呢?我首先以一个简单的、常规的文件为例,这个文件是上面提到的deskmon.dll。我们看一下View Dependencies的分析结果:

我们再把它的PE文件拿出来看下

我们把各个信息提取出来看下:

Characteristics;        0x00000000
TimeDateStamp;          0x3B7D74B7
MajorVersion;           0x0000
MinorVersion;           0x0000
Name;                   0x00002E6C
Base;                   0x00000001
NumberOfFunctions;      0x00000002 
NumberOfNames;          0x00000002
AddressOfFunctions;     0x00002E58
AddressOfNames;         0x00002E60
AddressOfNameOrdinals;  0x00002E68

可以看到这个Dll的导出地址表有2个元素,导出名称表和导出序数表也是有2个元素的。用之前《PE文件和COFF文件格式分析——RVA和RA相互计算》介绍的算法,我们可以得出

导出地址表RVA(0x00002E58)对应的RA是0x00002258。两个元素分别为{ {0,0x0002218},{1,0x00002534}}。和View Dependencis分析结果对比发现,这组数据是一致的。

导出名称表RVA(0x00002E60)对应的RA是0x00002260,其数据是{0,0x00002E78}和{1,0x00002E88}。0x00002E78是函数名的RVA,其对应的RA是0x00002278,即字符串“DllCanUnloadNow”;0x00002E88也是函数名称的RVA,其RA是0x00002288,即字符串“DllGetClassObject”。于是可以把导出函数名表看成{{0,DllCanUnloadNow},{1,DllGetClassObject}}。这个数据和View Dependencies中信息一致。

导出序数表RVA(0x00002E68)对应的RA是0x00002268,其数据是{{0,0x0000},{1,0x0001}}。但是这并不是最终数据,刚才我在介绍导出序数表时,说过这个表保存的是相对Base的偏移,该文件的Base是1,于是真实的数据是{{0,0x0001},{1,0x0002}}。

我们用图来说一下这三者的关系。

比如我们试图得到DllGetClassObject的函数地址。我们现在名称表中找到它的index是1。然后在序数表中找到index是1的元素的值0x00000002,。0x00000002要减去Base的值1得到值1。最后在地址表中找到index为1的元素的值,这个值就是DllGetClassObject函数的入口地址。表达式是

i = Search_ExportNamePointerTable (ExportName);
ordinal = ExportOrdinalTable [i];
SymbolRVA = ExportAddressTable [ordinal - OrdinalBase];

看了上面的逻辑, 我们在序数表中“加上”Base,然后要通过名称去找函数入口时又要从序数表中“减去”Base,是不是这儿Base是多余的?如果单从通过名称获取函数地址来看,Base的确是多余的。那么如果通过序数来获取函数地址呢?我构造了一个DLL——DllTestIndex.dll(工程地址)

LIBRARY	"DllTestIndex"
EXPORTSRet1 @2.Ret2 @4Ret3 @8Ret4 @6

发现通过序数去得到Ordinal为3的函数地址时会出错。这儿有两种可能:

A GetProcAddress看到函数地址是0x00000000就认为获取出错

B GetProcAddress是发现序数3不在序数表中(该文件导出序数表为{2,4,6,8},于是返回出错。

那么到底是那种呢?我将这个文件修改成如下,即将Ordinal为3的函数地址修改成一个有效的函数地址,得到一个文件DllTestIndex_Modify

如果是B原因,则此时我们去获取Ordinal为3的函数地址还是会失败。可是结果呢?GetProcAddress成功了,并正确返回了0x00011069这个函数入口地址。这个实验证明A原因是对的。这从而证明Base这个字段,对通过函数名寻找函数入口地址的算法的确是多余的信息。如果真要找个原因,可能从文件大小的说起。PE文件序数表的元素是WORD为单位的,而Base是DWORD。那么就是说,我们最多可以有0x10000(0x0000~0xFFFF)个导出函数。假如这些函数都在导出序数表中有对应元素,且导出序数表每个元素用DWORD描述,则需要sizeof(DWORD)*0x10000的空间。如果采用Base+WORD的方法,则只需要sizeof(WORD)*0x10000+sizeof(DWORD)的空间。采用后者最多可以节省0x3FFF8(0x40000-8)byte空间。其实这个空间很小的,可以忽略不计的。

除了之上那个非常强求的原因,Base就没用了么?不是!我们继续看上面那个例子,从我们DEF文件中看出,我们希望导出的4个函数的序号分别是2、4、8、6。我们看下PE文件中的布局

我们看到信息如下:

Base是0x00000002;NumberOfFunctions是0x00000007;导出函数地址分别为0x00011069、0x00000000、0x000110F5、0x00000000、0x000110A5、0x00000000、0x000110C3;导出名称是按我们在DEF申明的顺序是一致的,分别是:Ret1、Ret2、Ret3、Ret4。导出序数表是0x0000、0x0002、0x0006、0x0004。

注意View Dependencies的Ordinal列,该列的信息是函数地址的Index加上Base的值。于是

当我们如此调用时

typedef int(WINAPI* PRetN)();void ExprotFunc(LPSTR lpFileName) {HMODULE hDll = NULL;do {printf("%s\n", lpFileName);hDll = LoadLibraryA(lpFileName);if ( NULL == hDll ) {break;}for ( int nIndex = 0; nIndex <  10; nIndex ++ ) {PRetN pRetn = (PRetN)GetProcAddress( hDll, (LPCSTR)(LPVOID)(nIndex) );if ( NULL != pRetn ) {printf("nIndex is %d: Value is %d\n",nIndex,pRetn());}}FreeLibrary(hDll);} while (0);
}int _tmain(int argc, _TCHAR* argv[])
{ExprotFunc("DllTestIndex.dll");ExprotFunc("DllTestIndex_Modify.dll");system("pause");return 0;
}

此时GetProcAddress的第二个参数就是上图中间一列的信息,即View Dependencies的Ordinals信息。这儿要特别注意这个Ordinals和导出序数表(AddressOfNameOrdinals指向的表)不是一个东西。这样我就解答了上面我们Mark的那个问题——Base到底是为什么设计的?是为了通过序数导出函数而设计的。

之后我将会介绍几个对导出表好玩的应用。

最后贴一段导出表解析的代码

BOOL CGetPEInfo::GetExportInfo()
{BOOL bSuc = ( 0 == m_ExpDir.Characteristics ) ? TRUE : FALSE;if ( FALSE == bSuc ) {_ASSERT(FALSE);}m_ExpFullInfo.ExpDir = m_ExpDir;std::string wszTime;if ( FALSE == GetTime( m_ExpDir.TimeDateStamp, wszTime ) ) {_ASSERT(FALSE);}m_ExpFullInfo.wszTime = wszTime;std::string wszDllName;DWORD dwNameRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.Name, dwNameRA ) ) {_ASSERT(FALSE);}else {LPBYTE lpFileName = m_lpFileStart + dwNameRA;if ( lpFileName < m_lpFileStart || lpFileName > m_lpFileEnd ) {wszDllName.clear();}else {wszDllName = (LPSTR)lpFileName;}}m_ExpFullInfo.szImgName = wszDllName;MapIMAGE_Export_Address_Table MapImageExpAddrTable;DWORD dwFunAddrTableRA = 0;if ( FALSE ==  GetRAByRVA( m_ExpDir.AddressOfFunctions, dwFunAddrTableRA ) ) {if ( 0 != m_ExpDir.AddressOfFunctions ) {//_ASSERT(FALSE);}else {}}else {LPBYTE lpStart = m_lpFileStart + dwFunAddrTableRA;for ( WORD i = 0; i < m_ExpDir.NumberOfFunctions; i++ ) {IMAGE_Export_Address_Table ImgExpAddrTable;if ( FALSE == SafeCopy( &ImgExpAddrTable, lpStart, sizeof(IMAGE_Export_Address_Table)) ) {break;}MapImageExpAddrTable[i] = ImgExpAddrTable;lpStart += sizeof(IMAGE_Export_Address_Table);}}MapIMAGE_Export_Name_Pointer_Table MapImageExpNamePointerTable;DWORD dwFunNameTableRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfNames, dwFunNameTableRA ) ) {if ( 0 != m_ExpDir.AddressOfNames ) {_ASSERT(FALSE);}else {}}else {LPBYTE lpStart = m_lpFileStart + dwFunNameTableRA;for ( DWORD i = 0; i < m_ExpDir.NumberOfNames; i++ ) {IMAGE_Export_Name_Pointer_Table ImgExpNamePointer;if ( FALSE == SafeCopy( &ImgExpNamePointer, lpStart , sizeof(IMAGE_Export_Name_Pointer_Table) ) ) {break;}MapImageExpNamePointerTable[i] = ImgExpNamePointer;lpStart += sizeof(IMAGE_Export_Name_Pointer_Table);}}MapIMAGE_Export_Ordinal_Table MapImageExpOrdinalTable;DWORD dwOrdinalTableRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfNameOrdinals, dwOrdinalTableRA ) ) {if (  0 != m_ExpDir.AddressOfNameOrdinals ) {_ASSERT(FALSE);}else {//C:\Config.Msi\1ecac1a.rbf}}else {LPBYTE lpStart = m_lpFileStart + dwOrdinalTableRA;for ( WORD i = 0; i < m_ExpDir.NumberOfNames; i++ ) {IMAGE_Export_Ordinal_Table ImgExpOrdinalTable;if ( FALSE == SafeCopy( &ImgExpOrdinalTable, lpStart, sizeof(IMAGE_Export_Ordinal_Table) ) ) {break;}MapImageExpOrdinalTable[i] = ImgExpOrdinalTable;lpStart += sizeof(IMAGE_Export_Ordinal_Table);}}EXPORT_TABLE_FULL_INFO ExpTableFullInfo;if ( 0 != m_ExpDir.Base && 1 != m_ExpDir.Base ) {_ASSERT(FALSE);}if ( m_ExpDir.NumberOfNames != m_ExpDir.NumberOfFunctions ) {// _ASSERT(FALSE);}//  应该按地址数量来算for ( MapIMAGE_Export_Address_TableIter ImgExpAdIter = MapImageExpAddrTable.begin();ImgExpAdIter != MapImageExpAddrTable.end();ImgExpAdIter++ ){std::string strName;strName.empty();ExpTableFullInfo.wHint = 0xFFFF;ExpTableFullInfo.wOrdinal = ImgExpAdIter->first + (WORD)m_ExpDir.Base;DWORD dwRVA = ImgExpAdIter->second.dwExportRVA;if ( FALSE == IsRVAinSection( dwRVA, IMAGE_DIRECTORY_ENTRY_EXPORT )&& 0 != dwRVA ) {ExpTableFullInfo.bForwarderRVA = FALSE;ExpTableFullInfo.dwRVA.dwExportRVA = dwRVA;}else{ExpTableFullInfo.dwRVA.dwForwarderRVA = dwRVA;ExpTableFullInfo.bForwarderRVA = TRUE;DWORD dwForwarderRA;if ( FALSE == GetRAByRVA( ExpTableFullInfo.dwRVA.dwForwarderRVA, dwForwarderRA ) ) {if (  0 != ExpTableFullInfo.dwRVA.dwForwarderRVA ) {_ASSERT(FALSE);}else {// C:\4f1b3cac6fdc7b2cb9092b46e7c0fc71\Mobile Partner-dial\Mobile Partner-dial\mfc40u.dll// _ASSERT(FALSE);}//continue;}else {ExpTableFullInfo.strForwarder = (LPSTR)(m_lpFileStart + dwForwarderRA);}}MapIMAGE_Export_Ordinal_TableIter ImgExpOrdIter = MapImageExpOrdinalTable.begin();for ( ;ImgExpOrdIter != MapImageExpOrdinalTable.end();ImgExpOrdIter++ ){if ( ImgExpAdIter->first != ImgExpOrdIter->second.dwOrdinal ) {continue;}ExpTableFullInfo.wHint = ImgExpOrdIter->first;for ( MapIMAGE_Export_Name_Pointer_TableIter ImgExpNamePointerIter = MapImageExpNamePointerTable.begin();ImgExpNamePointerIter != MapImageExpNamePointerTable.end();ImgExpNamePointerIter++ ){if ( ImgExpNamePointerIter->first != ExpTableFullInfo.wHint ) {continue;}DWORD dwNamePointerRA = 0;if ( FALSE == GetRAByRVA( ImgExpNamePointerIter->second.dwPointer, dwNamePointerRA ) ) {continue;}else {strName =(LPSTR)(m_lpFileStart + dwNamePointerRA);}break;}break;}ExpTableFullInfo.strFuncName = strName;m_ExpFullInfo.vecExpTable.push_back( ExpTableFullInfo );}return bSuc;
}

相关文章:

将Quartz.NET集成到 Castle中

Castle是针对.NET平台的一个开源项目&#xff0c;从数据访问框架ORM到IOC容器&#xff0c;再到WEB层的MVC框架、AOP&#xff0c;基本包括了整个开发过程中的所有东西&#xff0c;为我们快速的构建企业级的应用程序提供了很好的服务.具体可参看TerryLee的Castle 开发系列文章。 …

《评人工智能如何走向新阶段》后记(再续10)

本文由AI科技大本营下载自视觉中国106.百度自研的飞桨&#xff08;Paddle paddle&#xff09;框架是中国自研的首个开源产业极人工智能深度学习框架&#xff0c;目前飞桨已累计服务150多万开发者&#xff0c;在定制化训练平台上企业用户超过6.5万&#xff0c;发布了16.9万模型&…

水管工游戏 (深搜)

水管工游戏 本题依然是采用搜索&#xff0c;深搜&#xff0c;广搜都可以&#xff0c;本代码采用深搜&#xff0c;此题在搜索时需要增加一些判断条件以及下一步要搜索的位置即可。 代码如下&#xff1a; #include<stdio.h> int a[51][51]; int book[51][51],n,m,flag0,top…

PE文件和COFF文件格式分析——导出表的应用——一种插件模型

可能在很多人想想中&#xff0c;只有DLL才有导出表&#xff0c;而Exe不应该有导出表。而在《PE文件和COFF文件格式分析——导出表》中&#xff0c;我却避开了这个话题。我就是想在本文中讨论下载Exe中存在导出表的场景。&#xff08;转载请指明出于breaksoftware的csdn博客&…

IBatis.Net学习笔记九--动态选择Dao的设计分析

在IBatis.Net中可以通过配置文件动态选择数据库、动态选择Dao对象。Dao对象也就是操作数据库的类&#xff0c;通过配置文件我们可以选择DataMapper的方式、Ado的方式、NHibernet的方式以前其他第三方的方式来操作数据库。有利于系统的灵活性和可扩展性。通过分析动态选择Dao的设…

Pytorch和Tensorflow,谁会笑到最后?

作者 | 土豆变成泥来源 | 知秋路&#xff08;ID:gh_4a538bd95663&#xff09;【导读】作为谷歌tensorflow某项目的Contributor&#xff0c;已经迅速弃坑转向Pytorch。目前Tensorflow还没有被Pytorch比下去&#xff0c;但之后极大概率被比下去。01 在学术界Pytorch已经超越Tenso…

HTTP请求的过程

HTTP通信机制是在一次完整的HTTP通信过程中&#xff0c;Web浏览器与Web服务器之间将完成下列7个步骤&#xff1a; 1. 建立TCP连接在HTTP工作开始之前&#xff0c;Web浏览器首先要通过网络与Web服务器建立连接&#xff0c;该连接是通过TCP来完成的&#xff0c;该协议与IP协议共同…

JSTL+EL表达式方法获取Oracle的Clob字段内容

我们在页面获得数据的时候一般的类型还是很好获得的,但是一遇到Clob类型就比较麻烦,最常用的方法是用一个流将其读取出来.使用MVC框架的时候这些都是无所谓的事情,因为反正是写在java类中怎么写都行,可是不使用MVC框架,使用jsp页面JSTL的sql标签去读取数据库的数据这种方式就麻…

通向人工智能产业落地化的道路在哪?

整理 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;世事浮云&#xff0c;白云苍狗&#xff0c;转眼间关于人工智能的研究已历经两个世纪。在研究者和践行者的不懈努力之下&#xff0c;如今人工智能应用已遍地可见&#xff0c;无论是繁华都市还是偏远小镇&…

PE文件和COFF文件格式分析——导出表的应用——通过导出表隐性加载DLL

通过导出表隐性加载DLL&#xff1f;导出表&#xff1f;加载DLL&#xff1f;还隐性&#xff1f;是的。如果觉得不可思议&#xff0c;可以先看《PE文件和COFF文件格式分析——导出表》中关于“导出地址表”的详细介绍。&#xff08;转载请指明出于breaksoftware的csdn博客&#x…

系统启动时,spring配置文件解析失败,报”cvc-elt.1: 找不到元素 'beans' 的声明“异常...

现象&#xff1a;spring加载配置文件applicationContext.xml出错,抛出nested exception is og.xml.sax.SAXParseException; lineNumber: 12; columnNumber: 47; cvc-elt.1: 找不到元素 beans 的声明r的异常信息。 造成该异常原因有两种&#xff1a;第一,配置文件头部配置的xsd版…

DllMain中不当操作导致死锁问题的分析--死锁介绍

最近在网上看到一些关于在DllMain中不当操作导致死锁的问题&#xff0c;也没找到比较确切的解答&#xff0c;这极大吸引了我研究这个问题的兴趣。我花了一点时间研究了下&#xff0c;正好也趁机研究了下进程对DllMain的调用规律。因为整个研究篇幅比较长&#xff0c;我觉得还是…

XGBoost缺失值引发的问题及其深度分析 | CSDN博文精选

作者 | 兆军&#xff08;美团配送事业部算法平台团队技术专家&#xff09;来源 | 美团技术团队&#xff08;*点击阅读原文&#xff0c;查看美团技术团队更多文章&#xff09;背景XGBoost模型作为机器学习中的一大“杀器”&#xff0c;被广泛应用于数据科学竞赛和工业领域&#…

什么是CPI指数和GDP

即消费者物价指数(Consumer Price Index)&#xff0c;英文缩写为CPI&#xff0c;是反映与居民生活有关的产品及劳务价格统计出来的物价变动指标&#xff0c;通常作为观察通货膨胀水平的重要指标。如果消费者物价指数升幅过大&#xff0c;表明通胀已经成为经济不稳定因素&#x…

The Ultimate Guide To iPhone Resolutions

2019独角兽企业重金招聘Python工程师标准>>> ios 屏幕尺寸 像素 等说明 转载于:https://my.oschina.net/starmier/blog/467271

DllMain中不当操作导致死锁问题的分析--进程对DllMain函数的调用规律的研究和分析

不知道大家是否思考过一个过程&#xff1a;系统试图运行我们写的程序&#xff0c;它是怎么知道程序起始位置的&#xff1f;很多同学想到&#xff0c;我们在编写程序时有个函数&#xff0c;类似Main这样的名字。是的&#xff01;这就是系统给我们提供的控制程序最开始的地方&…

力挺Python!同是程序员,为啥同事年前就实现了财务自由?

人红是非多&#xff0c;最近Python就遇到了这样的问题。与技术社区上一片「形势大好」对比鲜明的是&#xff0c;国内技术圈却一直存在对Python&#xff0c;「力挺」和「吃瓜」两派阵营&#xff0c;针锋相对&#xff0c;那么&#xff0c;Python到底有没有用&#xff0c;真相究竟…

C# 判断远程文件是否存在

#region 判断远程文件是否存在/// <summary>/// 判断远程文件是否存在/// </summary>/// <param name"fileUrl"></param>/// <returns></returns>public static bool RemoteFileExists(string fileUrl){HttpWebRequest re null…

DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子

有了前面两节的基础&#xff0c;我们现在切入正题&#xff1a;研究下DllMain为什么会因为不当操作导致死锁的问题。首先我们看一段比较经典的“DllMain中死锁”代码。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; //主线程中 HMODULE h LoadLibraryA(strD…

性能超FPN!北大、阿里等提多层特征金字塔网络

作者 | Qijie Zhao等编译 | 李杰出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;特征金字塔网络具有处理不同物体尺度变化的能力&#xff0c;因此被广泛应用到one-stage目标检测网络&#xff08;如DSSD&#xff0c;RetinaNet&#xff0c;RefineDet&#xff09;和two-…

什么是WIFI

WIFI全称Wireless Fidelity&#xff0c;又称802.11b标准&#xff0c;它的最大优点就是传输速度较高&#xff0c;可以达到11Mbps&#xff0c;另外它的有效距离也很长&#xff0c;同时也与已有的各种802.11DSSS设备兼容。 WIFI是由AP(Access Point)和无线网卡组成的无线网络。…

Android入门——电话拨号器和4种点击事件

关于HelloWorld为,电话拨号程序还AndroidA入门demo,从这个样例我们要理清楚做安卓项目的思路。大体分为三步&#xff1a; 1.理解需求&#xff0c;理清思路 2.设计UI 3.代码实现 电话拨号器 1. 理解需求&#xff1a; *一个文本框——用来接收电话号码 *一个button——用来触发事…

DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子2

本文介绍使用Windbg去验证《DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子》中的结论&#xff0c;调试对象是文中刚开始那个例子。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 1 g 让程序运行起来 2 ctrlbreak 中断程序 3 ~ 查看…

从入门到深入:移动平台模型裁剪与优化的技术探索与工程实践

可以看到&#xff0c;通过机器学习技术&#xff0c;软件或服务的功能和体验得到了质的提升。比如&#xff0c;我们甚至可以通过启发式引擎智能地预测并调节云计算分布式系统的节点压力&#xff0c;以此改善服务的弹性和稳定性&#xff0c;这是多么美妙。而对移动平台来说&#…

我在不炎熱也不抑鬱的秋天,依然不抽煙

写过几次电影的观后感,挺过瘾.最近看到my little airport的那张新唱片,再也没有办法保持沉默了 为什么人家的唱片名都起的和小说一样,难得是为了证明听歌的人们都不喜欢动笔吗? 于是,我建了个类别,叫 我也会听歌.很明显,这里面会塞一些和歌相关的东西 这是第一篇

ubuntu安装redis的方法以及PHP安装redis扩展、CI框架sess使用redis的方法

为什么80%的码农都做不了架构师&#xff1f;>>> 再一次被网上那些教程误导后决定自己写一个。真心被那些奇怪的教程误导了好几次&#xff0c;之前研究其它东西的时候也是。蛋疼啊。 安装redis 直接用apt-get命令即可 sudo apt-get install redis-server 安装的时候…

浅谈数据库设计技巧

说到数据库&#xff0c;我认为不能不先谈数据结构。1996年&#xff0c;在我初入大学学习计算机编程时&#xff0c;当时的老师就告诉我们说&#xff1a;计算机程序&#xff1d;数据结构&#xff0b;算法。尽管现在的程序开发已由面向过程为主逐步过渡到面向对象为主&#xff0c;…

避免神经网络过拟合的5种技术(附链接) | CSDN博文精选

作者 | Abhinav Sagar翻译 | 陈超校对 | 王琦来源 | 数据派THU(ID:DatapiTHU)(*点击阅读原文&#xff0c;查看作者更多精彩文章&#xff09;本文介绍了5种在训练神经网络中避免过拟合的技术。 最近一年我一直致力于深度学习领域。这段时间里&#xff0c;我使用过很多神经网络&a…

DllMain中不当操作导致死锁问题的分析--加载卸载DLL与DllMain死锁的关系

前几篇文章一直没有在源码级证明&#xff1a;DllMain在收到DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH时会进入临界区。这个论证非常重要&#xff0c;因为它是使其他线程不能进入临界区从而导致死锁的关键。我构造了在DLL被映射到进程地址空间的场景&#xff0c;请看死锁时加载DL…

LinearLayout增加divider分割线

2019独角兽企业重金招聘Python工程师标准>>> 在android3.0及后面的版本在LinearLayout里增加了个分割线 android:divider"drawable/shape"<!--分割线图片--> android:showDividers"middle|beginning|end" <!--分割线位置--> 分割线…