【问题标题】:How to return the name of a variable stored at a particular memory address in C++如何返回存储在 C++ 中特定内存地址的变量的名称
【发布时间】:2013-07-04 00:04:57
【问题描述】:

在我的许多 Google 搜索结果都来自这个精彩的网站后,第一次在这里发帖。

基本上,我想找到存储在特定内存地址的变量的名称。我有一个我写的内存编辑应用程序,它编辑一个值,问题是每次保存这个值的应用程序被修补时,我必须将新的内存地址硬编码到我的应用程序中,然后重新编译,这需要很长时间维护它几乎不值得做。

我想做的是获取存储在某个内存地址的变量的名称,这样我就可以在运行时找到它的地址并将其用作要编辑的内存地址。

这都是用 C++ 编写的。

提前致谢!

编辑:

好吧,我决定要从 .txt 文件流式传输数据,但我不确定如何将字符串转换为 LPVOID 以用作 WriteProcessMemory() 中的内存地址。这是我尝试过的:

    string fileContents;

    ifstream memFile("mem_address.txt");
        getline(memFile, fileContents);
    memFile.close();

    LPVOID memAddress = (LPVOID)fileContents.c_str();

    //Lots of code..

    WriteProcessMemory(WindowsProcessHandle, memAddress, &BytesToBeWrote, sizeof(BytesToBeWrote), &NumBytesWrote);

代码在语法方面都是正确的,它可以编译和运行,但是 WriteProcessMemory 错误,我只能想象它与我的错误 LPVOID 变量有关。如果扩展我的问题的使用违反了规则,我深表歉意,如果是,我将删除我的编辑。

【问题讨论】:

    标签: c++ windows variables memory-address


    【解决方案1】:

    编译并生成一个所谓的map 文件。这可以使用 Visual-C++(/MAP 链接器选项)轻松完成。在那里您会看到符号(函数,...)及其起始地址。使用此映射文件(注意:每次重新编译时都必须更新),您可以将地址与名称匹配。

    这实际上并不容易,因为地址是相对于首选加载地址的,并且可能(随机化)与实际加载地址不同。

    可以在此处找到有关检索正确地址的一些旧提示:http://home.hiwaay.net/~georgech/WhitePapers/MapFiles/MapFiles.htm

    【讨论】:

    • 感谢您的所有回答,在我的情况下,我似乎无法完全完成我的想法,但至少我对这个主题有足够的了解以免我浪费更多时间思考如何做到这一点。
    【解决方案2】:

    通常,编译程序时不会保留变量的名称。如果您可以控制编译过程,您通常可以配置链接器和编译器以生成一个映射文件,列出所有全局变量在内存中的位置。但是,如果是这种情况,您可能可以通过不使用直接内存访问来更轻松地实现您的目标,而是创建一个您的外部程序可以调用的适当命令协议。

    如果您无法控制其他程序的编译过程,那么您可能运气不佳,除非该程序附带映射文件或调试符号,其中任何一个都可用于派生变量的名称从他们的地址。

    请注意,对于堆栈变量,派生它们的名称将需要完整的调试符号,并且是一个非常重要的过程。堆变量没有名字,所以你自然不会走运。此外,正如@jdehaan 的回答中提到的,在最好的情况下使用地图文件可能有点棘手。总而言之,最好有一个适当的控制协议,您可以使用它来完全避免对其他程序内存内容的任何依赖。

    最后,如果您无法控制其他程序,那么我建议将变量位置放入单独的数据文件中。这样你就不再需要每次都重新编译,甚至可以支持被戳的程序的多个版本。如果您愿意,您还可以使用某种自动更新服务从您的服务器中提取此数据文件的新版本。

    【讨论】:

      【解决方案3】:

      除非您实际拥有相关应用程序,否则没有标准方法可以执行此操作。如果您确实拥有该应用程序,您可以关注@jdehaan 的回答。

      在任何情况下,与其将内存地址硬编码到您的应用程序中,为什么不在某个地方托管一个简单的提要,您可以随时使用您需要为目标应用程序的每个版本更改的内存地址进行更新?这样,您无需每次都重新编译您的应用程序,而是在需要能够操作新版本时更新该提要。

      【讨论】:

      • 我不敢相信我从来没有想过这个!我肯定会考虑解析提要或文本文件来解决这个问题!
      【解决方案4】:

      你不能直接这样做;变量名实际上并不存在于编译的二进制文件中。如果程序是用 Java 或 C# 编写的,您也许可以做到这一点,它们确实在编译后的二进制文件中存储有关变量的信息。

      此外,这通常是不可能的,因为目标程序中值的最新副本总是可能位于 CPU 寄存器内部而不是内存中。如果有问题的程序是在发布模式下编译并启用优化,则更有可能发生这种情况。

      如果您可以确保目标程序在调试模式下编译,您应该能够使用编译器(.pdb 文件)发出的调试符号来将地址映射到变量,但在这种情况下,您需要启动目标进程,就好像它正在被调试一样——普通的读取进程内存和写入进程内存方法不起作用。

      最后,您的问题忽略了一个非常重要的考虑因素——即使存储了这样的信息,也不需要对应于特定地址的变量。

      【讨论】:

      • Write/ReadProcessMemory() 当然可以与调试符号一起使用,而无需以特定方式启动应用程序。 VC++ 还能如何附加到正在运行的进程?当然,从来没有人说过解释调试符号是容易...:)
      • @bdonlan:我假设局部变量。您必须停止程序执行才能查看它们何时进入和超出范围。这需要在调试器下启动目标进程。
      • 或者在事后附加调试器,但实际上,您需要停止执行才能稳定读取局部变量是否存在。
      【解决方案5】:

      如果您有相关应用的源代码并且无需考虑最佳内存使用情况,那么您可以在调试友好的结构中声明有趣的变量,类似于:

      typedef struct {
          const char head_tag[15] = "VARIABLE_START";
                char var_name[32];
                int  value;
          const char tail_tag[13] = "VARIABLE_END";
      } debuggable_int;
      

      现在,您的应用程序应该能够搜索程序的内存空间并查找头和尾标签。一旦找到您的可调试变量之一,它就可以使用var_namevalue 成员来识别和修改它。

      但是,如果您要达到这个长度,最好启用调试符号并使用常规调试器进行构建。

      【讨论】:

        【解决方案6】:

        比利·奥尼尔开始朝着正确的方向前进,但(IMO)并没有完全达到真正的目标。假设您的目标是 Windows,一个更简单的方法是使用 Windows 符号处理函数,特别是 SymFromName,它可以让您提供符号的名称,并且它将返回(除其他外)该符号的地址。

        当然,要执行上述任何操作,您必须在允许进行调试的帐户下运行。然而,至少对于全局变量,您不一定要停止目标进程来查找符号、地址等。事实上,如果进程选择,它可以在自身上使用这些变量(相当我早期了解这些功能的实验很少做到这一点)。这是我几年前写的一些演示代码,它至少给出了一个大致的想法(尽管它已经足够老了,它使用了SymGetSymbolFromName,比SymFromName 落后了几代人)。用调试信息编译它并退后——它会产生相当多的输出。

        #define UNICODE
        #define _UNICODE
        #define DBGHELP_TRANSLATE_TCHAR
        #include <windows.h>
        #include <imagehlp.h>
        #include <iostream>
        #include <ctype.h>
        #include <iomanip>
        #pragma comment(lib, "dbghelp.lib")
        
        int y;
        
        int junk() {
            return 0;
        }
        
        struct XXX { 
            int a;
            int b;
        } xxx;
        
        BOOL CALLBACK 
        sym_handler(wchar_t const *name, ULONG_PTR address, ULONG size, void *) {
            if (name[0] != L'_')
                std::wcout << std::setw(40) << name 
                    << std::setw(15) << std::hex << address 
                    << std::setw(10) << std::dec << size << L"\n";
            return TRUE;
        }
        
        int 
        main() {
            char const *names[] = { "y", "xxx"};
        
            IMAGEHLP_SYMBOL info;
        
            SymInitializeW(GetCurrentProcess(), NULL, TRUE);
        
            SymSetOptions(SYMOPT_UNDNAME);
        
            SymEnumerateSymbolsW(GetCurrentProcess(), 
                (ULONG64)GetModuleHandle(NULL),
                sym_handler,
                NULL);
        
            info.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
        
            for (int i=0; i<sizeof(names)/sizeof(names[0]); i++) {
                if ( !SymGetSymFromName(GetCurrentProcess(), names[i], &info)) {
                    std::wcerr << L"Couldn't find symbol 'y'";
                    return 1;
                }
        
                std::wcout << names[i] << L" is at: " << std::hex << info.Address << L"\n";
            }
        
            SymCleanup(GetCurrentProcess());
            return 0;
        }
        

        【讨论】:

        • 可能 WinDbg 的 "!ln" 命令是在内部使用类似的东西实现的
        • @chubsdad: 是的,我猜是这样(明显的区别是它通过了一些 other 进程,而不是上面提到的GetCurrentProcess()...)
        【解决方案7】:

        WinDBG 有一个特别有用的命令

        here

        给定一个内存位置,它将给出该位置的符号名称。有了正确的调试信息,它就是调试器(我的意思是进行调试的人 :))的福音!。

        这是我的系统 (XP SP3) 上的示例输出

        0:000> ln 7c90e514 (7c90e514)
        ntdll!KiFastSystemCallRet | (7c90e520) ntdll!KiIntSystemCall 精确匹配: ntdll!KiFastSystemCallRet()

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-12-10
          • 1970-01-01
          • 2013-01-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-07
          • 1970-01-01
          相关资源
          最近更新 更多