[这是冗长而充满细节的。我的具体问题由下面的粗体字问题介绍。]
概括
我们在 valgrind 下运行我们的一些测试套件,遇到了一个对我来说没有多大意义的错误。我正在寻找有关更详细地找出可能出了什么问题的建议。
- Valgrind 抱怨“大小为 8 的无效写入”。
- 该错误在每次运行中都是一致的,但随着应该不相关的代码更改、不同的编译器/stdlib 版本等的出现而出现。
- 写入的地址在堆栈上,据我所知,这是我们的代码要写入的完全合理的地址。
- 它的对齐方式与写入的大小一致。
- 它发生的地方在标准库的深处。
所有这些听起来都好像真正的问题在其他地方:某些东西正在被破坏并导致后来的混乱。但这是 valgrind 报告的第一个问题,所以如果其他地方存在内存踩踏,那么 valgrind 将无法捕捉到它。我怀疑要么我遗漏了一些明显的东西,要么存在一个微妙的问题,那些比我拥有更多 valgrind 专业知识的人可能能够指出我。
一些细节
以下是一些细节和一些具体问题。
这是在 x64 硬件上运行 Ubuntu 14.04 的 Linux 机器上。
这是 valgrind 在一个相当典型的例子中的抱怨:
==14259== Invalid write of size 8
==14259== at 0x662BBC9: __printf_fp (printf_fp.c:663)
==14259== by 0x6629792: vfprintf (vfprintf.c:1660)
==14259== by 0x664D578: vsnprintf (vsnprintf.c:119)
==14259== by 0x52DCE0F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==14259== by 0x52E3263: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_float<double>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, char, double) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==14259== by 0x52E354F: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, double) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==14259== by 0x52EEAF4: std::ostream& std::ostream::_M_insert<double>(double) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==14259== by 0x694725: CRVinfo::appendValue(std::string const&, double) (CRVinfo.cpp:197)
==14259== by 0x6902DB: CRVdouble::info(CRVinfo&) const (CRVdouble.cpp:103)
==14259== by 0x6913B4: CRVcollection::info(CRVinfo&) const (CRVcollection.cpp:60)
==14259== by 0x6913B4: CRVcollection::info(CRVinfo&) const (CRVcollection.cpp:60)
==14259== by 0x68F87F: CRVvalue::generate() (CRVvalue.cpp:71)
==14259== Address 0xffeffde68 is on thread 1's stack
==14259== in frame #0, created by __printf_fp (printf_fp.c:161)
以“CRV”开头的东西是我们的;它们上面的东西在 libstdc++ 和 glibc 中。Ubuntu 14.04 使用 2.19 版的 glibc——除了事实上它似乎使用的是 eglibc 2.19 而不是普通的 glibc 2.19;您可以在此处找到相关版本的 printf_fp.c 。
运行 valgrind--vgdb
并要求 gdb 进行反汇编声明(与上面链接的源代码一致)当 valgrind 停止我们时我们实际上将要执行的指令是callq __mpn_lshift
.
涉及“我们的”代码的最顶层堆栈帧如下所示:
void CRVinfo::appendValue(const std::string &name, double value){
addIndent();
addElementBegin(name);
std::ostringstream oss;
oss << value;
m_valueTree.append(oss.str());
addElementEnd(name);
}
问题就在里面oss << value;
。m_valueTree
是一个std::string
;你可以猜出什么样的事情addIndent
和addElementBegin
做什么;后者使用 stringstream 来做到这一点,前者没有。(可能不相关的注释:您可能认为这看起来效率低下,您是对的,但这根本不是性能关键代码。)
所以,无论如何,我们在一条callq
指令的地址 0xffeffde68 处得到一个大小为 8 的无效写入。您希望callq
写入 指向的内存rsp
,所以它确实如此(我已经验证此时rsp
等于 0xffeffde68)......但是 valgrind 反对这一点,我不清楚为什么。
(一个明显的猜测可能是我们的堆栈溢出了。但是(1)我认为这会发生在一个看起来更圆的地址上,并且(2)我试图增加堆栈大小但它没有使这些 valgrind 抱怨消失了,并且 (3) 我预计堆栈溢出时会出现段错误,但这并没有发生,并且 (4) 我们此时并没有使用太多堆栈;最早我已经能够探测到,rsp
是 0xfff000598,所以我们在故障点使用了不到 10k 的堆栈。)
问题:我是否应该清楚valgrind反对这篇文章的内容?如果没有,有没有办法让 valgrind 告诉我更多关于它为什么不喜欢它的信息?
问题:这里的直接问题是 valgrind 中的错误是否合理(尽管可能是由我们代码中的一些早期不当行为引起的)?如果是这样,有什么好的方法可以追踪或排除这些事情吗?
问题:这看起来像 glibc 或 libstdc++ 的任何已知问题吗?(到目前为止,我所做的此类网络搜索尚未发现任何此类已知问题。)
如果有用,请提供更多信息
如果我允许在此无效写入后继续执行,则 valgrind 会在__mpn_lshift
此处调用的函数内部抱怨大小为 8 的无效读取。它从同一地址读取并在 gdb 中反汇编毫不奇怪地表明它retq
是那__mpn_lshift
是罪魁祸首。
我的堆栈框架似乎都不是很大。Valgrind 不会抱怨大堆栈帧,询问堆栈是否移动,建议增加--max-stacksize
,或任何类似的东西。
在另一台具有稍微不同版本的 gcc 和可能不同版本的标准库的机器上,valgrind 再次报告大小为 8 的无效写入,__printf_fp
但在它的不同部分,这次不是在调用指令上。(不幸的是,这是在同事的计算机上,并且由于我们观察到了这一点,因此进行了一些更改,使他的版本显示出与我相同的失败,因此我无法自信地提供更多详细信息。但我是 95%确保失败发生在一条mov
指令上,并且严格写在当前堆栈帧内。)