zoukankan      html  css  js  c++  java
  • Select 使用不当引发的core,你应该知道的

    排查一个死机问题,搞了好几天时间,最终确定原因;最终确定问题原因,在此分享一下;

    第一步:常规根据core文件查看栈信息,gdb –c core xxxx

    如下rip不正确,指令地址错乱,栈信息已破坏;在此基础上准确定位非常困难,但是仍可发现一些线索;

    screenshot

    根据当前栈信息,大概寻找到怀疑的函数

    screenshot

    查看整个栈上下信息,看有无怀疑的函数:

    screenshot

    所以很有可能就是fetchNSAddrEv函数导致,需要重点关注;
    更深入的细节,限于汇编不深入,比较难分析,不过可以有另外途径;

    第二步:因为core是能复现出来,所以思路是重新编译版本,增加编译选项-fstack-protector

    -fstack-protector:
    启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。
    -fstack-protector-all:
    启用堆栈保护,为所有函数插入保护代码。
    详细参见:http://www.cnblogs.com/napoleon_liu/archive/2011/02/14/1953983.html
    复现后,栈结构如下:

    screenshot

    这个栈结构,看着比较爽了; AsyncConnect返回时stackcheck检查栈溢出了;

    第三步、审查代码,所以重点排查该函数:

    先贴下代码

    int CHttpHandler::AsyncConnect(const char * pszIpAddr, unsigned short usPort, float fSelectTimeout, const std::string& host)
    {
    	if (strlen(pszIpAddr) > 16)
    	{
    		SetErrMsg("invalid ip format, pszIpAddr=%s", pszIpAddr);
    		return -1;
    	}
    	m_nSockFD= socket(AF_INET, SOCK_STREAM, 0);
    	m_fSelectTimeout= fSelectTimeout;
    	if (m_nSockFD < 0)
    	{
    		SetErrMsg("init socket error, m_nSockFD=%d", m_nSockFD);
    		return -2;
    	}	
    	// set nonblock
    	int flags = fcntl(m_nSockFD, F_GETFL, 0);
    	if (fcntl(m_nSockFD, F_SETFL, flags | O_NONBLOCK) < 0)
    	{
    		close(m_nSockFD);
    		m_nSockFD = -1;		
    		SetErrMsg("set sock nonblock error");
    		return -3;
    	}
    	// set server ip, port
    	struct sockaddr_in serv_addr;
    	socklen_t addr_len;	
    	bzero(&serv_addr, sizeof(serv_addr));
    	serv_addr.sin_family = AF_INET;
    	inet_aton(pszIpAddr, &serv_addr.sin_addr);
    	serv_addr.sin_port = htons(usPort);
    	addr_len = sizeof(serv_addr);
    	//memset(m_szIPAddr, 0, 17);
    	memset(m_szIPAddr,0,sizeof(m_szIPAddr));
    	if(!host.empty())
    	{
    		if(host.length()>sizeof(m_szIPAddr)-1)
    		{
    			SetErrMsg("hostName too long ,hostName=%s,hostLen=%d,max_len=%d
    ",host.c_str(),host.length(),sizeof(m_szIPAddr));
    			return -9; 
    		}
    		memcpy(m_szIPAddr, host.c_str(), host.length());
    	}
    	else
    	{
    		memcpy(m_szIPAddr, pszIpAddr, strlen(pszIpAddr));
    	}
    	/*linger   m_sLinger;
    	m_sLinger.l_onoff   =   1;
    	m_sLinger.l_linger   =   0;
    	setsockopt(m_nSockFD, SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));*/
    	// connect
    	int nRetCode;
    	nRetCode = connect(m_nSockFD, (struct sockaddr *)&serv_addr, addr_len);
    	if (nRetCode < 0)
    	{
    		if (errno != EINPROGRESS)
    		{
    			SetErrMsg("connect error, errno=%d", errno);
    			close(m_nSockFD);
    			m_nSockFD = -1;
    			
    			return -4;
    		}
    	}
    	if (nRetCode == 0)
    	{
    		;
    	}
    	else
    	{
    		//warning  oss find select cause crash while select timeout
    		fd_set rset, wset;
    		FD_ZERO(&rset);
    		FD_SET(m_nSockFD, &rset);
    		wset = rset;
    		struct timeval tv;
    		tv.tv_sec = (int)m_fSelectTimeout;
    		long usec = int ((m_fSelectTimeout - (int)m_fSelectTimeout)*1000000 );
    		tv.tv_usec = usec;
    		nRetCode = select(m_nSockFD + 1, NULL, &wset, NULL, &tv);
    		if (nRetCode == 0)
    		{
    			// timeout
    			SetErrMsg("connect error: select timeout, select retcode=%d ", nRetCode);			
    			close(m_nSockFD);
    			m_nSockFD = -1;
    			return -5;
    		}
    		if ( FD_ISSET(m_nSockFD, &wset))//FD_ISSET(m_nSockFD, &rset) ||
    		{
    			int error;
    			socklen_t len = sizeof(error);
    			if (getsockopt(m_nSockFD, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
    			{
    				SetErrMsg("connect error: getsockopt");
    				close(m_nSockFD);
    				m_nSockFD = -1;
    				return -6;
    			}
    			if (error)
    			{
    				SetErrMsg("connect error in select operation, error=%d", error);
    				close(m_nSockFD);
    				m_nSockFD = -1;
    				return -7;
    			}
    		}
    		else
    		{
    			SetErrMsg("connect error");
    			close(m_nSockFD);
    			m_nSockFD = -1;
    			return -8;
    		}
    }
    

    几个人审查代码,看了好久,没有发现该函数有什么问题;

    第四步:检查日志及系统配置

    进步查看日志发现有大量的select超时的打印,而且core掉时,必然在超时事件之后,此时怀疑select调用是否会出问题;因此,首先修改select为epoll调用进行测试,问题不能复现了;

    五、如此,加深怀疑slecect相关处理

    @福巴找到内核代码查看select相关实现;
    screenshot
    当n值超过1024上限就会导致设置到数组之外,篡改掉内存;

    FD_SET(m_nSockFD, &rset); 在m_nSockFD超过1024时,会导致rset数组越界,篡改后续内存;这也佐证了为什么select很多超时错误,因为m_nSockFD大小越界,没有落在select监听的套接字集合内;
    从OSS环境上看,压测时,有大量连接并发处理,所以在压测时才最终发现该问题;

    六、总结:

    所有使用select系统调用的代码应提高警惕,系统使用的套接字超过默认配置的(1024,看系统配置)会导致这个潜在问题;

  • 相关阅读:
    centos中安装docker
    docker es
    Linux 定时备份数据库
    Linux 防火墙firewalld
    Linux Systemd
    Linux at定时任务
    Linux运行级别
    原来这就是网络
    LeetCode-897-递增顺序搜索树
    SSM整合配置文件
  • 原文地址:https://www.cnblogs.com/happyliu/p/9463887.html
Copyright © 2011-2022 走看看