如何深入了解明显的腐败

How to Drill Down on Apparent Corruption

本文关键字:何深入 了解      更新时间:2023-10-16

我已经使用C和C++相当长的时间了。 我有一个计算机科学辅修专业。 我熟悉这些语言提供的对进程内存的低级访问所固有的陷阱。 我在他们身上度过了几天和几周。

大约十年前,学习使用valgrind在捕获小访问错误等方面是救命稻草。 目前,我也将ASAN与clion一起使用,此类错误通常会被迅速发现和处理。

然而,我认为没有防弹,最近的一个问题让我完全难倒了。1我有一个对象,其中包含一个名为from的非公共sockaddr_storage字段。 这可以通过以下途径访问:

const sockaddr_storage* getSockAddr () {
return &from;
}

但是返回的地址是错误的。 从gdbreturn行上的断点开始:

Breakpoint 3, socketeering::Socket::getSockAddr (this=0x617000000400) at Socket.hpp:81
81          return &from;
(gdb) p this
$1 = (socketeering::UDPsocket * const) 0x617000000400
(gdb) p &from
$2 = (sockaddr_storage *) 0x617000000600
(gdb) p (const sockaddr_storage*)&from
$3 = (const sockaddr_storage *) 0x617000000600

似乎很明显返回的值必须0x617000000600。 但是没有:

(gdb) fin
Run till exit from #0  socketeering::Socket::getSockAddr (this=0x617000000400) at Socket.hpp:81
0x00000000004290ab in udpHandler::dataReady (this=0x631000014810, iod=0x617000000400, con=0x60e0000249b0) at /opt/cogware/C++/Socketeering2/demo/echo_server.cpp:66
66              auto sa = sock->getSockAddr();
Value returned is $4 = (const sockaddr_storage *) 0x617000000618
^^
(gdb) p sock
$5 = (socketeering::UDPsocket *) 0x617000000400

这不好 - 它是结构内部的18字节。 更糟糕的是,我无法用简单的 SSCCE 重现它:

class foo {
sockaddr_storage ss;
public:
foo () { cout << &ss << "n"; }
const sockaddr_storage* getSockAddr () { return &ss; }
}; 

这意味着这不是对规则等的误解。 这显然也不是逻辑错误。

一定是腐败吧?

这是一个单线程过程,如果我不是fin而是继续踩着看看发生了什么,实际上没有什么可看的。 一步到函数关闭,下一步是在具有错误值的赋值处。瓦尔格林德和阿桑都没有表示任何希金克斯。

我可以查看哪些内容以了解正在发生的事情? 显然,这两者之间出了问题:

return &from;

以及值的实际返回。 查看程序集转储以获取线索是留给我的唯一途径(假设这会有所帮助,我不是 ASM 人)?

我担心的答案是,除了在代码中搜索valgrind和ASAN没有发现的错误之外,别无他法。 找出在什么情况下他们不会发现腐败是一个起点。


  1. 我之前确实在一个现已删除的问题中提出了这个问题。 如果我读到这样的问题,任何人都能说:我们需要一个SSCCE,而腐败可能在代码的其他部分。关键是,我必须展示的信息中没有任何内容可以解释问题,但是,没有邀请每个人参加 10-20K LOC 项目,这就是我能做的。 所以我现在问的不是出了什么问题,而是"我怎么能确定出了什么问题?

查看程序集转储以寻找线索是留给我的唯一途径吗

是的,在这里使用disas命令是合适的方法。

(假设这会有所帮助,我不是 ASM 人)?

即使你不会编写汇编,通常也很容易阅读汇编。特别是如果它是类似于x86_64并且不涉及复杂的位摆动或浮点。这是一项可以很好地为您服务的技能。


通常这种问题是违反 ODR 的结果:在您的程序中的某个地方,您对socketeering::Socket有不同的定义,其中thisfrom之间的偏移量是24(它不是18字节,而是0x18字节0

通常,这种 ODR 违规来自在代码的不同部分使用不同的#defines,例如

class Socket {
#if defined(TRACING_ON)
char trace_buf[24];
#endif
sockaddr_storage from;
};

使用-DTRACING_ON在一个.cc文件中编译上面的结构,编译另一个没有它的.cc,将它们链接成一个二进制文件和 BOOM:您可能会准确地看到您所描述的错误。

有时,问题来自没有重新编译所有代码(例如,您可能有一个旧对象或共享库)。

它也可能来自将不同编译器构建的代码链接在一起,尽管这种情况很少见(通常如果编译器不兼容 ABI,它们会使用不同的名称重整来阻止程序链接)。

注意:如果Socket继承自其他类,则差异可能来自超类,而不是Socket本身。