Select 使用不當引起的core,你應該知道的

排查一個死機問題,搞了好幾天時間,最終肯定緣由;最終肯定問題緣由,在此分享一下;

第一步:常規根據core文件查看棧信息,gdb –c core xxxx

以下rip不正確,指令地址錯亂,棧信息已破壞;在此基礎上準肯定位很是困難,可是仍可發現一些線索;html

screenshot

根據當前棧信息,大概尋找到懷疑的函數數組

screenshot

查看整個棧上下信息,看有無懷疑的函數:併發

screenshot

因此頗有可能就是fetchNSAddrEv函數致使,須要重點關注;
更深刻的細節,限於彙編不深刻,比較難分析,不過能夠有另外途徑;socket

第二步:由於core是能復現出來,因此思路是從新編譯版本,增長編譯選項-fstack-protector

-fstack-protector:
啓用堆棧保護,不過只爲局部變量中含有 char 數組的函數插入保護代碼。
-fstack-protector-all:
啓用堆棧保護,爲全部函數插入保護代碼。
詳細參見:http://www.cnblogs.com/napoleon_liu/archive/2011/02/14/1953983.html
復現後,棧結構以下:函數

screenshot

這個棧結構,看着比較爽了; AsyncConnect返回時stackcheck檢查棧溢出了;測試

第三步、審查代碼,因此重點排查該函數:

先貼下代碼fetch

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\n",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;
        }
}

幾我的審查代碼,看了很久,沒有發現該函數有什麼問題;3d

第四步:檢查日誌及系統配置

進步查看日誌發現有大量的select超時的打印,並且core掉時,必然在超時事件以後,此時懷疑select調用是否會出問題;所以,首先修改select爲epoll調用進行測試,問題不能復現了;日誌

5、如此,加深懷疑slecect相關處理

@福巴找到內核代碼查看select相關實現;
screenshot
當n值超過1024上限就會致使設置到數組以外,篡改掉內存;code

FD_SET(m_nSockFD, &rset); 在m_nSockFD超過1024時,會致使rset數組越界,篡改後續內存;這也佐證了爲何select不少超時錯誤,由於m_nSockFD大小越界,沒有落在select監聽的套接字集合內;
從OSS環境上看,壓測時,有大量鏈接併發處理,因此在壓測時才最終發現該問題;

6、總結:

全部使用select系統調用的代碼應提升警戒,系統使用的套接字超過默認配置的(1024,看系統配置)會致使這個潛在問題;

相關文章
相關標籤/搜索