我需要调试一个丑陋而庞大的数学C库,可能是由f2c生成的。代码滥用本地静态变量,不幸的是它在某处似乎利用了这些自动初始化为0的事实。如果使用相同的输入两次调用其入口函数,则它会给出不同的结果。如果我卸载库并重新加载它,它可以正常工作。它需要很快,所以我想摆脱加载/卸载。
我的问题是如何使用valgrind或任何其他工具发现这些错误,而无需手动遍历整个代码。
我正在寻找声明本地静态变量的地方,先读取,然后再写。由于静态变量有时会通过指针进一步传递(是的 - 它太丑了),这个问题更加复杂。
我理解人们可以争辩说,自动工具不应该检测到这样的错误,因为在某些情况下,这正是预期的行为。还有,有没有办法让自动初始化的本地静态变量“脏”?
答案 0 :(得分:5)
魔鬼在细节中,但这可能适合你:
首先,获取Frama-C。如果您使用的是Unix,那么您的发行版可能包含一个包。该软件包不会是最后一个版本,但它可能已经足够好了,如果以这种方式安装它会节省一些时间。
说你的例子如下所示,只有这么大,以至于不明显出错:
int add(int x, int y)
{
static int state;
int result = x + y + state; // I tested it once and it worked.
state++;
return result;
}
输入如下命令:
frama-c -lib-entry -main add -deps ugly.c
选项-lib-entry -main add
表示“查看函数add
”。选项-deps
计算功能依赖性。您将在日志中找到这些“功能依赖项”:
[from] Function add:
state FROM state; (and default:false)
\result FROM x; y; state; (and default:false)
这列出了add
依赖的实际输入,以及从这些输入计算的实际输出,包括从中读取和修改的静态变量。在使用之前正确初始化的静态变量通常不会显示为输入,除非分析器在读取之前无法确定它始终被初始化。
日志显示state
为\result
的依赖关系。如果您希望返回的结果仅依赖于参数(意味着具有相同参数的两个调用产生相同的结果),那么这里可能存在错误,提示state
。
上述行中显示的另一个提示是该函数修改了state
。
这可能有所帮助。选项-lib-entry
意味着分析器不会假设任何非常量静态变量在调用分析函数时保持其值,因此对于您的代码可能过于不精确。有很多方法可以解决这个问题,但是你是否想赌博学习这些方法的时间取决于你。
编辑:这是一个更复杂的例子:
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;
}
在此示例中,相同的命令会生成结果:
[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
您在initialize_2
看到的是一个空的依赖列表,这意味着该函数不会分配任何内容。我将通过显示一个显式消息而不仅仅是一个空列表来使这个案例更清楚。如果你知道任何函数initialize_1
,initialize_2
或add
应该做什么,你可以将这个先验知识与分析结果进行比较,看看有什么不对initialize_2
和add
。
第二次编辑:现在我的例子显示了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');
}
{1}}会有所不同。这是最后一个例子:
frama-c -deps t.c
使用命令initialize_index
,为[from] Function initialize_index:
t[0..9] FROM i (and SELF)
计算的依赖关系是:
i
这意味着每个单元格都依赖于i
(如果i
是特定单元格的索引,则可以修改它)。每个单元格也可以保留其值(如果(and SELF)
表示另一个单元格):这在最新版本中以(and default:true)
提及表示,并且在先前版本中用更隐蔽的{{1}}表示
答案 1 :(得分:2)
静态代码分析工具非常擅长查找典型的编程错误,例如使用未初始化的变量。 Here是为C执行此操作的免费工具列表。
很遗憾,我不推荐列表中的任何工具。我只熟悉两种商业产品Coverity和Klocwork。覆盖率非常好(而且价格昂贵)。 Klocwork是如此(但更便宜)。
答案 2 :(得分:2)
我最终做的是通过'#define static'从代码中删除所有静态限定符。这会将未初始化的静态用法转换为无效使用,并且可以通过工具发现我正在搜索的滥用类型。
在我的实际情况中,这足以确定错误的位置,但在更一般的情况下,如果静态实际上正在做一些重要的事情,应该通过在代码无法继续时逐渐重新添加“静态”来改进它。
答案 3 :(得分:1)
我不知道有任何库为你做这个,但我会研究使用正则表达式来找到它们。像
这样的东西rgrep“static \ s * int”path / to / src / root | grep -v = | grep -v“(”
那应该返回所有声明没有等号的静态int变量,最后一个管道应该删除任何带有括号的东西(摆脱功能)。有一个很好的改变,这对你来说无法正常工作,但玩grep可能是你追踪它的最快方法。
当然,一旦找到一个有效的,你可以用所有其他类型的变量替换int来搜索它们。 HTH
答案 4 :(得分:0)
我的问题是如何发现这些错误...
但是这些不是错误:静态变量初始化为0的期望是完全有效的,就像为其分配一些其他值一样。
因此,要求一个能够自动找到非 - 错误的工具不太可能产生令人满意的结果。
根据您的描述,somefunc()
首次调用时会返回正确的结果,后续调用时会返回错误的结果。
调试此类问题的最简单方法是并排进行两个GDB会话:一个是新加载的(将计算正确的答案),另一个是“第二次迭代”(将计算错误的答案)。然后“并行”逐步执行两个会话,并查看其计算或控制流程开始发散的位置。
由于您通常可以有效地将问题分成两半,因此查找错误通常不会花费很长时间。 始终重现的错误是最容易找到的错误。就这么做。