2015-07-05 81 views
1

在過去的幾個月中,我一直在忙着調試在一個非常大的專有C++圖像處理庫中的某處發生的罕見崩潰,該庫使用針對ARM Cortex-A9 Linux目標的GCC 4.7.2進行編譯。由於一個常見的症狀是glibc抱怨堆腐敗,第一步是使用堆腐敗檢查器來捕獲oob內存寫入。我使用https://stackoverflow.com/a/17850402/3779334中描述的技術將所有調用free/malloc轉移到我自己的函數中,使用一定數量的已知數據填充每個已分配的內存塊來捕獲越界寫入 - 但是什麼都沒找到,即使使用as在每個分配的塊之前和之後大約爲1 KB(由於大量使用STL容器,因此分配了數十萬個塊,因此我無法進一步擴大填充範圍,再加上我認爲任何寫入超出1KB的塊都會超出範圍無論如何最終會觸發段錯誤)。這個界限檢查器在過去發現了其他問題,所以我不懷疑它的功能。什麼可能會導致互斥錯誤?

(有人說「Valgrind的」之前,是的,我已經試過太沒有結果無論是。)

現在,我的內存邊界檢查還有一個特點,它預先考慮與數據結構每一個分配的塊。這些結構都鏈接在一個長鏈表中,以便我偶爾遍歷所有分配和測試內存完整性。由於某些原因,即使此列表的所有操作都受到互斥鎖保護,列表也會被破壞。在調查這個問題時,似乎互斥體本身偶爾無法完成其工作。這裏是僞代碼:

pthread_mutex_t alloc_mutex; 
static bool boolmutex; // set to false during init. volatile has no effect. 

void malloc_wrapper() { 
    // ... 
    pthread_mutex_lock(&alloc_mutex); 
    if (boolmutex) { 
    printf("mutex misbehaving\n"); 
    __THROW_ERROR__; // this happens! 
    } 
    boolmutex = true; 
    // manipulate linked list here 
    boolmutex = false; 
    pthread_mutex_unlock(&alloc_mutex); 
    // ... 
} 

代碼評論「發生這種情況!」偶爾會達到,儘管這似乎是不可能的。我的第一個理論是互斥數據結構被覆蓋。我把互斥體放在一個結構體中,在它之前和之後有大的數組,但是當這個問題發生時,數組沒有任何變化,所以似乎沒有任何東西被覆蓋。

那麼..什麼樣的腐敗可能會導致這種情況發生,我如何找到並解決問題的原因?

還有一些筆記。測試程序使用3-4個線程進行處理。使用較少的線程運行似乎使腐敗不那麼常見,但不會消失。測試每次運行約20秒,並在絕大多數情況下成功完成(我可以有10個單元重複測試,第一次故障發生在5分鐘到幾個小時後)。當問題發生時,測試時間很晚(比如15秒),所以這不是一個不好的初始化問題。內存邊界檢查器從來沒有捕獲到實際的越界寫入,但glibc仍然偶爾會因損壞的堆錯誤而失敗(這種錯誤是否可以由oob寫入以外的其他情況引起?)。每次失敗都會生成一個包含大量跟蹤信息的核心轉儲;在這些轉儲中我沒有看到任何模式,沒有顯示比其他代碼更多的特定代碼段。這個問題似乎非常特定於一個特定的算法家族,並且在其他算法中不會發生,所以我很確定這不是一個零星的硬件或內存錯誤。我已經做了很多更多的測試來檢查oob堆的訪問,我不想列出這些文件來阻止這篇文章再次發佈。

在此先感謝您的幫助!

+3

似乎極不可能我們可以幫你在這裏。在這種情況下,你似乎完成了你應該做的大部分工作,除了把它縮小到一個更小的程序。而且,是的,我知道在這種情況下這很困難且耗時。但是,不,這不會讓它變得更不重要。祝你好運! –

+0

可能想使用此代碼嘗試Vagrind,並查看它是否顯示任何相關的內容 –

+1

您可以在平臺上使用gcc 4.8並嘗試使用AddressSanitizer(https://code.google.com/p/address-sanitizer/wiki/AddressSanitizer)嗎?閱讀https://en.wikipedia.org/wiki/AddressSanitizer。 –

回答

0

感謝所有評論者。當我最終決定編寫一個簡單的內存分配壓力測試 - 一個可以在每個CPU內核上運行線程的單元(我的單元是飛思卡爾i.MX6四核SoC)時,我嘗試了幾乎所有的建議,但都沒有結果。每個都以高速隨機順序分配和釋放內存。測試在幾分鐘或最多幾個小時內崩潰,出現glibc內存損壞錯誤。

將內核從3.0.35更新到3.0.101解決了問題;壓力測試和圖像處理算法現在都可以在一夜之間運行而不會失敗。該問題不會在具有相同內核版本的英特爾機器上重現,因此該問題通常針對ARM,或者可能包含與包含內核3.0.35的特定BSP版本一起提供的某些修補程序。

對於那些好奇的人,附上的是壓力測試的源代碼。設置NUM_THREADS到CPU核心數量,並與建設:

<cross-compiler-prefix>g++ -O3 test_heap.cpp -lpthread -o test_heap

我希望這個信息可以幫助別人。歡呼:)

// Multithreaded heap stress test. By Itay Chamiel 20151012. 

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <assert.h> 
#include <pthread.h> 
#include <sys/time.h> 

#define NUM_THREADS 4 // set to number of CPU cores 

#define ALIVE_INDICATOR NUM_THREADS 

// Each thread constantly allocates and frees memory. In each iteration of the infinite loop, decide at random whether to 
// allocate or free a block of memory. A list of 500-1000 allocated blocks is maintained by each thread. When memory is allocated 
// it is added to this list; when freeing, a random block is selected from this list, freed and removed from the list. 
void* thr(void* arg) { 
    int* alive_flag = (int*)arg; 
    int thread_id = *alive_flag; // this is a number between 0 and (NUM_THREADS-1) given by main() 
    int cnt = 0; 
    timeval t_pre, t_post; 
    gettimeofday(&t_pre, NULL); 

    const int ALLOCATE=1, FREE=0; 
    const unsigned int MINSIZE=500, MAXSIZE=1000; 
    const int MAX_ALLOC=10000; 
    char* membufs[MAXSIZE]; 
    unsigned int membufs_size = 0; 

    int num_allocs = 0, num_frees = 0; 

    while(1) 
    { 
     int action; 
     // Decide whether to allocate or free a memory block. 
     // if we have less than MINSIZE buffers, allocate. 
     if (membufs_size < MINSIZE) action = ALLOCATE; 
     // if we have MAXSIZE, free. 
     else if (membufs_size >= MAXSIZE) action = FREE; 
     // else, decide randomly. 
     else { 
      action = ((rand() & 0x1)? ALLOCATE : FREE); 
     } 

     if (action == ALLOCATE) { 
      // choose size to allocate, from 1 to MAX_ALLOC bytes 
      size_t size = (rand() % MAX_ALLOC) + 1; 
      // allocate and fill memory 
      char* buf = (char*)malloc(size); 
      memset(buf, 0x77, size); 
      // add buffer to list 
      membufs[membufs_size] = buf; 
      membufs_size++; 
      assert(membufs_size <= MAXSIZE); 
      num_allocs++; 
     } 
     else { // action == FREE 
      // choose a random buffer to free 
      size_t pos = rand() % membufs_size; 
      assert (pos < membufs_size); 
      // free and remove from list by replacing entry with last member 
      free(membufs[pos]); 
      membufs[pos] = membufs[membufs_size-1]; 
      membufs_size--; 
      assert(membufs_size >= 0); 
      num_frees++; 
     } 

     // once in 10 seconds print a status update 
     gettimeofday(&t_post, NULL); 
     if (t_post.tv_sec - t_pre.tv_sec >= 10) { 
      printf("Thread %d [%d] - %d allocs %d frees. Alloced blocks %u.\n", thread_id, cnt++, num_allocs, num_frees, membufs_size); 
      gettimeofday(&t_pre, NULL); 
     } 

     // indicate alive to main thread 
     *alive_flag = ALIVE_INDICATOR; 
    } 
    return NULL; 
} 

int main() 
{ 
    int alive_flag[NUM_THREADS]; 
    printf("Memory allocation stress test running on %d threads.\n", NUM_THREADS); 
    // start a thread for each core 
    for (int i=0; i<NUM_THREADS; i++) { 
     alive_flag[i] = i; // tell each thread its ID. 
     pthread_t th; 
     int ret = pthread_create(&th, NULL, thr, &alive_flag[i]); 
     assert(ret == 0); 
    } 

    while(1) { 
     sleep(10); 
     // check that all threads are alive 
     bool ok = true; 
     for (int i=0; i<NUM_THREADS; i++) { 
      if (alive_flag[i] != ALIVE_INDICATOR) 
      { 
       printf("Thread %d is not responding\n", i); 
       ok = false; 
      } 
     } 
     assert(ok); 
     for (int i=0; i<NUM_THREADS; i++) 
      alive_flag[i] = 0; 
    } 
    return 0; 
} 
相關問題