手动加载PE文件

今天手撸一下加载PE文件,并执行加载的PE文件。看完这一节之后相信大家会对PE文件的结构和在内存中的加载顺序有一个比较深刻的理解。
本文中可能对PE文件的基础知识介绍的不是很详细,建议大家先看看PE文件的基础结构,了解了这些基础知识后再看本文会简单许多。废话不多说,下边让我们进入正是环节吧~
主要流程分为这么几步:
1、读取PE文件到内存中;
2、申请用于加载PE文件的内存;
3、复制PE文件的所有节表到内存中;
4、修复IAT表;
5、修复重定位表;
6、转换并执行PE文件的入口函数。

读取PE文件

这块太简单了,就不多说了,直接上代码:

#include <Windows.h>HANDLE hFile = CreateFile(fileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);if (hFile){DWORD dwFileSize = GetFileSize(hFile,NULL);LPSTR fileData = new CHAR[dwFileSize];if(fileData ){RtlZeroMemory(fileData, dwFileSize);DWORD dwReadSize = 0;ReadFile(hFile,fileData ,dwFileSize,&dwReadSize,NULL);}CloseHandle(hFile);hFile = NULL;}

申请内存

申请内存之前我们需要先解析PE文件中的DOS头和NT头(PS:不知道这是啥的同学,强烈建议去翻翻PE基础结构的知识,务必!务必!务必!),从中获取到PE文件加载后的大小。
我们使用VirtualAlloc函数申请内存,获取到申请的内存地址后,需要将PE的DOS头和NT头复制到此块内存中,这也是PE文件加载的基础。

#include <Windows.h>
#include <winternl.h>BOOL AllocateMemory()
{//这里的fileData就是第一步加载的PE文件数据//Dos头PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)fileData;//Nt头PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)fileData + DosHeader->e_lfanew);//申请内存,返回的是申请到内存地址PVOID ImageBase = VirtualAlloc(NULL, NtHeaders->OptionalHeader.SizeOfImage,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);if (!ImageBase)return FALSE;//dos头、nt头和节表头复制到新申请的内存中RtlCopyMemory(ImageBase,DosHeader,NtHeaders->OptionalHeader.SizeOfHeaders);return TRUE;
}

复制节表到内存中

要想复制PE文件中的所有节表,我们需要知道两个数据,一个是节表的大小,另一个则是第一个节表的地址。找第一个节表的地址时,我们可以使用微软提供的宏IMAGE_FIRST_SECTION,传参就是我们的NT头指针。当然,我们也可以手动查找,就在NT头的后边。
找到所有节表之后,我们需要循环将节表的数据复制到我们申请的内存中。

#include <Windows.h>
#include <winternl.h>VOID CopyAllSections()
{//第一个节表PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(NtHeaders);//节表的大小DWORD dwSectionsSize = NtHeaders->FileHeader.NumberOfSections;for (DWORD i = 0; i < dwSectionsSize; i++){RtlCopyMemory((PVOID)((ULONG_PTR)ImageBase+ section_header->VirtualAddress),(PVOID)((ULONG_PTR)DosHeader + section_header->PointerToRawData),section_header->SizeOfRawData);section_header++;}//DOS头PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;//保存申请内存中的NT头PIMAGE_NT_HEADERS MemNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew);//PE文件的入口地址ULONG_PTR EntryPointer = (ULONG_PTR)ImageBase + MemNtHeaders->OptionalHeader.AddressOfEntryPoint;return VOID();
}

修复IAT表

好了,我们正式进入修复IAT表的阶段,这是加载PE文件两个重要过程之一。
IAT表也被叫做导入表,它包含了运行这个PE文件需要用到的库(dll),由于我们的基址已经改变了,所以我们需要将用到的库函数地址进行修复,使其正确的指向要调用的函数,不然会导致PE文件无法正常加载。
导入表从上到下可以分为两层,库名-函数名。
简单解析一下就是,一个PE文件包含多个库,每个库都包含多个函数。
1、我们可以从NT头的目录结构中找到导入表的地址。
2、需要注意的是,PE文件加载导入表的时候有两种情况,一种是根据函数序号获取函数地址,一种是根据函数名获取函数地址。而判断究竟使用了哪种方式。主要是根据OriginalFirstThunk字段的最高位判断。

BOOL RepairIAT()
{//MemNtHeaders 内存中的NT头数据if (MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)return FALSE;#ifdef _WIN64union{struct{ULONGLONG low : 63;ULONGLONG high : 1;}BitField;ULONGLONG Value;}Temp = { 0 };
#elseunion{struct{ULONG low : 31;ULONG high : 1;}BitField;ULONG Value;}Temp = { 0 };
#endif PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)ImageBase+ MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);while (pIID->Name){PIMAGE_THUNK_DATA pITD = (PIMAGE_THUNK_DATA)((ULONG_PTR)ImageBase+ pIID->OriginalFirstThunk);//记录真是函数地址的字段PULONG_PTR pFuncAddr = (PULONG_PTR)((ULONG_PTR)ImageBase + pIID->FirstThunk);while (*pFuncAddr != 0){HMODULE hModule = LoadLibraryA((LPSTR)((ULONG_PTR)ImageBase + pIID->Name));if (!hModule)return FALSE;Temp.Value = pITD->u1.AddressOfData;//根据函数序号获取函数地址if (Temp.BitField.high == 1){//修复函数地址*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, (LPCSTR)Temp.BitField.low);}//根据函数名获取函数地址else{//函数名PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)ImageBase+ pITD->u1.AddressOfData);//修复函数地址*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, pFuncName->Name);}pITD++;pFuncAddr++;}pIID++;}return TRUE;
}

修复重定位表

对于可执行文件来说,一般没有重定位表,而对于动态库(dll)文件来说,基本都会有重定位表。重定位表就是对导出函数的地址修正。
要修复重定位表,需要了解它的结构,一般是一个头+一组数据。

BOOL RepairReloc()
{//MemNtHeaders 内存中Nt头数据if (MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0){return FALSE;}//一个重定位的数据是2字节typedef struct BASE_RELOCATION_ENTRY {USHORT offset : 12;USHORT type : 4;}BASE_RELOCATION_ENTRY,*PBASE_RELOCATION_ENTRY;//NtHeaders是PE文件中的Nt头//这里是为了计算我们内存中加载PE文件的基址和PE文件默认基址的一个偏移INT_PTR offset = (INT_PTR)((ULONG_PTR)ImageBase - NtHeaders->OptionalHeader.ImageBase);PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase+m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);while (pIBR->VirtualAddress != 0){//重定位表的一个数据PBASE_RELOCATION_ENTRY pBlock = (PBASE_RELOCATION_ENTRY)((ULONG_PTR)pIBR+sizeof(IMAGE_BASE_RELOCATION));//一组重定位数据的个数DWORD NumberOfBlocks = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(BASE_RELOCATION_ENTRY);for (DWORD i = 0; i < NumberOfBlocks; i++, pBlock++){//类型是IMAGE_REL_BASED_ABSOLUTE的不需要进行重定位if (pBlock->type != IMAGE_REL_BASED_ABSOLUTE){//需要重定位的地址PINT_PTR RepairAddr = (PINT_PTR)((ULONG_PTR)ImageBase + pIBR->VirtualAddress + pBlock->offset);if (*RepairAddr <= 0)return FALSE;//重定位*RepairAddr += offset;}}pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pIBR + pIBR->SizeOfBlock);}return TRUE;
}

转换函数入口

从第二步中我们可以得到函数入口地址,直接执行即可。这是对于可执行文件而言的,如果是动态库文件,我们需要转换一下

VOID CallEntryPoint()
{if (IsDllFile()){typedef BOOL(APIENTRY *_DllMain)(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);//EntryPointer 是第二步获取到的函数地址((_DllMain)EntryPointer)((HMODULE)ImageBase, DLL_PROCESS_ATTACH,NULL);//如果动态库中有导出函数,则可以使用这个转换一下,获取导出函数地址(MemGetProcAddress)的下边再说typedef int(*_fntestdll)(void);_fntestdll fntestdll = (_fntestdll)MemGetProcAddress("fntestdll");fntestdll();}else{((void(*)())(EntryPointer))();}return VOID();
}

下边我们说一下如何获取动态库中导出函数,简单的原理就是从修复完的PE文件内存中解析导出表,然后从导出表中获取相应的函数。
其中我们需要知道导出表的三个地址:函数名地址、导出函数序号地址、导出函数地址
导出函数序号地址记录的是相应函数名的函数序号,再根据函数序号从导出函数地址中获取函数的地址。
原因嘛,导出函数地址是按函数序号从小到大顺序记录的,但是函数名地址中保存地址和导出函数地址顺序是不一样的。一般函数名地址排序方式是按首字母排序的。

PVOID MemGetProcAddress(LPCSTR funcName)
{if (MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)return NULL;PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)ImageBase + m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);PDWORD pNameAddress = (PDWORD)((ULONG_PTR)ImageBase + pIED->AddressOfNames);PDWORD pFuncAddress = (PDWORD)((ULONG_PTR)ImageBase + pIED->AddressOfFunctions);PWORD pNameOrdinalsAddress = (PWORD)((ULONG_PTR)ImageBase + pIED->AddressOfNameOrdinals);for (DWORD i = 0; i < pIED->NumberOfNames; i++){if (strcmp(funcName, (LPCSTR)(ULONG_PTR)ImageBase + *pNameAddress) == 0){return (PVOID)((ULONG_PTR)ImageBase + pFuncAddress[pNameOrdinalsAddress[i]]);}pNameAddress++;}return PVOID();
}

好了,手动加载PE文件并执行的全部流程就到此为止了,刚开始的时候大家可能感觉理解起来有点费劲,但是熟悉了之后就感觉还好,为了方便大家,下边给大家附上全部代码(PS:使用C++写的)

DealPEFile.h

#pragma once
#include <Windows.h>
#include <winternl.h>class DealPEFile
{
public:DealPEFile(LPCWSTR fileName);~DealPEFile();VOID LoadMemory();
private://判断是否是PE文件BOOL IsValidPE();//申请内存BOOL AllocateMemory();//拷贝所有节表到内存中VOID CopyAllSections();//修复IAT表BOOL RepairIAT();//修复重定位表BOOL RepairReloc();//获取导出函数PVOID MemGetProcAddress(LPCSTR funcName);//判断是否是Dll文件BOOL IsDllFile();//执行PE文件的入口函数VOID CallEntryPoint(DealPEFile* pDealPEFile);private://文件数据LPSTR m_fileData;//PE基地址PVOID m_ImageBase;//Dos头PIMAGE_DOS_HEADER m_DosHeader;//Nt头PIMAGE_NT_HEADERS m_NtHeaders;//内存中Nt头PIMAGE_NT_HEADERS m_MemNtHeaders;//PE文件入口地址ULONG_PTR m_EntryPointer;
};

DealPEFile.cpp

#include "DealPEFile.h"DealPEFile::DealPEFile(LPCWSTR fileName)
{m_fileData = NULL;m_DosHeader = NULL;m_NtHeaders = NULL;if (fileName != NULL && fileName[0] != L'\0'){HANDLE hFile = CreateFile(fileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);if (hFile){DWORD dwFileSize = GetFileSize(hFile,NULL);m_fileData = new CHAR[dwFileSize];RtlZeroMemory(m_fileData, dwFileSize);DWORD dwReadSize = 0;ReadFile(hFile,m_fileData,dwFileSize,&dwReadSize,NULL);CloseHandle(hFile);hFile = NULL;}if (m_fileData){m_DosHeader = (PIMAGE_DOS_HEADER)m_fileData;m_NtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)m_DosHeader + m_DosHeader->e_lfanew);}}
}DealPEFile::~DealPEFile()
{if (m_fileData){delete[] m_fileData;m_fileData = NULL;}m_DosHeader = NULL;m_NtHeaders = NULL;
}VOID DealPEFile::LoadMemory()
{if (m_fileData == NULL)return;//判断是否是ie文件,申请加载PE文件的内存if (!IsValidPE() || !AllocateMemory())return;//复制所有节表到内存中CopyAllSections();//修复IAT表if (!RepairIAT())return;//修复重定位表if (!RepairReloc())return;//执行PE文件的入口函数CallEntryPoint(this);return;
}BOOL DealPEFile::IsValidPE()
{if (m_DosHeader->e_magic != IMAGE_DOS_SIGNATURE || m_NtHeaders->Signature != IMAGE_NT_SIGNATURE)return FALSE;#ifdef _WIN64if (m_NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64){return FALSE;}
#elseif (m_NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_I386){return FALSE;}
#endifreturn TRUE;
}BOOL DealPEFile::AllocateMemory()
{m_ImageBase = VirtualAlloc(NULL, m_NtHeaders->OptionalHeader.SizeOfImage,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);if (!m_ImageBase)return FALSE;RtlCopyMemory(m_ImageBase,m_DosHeader,m_NtHeaders->OptionalHeader.SizeOfHeaders);return TRUE;
}//拷贝所有节表到内存中
VOID DealPEFile::CopyAllSections()
{PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(m_NtHeaders);//节表数量DWORD dwSectionsSize = m_NtHeaders->FileHeader.NumberOfSections;for (DWORD i = 0; i < dwSectionsSize; i++){RtlCopyMemory((PVOID)((ULONG_PTR)m_ImageBase+ section_header->VirtualAddress),(PVOID)((ULONG_PTR)m_DosHeader + section_header->PointerToRawData),section_header->SizeOfRawData);section_header++;}PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)m_ImageBase;m_MemNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew);m_EntryPointer = (ULONG_PTR)m_ImageBase + m_MemNtHeaders->OptionalHeader.AddressOfEntryPoint;return VOID();
}//修复IAT表
BOOL DealPEFile::RepairIAT()
{if (m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)return FALSE;#ifdef _WIN64union{struct{ULONGLONG low : 63;ULONGLONG high : 1;}BitField;ULONGLONG Value;}Temp = { 0 };
#elseunion{struct{ULONG low : 31;ULONG high : 1;}BitField;ULONG Value;}Temp = { 0 };
#endif //导入表PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)m_ImageBase+ m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);while (pIID->Name){PIMAGE_THUNK_DATA pITD = (PIMAGE_THUNK_DATA)((ULONG_PTR)m_ImageBase+ pIID->OriginalFirstThunk);//函数地址PULONG_PTR pFuncAddr = (PULONG_PTR)((ULONG_PTR)m_ImageBase + pIID->FirstThunk);while (*pFuncAddr != 0){HMODULE hModule = LoadLibraryA((LPSTR)((ULONG_PTR)m_ImageBase + pIID->Name));if (!hModule)return FALSE;Temp.Value = pITD->u1.AddressOfData;if (Temp.BitField.high == 1){*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, (LPCSTR)Temp.BitField.low);}else{PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)m_ImageBase+ pITD->u1.AddressOfData);*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, pFuncName->Name);}pITD++;pFuncAddr++;}pIID++;}return TRUE;
}//修复重定位表
BOOL DealPEFile::RepairReloc()
{if (m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0){return FALSE;}typedef struct BASE_RELOCATION_ENTRY {USHORT offset : 12;USHORT type : 4;}BASE_RELOCATION_ENTRY,*PBASE_RELOCATION_ENTRY;//偏移INT_PTR offset = (INT_PTR)((ULONG_PTR)m_ImageBase - m_NtHeaders->OptionalHeader.ImageBase);//重定位表PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)m_ImageBase+m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);while (pIBR->VirtualAddress != 0){//重定位块数据PBASE_RELOCATION_ENTRY pBlock = (PBASE_RELOCATION_ENTRY)((ULONG_PTR)pIBR+sizeof(IMAGE_BASE_RELOCATION));//一块重定位数据的数量DWORD NumberOfBlocks = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(BASE_RELOCATION_ENTRY);for (DWORD i = 0; i < NumberOfBlocks; i++, pBlock++){if (pBlock->type != IMAGE_REL_BASED_ABSOLUTE){//修复地址PINT_PTR RepairAddr = (PINT_PTR)((ULONG_PTR)m_ImageBase + pIBR->VirtualAddress + pBlock->offset);if (*RepairAddr <= 0)return FALSE;*RepairAddr += offset;}}pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pIBR + pIBR->SizeOfBlock);}return TRUE;
}//获取导出函数
PVOID DealPEFile::MemGetProcAddress(LPCSTR funcName)
{if (m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)return NULL;PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)m_ImageBase + m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);//函数名地址PDWORD pNameAddress = (PDWORD)((ULONG_PTR)m_ImageBase + pIED->AddressOfNames);//函数地址PDWORD pFuncAddress = (PDWORD)((ULONG_PTR)m_ImageBase + pIED->AddressOfFunctions);//函数名序号地址PWORD pNameOrdinalsAddress = (PWORD)((ULONG_PTR)m_ImageBase + pIED->AddressOfNameOrdinals);for (DWORD i = 0; i < pIED->NumberOfNames; i++){if (strcmp(funcName, (LPCSTR)(ULONG_PTR)m_ImageBase + *pNameAddress) == 0){return (PVOID)((ULONG_PTR)m_ImageBase + pFuncAddress[pNameOrdinalsAddress[i]]);}pNameAddress++;}return PVOID();
}//判断是否是Dll文件
BOOL DealPEFile::IsDllFile()
{return m_MemNtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL;
}//执行PE文件的入口函数
VOID DealPEFile::CallEntryPoint(DealPEFile* pDealPEFile)
{if (IsDllFile()){typedef BOOL(APIENTRY *_DllMain)(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);((_DllMain)pDealPEFile->m_EntryPointer)((HMODULE)m_ImageBase, DLL_PROCESS_ATTACH,NULL);typedef int(*_fntestdll)(void);_fntestdll fntestdll = (_fntestdll)MemGetProcAddress("fntestdll");fntestdll();}else{((void(*)())(pDealPEFile->m_EntryPointer))();}return VOID();
}

此代码加载的PE文件是我测试使用的,测试的代码也给大家附上
Test.exe

#include <Windows.h>#pragma comment(linker,"/entry:Test")int Test()
{MessageBox(NULL,L"Test Exe!!!", L"提示", MB_OK);return 0;
}

Test.dll

dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:MessageBox(NULL,L"test dll Main",L"提示",MB_OK);break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}

testdll.h


#ifdef __cplusplus
extern "C" {
#endif__declspec(dllexport) int fntestdll(void);
#ifdef __cplusplus
}
#endif

testdll.cpp

#include "testdll.h"
#include <Windows.h>// 这是导出函数的一个示例。
int fntestdll(void)
{MessageBox(NULL,L"fntestdll",L"提示",MB_OK);return 42;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/837086.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库面试总结

数据库相关 mysql使用的函数 字符相关: concant() 连接字符 trim()去除字符的首尾空格 space(n) 返回n个空格 char_length() 返回字符的个数 ucase()/upper()将字符串 s 的所有字母变成大写字母 lcase()/lower() 将字符串 s 的所有字母变成小写字母 substr/substring/mid(s, …

第十五章 数据管理成熟度评估练习

单选题 (每题1分,共19道题) 1、 [单选] 下列选项中属于数据管理成熟度2级特征的选项是? A:很少或没有治理;有限的工具集;单个竖井(系统)内定义角色;控件(如果有的话的应用完全不一致);未解决的数据质量问题 B:治理开始出现;引入一致的工具集;定义了一些角色和…

杂记-记一次前端打包问题解决过程

背景 若干年没更新发布的前端项目&#xff0c;突然来了个小需求&#xff0c;需求完成耗时5min&#xff0c;打包问题解决2小时 问题 error commander12.0.0: The engine “node” is incompatible with this module. Expected version “>18”. Got “10.22.1” 这个错误…

卷积神经网络边缘识别

为什卷积神经网络能够识别图片呢&#xff1f;是基于图片相似度比较&#xff0c;两张图片的点击越大说明两张图片越像&#xff0c;比如我们那狗胡子的图片去比较&#xff0c;如果相似度很高&#xff0c;就是认为这个动物更像狗。点积越大&#xff0c;图片越相似&#xff0c;这个…

vivado Virtex UltraScale 配置存储器器件

Virtex UltraScale 配置存储器器件 下表所示闪存器件支持通过 Vivado 软件对 Virtex UltraScale ™ 器件执行擦除、空白检查、编程和验证等配置操作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列非易失…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (六)

LlaMA 3 系列博客 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (一) 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (二) 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (三) 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (四) 基于 LlaMA…

第三课,python基础语法(二),基本算术运算符、3种数据类型、变量命名规则

一&#xff0c;基本算术运算 数学中&#xff1a;&#xff0c;-&#xff0c;&#xff0c; *小练习 请在程序中&#xff0c;定义如下变量&#xff1a; 钱包余额(变量名&#xff1a;money)&#xff0c;初始余额50 请通过程序计算&#xff0c;再购买了&#xff1a; 冰淇淋10元可…

微信小程序、uniapp密码小眼睛

直接上代码喔喔喔喔喔喔喔喔~~ <input name"username" password"{{passwordHideShow}}" placeholder-style"color:#bdbdbd" type"text"maxlength"20" value"{{passwordNumber}}" bindinput"passwordInput…

荷香堪筑梦,鸳鸯和月寻。(变相BFS搜索)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 3 4 2 .... ***. ..a. 输出 yes 思路&#xff1a; 根据题意&#xff0c;这里 1 s 可以移动多次&#xff0c;我们将每次可以移动避开雪的的位置存储起来&#xff0c;判断当…

randperm函数打乱索引的用法

randperm&#xff0c;用于生成一个随机排列。这个函数可以被用于许多需要随机排列的情况&#xff0c;如数据集的划分、模型的训练等等。通过randperm函数&#xff0c;你可以得到一个随机的、不重复的排列。 下面是randperm函数的一些用法示例&#xff1a; 1. 生成一个长度为n…

地埋式可燃气体监测终端,地下燃气管网安全“哨兵”

在现代都市的繁华之下&#xff0c;一条条地下燃气管网承载着城市的生命与活力&#xff0c;但管网老化腐蚀&#xff0c;第三方施工破坏&#xff0c;巡检维修不到位等问题&#xff0c;时刻影响着燃气管网安全运行&#xff0c;甚至威胁人民群众的生命财产安全。 为实现对燃气管网…

实现一个拆分实数的整数和小数部分的函数指针

#include <stdio.h> // 定义常量 #define LARGE_NUMBER 1000000000 // 定义一个常量&#xff0c;方便后续使用 void splitFloatIntoParts(float x, int *intpart, float *fracpart); // 声明一个分割浮点数为整数和小数部分的函数 int main() // 主函数开始 { …

轻松寄快递,推荐一款寄快递优惠的小程序!

不管我们经不经常寄快递&#xff0c;我们总是要处理寄快递收快递的事情&#xff0c;可是我们不想这么麻烦来处理寄快递的事情&#xff0c;怎么办呢&#xff1f;这里推荐大家使用闪侠惠递来寄快递&#xff0c;因为使用闪侠惠递寄快递&#xff0c;不仅是带给你便宜的寄快递价格&a…

linux进阶高级配置,你需要知道的有哪些(10)-远程访问

1、ssh协议的功能 为客户机提供安全的shell环境&#xff08;字符界面&#xff09;&#xff0c;用于远程管理 2、openssh的服务说明 服务名&#xff1a;sshd 重启服务&#xff1a;systemctl restart sshd 主配置文件&#xff1a;/etc/ssh/sshd/_config 端口号&#xff1a;tcp 2…

代码随想录第五十一天|最长递增子序列、最长连续递增序列、最长重复子数组

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09;

【数据库原理及应用】期末复习汇总高校期末真题试卷11

试卷 一、填空题(每题 1 分&#xff0c;共10 分)    1. 数据库管理技术的发展经历了三个阶段&#xff1a;人工管理阶段&#xff0c;文件系统阶段和__________阶段。 2.实体完整性约束规定__________的取值不能为空值。 3. 计算机系统有三类安全性问题&#xff0c;即_____…

企业使用合同档案管理系统软件有什么好处

使用合同档案管理系统软件可以带来以下好处&#xff1a; 1. 提高效率&#xff1a;合同管理软件可以自动化合同流程&#xff0c;包括创建、审批、签署和归档等。通过自动化&#xff0c;可以节省大量时间和精力&#xff0c;提高工作效率。 2. 降低风险&#xff1a;玖拓档案合同管…

重构四要素:目的、对象、时机和方法

目录 1.引言 2.重构的目的:为什么重构(why) 3.重构的对象:到底重构什么(what) 4.重构的时机:什么时候重构(when) 5.重构的方法:应该如何重构(how) 6.思考题 1.引言 一些软件工程师对为什么要重构(why)、到底重构什么(what)、什么时候重构(when)应该如何重构(how)等问题的…

3.使用uView让tabbar更优雅

文章目录 1. 使用uView让tabbar更优雅1.1. 怎么才优雅&#xff1f;1.2. uView的tabbar合适吗&#xff1f;1.3. 引入项目过程1.3.1. 修改pages.json1.3.2. 把demo里面的pages先拷贝过来1.3.3. 引入tabbar的图片1.3.4. 运行 1.4. 我们自己的项目适配 1. 使用uView让tabbar更优雅 …

【算法】滑动窗口——最小覆盖子串

本节博客是对“最小覆盖子串”题目由暴力求解到滑动窗口的思路解析&#xff0c;有需要借鉴即可。 目录 1.题目2.滑动窗口解法3.总结 1.题目 题目链接&#xff1a;LINK 这个题目是困难难度&#xff0c;感觉是一个中等题目的感觉。 首先我肯定想到的是暴力求解的方法&#xff…