GRETunnelInterface类实例化时Valgrind报无效读写问题求助
我的C++多线程网络拓扑程序运行时出现随机崩溃,经Valgrind排查,问题定位到GRETunnelInterface类。该类继承链为GRETunnelInterface -> VirtualInterface -> Interface,一旦实例化此类,Valgrind会在其构造函数初始化实例变量阶段报告无效读/写错误;后续对该类实例变量的任何读写操作都会触发更多同类错误,最终导致应用崩溃。
已确认所有基类的实例变量均在对应构造函数中完成初始化,且未实例化GRETunnelInterface类时,应用无Valgrind错误。应用中每个node对应独立线程,线程间完全隔离。
类定义
class GRETunnelInterface : public VirtualInterface { private: protected: public: uint32_t tunnel_id; Interface *tunnel_src_intf; uint32_t tunnel_src_ip; uint32_t tunnel_dst_ip; uint32_t lcl_ip; uint8_t mask; VirtualPort *virtual_port_intf; enum GreTunnelConfigEnum { GRE_TUNNEL_TUNNEL_ID_SET = 1, GRE_TUNNEL_SRC_INTF_SET = 2, GRE_TUNNEL_SRC_ADDR_SET = 4, GRE_TUNNEL_DST_ADDR_SET = 8, GRE_TUNNEL_OVLAY_IP_SET = 16, GRE_TUNNEL_ADMIN_SHUT_SET = 32 }; uint16_t config_flags; GRETunnelInterface(uint32_t tunnel_id); virtual ~GRETunnelInterface(); ... other methods.... }
Valgrind错误日志片段
==906104== Invalid write of size 2 ==906104== at 0x144F68: GRETunnelInterface::GRETunnelInterface(unsigned int) (Interface.cpp:961) ==906104== by 0x129007: gre_tunnel_create(node_*, unsigned short) (gre.cpp:39) ==906104== by 0x1287F7: gre_tunnel_config_handler(int, stack*, op_mode) (grecli.cpp:56) ==906104== Invalid read of size 2 ==906104== at 0x144F70: GRETunnelInterface::GRETunnelInterface(unsigned int) (Interface.cpp:962) ==906104== by 0x129007: gre_tunnel_create(node_*, unsigned short) (gre.cpp:39) ==906104== by 0x1287F7: gre_tunnel_config_handler(int, stack*, op_mode) (grecli.cpp:56)
构造函数实现
GRETunnelInterface::GRETunnelInterface(uint32_t tunnel_id) : VirtualInterface(std::string("tunnel") + std::to_string(tunnel_id), INTF_TYPE_GRE_TUNNEL) { this->tunnel_id = tunnel_id; this->config_flags = 0; this->config_flags |= GRE_TUNNEL_TUNNEL_ID_SET; this->tunnel_src_intf = NULL; this->tunnel_src_ip = 0; this->tunnel_dst_ip = 0; this->lcl_ip = 0; this->mask = 0; this->virtual_port_intf = NULL; } VirtualInterface::VirtualInterface(std::string ifname, InterfaceType_t iftype) : Interface(ifname, iftype) { } Interface::Interface(std::string if_name, InterfaceType_t iftype) { this->if_name = if_name; this->iftype = iftype; this->config_ref_count = 0; this->dynamic_ref_count = 0; this->att_node = NULL; memset(&this->log_info, 0, sizeof(this->log_info)); this->link = NULL; this->is_up = true; this->ifindex = get_new_ifindex(); this->cost = INTF_METRIC_DEFAULT; this->pkt_recv = 0; this->pkt_sent = 0; this->xmit_pkt_dropped = 0; this->recvd_pkt_dropped = 0; this->l2_egress_acc_lst = NULL; this->l2_ingress_acc_lst = NULL; this->l3_ingress_acc_lst2 = NULL; this->l3_egress_acc_lst2 = NULL; this->isis_intf_info = NULL; }
构造函数调用逻辑
bool gre_tunnel_create (node_t *node, uint16_t tunnel_id) { Interface *tunnel; byte intf_name[IF_NAME_SIZE]; snprintf ((char *)intf_name, IF_NAME_SIZE, "tunnel%d", tunnel_id); tunnel = node_get_intf_by_name(node, (const char *)intf_name); if (tunnel) { return false; } int empty_intf_slot = node_get_intf_available_slot(node); if (empty_intf_slot < 0) { cprintf ("Error : No NIC slot available in a device\n"); return false; } tunnel = new GRETunnelInterface(tunnel_id); if (!tunnel ) { cprintf ("Error : GRE Tunnel creation failed\n"); return false; } node->intf[empty_intf_slot] = tunnel; tunnel->att_node = node; tunnel->InterfaceLockStatic(); return true; }
编译环境
asagar@lima-default:~/tcpip_stack$ g++ --version g++ (Ubuntu 13.2.0-23ubuntu4) 13.2.0
可能的原因分析
基类内存越界写入:重点检查
Interface构造函数中的memset(&this->log_info, 0, sizeof(this->log_info))。如果log_info是在子类中被扩展的结构体,基类中sizeof(log_info)仅计算基类部分的大小,memset会覆盖超出基类范围的内存,恰好破坏GRETunnelInterface的成员(比如config_flags),导致后续读写该成员时触发Valgrind错误。类成员布局对齐异常:64位系统下,
GRETunnelInterface的uint8_t mask(1字节)后会有7字节的填充以对齐后续的指针成员。若编译器内存布局计算异常,或基类成员布局导致子类成员偏移错误,会使config_flags的读写访问到非法内存区域。node->intf数组越界:验证node_get_intf_available_slot的返回值是否严格小于node->intf数组的实际长度。若返回索引超出数组范围,node->intf[empty_intf_slot] = tunnel会写入数组外内存,可能破坏GRETunnelInterface实例的内存空间,或被其他内存操作反向破坏。全局资源线程安全问题:
Interface构造函数中调用的get_new_ifindex()若依赖全局变量自增,即使线程隔离,未正确同步的全局变量可能导致ifindex值异常,间接引发内存操作错误(如ifindex用作数组索引时越界)。虚析构函数缺失:
VirtualInterface和Interface未声明虚析构函数,仅GRETunnelInterface有虚析构。虽当前错误出现在构造阶段,但不完整的虚析构声明可能导致编译器生成的继承结构内存布局异常,间接引发成员访问错误。
内容的提问来源于stack exchange,提问作者Abhishek Sagar

