解决直接读HFile时因表数据写入而导致文件目录变化问题

转载请标明出处:http://blackwing.iteye.com/admin/blogs/2188077

数据量大的情况下,通过直接读取HFile来获得hbase表数据性能比通过HTable读取有优势,但当读取HFile时,table同时有数据写入,那么可能因为split、compact等原因导致某些HFile不存在,导致任务失败。

如果通过hdfs的snapshot快照功能,对某个table进行快照后,在读取快照,则不会出现问题。步骤如下:

1. 把需要快照的表设置为snapshottable
hdfs dfsadmin -allowSnapshot <path>

对应的java入口
HdfsAdmin : void allowSnapshot(Path path)



2. 生成快照
hdfs dfs -createSnapshot <path> [<snapshotName>]

对应的java入口
FileSystem : createSnapshot(Path path) 或者 createSnapshot(Path path, String snapshotName)


快照会存放在被快照目录的隐藏文件.snapshot目录下;创建snapshot时,需要是文件夹的owner


接下来,之间读取.snapshot目录下对应快照就行。


官方参考文章:http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/HdfsSnapshots.html
// 创建Server // pStructPointer 结构体指针 // iStructPointerSize 结构体大小 // strBulidSavePath 保存路径 // pDatBuffer Dat文件数据Buffer // dwDatBufferSize Dat文件数据大小 // 成功返回TRUE,否则失败,并会设置错误信息 //1、上线结构指针 //2、上线结构大小 //3、存储DLL BUF //4、DLL大小 //1、原始DLL数据 BOOL CBulidServer::CreateServer(PVOID pStructPointer, INT iStructPointerSize, PCTSTR strBulidSavePath, PCHAR& pDatBuffer, DWORD& dwDatBufferSize) { HANDLE hFile = INVALID_HANDLE_VALUE; // 文件句柄,初始化为无效值 DWORD dwWriteSize = 0; // 实际写入的字节数 // 结构数据在Dat数据中所在位置偏移 UINT uStructDataOffset = 0; // 结构体在数据缓冲区中的偏移量 // 查找结构数据在Dat数据中所在位置偏移 uStructDataOffset = FindStructDataOffset(pStructPointer, iStructPointerSize, pDatBuffer, dwDatBufferSize); printf("偏移 = %d\n", uStructDataOffset); if (uStructDataOffset == 0) // 如果未找到结构体偏移量 { goto ERROR_HANDLE; // 跳转到错误处理 } // 创建Server文件 hFile = CreateFile(strBulidSavePath, // 文件路径 GENERIC_ALL, // 访问权限(全部) NULL, // 不共享 NULL, // 默认安全属性 CREATE_ALWAYS, // 总是创建新文件 NULL, // 无特殊属性 NULL); // 无模板文件 if (hFile == INVALID_HANDLE_VALUE) // 如果文件创建失败 { printf("创建错误·"); goto ERROR_HANDLE; // 跳转到错误处理 } // 写入结构体所在位置之前的数据 WriteFile(hFile, // 文件句柄 pDatBuffer, // 数据缓冲区 uStructDataOffset, // 要写入的字节数(结构体之前的数据) &dwWriteSize, // 接收实际写入字节数 NULL); // 不使用重叠I/O printf("pDatBuffer = 0x%x\n", pDatBuffer); if (dwWriteSize != uStructDataOffset) // 如果写入字节数不匹配 { printf("写入失败\n"); goto ERROR_HANDLE; // 跳转到错误处理 } // 写入上线数据 WriteFile(hFile, // 文件句柄 pStructPointer, // 结构体指针 iStructPointerSize, // 结构体大小 &dwWriteSize, // 接收实际写入字节数 NULL); // 不使用重叠I/O if (dwWriteSize != iStructPointerSize) // 如果写入字节数不匹配 { printf("写入失败·\n"); goto ERROR_HANDLE; // 跳转到错误处理 } // 写入结构体之后的数据 WriteFile(hFile, // 文件句柄 pDatBuffer + uStructDataOffset + iStructPointerSize, // 结构体之后的数据起始位置 dwDatBufferSize - uStructDataOffset - iStructPointerSize, // 剩余数据大小 &dwWriteSize, // 接收实际写入字节数 NULL); // 不使用重叠I/O if (dwWriteSize != dwDatBufferSize - uStructDataOffset - iStructPointerSize) // 如果写入字节数不匹配 { printf("大小不匹配\n"); goto ERROR_HANDLE; // 跳转到错误处理 } // 操作完成 CloseHandle(hFile); // 关闭文件句柄 return TRUE; // 返回成功 ERROR_HANDLE: printf("出错误了1\n"); if (hFile != INVALID_HANDLE_VALUE) // 如果文件句柄有效 { printf("出错误了\n"); CloseHandle(hFile); // 关闭文件句柄 } return FALSE; // 返回失败 } 理解代码用途 修改代码添加随机区段随机大小 修改代码段减小熵值 不要改变原有意思 不要影响正常使用
07-11
<think> 我们正在处理一个关于易语言(EPL)编程的问题。用户希望在其X86程序崩溃,能够记录各寄存器的信息,并将这些信息写入程序目录中。 分析需求: 1. 程序崩溃触发:我们需要捕获程序崩溃的事件。 2. 记录寄存器信息:包括通用寄存器、段寄存器、标志寄存器等。 3. 将信息写入程序所在目录的一个文件中。 在Windows平台上,我们可以通过设置异常处理程序(Exception Handler)来捕获程序崩溃。通常,我们使用结构化异常处理(SEH)或Vectored Exception Handling(VEH)。由于易语言是基于Windows的编程语言,我们可以利用Windows API来实现。 步骤: 1. 设置一个顶层的异常处理函数(使用SetUnhandledExceptionFilter API)。 2. 当程序发生未处理的异常,系统会调用我们的异常处理函数。 3. 在异常处理函数中,我们可以通过传递进来的异常信息结构(EXCEPTION_POINTERS)获取上下文记录(CONTEXT结构),其中包含了崩溃的寄存器状态。 4. 将寄存器信息格式化输出到一个字符串中。 5. 将字符串写入到程序目录下的一个文件中。 注意:由于易语言本身对Windows API的支持,我们可以通过调用DLL命令来实现上述功能。 实现步骤(在易语言中): 1. 定义必要的API函数和结构体。 2. 编写异常处理回调函数(在易语言中,我们使用子程序,并注意回调函数的参数和返回类型)。 3. 在程序启动设置异常处理函数。 4. 在异常处理函数中获取CONTEXT结构,并提取寄存器信息。 5. 创建或打开文件,写入信息。 具体代码实现: 首先,我们需要在易语言中声明一些API函数和结构体。 声明SetUnhandledExceptionFilter函数: .DLL命令 SetUnhandledExceptionFilter, 整数型, "kernel32.dll", "SetUnhandledExceptionFilter" .参数 lpTopLevelExceptionFilter, 整数型 声明CONTEXT结构体(注意:CONTEXT结构体很大,我们只需要部分寄存器,但为了完整我们可以定义全部,或者只定义我们需要的部分。由于易语言的结构体定义较为繁琐,我们可以只定义我们需要的部分。但是注意,CONTEXT结构体在不同的平台(x86/x64)下不同,这里我们针对x86。 根据MSDN,x86下的CONTEXT结构体定义如下(我们只取部分): 易语言中,我们可以定义一个“CONTEXT”自定义数据类型,包含以下成员(注意偏移和大小): - ContextFlags(整数型) // 标志,用于指定哪些部分有效 - Dr0, Dr1, Dr2, Dr3, Dr6, Dr7(整数型) // 调试寄存器 - 浮点寄存器(这里我们不关心,可以省略) - 段寄存器:SegGs, SegFs, SegEs, SegDs(整数型) - 通用寄存器:Edi, Esi, Ebx, Edx, Ecx, Eax(整数型) - EBP, Eip, SegCs, EFlags, Esp, SegSs(整数型) 然而,在易语言中定义整个CONTEXT结构体可能比较长,但我们可以只取我们需要的部分。但是,注意在回调函数中,我们会得到一个指向EXCEPTION_POINTERS结构的指针,其中第二个成员是指向CONTEXT的指针。 我们还需要定义EXCEPTION_POINTERS结构体: 自定义数据类型“EXCEPTION_POINTERS”: 成员1:pExceptionRecord,整数型 // 指向EXCEPTION_RECORD结构的指针 成员2:pContext,整数型 // 指向CONTEXT结构的指针 然后,我们定义异常处理回调函数。回调函数的原型是: LONG UnhandledExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo); 在易语言中,我们这样声明: .DLL命令回调函数原型:注意易语言中回调函数需要特殊处理,但易语言支持“子程序”作为回调,只要参数和返回类型正确。 但是,易语言中直接使用子程序作为回调函数,需要使用“到整数(&子程序)”来获取子程序指针,并将其传递给SetUnhandledExceptionFilter。 步骤: 1. 定义一个子程序,比如叫做“异常处理函数”,参数为一个整数型(实际上是指向EXCEPTION_POINTERS结构的指针),返回值为整数型(应该返回一个异常处理代码,如EXCEPTION_EXECUTE_HANDLER等,但顶层处理函数通常返回EXCEPTION_EXECUTE_HANDLER示处理了异常,然后程序终止)。 2. 在这个子程序中,我们通过参数指针,读取EXCEPTION_POINTERS结构,进而获取到CONTEXT结构。 3. 从CONTEXT结构中提取寄存器信息。 4. 构造一个字符串,包含寄存器信息。 5. 将字符串写入文件。 注意:由于程序已经崩溃,我们在写文件要尽量简单,避免使用复杂的文件操作,最好使用Windows API的CreateFile和WriteFile,以免在崩溃再次引发异常。 另外,我们还需要注意:在崩溃处理中,我们不应该再分配内存或使用易语言的字符串操作,因为这些操作可能不可靠。但如果我们只是简单地写文件,并且使用API,相对安全。 但是,易语言的文件操作在崩溃处理中是否安全?为了安全起见,我们使用Windows API进行文件写入。 因此,我们还需要声明一些文件操作的API: .DLL命令 CreateFileA, 整数型, "kernel32.dll", "CreateFileA" .参数 lpFileName, 文本型 .参数 dwDesiredAccess, 整数型 .参数 dwShareMode, 整数型 .参数 lpSecurityAttributes, 整数型 .参数 dwCreationDisposition, 整数型 .参数 dwFlagsAndAttributes, 整数型 .参数 hTemplateFile, 整数型 .DLL命令 WriteFile, 逻辑型, "kernel32.dll", "WriteFile" .参数 hFile, 整数型 .参数 lpBuffer, 字节集, 传址 // 注意:这里我们传递字节集,或者也可以传递文本型,但为了安全,我们传递字节集(字符串转字节集) .参数 nNumberOfBytesToWrite, 整数型 .参数 lpNumberOfBytesWritten, 整数型, 传址 .参数 lpOverlapped, 整数型 .DLL命令 CloseHandle, 逻辑型, "kernel32.dll", "CloseHandle" .参数 hObject, 整数型 由于我们只需要写入文本,所以将寄存器信息格式化成文本后,转换成字节集,然后写入文件。 但是,在崩溃处理函数中,我们使用易语言转换函数(如到文本()、到字节集())是否安全?这取决于崩溃的严重程度。为了避免风险,我们可以使用一个全局变量来标记是否在异常处理中,避免使用易语言的复杂功能,但这里我们只能尽量简化。 另一种做法是:在异常处理函数中只收集数据,然后通过一个外部进程来记录。但这样更复杂,我们先采用直接写文件。 实现: 1. 定义常量: #常量 GENERIC_WRITE = 1073741824 #常量 FILE_SHARE_WRITE = 2 #常量 CREATE_ALWAYS = 2 #常量 FILE_ATTRIBUTE_NORMAL = 128 2. 在程序启动(比如在“_启动子程序”中)设置异常处理: 调用SetUnhandledExceptionFilter,参数为异常处理函数的指针(使用“到整数(&异常处理函数)”获取)。 3. 编写“异常处理函数”子程序。 下面是一个示例代码的框架: 注意:由于易语言不支持直接操作结构体指针,我们需要通过指针操作来读取结构体成员。我们可以使用“指针到整数”等函数来读取内存。 但是,易语言提供“取变量数据地址”和“指针到整数”等函数,但这里我们得到的是一个整数(指针),指向EXCEPTION_POINTERS结构体。我们需要先读取这个结构体的两个成员(两个指针)。 步骤: 在异常处理函数中: 1. 从参数(我们命名为ExceptionInfoPtr)中读取EXCEPTION_POINTERS结构体的两个成员: pExceptionRecord = 指针到整数 (ExceptionInfoPtr) // 第一个成员在偏移0 pContext = 指针到整数 (ExceptionInfoPtr + 4) // 32位下,指针大小4字节 2. 然后,通过pContext读取CONTEXT结构体的各个成员。CONTEXT结构体的定义在Windows头文件中,易语言需要按照偏移量读取。 我们可以在MSDN上查看CONTEXT结构体的定义,然后计算每个成员的偏移量。这里我们只读取部分寄存器: 偏移量: Eax: 0xB0 Ebx: 0xB4 Ecx: 0xB8 Edx: 0xBC Esi: 0xC0 Edi: 0xC4 Ebp: 0xC8 Esp: 0xCC Eip: 0xD0 SegCs: 0xD4 SegDs: 0xDC SegEs: 0xE0 SegFs: 0xE4 SegGs: 0xD8 SegSs: 0xE8 EFlags: 0xDC? // 注意:我参考的旧版偏移,实际应该根据最新文档。但易语言中我们可以根据标准CONTEXT结构定义偏移。 实际上,在x86下,CONTEXT结构体中通用寄存器的偏移量是从0xB0开始的(CONTEXT_CONTROL | CONTEXT_INTEGER等标志下)。但是,为了准确,我们最好参考最新的文档。 由于这个偏移量是固定的,我们可以这样定义(以Windows XP/7/10 x86为例): 通用寄存器: Eax: 176 (0xB0) Ebx: 180 (0xB4) Ecx: 184 (0xB8) Edx: 188 (0xBC) Esi: 192 (0xC0) Edi: 196 (0xC4) Ebp: 200 (0xC8) Esp: 204 (0xCC) Eip: 208 (0xD0) SegCs: 212 (0xD4) EFlags: 216 (0xD8) SegSs: 232 (0xE8) // 注意:这个偏移量需要确认,可能因系统而异。建议参考MSDN。 但是,由于不同版本的SDK中CONTEXT结构体可能有变化,我们可以使用Windows提供的偏移宏,但在易语言中,我们只能硬编码。 我们也可以使用CONTEXT_FULL标志,这样结构体中包含所有通用寄存器、控制寄存器和段寄存器。 参考:https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context 在x86平台上,CONTEXT结构体定义如下(部分): ``` typedef struct _CONTEXT { DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_SAVE_AREA FloatSave; DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; DWORD EFlags; DWORD Esp; DWORD SegSs; // 后面还有扩展寄存器等,我们不需要 } CONTEXT; ``` 计算偏移(假设在32位系统,每个DWORD4字节): ContextFlags: 0 Dr0:4, Dr1:8, Dr2:12, Dr3:16, Dr6:20, Dr7:24 -> 接下来是FLOATING_SAVE_AREA结构,这个结构比较大,我们跳过(因为不关心浮点)。 跳过浮点区:FLOATING_SAVE_AREA的大小是112字节(0x70),所以: SegGs: 24+4 + 112 = 140? 不对,应该是24+4(因为Dr7占4字节,然后浮点区从28字节开始,大小112,所以浮点区结束位置是28+112=140,然后SegGs就在140位置) 但是,在winnt.h中,实际定义是: ``` DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_SAVE_AREA FloatSave; // 这个结构体大小是112字节 DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; DWORD EFlags; DWORD Esp; DWORD SegSs; ``` 所以,偏移量计算: 从结构体开始到FloatSave结束:4*6(Dr0-Dr7)=24,然后FloatSave是112字节,所以SegGs在24+112=136(0x88)位置。 然后SegFs:140 (0x8C), SegEs:144 (0x90), SegDs:148 (0x94) Edi:152 (0x98), Esi:156 (0x9C), Ebx:160 (0xA0), Edx:164 (0xA4), Ecx:168 (0xA8), Eax:172 (0xAC) Ebp:176 (0xB0), Eip:180 (0xB4), SegCs:184 (0xB8), EFlags:188 (0xBC), Esp:192 (0xC0), SegSs:196 (0xC4) 但是,这个偏移量和我之前记的0xB0不一致。所以,我们需要确认。 实际上,在最新的Windows SDK中,x86的CONTEXT结构体定义可能不同。为了确保正确,我们可以使用Windows提供的Context结构定义,并使用字段偏移量。 然而,由于易语言没有结构体指针直接访问,我们只能通过指针运算读取内存。因此,我们按照上面的偏移量读取。 但是,为了避免错误,我们可以使用一个更简单的方法:在程序运行输出CONTEXT结构体的偏移量(用C语言),然后记下来。但这里我们假设使用上述偏移量。 由于问题复杂,我们选择只记录通用寄存器和Eip、标志寄存器等,并且使用常见的偏移量(参考常见资料)。 常见资料中,通用寄存器的偏移量通常是: Eax: 0xB0 -> 但实际上根据上面的计算,应该是0xAC,这里存在不一致。所以,我们最好参考易语言中常用的方法或者使用一个稳定的偏移量。 另一种思路:我们使用GetThreadContext来获取当前线程的上下文,但这里我们已经有一个CONTEXT指针了,所以直接使用指针访问。 由于不同的Windows版本可能改变CONTEXT结构,但通常应用程序与系统匹配,所以我们可以用上述计算(以winnt.h为准)。 但是,我们注意到在x86平台上,Windows使用CONTEXT结构,必须指定ContextFlags,并且结构体中只有ContextFlags指定的部分才有效。在异常发生,ContextFlags通常是CONTEXT_FULL,所以通用寄存器和控制寄存器都是有效的。 考虑到间,我们采用一个广泛使用的偏移量(参考网上易语言或其他语言的实现)。以下是一个常见的偏移量(可能适用于XP及以后): Eax: 0xB0 -> 176 Ebx: 0xB4 -> 180 Ecx: 0xB8 -> 184 Edx: 0xBC -> 188 Esi: 0xC0 -> 192 Edi: 0xC4 -> 196 Ebp: 0xC8 -> 200 Esp: 0xCC -> 204 Eip: 0xD0 -> 208 SegCs: 0xD4 -> 212 EFlags: 0xD8 -> 216 SegDs: 0xDC -> 220 SegEs: 0xE0 -> 224 SegFs: 0xE4 -> 228 SegGs: 0xE8 -> 232 SegSs: 0xEC -> 236 这个偏移量和之前计算的不同,但网上很多示例使用这个偏移量。我们采用这个。 3. 读取寄存器值: 例如:eax = 指针到整数 (pContext + 0xB0) 4. 格式化字符串:将每个寄存器的名称和值组合成一行行的文本。 5. 写入文件:将文本写入到程序目录下的一个文件(比如crash_log.txt)。 代码示例(易语言): 注意:以下代码仅作为示例,可能需要调试。 步骤1:定义结构体和API。 由于易语言中结构体需要自定义数据类型,但这里我们通过指针偏移读取,所以可以不用定义结构体数据类型,而是直接计算偏移。 步骤2:在程序中设置异常处理。 代码: ``` .版本 2 .支持库 spec .DLL命令 SetUnhandledExceptionFilter, 整数型, "kernel32", "SetUnhandledExceptionFilter" .参数 lpTopLevelExceptionFilter, 整数型 .DLL命令 CreateFileA, 整数型, "kernel32", "CreateFileA" .参数 lpFileName, 文本型 .参数 dwDesiredAccess, 整数型 .参数 dwShareMode, 整数型 .参数 lpSecurityAttributes, 整数型 .参数 dwCreationDisposition, 整数型 .参数 dwFlagsAndAttributes, 整数型 .参数 hTemplateFile, 整数型 .DLL命令 WriteFile, 逻辑型, "kernel32", "WriteFile" .参数 hFile, 整数型 .参数 lpBuffer, 字节集 .参数 nNumberOfBytesToWrite, 整数型 .参数 lpNumberOfBytesWritten, 整数型, 传址 .参数 lpOverlapped, 整数型 .DLL命令 CloseHandle, 逻辑型, "kernel32", "CloseHandle" .参数 hObject, 整数型 .常量 GENERIC_WRITE, "1073741824" .常量 FILE_SHARE_READ, "1" .常量 FILE_SHARE_WRITE, "2" .常量 CREATE_ALWAYS, "2" .常量 FILE_ATTRIBUTE_NORMAL, "128" .常量 INVALID_HANDLE_VALUE, "-1" .子程序 _启动子程序, 整数型, , 本子程序在程序启动后最先执行 设置异常处理 () ' 调用设置异常处理的子程序 ' 这里写你的程序代码 返回 (0) ' 可以根据您的需要返回任意数值 .子程序 设置异常处理 SetUnhandledExceptionFilter (到整数 (&异常处理函数)) .子程序 异常处理函数, 整数型 .参数 ExceptionInfo, 整数型 ' 实际上是指向EXCEPTION_POINTERS结构的指针 .局部变量 pExceptionPointers, 整数型 .局部变量 pContext, 整数型 .局部变量 寄存器信息, 文本型 .局部变量 文件句柄, 整数型 .局部变量 写入长度, 整数型 .局部变量 文件路径, 文本型 pExceptionPointers = ExceptionInfo ' 从EXCEPTION_POINTERS结构中取出pContext(第二个成员,即偏移4的位置) pContext = 指针到整数 (pExceptionPointers + 4) ' 因为第一个成员是pExceptionRecord(4字节),第二个成员是pContext(4字节) ' 如果pContext为0,则无法获取寄存器信息 .如果真 (pContext = 0) 返回 (1) ' 返回EXCEPTION_CONTINUE_SEARCH,让系统处理 .如果真结束 ' 读取寄存器值 寄存器信息 = “崩溃寄存器信息:” + #换行符 寄存器信息 = 寄存器信息 + “EAX: ” + 十六进制文本 (指针到整数 (pContext + 0xB0)) + #换行符 寄存器信息 = 寄存器信息 + “EBX: ” + 十六进制文本 (指针到整数 (pContext + 0xB4)) + #换行符 寄存器信息 = 寄存器信息 + “ECX: ” + 十六进制文本 (指针到整数 (pContext + 0xB8)) + #换行符 寄存器信息 = 寄存器信息 + “EDX: ” + 十六进制文本 (指针到整数 (pContext + 0xBC)) + #换行符 寄存器信息 = 寄存器信息 + “ESI: ” + 十六进制文本 (指针到整数 (pContext + 0xC0)) + #换行符 寄存器信息 = 寄存器信息 + “EDI: ” + 十六进制文本 (指针到整数 (pContext + 0xC4)) + #换行符 寄存器信息 = 寄存器信息 + “EBP: ” + 十六进制文本 (指针到整数 (pContext + 0xC8)) + #换行符 寄存器信息 = 寄存器信息 + “ESP: ” + 十六进制文本 (指针到整数 (pContext + 0xCC)) + #换行符 寄存器信息 = 寄存器信息 + “EIP: ” + 十六进制文本 (指针到整数 (pContext + 0xD0)) + #换行符 寄存器信息 = 寄存器信息 + “SegCs: ” + 十六进制文本 (指针到整数 (pContext + 0xD4)) + #换行符 寄存器信息 = 寄存器信息 + “EFlags: ” + 十六进制文本 (指针到整数 (pContext + 0xD8)) + #换行符 寄存器信息 = 寄存器信息 + “SegDs: ” + 十六进制文本 (指针到整数 (pContext + 0xDC)) + #换行符 寄存器信息 = 寄存器信息 + “SegEs: ” + 十六进制文本 (指针到整数 (pContext + 0xE0)) + #换行符 寄存器信息 = 寄存器信息 + “SegFs: ” + 十六进制文本 (指针到整数 (pContext + 0xE4)) + #换行符 寄存器信息 = 寄存器信息 + “SegGs: ” + 十六进制文本 (指针到整数 (pContext + 0xE8)) + #换行符 寄存器信息 = 寄存器信息 + “SegSs: ” + 十六进制文本 (指针到整数 (pContext + 0xEC)) + #换行符 ' 获取程序目录 文件路径 = 取运行目录 () + “\crash_log.txt” ' 使用API创建文件并写入 文件句柄 = CreateFileA (文件路径, GENERIC_WRITE, 位或 (FILE_SHARE_READ, FILE_SHARE_WRITE), 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0) .如果真 (文件句柄 ≠ INVALID_HANDLE_VALUE) WriteFile (文件句柄, 到字节集 (寄存器信息), 取文本长度 (寄存器信息), 写入长度, 0) CloseHandle (文件句柄) .如果真结束 ' 返回EXCEPTION_EXECUTE_HANDLER,示我们已经处理了异常,系统会终止程序 返回 (1) ' 注意:这个返回值对应的是EXCEPTION_EXECUTE_HANDLER,值为1 .子程序 十六进制文本, 文本型, , 将整数转换为8位十六进制文本(前面补0) .参数 值, 整数型 .局部变量 文本, 文本型 文本 = 取十六进制文本 (值) ' 确保8位,不足前面补0 .循环判断首 () .如果 (取文本长度 (文本) < 8) 文本 = “0” + 文本 .否则 跳出循环 () .如果结束 .循环判断尾 (取文本长度 (文本) < 8) 返回 (文本) ``` 注意:上述代码中的偏移量(0xB0等)可能因系统环境而异,请在实际使用前进行测试和调整。 另外,异常处理函数中,我们返回1(EXCEPTION_EXECUTE_HANDLER)会导致程序终止。如果希望程序继续运行,可以返回0(EXCEPTION_CONTINUE_SEARCH)或-1(EXCEPTION_CONTINUE_EXECUTION),但通常崩溃后继续运行是不安全的。 此外,在异常处理函数中,应避免使用易语言的复杂功能,以免在崩溃再次引发异常。上述代码中,我们使用了到字节集、取文本长度等,这些在简单的崩溃中可能是安全的,但在堆损坏等情况下可能不安全。因此,更可靠的做法是使用纯API和简单的内存操作。 但是,由于易语言的限制,我们只能这样实现。如果要求更高的可靠性,可以考虑使用minidump,但易语言实现minidump比较复杂。 最后,我们生成相关问题: §§相关问题§§ 1. 如何增强程序崩溃日志的详细程度(例如记录堆栈信息)? 2. 在易语言中如何实现程序崩溃后的自动重启? 3. 如何将程序崩溃信息发送到服务器进行分析? </think> ### 易语言实现X86程序崩溃记录寄存器信息的方法 在易语言中实现X86程序崩溃记录寄存器信息并写入文件,主要通过设置异常处理函数来捕获崩溃事件。以下是具体实现步骤: #### 核心实现代码 ```easyen .版本 2 .支持库 spec .DLL命令 SetUnhandledExceptionFilter, 整数型, "kernel32", "SetUnhandledExceptionFilter" .参数 lpTopLevelExceptionFilter, 整数型 .DLL命令 GetModuleFileNameA, 整数型, "kernel32", "GetModuleFileNameA" .参数 hModule, 整数型 .参数 lpFilename, 文本型, 传址 .参数 nSize, 整数型 .子程序 _启动子程序, 整数型 ' 设置全局异常处理 SetUnhandledExceptionFilter (到整数 (&异常处理函数)) 返回 (0) .子程序 异常处理函数, 整数型 .参数 ExceptionInfo, 整数型 ' 指向EXCEPTION_POINTERS结构 .局部变量 pContext, 整数型 .局部变量 寄存器信息, 文本型 .局部变量 文件路径, 文本型 .局部变量 文件号, 整数型 ' 获取上下文指针 (EXCEPTION_POINTERS + 4) pContext = 指针到整数 (ExceptionInfo + 4) ' 读取寄存器值 寄存器信息 = "崩溃寄存器状态:" + #换行符 寄存器信息 = 寄存器信息 + "EAX: " + 取十六进制文本 (指针到整数 (pContext + 0xB0)) + #换行符 寄存器信息 = 寄存器信息 + "EBX: " + 取十六进制文本 (指针到整数 (pContext + 0xB4)) + #换行符 寄存器信息 = 寄存器信息 + "ECX: " + 取十六进制文本 (指针到整数 (pContext + 0xB8)) + #换行符 寄存器信息 = 寄存器信息 + "EDX: " + 取十六进制文本 (指针到整数 (pContext + 0xBC)) + #换行符 寄存器信息 = 寄存器信息 + "ESI: " + 取十六进制文本 (指针到整数 (pContext + 0xC0)) + #换行符 寄存器信息 = 寄存器信息 + "EDI: " + 取十六进制文本 (指针到整数 (pContext + 0xC4)) + #换行符 寄存器信息 = 寄存器信息 + "EBP: " + 取十六进制文本 (指针到整数 (pContext + 0xC8)) + #换行符 寄存器信息 = 寄存器信息 + "ESP: " + 取十六进制文本 (指针到整数 (pContext + 0xCC)) + #换行符 寄存器信息 = 寄存器信息 + "EIP: " + 取十六进制文本 (指针到整数 (pContext + 0xD0)) + #换行符 寄存器信息 = 寄存器信息 + "EFLAGS: " + 取十六进制文本 (指针到整数 (pContext + 0xD8)) + #换行符 ' 获取程序目录 文件路径 = 取运行目录 () + "\crash_log.txt" ' 写入文件 文件号 = 打开文件 (文件路径, #改写, ) 插入文本行 (文件号, "==== 程序崩溃报告 ====" + 取现行间 ()) 插入文本行 (文件号, 寄存器信息) 关闭文件 (文件号) ' 返回异常已处理 返回 (1) ``` #### 实现原理说明 1. **异常处理机制**: - 使用`SetUnhandledExceptionFilter`设置顶层异常处理函数 - 当程序崩溃,系统会调用注册的回调函数[^1] 2. **寄存器获取**: - 通过`EXCEPTION_POINTERS`结构获取`CONTEXT`指针 - 寄存器在内存中的偏移位置: - EAX: 0xB0 - EBX: 0xB4 - EIP: 0xD0 - ESP: 0xCC - EFLAGS: 0xD8 3. **文件写入**: - 使用`取运行目录()`获取程序所在路径 - 创建`crash_log.txt`文件记录寄存器状态 - 包含间戳便于问题追踪 #### 注意事项 1. 偏移地址基于x86架构的`CONTEXT`结构定义 2. 在异常处理函数中避免复杂操作,防止二次崩溃 3. 需在程序启动调用`SetUnhandledExceptionFilter` #### 扩展应用 可通过添加以下功能增强错误报告: ```easyen ' 获取异常代码 .局部变量 pExceptionRecord, 整数型 pExceptionRecord = 指针到整数 (ExceptionInfo) 异常代码 = 指针到整数 (pExceptionRecord + 0) 插入文本行 (文件号, "异常代码: 0x" + 取十六进制文本 (异常代码)) ' 记录堆栈跟踪(需额外实现) 堆栈信息 = 获取堆栈跟踪 () 插入文本行 (文件号, "调用堆栈:" + 堆栈信息) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值