You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SymEnumLine获取的PSRCCODEINFO地址与实际地址不符问题排查

问题:SymEnumLine获取的PSRCCODEINFO.Address与实际汇编地址不符

问题描述

编写调试器代码时,使用SymEnumLine获取PSRCCODEINFO结构体的Address字段,发现该地址比被调试进程debugee.exe的实际汇编地址偏大,甚至处于mov指令中间。尝试改用SymEnumSourceLines及SymEnumSourceFilesW、SymEnumLinesW等宽字符版本均无效,且已确认传入SymEnumSourceFiles和SymEnumLines的地址完全一致。请问该差异的原因是什么?是否需要对line->Address进行偏移调整?

调试器代码

#include <windows.h>
#include <DbgHelp.h>
#include <Psapi.h>

#include <iostream>
#include <string>
#include <filesystem>

std::string get_last_error_message() {
    //Get the error message ID, if any.
    DWORD errorMessageID = ::GetLastError();
    if(errorMessageID == 0) {
        return std::string(); //No error message has been recorded
    }
    
    LPSTR messageBuffer = nullptr;

    //Ask Win32 to give us the string version of that message ID.
    //The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
    size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
    
    //Copy the error message into a std::string.
    std::string message(messageBuffer, size);
    
    //Free the Win32's string's buffer.
    LocalFree(messageBuffer);
            
    return message;
}

DWORD64 load_module(HANDLE process, std::string_view path, DWORD64 base_addr)
{
    return SymLoadModule64(process, NULL, path.data(), NULL, base_addr, 0);
}

bool load_module_info(DWORD64 __module, PROCESS_INFORMATION &process_info)
{
    IMAGEHLP_MODULE64 module_info;
    module_info.SizeOfStruct = sizeof(module_info);
    BOOL bSuccess = SymGetModuleInfo64(process_info.hProcess, __module, &module_info);

    struct SymEnumSourceFilesContext {
        HANDLE process;
        DWORD64 start_address;
    } context;

    context.process = process_info.hProcess;
    context.start_address = __module;

    if(bSuccess && module_info.SymType == SymPdb)
    {
        SymEnumSourceFiles(context.process, context.start_address, NULL, [](PSOURCEFILE source_file, PVOID user_context) -> BOOL {
            //----------------------------------------------------^
            // Can filter files, like "*.cpp"
            // Todo: let virtualize the mask

            SymEnumSourceFilesContext* context = (SymEnumSourceFilesContext*)user_context;

            SymEnumLines(context->process, context->start_address, NULL, source_file->FileName, [](PSRCCODEINFO line, PVOID user_context) -> BOOL {

                SymEnumSourceFilesContext* context = (SymEnumSourceFilesContext*)user_context;

                std::string filename = line->FileName;
                
                if(filename.ends_with("debugee.cpp")) {
                    //show only files for debugee

                    printf("%s:%i: %p\n", filename.c_str(), (int)line->LineNumber, (void*)line->Address);
                }
                return true;
            }, (void*)context);

            return true;
        }, &context);

        return true;
    }

    return false;
}

std::string GetFileNameFromHandle(HANDLE hFile) 
{
    BOOL bSuccess = FALSE;
    TCHAR buffer[MAX_PATH+1];

    // Get the file size.
    DWORD dwFileSizeHi = 0;
    DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi); 

    if( dwFileSizeLo != 0 || dwFileSizeHi != 0 )
    {     
        // Create a file mapping object.
        HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0,  1, NULL);

        if (hFileMap) 
        {
            // Create a file mapping to get the file name.
            void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);

            if (pMem) 
            {
                // Gets the full path of the file, instead of the drive letter (ie C://), it has the device name
                if (GetMappedFileName (GetCurrentProcess(), pMem,  buffer, MAX_PATH)) 
                {
                    TCHAR drive_letters[MAX_PATH+1];
                    const char* it = drive_letters;
                    if (GetLogicalDriveStrings(MAX_PATH, drive_letters)) 
                    {
                        std::string drive_path = "x:";
                        char drive_letter = *it;

                        std::string file_name = buffer;
                        while(drive_letter) {
                            drive_path[0] = drive_letter;

                            if (QueryDosDevice(drive_path.data(), buffer, MAX_PATH))
                            {
                                std::string_view view = buffer;
                                if(file_name.starts_with(view)) {
                                    return drive_path + file_name.substr(view.size());
                                }
                            } else {
                                printf("failed to query dos device for drive letter '%c': %s\n", drive_letter, get_last_error_message().c_str());
                                break;
                            }

                            //removes the drive name and the null termination
                            while (*it++);
                        }
                    } else {
                        printf("failed to get logical drives: '%s'\n", get_last_error_message().c_str());
                    }
                }
                else {
                    printf("failed to get get mapped file names: '%s'\n", get_last_error_message().c_str());
                }
            }
        }
    }

    return "unknow path";
}

int main()
{
    PROCESS_INFORMATION m_process_info;
    STARTUPINFO m_startup_info;
    DEBUG_EVENT m_debug_event = { 0 };

    ZeroMemory( &m_startup_info, sizeof(m_startup_info) ); 
    m_startup_info.cb = sizeof(m_startup_info); 

    ZeroMemory( &m_process_info, sizeof(m_process_info) );

    std::string debugee_path = std::filesystem::absolute("debugee.exe").string();

    if(!CreateProcess (debugee_path.c_str(), NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL,NULL, &m_startup_info, &m_process_info )) {
        std::cout << "error to create process: " << get_last_error_message() << std::endl;
        return 0;
    }

    BOOL could_initialize_sym = SymInitialize(m_process_info.hProcess, NULL, false);

    if(!could_initialize_sym) {
        std::cout << "error to initialize symbols: " << get_last_error_message() << std::endl;
        return 0;
    }

    while(1) {
        WaitForDebugEvent(&m_debug_event, INFINITE); 

        DWORD status = DBG_CONTINUE;

        switch (m_debug_event.dwDebugEventCode) 
        { 
            case CREATE_PROCESS_DEBUG_EVENT:
            {
                std::string process_name = GetFileNameFromHandle(m_debug_event.u.CreateProcessInfo.hFile);
                DWORD64 __module = load_module(m_process_info.hProcess, process_name, (DWORD64)m_debug_event.u.CreateProcessInfo.lpStartAddress);

                if(__module) {
                    bool modules_has_been_loaded = load_module_info(__module, m_process_info);
                }
            
                break;
            }
            default:
            break;
        }

        ContinueDebugEvent(m_debug_event.dwProcessId, m_debug_event.dwThreadId, status);
    }
}

被调试程序代码

#include <iostream>

int main () {
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

CMakeLists.txt

project(minimal-sample)

add_executable(debugger ${CMAKE_CURRENT_LIST_DIR}/debugger.cpp)
target_link_libraries(debugger Dbghelp.lib)

add_executable(debugee ${CMAKE_CURRENT_LIST_DIR}/debugee.cpp)

复现步骤

  • 构建两个目标(debugger和debugee)
  • 运行debugger.exe
  • 对比debugee.exe的反汇编视图与调试器输出,即可看到地址差异

回答

原因分析

  1. 模块基址传入错误
    你调用SymLoadModule64时,传入的base_addr是m_debug_event.u.CreateProcessInfo.lpStartAddress,但这个地址是进程的入口点(如main函数起始地址),而非模块的实际加载基址。模块基址应该从CREATE_PROCESS_DEBUG_EVENT的u.CreateProcessInfo.lpBaseOfImage获取,这才是被调试EXE模块在虚拟内存中的起始地址。错误的基址会导致符号系统计算出错误的偏移,进而让PSRCCODEINFO.Address出现偏差。

  2. 符号加载时机不当
    CREATE_PROCESS_DEBUG_EVENT触发时,被调试进程的模块可能尚未完全加载完成,此时调用符号枚举函数可能获取到不完整或错误的信息。

解决方案

  1. 修正模块基址参数
    修改CREATE_PROCESS_DEBUG_EVENT中的代码,使用正确的模块基址:

    DWORD64 __module = load_module(m_process_info.hProcess, process_name, (DWORD64)m_debug_event.u.CreateProcessInfo.lpBaseOfImage);
    
  2. 补充DLL模块加载处理
    新增对LOAD_DLL_DEBUG_EVENT的处理,确保所有模块(包括DLL)的符号都能正确加载:

    case LOAD_DLL_DEBUG_EVENT:
    {
        std::string dll_name = GetFileNameFromHandle(m_debug_event.u.LoadDll.hFile);
        DWORD64 dll_base = (DWORD64)m_debug_event.u.LoadDll.lpBaseOfDll;
        DWORD64 loaded_module = load_module(m_process_info.hProcess, dll_name, dll_base);
        if(loaded_module) {
            load_module_info(loaded_module, m_process_info);
        }
        break;
    }
    
  3. 优化符号初始化设置
    调用SymInitialize时将第三个参数设为true,让系统自动加载模块符号,简化流程:

    BOOL could_initialize_sym = SymInitialize(m_process_info.hProcess, NULL, true);
    

经过以上调整后,PSRCCODEINFO.Address即可与实际汇编地址匹配,无需额外进行偏移调整。


内容的提问来源于stack exchange,提问作者Moonslate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:09:55