跟踪未初始化的静态变量 [英] tracking uninitialized static variables

查看:40
本文介绍了跟踪未初始化的静态变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要调试一个丑陋而庞大的数学 C 库,可能曾经由 f2c 生成.代码滥用了local 静态变量,不幸的是,它似乎在某处利用了这些自动初始化为 0 的事实.如果使用相同的输入调用其入口函数两次,则会给出不同的结果.如果我卸载库并重新加载它,它可以正常工作.它需要很快,所以我想摆脱加载/卸载.

我的问题是如何使用 valgrind 或任何其他工具来发现这些错误,而无需手动遍历整个代码.

我正在寻找声明局部静态变量的地方,先读取,然后再写入.由于静态变量有时通过指针进一步传递(是的 - 它是如此丑陋),这个问题变得更加复杂.

我理解有人会争辩说,这样的错误不应该被自动工具检测到,因为在某些情况下,这正是预期的行为.还有,有没有办法让自动初始化的局部静态变量脏"?

解决方案

细节决定成败,但这可能对你有用:

首先,获取 Frama-C.如果您使用的是 Unix,您的发行版可能有一个包.该软件包不会是最后一个版本,但它可能已经足够好了,如果您以这种方式安装它,它将为您节省一些时间.

假设您的示例如下所示,只是大得多以至于不明显有什么问题:

int add(int x, int y){静态 int 状态;int 结果 = x + y + 状态;//我测试过一次,效果很好.状态++;返回结果;}

输入如下命令:

frama-c -lib-entry -main 添加 -deps 丑陋.c

Options -lib-entry -main add 意思是查看函数add".选项 -deps 计算函数依赖.你会在日志中找到这些功能依赖":

[from] 函数添加:状态 FROM 状态;(和默认值:false)\result FROM x;y;状态;(和默认值:false)

这列出了 add 的结果所依赖的 actual 输入,以及从这些输入计算的 actual 输出,包括从中读取的静态变量和修改.在使用之前正确初始化的静态变量通常不会作为输入出现,除非分析器无法确定它在被读取之前总是被初始化.

日志显示 state 作为 \result 的依赖项.如果您期望返回的结果仅取决于参数(意味着使用相同参数的两次调用会产生相同的结果),则提示此处可能存在错误,变量 state.>

上面几行显示的另一个提示是该函数修改了state.

这可能有帮助,也可能没有.选项 -lib-entry 意味着分析器不假设任何非常量静态变量在被分析的函数被调用时保持其值,因此这对您的代码来说可能太不精确了.有很多方法可以解决这个问题,但是您是否愿意花时间学习这些方法取决于您.

这是一个更复杂的例子:

void initialize_1(int *p){*p = 0;}void initialize_2(int *p){*p;//我在这里犯了一个错误.}int add(int x, int y){静态 int state1;静态 int state2;初始化_1(&state1);初始化_2(&state2);//这是安全的,因为我已经初始化了 state1 和 state2:int 结果 = x + y + state1 + state2;状态1++;状态2++;返回结果;}

在这个例子中,同样的命令产生了结果:

[from] 函数 initialize_1:状态 1 FROM p[来自] 函数 initialize_2:[来自] 函数添加:state1 FROM \nothing状态 2 来自状态 2\result FROM x;y;状态2

您看到的 initialize_2 是一个空的依赖项列表,这意味着该函数不分配任何内容.我将通过显示一个明确的消息而不是一个空列表来使这个案例更清楚.如果您知道任何函数 initialize_1initialize_2add 应该做什么,您可以将这些先验知识与分析并发现 initialize_2add 有问题.

第二次现在我的例子显示了 initialize_1 的一些奇怪的东西,所以也许我应该解释一下.变量 state1 依赖于 p,因为 p 用于写入 state1,如果 p 不同,那么 state1 的最终值就会不同.这是最后一个例子:

int t[10];void initialize_index(int i){t[i] = 1;}int main(int argc, char **argv){initialize_index(argv[1][0]-'0');}

使用命令 frama-c -deps t.c,为 initialize_index 计算的依赖为:

[from] 函数 initialize_index:t[0..9] 从我(和自我)

这意味着每个单元格都依赖于 i(如果 i 是那个特定单元格的索引,它可能会被修改).每个单元格也可以保留它的值(如果 i 表示另一个单元格):这在最新版本中用 (and SELF) 表示,并用 more 表示在以前的版本中模糊 (和默认值:true).

I need to debug an ugly and huge math C library, probably once produced by f2c. The code is abusing local static variables, and unfortunately somewhere it seems to exploit the fact that these are automatically initialized to 0. If its entry function is called with the same input twice, it is giving different results. If I unload the library and reload it again, it works correctly. It needs to be fast, so I would like to get rid of the load/unload.

My question is that how to uncover these errors with valgrind or by any other tool without manually walking through the entire code.

I am hunting places where a local static variable is declared, read first, and written only later. The problem is even further complicated by the fact that the static variables are sometimes passed further via pointers (yep - it is so ugly).

I understand that one can argue that mistakes like this should not be necessary detected by an automatic tool, as in some scenarios this is exactly the intended behaviour. Still, is there a way to make the auto-initialized local static variables "dirty"?

解决方案

The devil is in the details, but this may work for you:

First, get Frama-C. If you are using Unix, your distribution may have a package. The package won't be the last version but it may be good enough and it will save you some time if you install it this way.

Say your example is as below, only so much bigger that it's not obvious what is wrong:

int add(int x, int y)
{
  static int state;
  int result = x + y + state; // I tested it once and it worked.
  state++;
  return result;
}

Type a command like:

frama-c -lib-entry -main add -deps ugly.c

Options -lib-entry -main add mean "look at function add". Option -deps computes functional dependencies. You'll find these "functional dependencies" in the log:

[from] Function add:
     state FROM state; (and default:false)
     \result FROM x; y; state; (and default:false)

This lists the actual inputs the results of add depend on, and the actual outputs computed from these inputs, including static variables read from and modified. A static variable that was properly initialized before being used would normally not appear as input, unless the analyzer was unable to determine that it was always initialized before being read from.

The log shows state as dependency of \result. If you expected the returned result to depend only on the arguments (meaning two calls with the same arguments produce the same result), it's a hint there may be something wrong here, with the variable state.

Another hint shown in the above lines is that the function modifies state.

This may help or not. Option -lib-entry means that the analyzer does not assume that any non-const static variable has kept its value at the time the function under analysis is called, so that may be too imprecise for your code. There are ways around that, but then it is up to you whether you want to gamble the time it takes to learn these ways.

EDIT: here is a more complex example:

void initialize_1(int *p)
{
  *p = 0;
}

void initialize_2(int *p)
{
  *p; // I made a mistake here.
}

int add(int x, int y)
{
  static int state1;
  static int state2;

  initialize_1(&state1);
  initialize_2(&state2);

  // This is safe because I have initialized state1 and state2:
  int result = x + y + state1 + state2; 

  state1++;
  state2++;
  return result;
}

On this example, the same command produces the results:

[from] Function initialize_1:
         state1 FROM p
[from] Function initialize_2:
[from] Function add:
         state1 FROM \nothing
         state2 FROM state2
         \result FROM x; y; state2

What you see for initialize_2 is an empty list of dependencies, meaning the function assigns nothing. I will make this case clearer by displaying an explicit message rather than just an empty list. If you know what any of the functions initialize_1, initialize_2 or add is supposed to do, you can compare this a priori knowledge to the results of the analysis and see that something is wrong for initialize_2 and add.

SECOND EDIT: and now my example shows something strange for initialize_1, so perhaps I should explain that. Variable state1 depends on p in the sense that p is used to write to state1, and if p had been different, then the final value of state1 would have been different. Here is a last example:

int t[10];

void initialize_index(int i)
{
  t[i] = 1;
}

int main(int argc, char **argv)
{
  initialize_index(argv[1][0]-'0');
}

With the command frama-c -deps t.c, the dependencies computed for initialize_index are:

[from] Function initialize_index:
         t[0..9] FROM i (and SELF)

This means that each of the cells depends on i (it may be modified if i is the index of that particular cell). Each cell may also keep its value (if i indicates another cell): this is indicated with the (and SELF) mention in the latest version, and was indicated with a more obscure (and default:true) in previous versions.

这篇关于跟踪未初始化的静态变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆