簡體   English   中英

在 ubuntu 22.04、libc 2.35 上重載新/刪除運算符並調用 SDL2 SDL_CreateRenderer 導致分段錯誤

[英]Overloading new/delete operators on ubuntu 22.04, libc 2.35 and calling SDL2 SDL_CreateRenderer causes segmentation fault

我在 linux (ubuntu) 中有自己的內存管理器,以便檢測我的 c/c++ 代碼中的內存泄漏或內存損壞。

要使用我自己的內存管理器,我已經重載了這樣的新/刪除全局運算符,

void *operator new (size_t _size){
      return malloc(_size);
}

void operator delete(void *_pointer){
   free(_pointer);
}

代碼 1.1

但是為了添加一些指針信息,我為頭元數據保留了額外的內存,例如,在這個結構中保存分配器類型和分配的大小,

typedef struct{
    size_t size;
   ALLOCATOR_TYPE allocator_type
}PointerHeaderInformation;

其中 ALLOCATOR_TYPE 是,

typedef enum{
    UNKNOWN_ALLOCATOR=0,
    NEW_ALLOCATOR,
    NEW_ALLOCATOR_WITH_BRACETS,
    MAX_ALLOCATE_TYPES
}ALLOCATOR_TYPE;

代碼 1.2

所以我修改了代碼 1.1 中看到的新運算符來分配指針和設置頭信息,正如我們在下面的代碼中看到的那樣,

void *operator new(size_t _size){
    // set size for request _size and header information
    size_t size=sizeof(HeaderPointerInformation)+_size;
    
    // here the pointer allocation
    char *pointer=(char *)malloc(size);
    if(pointer==NULL){
       throw std::bad_alloc();
    }
    // setup header information
    ((HeaderPointerInformation *)(pointer))->type_allocator=NEW_ALLOCATOR;
    ((HeaderPointerInformation *)(pointer))->size=_size;

    // returns the pointer + sizeof(HeaderPointerInformation)
    return pointer+sizeof(HeaderPointerInformation);
}

代碼 1.3

正如我們在代碼 1.3 中看到的,它返回分配的指針 + sizeof(HeaderPointerInformation) 以不覆蓋標頭信息。

另一方面,當我們想要使用自定義分配器釋放分配的指針時,它必須首先獲取標頭指針,檢查分配器的類型並使用free函數釋放指針。 例如對於重載的delete運算符,

void  operator  delete(void  *pointer) noexcept{

    // check NULL
    if(pointer == NULL){
        return;
    }

    // get header information
    HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-SIZEOF_ALIGNED_HEADER);

    // check type allocator
    if(header->type_allocator != NEW_ALLOCATOR){
        fprintf(stderr,"pointer was not allocated as 'new' allocator\n");
    }

    // header is the base of allocated pointer so it's freed
    free(header);
}

代碼 1.4

當我嘗試使用 SDL_CreateRenderer 創建渲染器時,我正在嘗試與 SDL2 和重載的新/刪除運算符結合使用時發生的異常分段錯誤。 SDL_CreateRenderer 執行一系列調用,直到llvm::Regex::Regex() 中的分段錯誤發生之前最后已知的第 70 行 dl-init.c,如下圖所示。

在此處輸入圖像描述

我以前的 ubuntu 發行版、22.04、18.04 等從來沒有遇到過任何問題,但是在最近的 ubuntu 發行版 22.04 中,它帶有 libc 2.35,所以 libc 中的某些內容發生了變化。

這個分段錯誤與重載運算符 new/delete 運算符有關,因為如果我不使用重載運算符,問題就消失了。

我創建了一個非常簡單的代碼來產生這個分段錯誤,

#include    <SDL2/SDL.h>
#include    <stdlib.h>
#include    <stdio.h>
#include    <stddef.h>
#include    <new>
#include    <cstddef>

// type of allocators
typedef enum{
    UNKNOWN_ALLOCATE=0,
    NEW_ALLOCATOR,
    NEW_ALLOCATOR_WITH_BRACETS,
    MAX_ALLOCATE_TYPES
}ALLOCATOR_TYPE;

// metadata information included in the pointer allocated
typedef  struct{
    char    type_allocator;
    int     size;
}HeaderPointerInformation;

void *newPointer(size_t _size, ALLOCATOR_TYPE _allocator_type){
    size_t size=sizeof(HeaderPointerInformation)+_size;
    char *pointer=(char *)malloc(size);
    if(pointer==NULL){
        throw std::bad_alloc();
    }

    ((HeaderPointerInformation *)(pointer))->type_allocator=_allocator_type;
    ((HeaderPointerInformation *)(pointer))->size=size;

    // returns the pointer base (allocated pointer with offset of metadata information)
    return pointer+sizeof(HeaderPointerInformation);
}

void*  operator  new(size_t  _size) {
    return newPointer(_size,NEW_ALLOCATOR);
}
//--------------------------------------------------------------------------------------------
void*  operator  new[](size_t  _size) {
    return  newPointer(_size,NEW_ALLOCATOR_WITH_BRACETS);
}
//--------------------------------------------------------------------------------------------
void  operator  delete(void  *pointer) noexcept{

    // return if pointer==NULL
    if(pointer == NULL){
       return;
    }

    // get header information
    HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-sizeof(HeaderPointerInformation));

    // check type allocator
    if(header->type_allocator != NEW_ALLOCATOR){
        fprintf(stderr,"pointer was not allocated as 'new' allocator\n");
    }

    // header is the base of allocated pointer so it's freed
    free(header);
}
//--------------------------------------------------------------------------------------------
void  operator  delete[](void  *pointer)  noexcept{

    // return if pointer==NULL
    if(pointer == NULL){
       return;
    }

    // get header information
    HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-sizeof(HeaderPointerInformation));

    // check type allocator
    if(header->type_allocator != NEW_ALLOCATOR_WITH_BRACETS){
        fprintf(stderr,"pointer was not allocated as 'new []' allocator\n");
    }

    // header is the base of allocated pointer so it's freed
    free(header);
}

int main(int argc, char *argv[]){

    printf(
        "align(std::max_align_t):%i\n"
        "sizeof(HeaderPointerInformation):%i\n"
        ,(int)(alignof(std::max_align_t))
        ,(int)(sizeof(HeaderPointerInformation))
   );

    //--------------------------------------------------
    // BEGIN: test that overloaded new/delete works as expected
    int *i=new int;
    int *ii=new int[2];

    delete i;
    delete [] ii;
    // END: test that overloaded new/delete works as expected
    //--------------------------------------------------

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr,"Unable to init video subsystem: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_Window *window = SDL_CreateWindow("test",50,50,100,100,0);

    if (!window) {
        fprintf(stderr,"Unable to create window: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    // Calling SDL_CreateRenderer it causes segmentation fault at llvm::Regex::Regex
    SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_WINDOW_SHOWN | SDL_RENDERER_ACCELERATED);

    if (!renderer) {
        fprintf(stderr,"Unable to create renderer: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    if(renderer != NULL){
        SDL_DestroyRenderer(renderer);
    }

    if(window != NULL){
        SDL_DestroyWindow(window);
    }
}

代碼 1.5

正如我們在代碼 1.5 中看到的,首先是 delete/new 運算符實現,然后在主代碼中它使用重載運算符 new/delete 執行測試以檢查其功能,剩下的只是 SDL2 初始化。

編譯就像下面一樣簡單,

g++ main.cpp -Ipath_include_sdl2 -Lpath_lib_sdl2 -lSDL2 -lm -o main

注意:如有必要,您必須提供path_include_sdl2path_lib_sdl2


為什么不像一些評論所建議的那樣使用 gnu 編譯器本身附帶的地址清理器(又名 ASAN)工具?

我還不知道我是否會使用 ASAN 進行切換,但原則上我不會,因為我的代碼也是通過 mingw 在 Windows 上編譯的,而 AFAIK mingw ASAN 尚未實現。


編輯關於 user17732522(評論)我有更改代碼 1.5,

  • 固定點 2,所以在重載的 new 和 new[] 運算符上,如果執行malloc后指針的結果為 NULL,我添加了條件以拋出 sdt::bad_alloc,

void *newPointer(size_t _size, ALLOCATOR_TYPE _allocator_type){

    ...

    char *pointer=(char *)malloc(size);
    if(pointer==NULL){
        throw std::bad_alloc();
    }

    ...
}

  • 固定點 5,因此在重載的 delete 和 delete[] 運算符上,它已將noexcept(true)更改為noexcept

    void  operator  delete(void  *pointer) noexcept{
      ...
    }
            
    void  operator  delete[](void  *pointer)  noexcept{
      ...
    }

  • 固定點6,把free((void *)header)改成了free(header)

  • 固定點 7,在重載函數delete上添加了如果傳遞的指針為 NULL 則返回的條件


    void  operator  delete(void  *pointer) noexcept{
           // check NULL
           if(pointer == NULL){
               return;
           }
           ...
    }

    void  operator  delete[](void  *pointer) noexcept{
           // check NULL
           if(pointer == NULL){
               return;
           }
           ...
    }
            
    void  operator  delete[](void  *pointer)  noexcept{
      ...
    }

多個問題:

  1. 您不確保從operator new替換返回的指針至少與__STDCPP_DEFAULT_NEW_ALIGNMENT__一樣嚴格對齊,此重載必須始終保證這一點。 即使malloc保證返回適當對齊的內存,您的偏移量也可能會更改您返回的指針的對齊方式。 但是malloc也可能沒有充分對齊。 malloc保證至少與alignof(std::max_align_t)對齊,它可能小於__STDCPP_DEFAULT_NEW_ALIGNMENT__

  2. 您正在替換的operator new重載不得返回空指針。 您需要檢查malloc的結果,如果它是空指針,您必須拋出std::bad_alloc 因為您正在取消引用您的實現中的指針,這更直接重要。

不太嚴重的問題:

  1. 您訪問標頭結構的方式是別名違規。 您的標頭結構是一種隱式生命周期類型,因此您不一定需要顯式地對其進行new ,但是您需要std::launder指針才能訪問它。 這對於operator new來說已經足夠了,但是我認為沒有任何嚴格符合標准的方法來實現operator delete以便能夠訪問標頭,所以我想你需要依賴編譯器在這里表現得“合理”反正。

  2. 如果您為沒有對齊參數的重載替換operator newoperator delete ,您可能還想用std::align_val_t參數替換它們,以防程序分配的內存對齊要求比__STDCPP_DEFAULT_NEW_ALIGNMENT__更嚴格。

  3. 釋放函數上的noexcept(true)是多余的。 默認情況下它們是noexcept(true) 這也被規范地寫成noexcept ,而不是noexcept(true)

  4. free((void void* free((void *)header);中的 void* 是多余的。 它隱含地工作(並且您在operator new的返回語句中使用它)。

(編輯)其他嚴重問題:

  1. 您在此處替換的operator delete重載必須接受空指針值作為參數。 您當前的實現沒有,因為它立即對指針進行指針運算。 在嘗試訪問標頭之前,您需要檢查空指針並在這種情況下從函數返回。

同樣在您嘗試做的事情的上下文中:已經有提供您嘗試實現的功能的工具,例如 ASAN 和 valgrind。

正如 user17732522 指出的那樣,返回的指針必須在塊中正確對齊,因為alignof(std::max_align_t)在我的機器中是16

在重載 operator new 的情況下,在通過malloc分配指針(返回完美且對齊的指針)后,我返回一個指針作為指針基數,偏移量為sizeof(HeaderPointerInformation) ,如果為8 ,則它返回一個未對齊的指針

因此,要解決此問題,必須計算一個有效的偏移量轉換sizeof(HeaderPointerInformation)作為alignof(std::max_align_t)的倍數,就像它描述的以下行一樣,

#define SIZEOF_ALIGNED_HEADER       (sizeof(HeaderPointerInformation)/BLOCK_ALIGNMENT+1)*BLOCK_ALIGNMENT

BLOCK_ALIGMENT 確實定義為,

#define BLOCK_ALIGNMENT             alignof(std::max_align_t)

最后,我展示了重載函數返回/獲取具有正確偏移量的指針的修改代碼 1.5,


#define BLOCK_ALIGNMENT             alignof(std::max_align_t)
#define SIZEOF_ALIGNED_HEADER       (sizeof(HeaderPointerInformation)/BLOCK_ALIGNMENT+1)*BLOCK_ALIGNMENT


void *newPointer(size_t _size, ALLOCATOR_TYPE _allocator_type){
    size_t size=SIZEOF_ALIGNED_HEADER+_size;

    char *pointer=(char *)malloc(size);

    ...

    // returns the pointer base (allocated pointer with offset of metadata information)
    return pointer+SIZEOF_ALIGNED_HEADER;
}


void  operator  delete(void  *pointer) noexcept{

    ...

    // get header information
    HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-SIZEOF_ALIGNED_HEADER);

    ...
    
    // header is the base of allocated pointer so it's freed
    free(header);
}

void  operator  delete[](void  *pointer)  noexcept{

    ...

    // get header information
    HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-SIZEOF_ALIGNED_HEADER);
        
    ...
    
    // header is the base of allocated pointer so it's freed
    free(header);
}


暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM