相当于BitConverter的C++
C++ equivalent of BitConverter
我正在尝试读取文件的PE头以获取一些信息。对于.NET
和C#
,我使用BitConverter
将读取文件后获得的Byte数组转换为等效的整数。我希望对C++
也这样做,但不确定最佳方法。我使用unsigned
char
array
作为Byte array
的等价物。
代码如下。。
uint16_t GetAppCompiledMachineType(string fileName)
{
const int ptr_offset = 4096;
const int mac_offset = 4;
char *data = new char[4096];
fstream f;
f.open(fileName, ios::in | ios::binary );
f.read(data, 4096);
int32_t pe_addr= *reinterpret_cast<int32_t*>(data, ptr_offset);
uint16_t machineUint = *reinterpret_cast<std::uint16_t*>(data, pe_addr + mac_offset);
return machineUint;
}
int _tmain(int argc, _TCHAR* argv[])
{
string fileName = "<some_path>\depends.exe";
uint16_t tempInt = GetAppCompiledMachineType(fileName);
cout<<tempInt;
std::getchar();
return 0;
}
我将使用O/p查询PE标头以获取信息。这里需要相当于BitCOnverter的软件。希望它能起作用。
更新:感谢回复。根据建议,我正在尝试使用强制转换,将character array
转换为Int
,读取PE Header
,但这会导致访问冲突未处理异常。这是完整的代码,文件是有效的并且正在被读取。我尝试了调试,并禁用了优化,但没有成功。
请告知。
非常感谢。
您有一个字节数组指针(char* data
),然后简单地将指针移动到需要data + PE_POINTER_OFFSET
的偏移量,将指针强制转换为整数(int*)(data + PE_POINTER_OFFSET)
,并尊重指针以获得值:
int32_t head_addr = *reinterpret_cast<int32_t*>(data + PE_POINTER_OFFSET);
uint16_t machineUint = *reinterpret_cast<uint16_t*>(data + head_addr + macoffset);
编辑1:您正在尝试阅读PE,因此我可以放心地假设您的环境是Windows。x86和x64都支持未对齐的内存访问(当然,你会为此付出性能代价,但可能不会注意到什么,你会节省memcpy
秒)。
Itanimum(如果您必须支持它的话)和(非常旧的)ARM可能是一个问题:对于第一个,只对您的char数组使用__unaligned
,对于第二个(如果您不让编译器为您做这项工作),您可以使用__packed
。
还要注意的是,这些假设(加上endianness)是有效的,因为您在Windows环境中使用PE文件,如果您必须编写可移植代码或读取其他内容,那么这不是正确的方法(简而言之,您必须寻址单个字节并使用固定顺序复制它们)。
EDIT 2:根据您使用的更新代码,问题是*reinterpret_cast<int32_t*>(data, ptr_offset)
,请注意,您没有将指针与偏移量相加,而且偏移量也是无效的(如果我没有错的话,应该是60)。你在那里做的是从地址为4096的绝对位置读取,这将导致访问违规。代码中:
uint16_t GetAppCompiledMachineType(string fileName)
{
const int32_t PE_POINTER_OFFSET = 60;
const int32_t MACHINE_OFFSET = 4;
char data[4096];
fstream f;
f.open(fileName, ios::in | ios::binary);
f.read(data, sizeof(data));
int32_t pe_header_offset = *reinterpret_cast<int32_t*>(
data + PE_POINTER_OFFSET);
// assert(pe_header_offset + MACHINE_OFFSET < sizeof(data));
return *reinterpret_cast<std::uint16_t*>(
data + pe_header_offset + MACHINE_OFFSET);
}
这个代码离生产质量还有很长的路要走,但请注意一些变化:
- 缓冲区
data
不是动态分配的,那么您就不需要释放内存(您没有释放分配的内存,当进程退出时,Windows会为您释放内存,但如果您多次调用该函数,则会消耗内存) - 使用静态分配的数组,您可以使用
sizeof()
来确定缓冲区大小(作为read()
的输入) PE_POINTER_OFFSET
现在具有正确的值(60而不是4096)- 从
data
的偏移现在被正确地计算(作为data
与PE_POINTER_OFFSET
的总和)
所有这些都表明,我们仍在使用缓冲方法,但这在这里非常无用,因为fstream
将为我们管理这一点。让我们简化我们的代码(副作用是使其更健壮,我们不认为PE标头适合我们的4K缓冲区)。
uint16_t GetAppCompiledMachineType(string fileName)
{
const int32_t PE_POINTER_OFFSET = 60;
const int32_t MACHINE_OFFSET = 4;
fstream f(fileName, ios::in | ios::binary);
int32_t pe_header_offset:
f.seekg(PE_POINTER_OFFSET); f >> pe_header_offset;
uint16_t machineType;
f.seekg(pe_header_offset + MACHINE_OFFSET); f >> machineType;
return machineType;
}
现在它可以在没有强制转换和转换的情况下工作(但仍然假设PE和机器端序匹配)。
重构以消除某些架构上的数据对齐问题:
template<class T>
T from_buffer(uint8_t* buffer, size_t offset)
{
T t_buf = 0;
memcpy(&t_buf, buffer + offset, sizeof(T));
return t_buf;
}
int32_t head_addr = from_buffer<in32_t>(buffer, PE_POINTER_OFFSET);
uint16_t machineUint = from_buffer<uint16_t>(buffer, size_t(head_addr + macoffset));
最好的方法是声明PE文件格式的结构。示例:
struct dos_header {
char signature[2] = "MZ";
boost::int16_t lastsize;
..
boost::int16_t reserved2[10];
boost::int32_t e_lfanew;
}
注:
- 最好使用预期大小的跨平台整数(int32_t,而不是long)
- 小心结构对齐(如果对齐有问题,请使用
#pragma pack(8)
) - 这些结构在
windows.h
中声明,但对于跨平台开发,我建议单独声明并可移植 - 在64位体系结构中,一些结构发生了变化
当您有映射的结构时,您可以将缓冲区强制转换为指向该结构的指针并访问成员。
样品:
if (offset + sizeof(dos_header) > size_data) {
// handle the error
// exit
}
const dos_header* dh = static_cast<const dos_header*>(data + offset);
std::cout << dh->e_lfanew << std::endl;