如何从调试进程中访问与重建HBITMAP数据
问题:直接从调试进程内存提取HBITMAP的高效方法?
我正在开发一款Visual Studio 2022扩展,目标是从调试进程的内存中提取HBITMAP对象进行分析。理想状态下,希望直接访问并重建原始位图数据,不需要把每个HBITMAP转换成PNG格式。
已尝试的方法
- 使用DuplicateHandle复制HBITMAP句柄,失败并报错:
句柄无效。
- 使用ReadProcessMemory读取HBITMAP数据,失败并报错:
仅完成ReadProcessMemory或WriteProcessMemory请求的一部分。
- 将HBITMAP转换为PNG(可行但开销大):
可以把HBITMAP转换为PNG字节数组并在扩展中读取,但每次位图转换都会产生额外开销,希望避免这种方式。
核心问题
是否有其他方法可以直接从调试进程的内存中读取并重建HBITMAP?或者有没有更高效的进程间处理和传输HBITMAP数据的方式,从而避免转换为PNG?
复现代码
Visual Studio扩展源码
using EnvDTE; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Drawing; using System.Drawing.Imaging; using Task = System.Threading.Tasks.Task; using System.ComponentModel; namespace VSIX; [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [Guid(VSIXPackage.PackageGuidString)] [ProvideAutoLoad(UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)] public sealed class VSIXPackage : AsyncPackage { public const string PackageGuidString = "f9a8aea3-f579-4816-9cb5-4ae3a5d68ef7"; private DebugWatcher _debugWatcher; protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) { await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); _debugWatcher = new DebugWatcher(); } } public class DebugWatcher { private DTE _dte; private DebuggerEvents _debuggerEvents; public DebugWatcher() { _ = InitializeAsync(); } private async Task InitializeAsync() { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); _dte = await ServiceProvider.GetGlobalServiceAsync(typeof(DTE)) as DTE; if (_dte == null) return; _debuggerEvents = _dte.Events.DebuggerEvents; _debuggerEvents.OnEnterBreakMode += OnEnterBreakMode; } [DllImport("kernel32.dll")] private static extern uint GetLastError(); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); [DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr hObject); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, bool bInheritHandle, uint dwOptions); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead); private void OnEnterBreakMode(dbgEventReason Reason, ref dbgExecutionAction ExecutionAction) { ThreadHelper.ThrowIfNotOnUIThread(); int debuggedProcessId = _dte.Debugger.CurrentProcess.ProcessID; const uint PROCESS_DUP_HANDLE = 0x0040; const uint PROCESS_VM_READ = 0x0010; const uint PROCESS_QUERY_INFORMATION = 0x0400; var sourceProcessHandle = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, debuggedProcessId); var targetProcess = System.Diagnostics.Process.GetCurrentProcess(); var targetProcessHandle = targetProcess.Handle; if (sourceProcessHandle == null || targetProcessHandle == null) return; try { /* --- Reading from HBITMAP ---- <- fail */ Expression hbm = _dte.Debugger.GetExpression("hbm"); foreach (Expression expr in hbm.DataMembers) { Debug.WriteLine($"\nName: {expr.Name}, Type: {expr.Type}, Value: {expr.Value}"); // => prints only: Name: unused, Type : , Value : <Unable to read memory> } string address = hbm.Value.Replace("0x", ""); if (!long.TryParse(hbm.Value.Replace("0x", ""), System.Globalization.NumberStyles.HexNumber, null, out long hbmLong)) return; IntPtr bitmapHandle = new IntPtr(hbmLong); const int BITMAPINFOHEADER_SIZE = 40; byte[] headerBuffer = new byte[BITMAPINFOHEADER_SIZE]; if (!ReadProcessMemory(sourceProcessHandle, bitmapHandle, headerBuffer, BITMAPINFOHEADER_SIZE, out var bytesReadHeader)) { uint error = GetLastError(); Debug.WriteLine($"Error: {new Win32Exception((int)error).Message}"); // Error: Only part of a ReadProcessMemory or WriteProcessMemory request was completed } const uint DUPLICATE_SAME_ACCESS = 0x00000002; bool success = DuplicateHandle( sourceProcessHandle, // Source process (debugged process) bitmapHandle, // The bitmap handle we want to duplicate targetProcessHandle, // Target process (our VS extension) out IntPtr duplicatedBitmapHandle, // Where the new handle will be stored 0, // Access (0 because we're using DUPLICATE_SAME_ACCESS) false, // Don't inherit handle DUPLICATE_SAME_ACCESS // Copy same access rights ); if (!success) { uint error = GetLastError(); Debug.WriteLine($"Error: {new Win32Exception((int)error).Message}"); // Error: The handle is invalid } /* --- Reading from std::vector<uint8_t> ---- <- works */ Expression pngData = _dte.Debugger.GetExpression("pngData"); int size = int.Parse(pngData.DataMembers.Item("[capacity]")?.Value); address = pngData.DataMembers.Item("[allocator]").DataMembers.Item("[Raw View]").DataMembers.Item("_Myval2").DataMembers.Item("_Myfirst").Value; if (!long.TryParse(address.Replace("0x", ""), System.Globalization.NumberStyles.HexNumber, null, out long addressLong)) return; byte[] buffer = new byte[size]; if (!ReadProcessMemory(sourceProcessHandle, new IntPtr(addressLong), buffer, buffer.Length, out var bytesRead)) return; MemoryStream ms = new MemoryStream(buffer); Image image = Image.FromStream(ms); image.Save("C:\\Users\\Cesar\\Downloads\\VS.png", ImageFormat.Png); } catch (ArgumentException) { Debug.WriteLine("ArgumentException"); } catch (Exception ex) { Debug.WriteLine($"Error evaluating expression: {ex.Message}"); } } }
调试进程源码
#include <iostream> #include <Windows.h> #include <gdiplus.h> #include <vector> using namespace Gdiplus; using namespace DllExports; #pragma comment (lib,"Gdiplus.lib") // => cleanups omitted for brevity std::vector<uint8_t> hBitmapToPngArray(HBITMAP hBitmap, const wchar_t* pngPath) { std::vector<uint8_t> pngData; IStream* pStream = nullptr; if (FAILED(CreateStreamOnHGlobal(NULL, TRUE, &pStream))) return pngData; std::unique_ptr<Gdiplus::Bitmap> bitmap(new Gdiplus::Bitmap(hBitmap, NULL)); if (!bitmap || bitmap->GetLastStatus() != Gdiplus::Ok) return pngData; CLSID pngClsid; UINT num = 0; UINT size = 0; GetImageEncodersSize(&num, &size); if (size == 0) return pngData; std::vector<BYTE> buffer(size); Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)buffer.data(); GetImageEncoders(num, size, pImageCodecInfo); for (UINT j = 0; j < num; ++j) { if (wcscmp(pImageCodecInfo[j].MimeType, L"image/png") == 0) { pngClsid = pImageCodecInfo[j].Clsid; break; } } Gdiplus::Status status = bitmap->Save(pStream, &pngClsid, NULL); if (status != Gdiplus::Ok) return pngData; STATSTG statstg = { 0 }; if (FAILED(pStream->Stat(&statstg, STATFLAG_DEFAULT))) return pngData; LARGE_INTEGER seekPos = { 0 }; pStream->Seek(seekPos, STREAM_SEEK_SET, NULL); pngData.resize(statstg.cbSize.LowPart); ULONG bytesRead; bitmap->Save(pngPath, &pngClsid, NULL); pStream->Release(); return pngData; } int main() { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); HWND hwnd = FindWindow(NULL, L"Untitled - Notepad"); if (!hwnd) { MessageBox(NULL, L"Window not found!", L"Error", MB_ICONERROR); return 0; } RECT rc; GetWindowRect(hwnd, &rc); int width = rc.right - rc.left; int height = rc.bottom - rc.top; HDC hdcWindow = GetDC(hwnd); HDC hdcMemDC = CreateCompatibleDC(hdcWindow); HBITMAP hbm = CreateCompatibleBitmap(hdcWindow, width, height); SelectObject(hdcMemDC, hbm); if (!PrintWindow(hwnd, hdcMemDC, PW_RENDERFULLCONTENT)) { MessageBox(NULL, L"PrintWindow failed!", L"Error", MB_ICONERROR); return 0; } std::vector<uint8_t> pngData = hBitmapToPngArray(hbm, L"C:\\Users\\Cesar\\Downloads\\process.png"); while (true) { Sleep(100); } return 0; }
内容的提问来源于stack exchange,提问作者Cesar
相关产品推荐
相关产品推荐

