如何监控正在运行的进程中的特定变量?

逆向工程 二元分析 C++ 记忆
2021-06-15 23:13:22

我想监视、记录应用程序中的特定变量,但我不确定解决此问题的正确方法是什么。

该应用程序是 C(5%) 和 C++(95%) 的混合体,它比较大,大约有 500 万行代码。目标操作系统是 CentOS 7,我的应用程序总是使用调试标志 (-g3 -ggdb) 编译,如果需要,我可以添加/删除标志以实现我正在寻找的东西。在理想情况下,我不想在主应用程序中检测代码,主要是由于应用程序的大小,和/或向我的应用程序添加任何开销。

我还没有开始实施任何东西,但我有两种不同的方法:

  1. 编写第二个程序,给定一个变量名,这个变量在我的主程序中声明,它:

    • 解析主程序的调试符号
    • 找到我们感兴趣的变量的地址
    • 以某种方式设法访问主应用程序的虚拟地址空间并使用我们在上一步中找到的地址读取变量的值

我仍然不确定如何处理不同的数据类型,也许我可以通过解析主程序以某种方式推断数据类型,但我现在可以确定。

我知道操作系统不会让我轻松访问另一个进程的内存,但应该有一个技巧。那是什么伎俩?有没有更直接的方法来做到这一点?与此相关的开销是多少?有没有办法也写入变量?

  1. 在主应用程序中添加逻辑:

    • 通过网络将变量名称传递给应用程序
    • 该应用程序读取并解析自己的二进制文件
    • 找到我们感兴趣的变量的地址,我们就完成了

第二种方法更有趣,因为一切都在它的地址空间中,所以不需要任何技巧。我仍然必须知道变量的类型。

我相信其他人也遇到过类似的问题,但我不知道这个过程叫什么。我应该搜索什么来获取更多信息?我知道这是一个非常普遍的问题,任何指针都非常感谢。

2个回答

好吧,如果您可以使用调试器,那么您就可以使用各种监控技术。
下面的示例适用于正在由 windbg 调试的 Windows 应用程序。

但是 gdb 同样是多才多艺的,你应该能够适应 GDB 来做这里正在做的事情

calc 是 windows 计算器,它可以在以下基数下工作hex、dec、oct 和 bin

它将基数存储在名为g_nRadix的全局变量中

0:004> x /v calc!g_nRadix
pub global 00524058             0 calc!g_nRadix = <no type information>

现在假设我想监视这个变量并在它被写入时记录

我可以编写一个包含一些命令的小 txt 文件,并在使用 windbg 启动应用程序时运行它,如下所示

包含调试器命令的 txt 文件(又名脚本文件)

C:\>cat monvar.txt
g @$exentry
ba w 4 calc!g_nRadix ".echotime;dd calc!g_nRadix l1;gc"
g
C:\>

数据断点是特定于上下文的,所以我执行直到入口点地址(类似于 linux 应用程序中的 _start 函数)第一行 g @$exentry

当我在入口点时,我在这个变量上设置了一个写数据断点并运行可执行文件的第二行和第三行

我在调试器中启动应用程序,如下面的屏幕截图中的命令提示符标题所示

当我改变 gui 中的基数时

调试器将打印时间戳和我正在监视的变量的内容并继续运行

在此处输入图片说明

您正在寻找的称为价值分析。这是一种技术,旨在记录某些变量(如循环边界)的值,以便提取所有可能的值并使用它们来微调代码以获得更好的性能。

你应该知道的一件事,一旦你编译你的代码,调试信息与否,就没有变量的概念了。而且,如果您使用编译器优化标志,并且您应该这样做,那么变量将被优化掉。

这就是说,您最好的选择是通过您的代码并手动打印(甚至通过解析脚本);或者您可以使用编译器插件。

如果您使用GCC,我建议您编写一个由您自己的指令引导的插件,即#pragma watch var,然后指示编译器使用库中的函数在 GIMPLE 级别进行检测 - 我们称之为watch_var.so该库的函数类似于以下原型:dump_int_var_value(FILE *fd, char *var_name, int var_value)

只有在编译器级别才能完全自动化此过程,因为您将知道变量何时声明、何时使用(读取和写入)及其类型。上面提到的 pragma 指令可以有两种风格:

1) 放置在变量声明之前,var1 & var2 将被监视。

 #pragma watch var
 int var1, var2;

2) 放置在块的开头,x 和 y 将在第一个示例中被观察。而且,只有我会在第二个被监视。

 #pragma watch var 
 int function1(int a, int b)
 {
     int x = a, y = b;
     return x + y;
 }

 #pragma watch var
 if (a < b)
 {
     for (int i = 0; i < n; i++)
        t[i] = a * i;
 }

从这里,我想你明白了。

从我的立场来看,我认为这是处理变量的唯一干净的解决方案,而无需重新发明轮子或钻研复杂的技巧。