今天遇到一個由於內存訪問越界而引起的bug

  今天對前幾天的程序進行了一些修改, 運行時程序出現段錯誤, 用gdb調試一下, 通過查看調用堆棧,得知段錯誤是由於調用了一個隊列的成員函數 isQueueEmpty 引起的, isQueueEmpty函數的作用是用來判斷隊列是否爲空的, 看了一下isQueueEmpty 的定義,發現isQueueEmpty 函數體只有一條語句:

  template <class Type>
  bool TQueue<Type>::isQueueEmpty()
  {
      return (m_pQueueHead == NULL);
  }

  怎麼看這條語句都不像會出現段錯誤呀?難道是gdb誤報?趕緊把函數體改下以下這樣,打印一些信息看看:

  template <class Type>
  bool TQueue<Type>::isQueueEmpty()
  {
      printf( "start isQueueEmpty()");
      printf( "m_pQueueHead = %p/n", m_pQueueHead  );
      if (m_pQueueHead == NULL) {
            printf( "end isQueueEmpty() 1");
            return true;
      }
      printf( "end isQueueEmpty() 2");
      return false;
  }

  再次編譯運行後發現,程序只打印了start isQueueEmpty()就over了...  也就是說,只要訪問m_pQueueHead,就會引起段錯誤,可是, m_pQueueHead雖然是一個指針變量,就算所指向的內容有誤,但這裏並沒有引用指針所指向的內容呀...
再次運行單元測試程序,重點測試一下TQueue<Type>模板類,沒有發現問題...

  之前可沒有見過這個錯誤,可能是今天新增的代碼引起的,懷着試一試的想法,根據程序的運行路徑,用排除法一步一步地註釋着今天新增的代碼,最終發現,只要調用了一個名叫getIPAddress的函數,isQueueEmpty()函數的調用就會引起段錯誤。

  getIPAddress的函數用來取得本機的IP地址,這個函數是項目公共庫裏面的一個函數,是上一個版本就留下來的,所以拿過來用時沒有懷疑過它的正確性,它的函數聲明很簡單:
  int getIPAddress(const char* ifname, char* ipaddr);
  參數ipaddr用來傳入存放IP地址的緩衝區,奇怪的是沒有要求傳入緩衝區的大小,於是打開公共庫的源文件,定位到getIPAddress函數的定義,找到了兩行可疑的代碼:

  int getIPAddress(const char* ifname, char* ipaddr)
  {
      ...
      memset(ipaddr, 0, IPADDR_MAXLEN);  //它假設了ipaddr的大小爲IPADDR_MAXLEN
      strncpy( ipaddr, inet_ntoa(addr.sin_addr), IPADDR_MAXLEN-1);
      ...
  }

  看了一個IPADDR_MAXLEN定義的大小,嚇着了,是512
  而我定義ipaddr的時候只定義了50了字節,當然造成越界了:

  char ipaddr[50];   //我認爲50個字節存放一個IP地址已經是足夠有餘了
  getIPAddress( "eth0", ipaddr );

  而在以前的項目上,使用此公共庫的這個函數沒有出錯的原因,可能公共庫的設計者和使用者是同一個人,所以隱藏了這個函數界面的設計錯誤,例如下面這樣調用就不會引起越界:

  char ipaddr[IPADDR_MAXLEN];
  getIPAddress( "eth0", ipaddr);

  把代碼改爲ipaddr定義IPADDR_MAXLEN個字節,重新編譯和運行程序,段錯誤消失了,真相大白,是由於內存越界引起了程序的其它地方報錯。

  最後,getIPAddress的函數界面應該設計成這樣(其它類似的接口函數都一樣),或者在註釋中寫明,必須按ipaddr[IPADDR_MAXLEN];的形式去定義ipaddr,新的函數聲明如下:
        int getIPAddress(const char* ifname, char* ipaddr, size_t nDestMaxSize );    //增加一個參數,指示緩衝區的最大長度
        而函數內部改爲:
        int getIPAddress(const char* ifname, char* ipaddr, size_t nDestMaxSize ); 
        {
               ...
           memset(ipaddr, 0, nDestMaxSize );  //它假設了ipaddr的大小爲IPADDR_MAXLEN
         strncpy( ipaddr, inet_ntoa(addr.sin_addr), nDestMaxSize -1);
               ...
       }

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章