type MINIDUMP_IO_CALLBACK struct {
Handle uintptr //not relevant
Offset uint64 //The offset for the write operation from the start of the minidump data.
Buffer uintptr //A pointer to a buffer that contains the data to be written.
BufferBytes uint32 //The size of the data buffer, in bytes
}
// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_io_callback
var dumpBuffer []byte //all bytes should be copied here
func copyDumpBytes(ioStruct *MINIDUMP_IO_CALLBACK) {
size := unsafe.Sizeof(ioStruct.Buffer)
dumpData := make([]byte, size)
binary.LittleEndian.PutUint64(dumpData, uint64(ioStruct.Buffer)) // getting byte slice from pointer
requiredSize := ioStruct.Offset + uint64(ioStruct.BufferBytes) // calculating required size to write it at the required offset
if requiredSize > uint64(len(dumpBuffer)) {
padding := make([]byte, requiredSize)
dumpBuffer = append(dumpBuffer, padding...) // allocating new space in buffer, if existing is not enough
}
copy(dumpBuffer[ioStruct.Offset:], dumpData) //copying bytes itself into the buffer at the required offset
}
copyDumpBytes()函数在执行过程中被callback函数多次 * 调用。它需要将接收到的所有数据存储到dumpBuffer**字节片中。
字节片应该代表一个文件(windows进程minidump文件),但当存储到文件中并使用相关程序(WinDbg)打开时,它无法识别文件结构。
ioutil.WriteFile("test.dmp", dumpBuffer, 0644)
我的猜测是,我搞砸了这个代码的某个地方,而复制字节。
1条答案
按热度按时间83qze16e1#
这是一个不完整的答案(正如其结束时所解释的那样),但在这一点上,我想说你的方法的主要问题是
通过这段代码,您可以获得指针大小的变量
ioStruct.Buffer
的大小,在给定的平台上 * 是常量 *-i386上是32位,amd 64上是64位(64位Windows是LLP 64平台)。相反,
uintptr
类型的Buffer
字段是您应该复制的内存块的 * 指针 *(地址),BufferBytes
字段包含Buffer
字段所指向的数据大小(以字节为单位)。所以,为了正确地复制数据,你应该从原始内存指针中产生一个正确的Go切片。
当Go ≥ 1.17时,您可以使用
unsafe.Slice
来实现¹:这里:
1.表达式
unsafe.Pointer(iocb.Buffer)
产生了一个“适当的”指针,它可以参与Go表达式对指针的某些操作(uintptr
不能-它是一个整数类型)。1.然后将结果类型转换为
*byte
,以生成指向byte的指针(实际上是指向零到任意数量相邻字节的内存块)。1.最后,对intrinsic function
unsafe.Slice
的调用产生一个正确的Go切片[]byte
,其支持数组是Buffer
指向的内存块,并且包含BufferBytes
元素(字节)。然后,你可以在所有常用的Go表达式和函数调用中自由地使用结果切片,包括
append()
。请注意,虽然你已经从系统传递给我们的内存中创建了一个Go切片,但你并不拥有该内存,所以你必须确保没有对该内存的引用(指针),包括我们的切片,在处理程序的代码中生存下来,否则你会遇到一个经典的“释放后使用”问题。
append()
复制源的内存,因此在我的示例中不存在这个问题。一个让我担心的问题是,
Offset
成员被记录为从小型转储数据开始的写入操作偏移量。
它 * 可能 * 意味着不能保证回调将被“以串行方式”调用-也就是说,数据片段顺序地彼此跟随,之间没有间隙。相反,手册页面的措辞表明,可以调用回调,其中数据块将被写入结果minidump文件的 * 随机 * 位置。
为了理解我的意思,考虑Win32 API函数将结果minidump的数据传递给我们,并决定将其分为四个块,如下所示:
我要说的是,文档并不能保证你会按照上面块的标记顺序收到这四个回调;相反,文档是以一种允许以任何顺序调用回调的方式制定的,比如说,像这样:
offset_4
,size_4
;offset_2
,size_2
;offset_1
,size_1
;offset_3
、size_3
。如果这是真的,你就不能忽略
Offset
而依赖append
-ing数据,而是应该以某种方式预分配结果缓冲区并用结果数据“修补”,或者直接将数据写入文件(每次在写入下一个接收到的块之前使用seek操作定位文件指针)。不管怎么说,这是一个完全不同的问题,与围棋无关。
¹在旧版本的围棋中,你通常会使用一个相当标准的技巧来做同样的事情:
让我们来分解一下:
1.如上所述,表达式
unsafe.Pointer(iocb.Buffer)
从uintptr
类型的值中生成一个“正确的”Go指针。1.然后,生成的指针被类型转换为类型
*[1<<31 - 1]byte
,这是一个指向(*
)大小为1<<32 - 1
(这是Go数组在i386架构上的最大大小)的字节数组的指针。这两个表达式的结果是一个指向字节数组的指针。它的长度是假的(“无限”),但这是由表达式的最后一部分处理的。
[:iocb.BufferBytes]
是一个切片表达式,它从源字节数组中产生一个字节切片[]byte
,并使切片引用数组中从索引0到索引iocb.BufferSize-1
的元素。