rdebug.c

/*
 *
 *  RLDebugger - Resource Leakage Debugger
 *  Autor: Tomasz Jaworski, 2018-2019
 *
 */

#define _RLDEBUG_IMPLEMENTATION_
#define _RLDEBUG_API_

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

#include <errno.h>
#include <time.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>


#include "rdebug.h"


#define RLD_MAGIC1          0xc8fccdb505c6ac13  
#define RLD_MAGIC2          0xece706d3d0533953

#define ALIGN(n, __type)    (n & ~(sizeof(__type)-1)) + sizeof(__type)*!!(n & (sizeof(__type)-1))
#define MIN(__x, __y) ((__x) < (__y) ? (__x) : (__y))
#define MAX(__x, __y) ((__x) > (__y) ? (__x) : (__y))

#define IS_ALLOCATION_FUNCTION_CODE(__code) ((__code) == HFC_CALLOC || (__code) == HFC_MALLOC || (__code) == HFC_REALLOC || (__code) == HFC_STRDUP || (__code) == HFC_STRNDUP)


#if defined(_TEST_BOOTSTRAP)
    #define BOLD(str) "<b>" str "</b>"
    #define BYELLOW(str) "<span style=\"background-color:yellow\">" str "</span>"
    #define BOLDGREEN(str) "<span style=\"color:green; font-weight:bold\">" str "</span>"
    #define BOLDRED(str) "<span style=\"color:red;font-weight:bold\">" str "</span>"
    #define BOLDPINK(str) "<span style=\"color:magenta;font-weight:bold\">" str "</span>"
#else
    #define BOLD(str) str
    #define BYELLOW(str) str
    #define BOLDGREEN(str) str
    #define BOLDRED(str) str
    #define BOLDPINK(str) str
#endif

enum resource_type_t {
    RT_MEMORY,
    RT_STREAM
};

struct block_fence_t {
    uint8_t pattern[32];
};

struct rld_settings_t {

    //rldebug_callback_t callback;
    enum message_severity_level_t lowest_reported_severity;
    
    // ogranicznik wielkości sterty
    struct {
        int global_limit_active;
        size_t global_limit_value;
        int global_disable;
    } heap;
};


enum rld_message_t {
    RLD_UNDEFINED = 0,
    RLD_HEAP_FUNCTIONS_DISABLED,
    
    RLD_MALLOC_NO_MEMORY,
    RLD_MALLOC_NO_MEMORY_DUE_LIMIT,
    RLD_MALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT,
    RLD_MALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT,
    RLD_MALLOC_FAILED_DUE_SUCCESS_LIMIT,
    RLD_MALLOC_SUCCESSFUL,
    
    RLD_FREE_NULL,
    RLD_FREE_INVALID_POINTER,
    RLD_FREE_SUCCESSFUL,

    RLD_CALLOC_NO_MEMORY,
    RLD_CALLOC_NO_MEMORY_DUE_LIMIT,
    RLD_CALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT,
    RLD_CALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT,
    RLD_CALLOC_FAILED_DUE_SUCCESS_LIMIT,
    RLD_CALLOC_SUCCESSFUL,
    
    RLD_REALLOC_NO_MEMORY,
    RLD_REALLOC_NO_MEMORY_DUE_LIMIT,
    RLD_REALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT,
    RLD_REALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT,
    RLD_REALLOC_FAILED_DUE_SUCCESS_LIMIT,
    RLD_REALLOC_INVALID_POINTER,
    RLD_REALLOC_SUCCESSFUL,

    //
    
    RLD_STRDUP_NULL,
    RLD_STRDUP_NO_MEMORY,
    RLD_STRDUP_NO_MEMORY_DUE_LIMIT,
    RLD_STRDUP_NO_MEMORY_DUE_SINGLESHOT_LIMIT,
    RLD_STRDUP_NO_MEMORY_DUE_CUMULATIVE_LIMIT,
    RLD_STRDUP_FAILED_DUE_SUCCESS_LIMIT,
    RLD_STRDUP_SUCCESSFUL,
    
    RLD_STRNDUP_NULL,
    RLD_STRNDUP_NO_MEMORY,
    RLD_STRNDUP_NO_MEMORY_DUE_LIMIT,
    RLD_STRNDUP_NO_MEMORY_DUE_SINGLESHOT_LIMIT,
    RLD_STRNDUP_NO_MEMORY_DUE_CUMULATIVE_LIMIT,
    RLD_STRNDUP_FAILED_DUE_SUCCESS_LIMIT,
    RLD_STRNDUP_SUCCESSFUL,

    //

    RLD_HEAP_BROKEN,
    RLD_HEAP_DATA_OUT_OF_BOUNDS,

    //
    
    RLD_FOPEN_SUCCESSFUL,
    RLD_FOPEN_FAILED,
    RLD_FOPEN_FAILED_DUE_SUCCESS_LIMIT,

    RLD_FCLOSE_NULL_STREAM,
    RLD_FCLOSE_INVALID_STREAM,
    RLD_FCLOSE_SUCCESSFUL,
    
};


struct resource_t {
    uint64_t magic1;
    
    enum resource_type_t type; // typ zasobu
    union {
        
        // blok pamieci
        struct {
            size_t size;
            void* base_pointer;
            enum heap_function_code_t allocated_by;
            
            struct block_fence_t head_fence;
            struct block_fence_t tail_fence;
        } memory;

        // strumien
        struct {
            char* name;
            char* mode;
            FILE* stream;
        } stream;
    };

    // wspolne dane
    const char* source_file;
    int source_line;
    
    struct resource_t *pnext, *pprev; // następny i poprzedni zasób
    
    uint32_t checksum;
    uint64_t magic2;
};


static struct resource_base_t {
    struct resource_t *phead, *ptail;
    
    size_t current_heap_size;
    size_t top_heap_size;
    
    struct rld_settings_t settings;

    FILE* debug_output_stream;

    jmp_buf exit_hook;
    int exit_hooked;
    int exit_allowed;
} rbase;

#define RLD_STREAM rbase.debug_output_stream


enum resource_validate_error_t {
    RVE_SUCCESS = 0,
    
    // blok zasobów
    
    RVE_INVALID_MAGIC1,
    RVE_INVALID_MAGIC2,
    RVE_INVALID_CHECKSUM,
    
    // RT_MEMORY - blok pamięci
    
    RVE_INVALID_HEAD_FENCE,
    RVE_INVALID_TAIL_FENCE
    
    // RT_STREAM
    // todo?
};

static struct limit_descriptor_t {
    enum heap_function_code_t call_type;

    // funkcje sterty
    struct {
        size_t singleshot;

        struct {
            size_t limit;
            size_t sum;
        } cumulative;
    } heap;

    // limit sukcesów
    struct {
        size_t limit;
        size_t counter;
    } success;

} rld_limit[__hfc_max];

static char* rld_severity_text[] = {
    [MSL_QUIET] = "<cisza>",
    [MSL_INFORMATION] = "Informacja",
    [MSL_WARNING] = "Ostrzeżenie",
    [MSL_FAILURE] = BOLDRED("PORAŻKA")
};

static char* rld_resource_validate_error_message[] = {
    [RVE_SUCCESS] = "Ok (RVE_SUCCESS)",
    [RVE_INVALID_MAGIC1] = "Zamazany początek bloku opisującego zasób (RVE_INVALID_MAGIC1)",
    [RVE_INVALID_MAGIC2] = "Zamazany koniec bloku opisującego zasób (RVE_INVALID_MAGIC2)",
    [RVE_INVALID_CHECKSUM] = "Zamazana suma kontrolna bloku opisującego zasób (RVE_INVALID_CHECKSUM)",
    [RVE_INVALID_HEAD_FENCE] = "Wykryto modyfikację obszaru pamięci przed zaalokowanym blokiem (RVE_INVALID_HEAD_FENCE)",
    [RVE_INVALID_TAIL_FENCE] = "Wykryto modyfikację obszaru pamięci za zaalokowanym blokiem (RVE_INVALID_TAIL_FENCE)"
};

static char* rld_message_text[] = {
    [RLD_UNDEFINED] = "Błąd nieokreślony; skontaktuj się z autorem",
    [RLD_HEAP_FUNCTIONS_DISABLED] = "Funkcje operacji na stercie są zablokowane; proszę ich nie używać",
    
    [RLD_MALLOC_NO_MEMORY] = "Brak wolnej pamięci dla funkcji malloc",
    [RLD_MALLOC_NO_MEMORY_DUE_LIMIT] = "Brak wolnej pamięci dla funkcji malloc (heap limit)",
    [RLD_MALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT] = "Przekroczono limit alokowanego bloku w jednym wywołaniu funkcji malloc()",
    [RLD_MALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT] = "Przekroczono skumulowany limit alokowanej pamięci dla funkcji malloc()",
    [RLD_MALLOC_FAILED_DUE_SUCCESS_LIMIT] = "Funkcja malloc() zakończyła się porażką ze wględu na wyczerpany limit wywołań, które mogą zakończyć się sukcesem",
    [RLD_MALLOC_SUCCESSFUL] = "Blok pamięci o żądanej wielkości został pomyślnie przydzielony",

    [RLD_FREE_NULL] = "Próba zwolnienia wskaźnika NULL",
    [RLD_FREE_INVALID_POINTER] = "Próba zwolnienia niezaalokowanego wcześniej bloku pamięci (nieznany wskaźnik)",
    [RLD_FREE_SUCCESSFUL] = "Blok pamięci został pomyślnie zwolniony",
    
    [RLD_CALLOC_NO_MEMORY] = "Brak wolnej pamięci dla funkcji calloc",
    [RLD_CALLOC_NO_MEMORY_DUE_LIMIT] = "Brak wolnej pamięci dla funkcji calloc (heap limit)",
    [RLD_CALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT] = "Przekroczono limit alokowanego bloku w jednym wywołaniu funkcji calloc()",
    [RLD_CALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT] = "Przekroczono skumulowany limit alokowanej pamięci dla funkcji calloc()",
    [RLD_CALLOC_FAILED_DUE_SUCCESS_LIMIT] = "Funkcja calloc() zakończyła się porażką ze wględu na wyczerpany limit wywołań, które mogą zakończyć się sukcesem",
    [RLD_CALLOC_SUCCESSFUL] = "Blok pamięci o żądanej wielkości został pomyślnie przydzielony",

    [RLD_REALLOC_NO_MEMORY] = "Brak wolnej pamięci dla funkcji realloc; wielkość bloku nie uległa zmianie",
    [RLD_REALLOC_NO_MEMORY_DUE_LIMIT] = "Brak wolnej pamięci dla funkcji realloc; wielkość bloku nie uległa zmianie (heap limit)",
    [RLD_REALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT] = "Przekroczono limit alokowanego bloku w jednym wywołaniu funkcji realloc()",
    [RLD_REALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT] = "Przekroczono skumulowany limit alokowanej pamięci dla funkcji realloc()",
    [RLD_REALLOC_FAILED_DUE_SUCCESS_LIMIT] = "Funkcja realloc() zakończyła się porażką ze wględu na wyczerpany limit wywołań, które mogą zakończyć się sukcesem",
    [RLD_REALLOC_INVALID_POINTER] = "Próba zmiany rozmiaru niezaalokowanego wcześniej bloku pamięci (nieznany wskaźnik)",
    [RLD_REALLOC_SUCCESSFUL] = "Rozmiar bloku pamięci został zmieniony pomyślnie",

    //
    
    [RLD_STRDUP_NULL] = "Próba duplikowania tekstu o wskaźniku NULL",
    [RLD_STRDUP_NO_MEMORY] = "Brak wolnej pamięci dla funkcji strdup",
    [RLD_STRDUP_NO_MEMORY_DUE_LIMIT] = "Brak wolnej pamięci dla funkcji strdup (heap limit)",
    [RLD_STRDUP_NO_MEMORY_DUE_SINGLESHOT_LIMIT] = "Przekroczono limit alokowanego bloku w jednym wywołaniu funkcji strdup()",
    [RLD_STRDUP_NO_MEMORY_DUE_CUMULATIVE_LIMIT] = "Przekroczono skumulowany limit alokowanej pamięci dla funkcji strdup()",
    [RLD_STRDUP_FAILED_DUE_SUCCESS_LIMIT] = "Funkcja strdup() zakończyła się porażką ze wględu na wyczerpany limit wywołań, które mogą zakończyć się sukcesem",
    [RLD_STRDUP_SUCCESSFUL] = "Pamięć dla kopii tekstu została pomyślnie przydzielona",
    
    [RLD_STRNDUP_NULL] = "Próba duplikowania tekstu o wskaźniku NULL",
    [RLD_STRNDUP_NO_MEMORY] = "Brak wolnej pamięci dla funkcji strndup",
    [RLD_STRNDUP_NO_MEMORY_DUE_LIMIT] = "Brak wolnej pamięci dla funkcji strndup (heap limit)",
    [RLD_STRNDUP_NO_MEMORY_DUE_SINGLESHOT_LIMIT] = "Przekroczono limit alokowanego bloku w jednym wywołaniu funkcji strndup()",
    [RLD_STRNDUP_NO_MEMORY_DUE_CUMULATIVE_LIMIT] = "Przekroczono skumulowany limit alokowanej pamięci dla funkcji strndup()",
    [RLD_STRNDUP_FAILED_DUE_SUCCESS_LIMIT] = "Funkcja strndup() zakończyła się porażką ze wględu na wyczerpany limit wywołań, które mogą zakończyć się sukcesem",
    [RLD_STRNDUP_SUCCESSFUL] = "Pamięć dla ograniczonej kopii tekstu została pomyślnie przydzielona",


    [RLD_HEAP_BROKEN] = "Wykryto uszkodzenie pamięci starty. Któraś z poprzednich operacji wyszła po za swój zakres pamięci.",
    [RLD_HEAP_DATA_OUT_OF_BOUNDS] = "Wykryto naruszenie granic bloku pamięci.",


    //
    
    
    [RLD_FOPEN_SUCCESSFUL] = "Plik został pomyślnie otwarty",
    [RLD_FOPEN_FAILED] = "Nie udało się otworzyć pliku",
    [RLD_FOPEN_FAILED_DUE_SUCCESS_LIMIT] = "Funkcja fopen() zakończyła się porażką ze wględu na wyczerpany limit wywołań, które mogą zakończyć się sukcesem",
    
    [RLD_FCLOSE_NULL_STREAM] = "Próba zamknięcia pliku reprezentowanego wartością NULL",
    [RLD_FCLOSE_INVALID_STREAM] = "Próba zamknięcia nieotwartego wcześniej pliku (nieznany uchwyt pliku)",
    [RLD_FCLOSE_SUCCESSFUL] = "Plik został pomyślnie zamknięty",
    
};


//
//
//
//
//

static const char* only_name(const char* full_path);
static uint32_t calc_checksum(const void* restrict buffer, size_t size);
static void update_checksum(struct resource_t* pres);
static enum resource_validate_error_t validate_resource(const struct resource_t* pres);
static int validate_heap(const char* caller_source_file, int caller_source_line);

static void rldebug_init(void)
{
    memset(&rbase, 0, sizeof(struct resource_base_t));
    srand(time(NULL));
    
    rbase.settings.lowest_reported_severity = MSL_INFORMATION;
    rbase.exit_allowed = 0;

#if defined(_TEST_BOOTSTRAP)
    rbase.debug_output_stream = stdout;
#else
    rbase.debug_output_stream = stderr;
#endif
}


static struct resource_t* create_resource(enum resource_type_t res_type, const char* source_file, int source_line)
{
    struct resource_t* pres = (struct resource_t*)malloc(sizeof(struct resource_t));
    memset(pres, 0, sizeof(struct resource_t));
    assert(pres != NULL);

    pres->type = res_type;
    pres->pnext = NULL;
    pres->pprev = NULL;

    pres->source_file = source_file;
    pres->source_line = source_line;
    
    pres->magic1 = RLD_MAGIC1;
    pres->magic2 = RLD_MAGIC2;
    
    if (res_type == RT_MEMORY)
    {
        //pres->memory.head_pattern = ((uint64_t)rand() << 32) | (uint64_t)rand();
        //pres->memory.tail_pattern = ((uint64_t)rand() << 32) | (uint64_t)rand();
        
        for (int i = 0; i < 32; i++)
        {
            pres->memory.head_fence.pattern[i] = i + 1;
            pres->memory.tail_fence.pattern[i] = 32 - i;
        }
    }

    return pres;    
}

static void remove_resource(struct resource_t** ppres)
{
    assert(ppres != NULL && "remove_resource: pprese == NULL");
    struct resource_t* pres = *ppres;
    
    if (pres->type == RT_STREAM)
    {
        // nic do roboty
    } else if (pres->type == RT_MEMORY)
    {
        // nic do roboty
    }
    
    if (pres->pnext == NULL && pres->pprev == NULL && rbase.phead == pres && rbase.ptail == pres)
    {
        // tylko jeden element
        rbase.phead = NULL;
        rbase.ptail = NULL;
    } else if (pres->pprev == NULL && rbase.phead == pres)
    {
        // pierwszy
        rbase.phead = rbase.phead->pnext;
        rbase.phead->pprev = NULL;
        update_checksum(rbase.phead);
    } else if (pres->pnext == NULL && rbase.ptail == pres)
    {
        // ostatni
        rbase.ptail = rbase.ptail->pprev;
        rbase.ptail->pnext = NULL;
        update_checksum(rbase.ptail);
    } else if (pres->pnext != NULL && pres->pprev != NULL)
    {
        // środeczek
        struct resource_t *p1, *p2;
        p1 = pres->pprev;
        p2 = pres->pnext;
        pres->pprev->pnext = pres->pnext;
        pres->pnext->pprev = pres->pprev;
        update_checksum(p1);
        update_checksum(p2);
    } else
        assert(0 && "Naruszona spójność sterty");

    free(pres);
    *ppres = NULL;
}

static void add_resource(struct resource_t *pres)
{
    assert(pres != NULL && "add_resource: pres == NULL");

    if (rbase.phead == NULL)
    {
        rbase.phead = pres;
        rbase.ptail = pres;
    } else
    {
        pres->pprev = rbase.ptail;
        rbase.ptail->pnext = pres;
        rbase.ptail = pres;
    }
    
    update_checksum(pres);
    if (pres->pprev)
        update_checksum(pres->pprev);
        

    // Jeśli dodawany zasób jest blokiem pamięci, to zwiększ globalne statystyki zajęcia sterty
    if (pres->type == RT_MEMORY)
    {
        rbase.current_heap_size += pres->memory.size;
        rbase.top_heap_size = MAX(rbase.current_heap_size, rbase.top_heap_size);
    }
    //pres->checksum = 0;
    //pres->checksum = calc_checksum(pres, sizeof(struct resource_t));
}


static struct resource_t* find_resource(enum resource_type_t res_type, const void* handle)
{
    struct resource_t *presource = rbase.phead;
    for( ;presource != NULL; presource = presource->pnext)
    {
        if (presource->type != res_type)
            continue;
        
        if (res_type == RT_MEMORY && presource->memory.base_pointer == handle)
            return presource;
        if (res_type == RT_STREAM && presource->stream.stream == handle)
            return presource;
    }
    
    return NULL;
}

char* print_source_location(char* buffer, size_t buffer_size, const char* source_name, int source_line)
{
    if (source_name == NULL || source_line == -1)
    {
        if (buffer_size)
            buffer[0] = '\x0';
        return buffer;
    }

#if defined(_TEST_BOOTSTRAP)
    snprintf(buffer, buffer_size, "<a href=\"source/%s.html#line-%d\">%s:%d</a>",
        only_name(source_name), source_line, only_name(source_name), source_line);
#else
    snprintf(buffer, buffer_size, "%s:%d", only_name(source_name), source_line);
#endif
    return buffer;
}

static void report(enum message_severity_level_t severity, enum rld_message_t msg_id, const char* source_name, int source_line, const char* message)
{
    if (severity >= rbase.settings.lowest_reported_severity)
    {

        char location[128];
        print_source_location(location, sizeof(location), source_name, source_line);

        if (message == NULL)
            if (source_name == NULL || source_line == -1)
                fprintf(RLD_STREAM, BOLDPINK("Analiza zasobów")": %s: " BOLD("%s") "\n",
                    rld_severity_text[severity], rld_message_text[msg_id]);
            else
                fprintf(RLD_STREAM, BOLDPINK("Analiza zasobów")": %s dla %s: " BOLD("%s") "\n",
                    rld_severity_text[severity], location, rld_message_text[msg_id]);

        else
            if (source_name == NULL || source_line == -1)
                fprintf(RLD_STREAM, BOLDPINK("Analiza zasobów")": %s: " BOLD("%s") " [%s]\n",
                    rld_severity_text[severity], rld_message_text[msg_id], message);
            else
                fprintf(RLD_STREAM, BOLDPINK("Analiza zasobów")": %s dla %s: " BOLD("%s") " [%s]\n",
                    rld_severity_text[severity], location, rld_message_text[msg_id], message);
    }

    fflush(RLD_STREAM);
    
    if (severity == MSL_FAILURE)
        raise(SIGHEAP);
}

static void* setup_base_pointer(struct resource_t* pres)
{
    assert(pres->type == RT_MEMORY);
    
    uint8_t* base = pres->memory.base_pointer;
    *(struct block_fence_t*)base = pres->memory.head_fence;
    *(struct block_fence_t*)(base + sizeof(struct block_fence_t) + pres->memory.size) = pres->memory.tail_fence;
    
    void* user_space_pointer = base + sizeof(struct block_fence_t);
    return user_space_pointer;
}

static void* get_base_pointer(void* user_space_pointer)
{
    if (user_space_pointer == NULL)
        return NULL;
    return (uint8_t*)user_space_pointer - sizeof(struct block_fence_t);
}

void* _rldebug_heap_wrapper(enum heap_function_code_t call_type, void* user_pointer, size_t number, size_t size, const char* source_name, int source_line)
{
    char msg[128];
    validate_heap(source_name, source_line);

    //
    //
    //
    if (call_type == HFC_MALLOC)
    {
        // przekazanie liczby ujemnej daje liczbę dodatnią ponad SIZE_MAX/2
        if (number > SIZE_MAX >> 1)
            return NULL;
        
        if (rbase.settings.heap.global_disable)
            report(MSL_FAILURE, RLD_HEAP_FUNCTIONS_DISABLED, source_name, source_line, NULL);

        // limit wywołań mogących zakończyć się sukcesem
        if (++rld_limit[call_type].success.counter > rld_limit[call_type].success.limit)
        {
            report(MSL_INFORMATION, RLD_MALLOC_FAILED_DUE_SUCCESS_LIMIT, source_name, source_line, NULL);
            return NULL;
        }

        // limit pojedynczej alokacji
        if (number > rld_limit[call_type].heap.singleshot)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.singleshot);
            report(MSL_INFORMATION, RLD_MALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT, source_name, source_line, msg);
            return NULL;
        }

        // skumulowany limit alokacji
        if (number + rld_limit[call_type].heap.cumulative.sum > rld_limit[call_type].heap.cumulative.limit)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.cumulative.limit);
            report(MSL_INFORMATION, RLD_MALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT, source_name, source_line, msg);
            return NULL;
        }

        // ogranicznik sterty
        if (rbase.settings.heap.global_limit_active)
            if (rbase.current_heap_size + number > rbase.settings.heap.global_limit_value)
            {
                report(MSL_INFORMATION, RLD_MALLOC_NO_MEMORY_DUE_LIMIT, source_name, source_line, NULL);
                return NULL;
            }

        // normalna alokacja
        uint8_t* new_base_pointer = (uint8_t*)malloc(number + sizeof(struct block_fence_t) * 2);
        if (new_base_pointer == NULL)
        {
            report(MSL_INFORMATION, RLD_MALLOC_NO_MEMORY, source_name, source_line, NULL);
            return NULL;
        }

        struct resource_t *pres = create_resource(RT_MEMORY, source_name, source_line);
        pres->memory.size = number;
        pres->memory.base_pointer = new_base_pointer;
        pres->memory.allocated_by = call_type;

        add_resource(pres);
        rld_limit[call_type].heap.cumulative.sum += pres->memory.size;
        report(MSL_INFORMATION, RLD_MALLOC_SUCCESSFUL, source_name, source_line, NULL);

        return setup_base_pointer(pres);
    }

    //
    //
    //
    if (call_type == HFC_FREE)
    {
        if (rbase.settings.heap.global_disable)
            report(MSL_FAILURE, RLD_HEAP_FUNCTIONS_DISABLED, source_name, source_line, NULL);

        // libc - brak akcji
        if (user_pointer == NULL)
        {
            report(MSL_WARNING, RLD_FREE_NULL, source_name, source_line, NULL);
            return NULL;
        }

        void* base_pointer = get_base_pointer(user_pointer);

        struct resource_t *pres = find_resource(RT_MEMORY, base_pointer);
        if (pres == NULL)
            report(MSL_FAILURE, RLD_FREE_INVALID_POINTER, source_name, source_line, NULL);

        assert(IS_ALLOCATION_FUNCTION_CODE(pres->memory.allocated_by));
        rbase.current_heap_size -= pres->memory.size;
        rld_limit[pres->memory.allocated_by].heap.cumulative.sum -= pres->memory.size;
        assert(rld_limit[pres->memory.allocated_by].heap.cumulative.sum <= (SIZE_MAX >> 1));

        remove_resource(&pres);
        free(base_pointer);
        report(MSL_INFORMATION, RLD_FREE_SUCCESSFUL, source_name, source_line, NULL);

        return NULL;
    }
    
    //
    //
    //
    if (call_type == HFC_CALLOC)
    {
        // przekazanie liczby ujemnej daje liczbę dodatnią ponad SIZE_MAX/2
        size_t bytes = number * size;
        if (bytes > SIZE_MAX >> 1)
            return NULL;
        
        if (rbase.settings.heap.global_disable)
            report(MSL_FAILURE, RLD_HEAP_FUNCTIONS_DISABLED, source_name, source_line, NULL);

        // limit wywołań mogących zakończyć się sukcesem
        if (++rld_limit[call_type].success.counter > rld_limit[call_type].success.limit)
        {
            report(MSL_INFORMATION, RLD_CALLOC_FAILED_DUE_SUCCESS_LIMIT, source_name, source_line, NULL);
            return NULL;
        }


        // limit pojedynczej alokacji
        if (bytes > rld_limit[call_type].heap.singleshot)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.singleshot);
            report(MSL_INFORMATION, RLD_CALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT, source_name, source_line, msg);
            return NULL;
        }

        // skumulowany limit alokacji
        if (number + rld_limit[call_type].heap.cumulative.sum > rld_limit[call_type].heap.cumulative.limit)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.cumulative.limit);
            report(MSL_INFORMATION, RLD_CALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT, source_name, source_line, msg);
            return NULL;
        }

        // ogranicznik sterty
        if (rbase.settings.heap.global_limit_active)
            if (rbase.current_heap_size + bytes > rbase.settings.heap.global_limit_value)
            {
                report(MSL_INFORMATION, RLD_CALLOC_NO_MEMORY_DUE_LIMIT, source_name, source_line, NULL);
                return NULL;
            }
            
        // normalna alokacja
        uint8_t* new_base_pointer = (uint8_t*)malloc(bytes + sizeof(struct block_fence_t) * 2);
        if (new_base_pointer == NULL)
        {
            report(MSL_INFORMATION, RLD_CALLOC_NO_MEMORY, source_name, source_line, NULL);
            return NULL;
        } else
            memset(new_base_pointer, 0x00, bytes + sizeof(struct block_fence_t) * 2);

        
        struct resource_t *pres = create_resource(RT_MEMORY, source_name, source_line);
        pres->memory.size = bytes;
        pres->memory.base_pointer = new_base_pointer;
        pres->memory.allocated_by = call_type;

        add_resource(pres);
        rld_limit[call_type].heap.cumulative.sum += pres->memory.size;
        report(MSL_INFORMATION, RLD_CALLOC_SUCCESSFUL, source_name, source_line, NULL);

        return setup_base_pointer(pres);
    }
    
    //
    // void* realloc(void*, size_t)
    //
    if (call_type == HFC_REALLOC)
    {
        // przekazanie liczby ujemnej daje liczbę dodatnią ponad SIZE_MAX/2
        if (number > SIZE_MAX >> 1)
            return NULL;
        
        if (rbase.settings.heap.global_disable)
            report(MSL_FAILURE, RLD_HEAP_FUNCTIONS_DISABLED, source_name, source_line, NULL);

        // limit wywołań mogących zakończyć się sukcesem
        if (++rld_limit[call_type].success.counter > rld_limit[call_type].success.limit)
        {
            report(MSL_INFORMATION, RLD_REALLOC_FAILED_DUE_SUCCESS_LIMIT, source_name, source_line, NULL);
            return NULL;
        }

        // normalna alokacja        
        void* base_pointer = get_base_pointer(user_pointer);
        struct resource_t *pres = find_resource(RT_MEMORY, base_pointer);
        if (base_pointer != NULL && pres == NULL)
            report(MSL_FAILURE, RLD_REALLOC_INVALID_POINTER, source_name, source_line, NULL);

        // Jeżeli wywołanie zmienia rozmiar bufora, to analizuj tylko przyrost
        int64_t allocation_delta = (int64_t)number;
        if (pres != NULL)
            allocation_delta = (int64_t)number - (int64_t)pres->memory.size;

        // nie ma żadnej zmiany?
        if (allocation_delta == 0)
            return user_pointer;
            
        // limit pojedynczej alokacji
        if ((pres == NULL) && (number > rld_limit[call_type].heap.singleshot) ||
            (pres != NULL) && (number > pres->memory.size)
                           && (allocation_delta > (int64_t)rld_limit[call_type].heap.singleshot))
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.singleshot);
            report(MSL_INFORMATION, RLD_REALLOC_NO_MEMORY_DUE_SINGLESHOT_LIMIT, source_name, source_line, msg);
            return NULL;
        }

        // skumulowany limit alokacji
        if ((pres == NULL && (number + rld_limit[call_type].heap.cumulative.sum > rld_limit[call_type].heap.cumulative.limit)) ||
            (pres != NULL && (number > pres->memory.size)
                          && (allocation_delta + (int64_t)rld_limit[call_type].heap.cumulative.sum > (int64_t)rld_limit[call_type].heap.cumulative.limit)))
            {
                snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.cumulative.limit);
                report(MSL_INFORMATION, RLD_REALLOC_NO_MEMORY_DUE_CUMULATIVE_LIMIT, source_name, source_line, msg);
                return NULL;
            }

        // ogranicznik sterty
        if (rbase.settings.heap.global_limit_active)
            if ((int64_t)rbase.current_heap_size + allocation_delta > (int64_t)rbase.settings.heap.global_limit_value)
            {
                report(MSL_INFORMATION, RLD_REALLOC_NO_MEMORY_DUE_LIMIT, source_name, source_line, NULL);
                return NULL;
            }
                
        void* new_base_pointer = realloc(base_pointer, number + sizeof(struct block_fence_t) * 2);
        if (new_base_pointer == NULL)
        {
            // Teraz to już naprawdę brakuje pamięci :)
            report(MSL_INFORMATION, RLD_REALLOC_NO_MEMORY, source_name, source_line, NULL);
            return NULL;
        }

        if (base_pointer == NULL)
        {
            //
            // Nie przekazano wskaźnika do realloc() - funkcja działa jak malloc 
            //
            pres = create_resource(RT_MEMORY, source_name, source_line);
            pres->memory.size = number;
            pres->memory.base_pointer = new_base_pointer;
            pres->memory.allocated_by = call_type;

            add_resource(pres);
            rld_limit[call_type].heap.cumulative.sum += pres->memory.size;
            report(MSL_INFORMATION, RLD_REALLOC_SUCCESSFUL, source_name, source_line, NULL);

            return setup_base_pointer(pres);
        } else
        {

            size_t old_size = pres->memory.size;
            enum heap_function_code_t old_code = pres->memory.allocated_by;
            assert(IS_ALLOCATION_FUNCTION_CODE(old_code));

            pres->memory.allocated_by = call_type;
            pres->memory.size = number;
            pres->memory.base_pointer = new_base_pointer;
            pres->source_line = source_line;
            pres->source_file = source_name;
            
            update_checksum(pres);
            if (pres->pprev)
                update_checksum(pres->pprev);
            if (pres->pnext)
                update_checksum(pres->pnext);

            // wycofaj liczbę bajtów zaalokowaną w tym bloku poprzednim wywołaniem (old_code)
            rbase.current_heap_size -= old_size;
            rld_limit[old_code].heap.cumulative.sum -= old_size;
            assert(rld_limit[old_code].heap.cumulative.sum < (SIZE_MAX >> 1));

            // uzupełnij liczniki o nową wielkość bloku
            rbase.current_heap_size += pres->memory.size;
            rld_limit[call_type].heap.cumulative.sum += pres->memory.size;

            rbase.top_heap_size = MAX(rbase.current_heap_size, rbase.top_heap_size);
        }

        report(MSL_INFORMATION, RLD_REALLOC_SUCCESSFUL, source_name, source_line, NULL);
        return setup_base_pointer(pres);
    }   
    
    assert(0 && "_rldebug_heap_wrapper: niezaimplementowana funkcja");
}

char* _rldebug_str_wrapper(enum heap_function_code_t call_type, const char* pstring, size_t number, const char* source_file, int source_line)
{
    char msg[128];
    validate_heap(source_file, source_line);

    //
    //
    //
    if (call_type == HFC_STRDUP)
    {
        if (rbase.settings.heap.global_disable)
            report(MSL_FAILURE, RLD_HEAP_FUNCTIONS_DISABLED, source_file, source_line, NULL);

        if (pstring == NULL)
            report(MSL_FAILURE, RLD_STRDUP_NULL, source_file, source_line, NULL);

        // limit wywołań mogących zakończyć się sukcesem
        if (++rld_limit[call_type].success.counter > rld_limit[call_type].success.limit)
        {
            report(MSL_INFORMATION, RLD_STRDUP_FAILED_DUE_SUCCESS_LIMIT, source_file, source_line, NULL);
            return NULL;
        }

        size_t bytes = strlen(pstring) + 1;

        // limit pojedynczej alokacji
        if (bytes > rld_limit[call_type].heap.singleshot)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.singleshot);
            report(MSL_INFORMATION, RLD_STRDUP_NO_MEMORY_DUE_SINGLESHOT_LIMIT, source_file, source_line, msg);
            return NULL;
        }

        // skumulowany limit alokacji
        if (number + rld_limit[call_type].heap.cumulative.sum > rld_limit[call_type].heap.cumulative.limit)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.cumulative.limit);
            report(MSL_INFORMATION, RLD_STRDUP_NO_MEMORY_DUE_CUMULATIVE_LIMIT, source_file, source_line, msg);
            return NULL;
        }

        // ogranicznik sterty
        if (rbase.settings.heap.global_limit_active)
            if (rbase.current_heap_size + bytes > rbase.settings.heap.global_limit_value)
            {
                report(MSL_INFORMATION, RLD_STRDUP_NO_MEMORY_DUE_LIMIT, source_file, source_line, NULL);
                return NULL;
            }

        // normalna alokacja        
        uint8_t* new_base_pointer = (uint8_t*)malloc(bytes + sizeof(struct block_fence_t) * 2);
        if (new_base_pointer == NULL)
        {
            report(MSL_INFORMATION, RLD_STRDUP_NO_MEMORY, source_file, source_line, NULL);
            return NULL;
        }

        struct resource_t *pres = create_resource(RT_MEMORY, source_file, source_line);
        pres->memory.size = bytes;
        pres->memory.base_pointer = new_base_pointer;
        pres->memory.allocated_by = call_type;

        add_resource(pres);
        rld_limit[call_type].heap.cumulative.sum += pres->memory.size;
        report(MSL_INFORMATION, RLD_STRDUP_SUCCESSFUL, source_file, source_line, NULL);

        char* user_pointer = setup_base_pointer(pres);
        strcpy(user_pointer, pstring);

        return user_pointer;
    }
    
    //
    //
    //
    if (call_type == HFC_STRNDUP)
    {
        if (rbase.settings.heap.global_disable)
            report(MSL_FAILURE, RLD_HEAP_FUNCTIONS_DISABLED, source_file, source_line, NULL);

        if (pstring == NULL)
            report(MSL_FAILURE, RLD_STRNDUP_NULL, source_file, source_line, NULL);

        // limit wywołań mogących zakończyć się sukcesem
        if (++rld_limit[call_type].success.counter > rld_limit[call_type].success.limit)
        {
            report(MSL_INFORMATION, RLD_STRNDUP_FAILED_DUE_SUCCESS_LIMIT, source_file, source_line, NULL);
            return NULL;
        }

        size_t bytes = strlen(pstring);
        bytes = MIN(bytes, number);

        // limit pojedynczej alokacji
        if (bytes > rld_limit[call_type].heap.singleshot)
        {
            char msg[128];
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.singleshot);
            report(MSL_INFORMATION, RLD_STRNDUP_NO_MEMORY_DUE_SINGLESHOT_LIMIT, source_file, source_line, msg);
            return NULL;
        }

        // skumulowany limit alokacji
        if (number + rld_limit[call_type].heap.cumulative.sum > rld_limit[call_type].heap.cumulative.limit)
        {
            snprintf(msg, sizeof(msg), "wynosi on %lu bajtów", rld_limit[call_type].heap.cumulative.limit);
            report(MSL_INFORMATION, RLD_STRNDUP_NO_MEMORY_DUE_CUMULATIVE_LIMIT, source_file, source_line, msg);
            return NULL;
        }

        // ogranicznik sterty
        if (rbase.settings.heap.global_limit_active)
            if (rbase.current_heap_size + bytes > rbase.settings.heap.global_limit_value)
            {
                report(MSL_INFORMATION, RLD_STRNDUP_NO_MEMORY_DUE_LIMIT, source_file, source_line, NULL);
                return NULL;
            }

        // normalna alokacja        
        uint8_t* new_base_pointer = (uint8_t*)malloc(bytes + 1 + sizeof(struct block_fence_t) * 2);
        if (new_base_pointer == NULL)
        {
            report(MSL_INFORMATION, RLD_STRNDUP_NO_MEMORY, source_file, source_line, NULL);
            return NULL;
        }

        struct resource_t *pres = create_resource(RT_MEMORY, source_file, source_line);
        pres->memory.size = bytes + 1;
        pres->memory.base_pointer = new_base_pointer;
        pres->memory.allocated_by = call_type;

        add_resource(pres);
        rld_limit[call_type].heap.cumulative.sum += pres->memory.size;
        report(MSL_INFORMATION, RLD_STRNDUP_SUCCESSFUL, source_file, source_line, NULL);

        char* user_pointer = setup_base_pointer(pres);
        memcpy(user_pointer, pstring, bytes);
        user_pointer[bytes] = '\x0';

        return user_pointer;            
    }

    assert(0 && "_rldebug_str_wrapper: niezaimplementowana funkcja");
}


void* _rldebug_io_wrapper(enum heap_function_code_t call_type, FILE* stream, const char* stream_name, const char* stream_mode, const char* source_file, int source_line)
{
    char sys_message[256];
    validate_heap(source_file, source_line);

    //
    //
    //
    if (call_type == HFC_FOPEN)
    {
        // limit wywołań mogących zakończyć się sukcesem
        if (++rld_limit[call_type].success.counter > rld_limit[call_type].success.limit)
        {
            report(MSL_INFORMATION, RLD_FOPEN_FAILED_DUE_SUCCESS_LIMIT, source_file, source_line, NULL);
            return NULL;
        }

        FILE* fhandle = fopen(stream_name, stream_mode);
        if (fhandle == NULL)
        {
            snprintf(sys_message, sizeof(sys_message), "%s; errno=%d", strerror(errno), errno);
            report(MSL_INFORMATION, RLD_FOPEN_FAILED, source_file, source_line, sys_message);
            return NULL;
        }

        struct resource_t *pres = create_resource(RT_STREAM, source_file, source_line);
        pres->stream.name = strdup(stream_name);
        pres->stream.mode = strdup(stream_mode);
        pres->stream.stream = fhandle;

        assert(pres->stream.name != NULL && pres->stream.mode != NULL);

        add_resource(pres);
        report(MSL_INFORMATION, RLD_FOPEN_SUCCESSFUL, source_file, source_line, NULL);

        return fhandle;
    }
    
    //
    //
    //
    if (call_type == HFC_FCLOSE)
    {
        if (stream == NULL)
            report(MSL_FAILURE, RLD_FCLOSE_NULL_STREAM, source_file, source_line, NULL);

        struct resource_t *pres = find_resource(RT_STREAM, (void*)stream);

        if (pres == NULL)
            report(MSL_FAILURE, RLD_FCLOSE_INVALID_STREAM, source_file, source_line, NULL);

        intptr_t result = fclose(stream);
        remove_resource(&pres);
        report(MSL_INFORMATION, RLD_FCLOSE_SUCCESSFUL, source_file, source_line, NULL);
        return (void*)result;
    }

    assert(0 && "_rldebug_mem_wrapper: niezaimplementowana funkcja");
}

void __attribute__((noreturn)) _rldebug_stdlib_noreturn_wrapper(enum heap_function_code_t call_type, int iarg, const char* source_file, int source_line)
{
    validate_heap(source_file, source_line);

    //
    //
    //
    if (call_type == HFC_EXIT)
    {
        if (rbase.exit_hooked)
            longjmp(rbase.exit_hook, 0x0100 | (iarg) & 0xFF);

        if (rbase.exit_allowed)
            exit(iarg);

        // nie używać exit()
        char location[128];
        print_source_location(location, sizeof(location), source_file, source_line);
        fprintf(RLD_STREAM, "\n" BOLDRED("*** Użyto funkcji exit(int) w %s. Pozwala on na natychmiastowe wyjście z programu, co uniemożliwia finalizację testów.\n"), location);
        fprintf(RLD_STREAM, "*** Proszę poprawić swój kod tak aby niewykorzystywać funkcji exit()\n");
        fprintf(RLD_STREAM, "*** W przypadku wątpliwości proszę skontaktować się z autorem testu.\n");
        raise(SIGTERM);
    }

    assert(0 && "_rldebug_stdlib_noreturn_wrapper: niezaimplementowany call_type");
}


int rldebug_show_leaked_resources(int force_empty_summary)
{
    uint32_t blocks = 0;
    uint32_t streams = 0;
    uint64_t memory_leaked = 0;
    
    // policz elementy
    for(struct resource_t *presource = rbase.phead; presource != NULL; presource = presource->pnext)
    {
        blocks += presource->type == RT_MEMORY;
        streams += presource->type == RT_STREAM;
    }

    if (blocks || streams || force_empty_summary)
    {
        //fprintf(RLD_STREAM, BYELLOW("** "BOLD("RLDebug")" :: Analizator wycieku zasobów ***")"\n");
        fflush(RLD_STREAM);
    }

    // pamięć - lista wycieków
    if (blocks > 0)
    {
        fprintf(RLD_STREAM, "\n" BOLDRED("Wycieki pamięci") ":\n");
        fprintf(RLD_STREAM, "--------------------------------------------\n");
        fprintf(RLD_STREAM, " ID                Adres       Plik źródłowy\n");
        fprintf(RLD_STREAM, "           Liczba bajtów       Numer linii  \n");
        fprintf(RLD_STREAM, "--------------------------------------------\n");
        fflush(RLD_STREAM);
        
        int i = 1; 
        for(struct resource_t *presource = rbase.phead; presource != NULL; presource = presource->pnext, i++)
        {
            if (presource->type != RT_MEMORY)
                continue;

#if defined(_TEST_BOOTSTRAP)
            fprintf(RLD_STREAM, " %-3d  %18p       <a href=\"source/%s.html#line-%d\">%s</a>\n", i, presource->memory.base_pointer,
                only_name(presource->source_file), presource->source_line, only_name(presource->source_file));
#else
            fprintf(RLD_STREAM, " %-3d  %18p       %s\n", i, presource->memory.base_pointer, only_name(presource->source_file));
#endif
            fprintf(RLD_STREAM, "      %18lu       %d\n",    presource->memory.size, presource->source_line);
            fflush(RLD_STREAM);
            memory_leaked += presource->memory.size;
        }
        
        fprintf(RLD_STREAM, "--------------------------------------------\n");
    }
    
    // pamięć - podsumowanie wycieków
    if (blocks > 0 || force_empty_summary)
    {
        if (blocks > 0)
        {
            fprintf(RLD_STREAM, "Liczba niezwolnionych bloków pamięci: " BOLDRED("%d") " blok(ów)\n", blocks);
            fprintf(RLD_STREAM, "Sumaryczna wielkość wycieku pamięci: " BOLDRED("%lu") " bajt(ów)\n", memory_leaked);
        } else
            fprintf(RLD_STREAM, BOLDGREEN("Wszystkie bloki pamięci zostały pomyślnie zwolnione - brak wycieków.") "\n");
    }
    
    // pliki - lista niezamkniętych plików
    if (streams > 0)
    {
//      if (blocks)
//          fprintf(RLD_STREAM, "\n");
        fprintf(RLD_STREAM, "\n"BOLDRED("Niezamknięte pliki")":\n");
        fprintf(RLD_STREAM, "--------------------------------------------\n");
        fprintf(RLD_STREAM, " ID  Nazwa                     Plik źródłowy\n");
        fprintf(RLD_STREAM, "     Tryb                      Numer linii  \n");
        fprintf(RLD_STREAM, "--------------------------------------------\n");
        fflush(RLD_STREAM);
        
        int i = 1; 
        for(struct resource_t *presource = rbase.phead; presource != NULL; presource = presource->pnext, i++)
        {
            if (presource->type != RT_STREAM)
                continue;
                
            char fname[32] = {0};
            
            if (strlen(presource->stream.name) > 25)
            {
                strncpy(fname, presource->stream.name, 25-5);
                strcat(fname, "(...)");
            } else
                strncpy(fname, presource->stream.name, 25);
            

#if defined(_TEST_BOOTSTRAP)
            fprintf(RLD_STREAM, " %-3d %-25s <a href=\"source/%s.html#line-%d\">%s</a>\n", i, fname,
                only_name(presource->source_file), presource->source_line, only_name(presource->source_file));
#else
            fprintf(RLD_STREAM, " %-3d %-25s %s\n", i, fname, only_name(presource->source_file));
#endif

            fprintf(RLD_STREAM, "     %-25s %d\n",    presource->stream.mode, presource->source_line);
            fflush(RLD_STREAM);
        }
        
        fprintf(RLD_STREAM, "--------------------------------------------\n");

    }
    
    // pliki - podsumowanie
    if (streams > 0 || force_empty_summary)
    {
        if (streams > 0)
            fprintf(RLD_STREAM, "Liczba niezamkniętych plików: "BOLDRED("%d")"\n", streams);
        else
            fprintf(RLD_STREAM, BOLDGREEN("Wszystkie pliki zostały zamknięte.\n"));
    }

    assert(memory_leaked == rbase.current_heap_size);

    if (force_empty_summary)
    {
        fprintf(RLD_STREAM, BOLDGREEN("Nie wykryto uszkodzenia sterty.\n"));
        fprintf(RLD_STREAM, "\n");
    }

    return streams + blocks;
}


size_t rldebug_heap_get_leak_size(void)
{
    size_t leak = 0;

    // policz elementy
    for(struct resource_t *presource = rbase.phead; presource != NULL; presource = presource->pnext)
    {
        if (presource->type != RT_MEMORY)
            continue;

        leak += presource->memory.size;
    }

    return leak;
}

size_t rldebug_get_block_size(const void* ptr)
{
    for(struct resource_t *presource = rbase.phead; presource != NULL; presource = presource->pnext)
    {
        if (presource->type != RT_MEMORY)
            continue;
        if (ptr == (struct block_fence_t*)presource->memory.base_pointer + 1)
            return presource->memory.size;

    }
    return RLD_UNKNOWN_POINTER;
}

static const char* only_name(const char* full_path)
{
    char* p = strrchr(full_path, '/');
    return p ? p + 1 : full_path;
}

static uint32_t calc_checksum(const void* restrict buffer, size_t size)
{
    uint32_t chk = 0;
    const uint8_t* restrict ptr = (const uint8_t* restrict)buffer;
    while(size--)
        chk = ((chk ^ *ptr++) << 1) ^ !(chk & 1 << 31);
    return chk;
}

static void update_checksum(struct resource_t* pres)
{
    pres->checksum = 0;
    pres->checksum = calc_checksum(pres, sizeof(struct resource_t));
}


static enum resource_validate_error_t validate_resource(const struct resource_t* pres)
{
    // wstępne sprawdzenie
    if (pres->magic1 != RLD_MAGIC1)
        return RVE_INVALID_MAGIC1; // uszkodzona magiczna liczba na początku deskryptora zasobu
    if (pres->magic2 != RLD_MAGIC2)
        return RVE_INVALID_MAGIC2; // uszkodzona magiczna liczba na końcu deskryptora zasobu
        
    // i test właściwy
    struct resource_t temp = *pres;
    temp.checksum = 0;
    uint32_t chk = calc_checksum(&temp, sizeof(struct resource_t));
    if (chk != pres->checksum)
        return RVE_INVALID_CHECKSUM; // suma kontrolna zasobu jest błędna
        
    if (pres->type == RT_MEMORY)
    {
        struct block_fence_t* head_fence = (struct block_fence_t*)pres->memory.base_pointer;
        struct block_fence_t* tail_fence = (struct block_fence_t*)((uint8_t*)pres->memory.base_pointer + sizeof(struct block_fence_t) + pres->memory.size);
        if (memcmp(head_fence, &pres->memory.head_fence, sizeof(struct block_fence_t)) != 0)
            return RVE_INVALID_HEAD_FENCE; // uszkodzony płotek na początku bloku danych
        if (memcmp(tail_fence, &pres->memory.tail_fence, sizeof(struct block_fence_t)) != 0)
            return RVE_INVALID_TAIL_FENCE; // uszkodzony płotek na końcu bloku danych
    }
    
    return RVE_SUCCESS; // wszystko wydaje się być w porządku
}

static int validate_heap(const char* caller_source_file, int caller_source_line)
{
    for(struct resource_t *presource = rbase.phead; presource != NULL; presource = presource->pnext)
    {
        int vderror;
        if ((vderror = validate_resource(presource)) != RVE_SUCCESS)
        {
            char msg[512];
            //sprintf(msg, "vderror=%d", vderror);
            
            if (vderror == RVE_INVALID_MAGIC1 || vderror == RVE_INVALID_MAGIC2 || vderror == RVE_INVALID_CHECKSUM)
            {
                char loc1[512];
                print_source_location(loc1, sizeof(loc1), caller_source_file, caller_source_line);
                snprintf(msg, sizeof(msg), "Sterta zawiera uszkodzony blok pamięci. Problem zauważono w trakcie wykonywania %s. Opis: %s", loc1, rld_resource_validate_error_message[vderror]);

                report(MSL_FAILURE, RLD_HEAP_BROKEN, NULL, -1, msg);
            }

/*
            sprintf(msg, "vderror=%d; blok utworzony w %s:%d", vderror, only_name(presource->source_file), presource->source_line);

            if (vderror == RVE_INVALID_HEAD_FENCE || RVE_INVALID_TAIL_FENCE)
                report(MSL_FAILURE, RLD_HEAP_DATA_OUT_OF_BOUNDS, caller_source_file, caller_source_line, msg);
*/

            if (vderror == RVE_INVALID_HEAD_FENCE || RVE_INVALID_TAIL_FENCE)
            {
                char loc1[512], loc2[512];
                print_source_location(loc1, sizeof(loc1), presource->source_file, presource->source_line);
                print_source_location(loc2, sizeof(loc2), caller_source_file, caller_source_line);

                snprintf(msg, sizeof(msg), "Uszkodzony został blok zaalokowany w %s a samo uszkodzenie zauważono w trakcie wykonywania %s. Opis: %s", loc1, loc2, rld_resource_validate_error_message[vderror]);

                report(MSL_FAILURE, RLD_HEAP_DATA_OUT_OF_BOUNDS, NULL, -1, msg);
            }

            assert(!vderror);
            return 0;
        
        }
    }
    //printf("ok");
    return 1;
}


static void __attribute__ ((constructor)) __rldebug_startup()
{
    rldebug_init();
    rldebug_reset_limits();
}

//
//
// RLDebugger 
//
//

void rldebug_reset_limits(void)
{
    //
    rbase.settings.heap.global_limit_active = 0;
    rbase.settings.heap.global_disable = 0;

    for (int command_id = __hfc_min; command_id < __hfc_max; command_id++)
    {
        struct limit_descriptor_t *plim = &rld_limit[command_id];
        plim->call_type = command_id;

        // limity pamieci
//      plim->heap.general = RLD_HEAP_UNLIMITED;
        plim->heap.singleshot = RLD_HEAP_UNLIMITED;
        plim->heap.cumulative.limit = RLD_HEAP_UNLIMITED;
        plim->heap.cumulative.sum = 0;


        // limity sukcesów
        plim->success.limit = RLD_HEAP_UNLIMITED;
        plim->success.counter = 0;

    }
}

void rldebug_heap_set_global_limit(size_t heap_limit)
{
    if (heap_limit == RLD_HEAP_UNLIMITED)
    {
        // wyłącz ogranicznik
        rbase.settings.heap.global_limit_active = 0;
    } else
    {
        // włącz 
        rbase.settings.heap.global_limit_value = heap_limit;
        rbase.settings.heap.global_limit_active = 1;
    }
}


void rldebug_heap_disable_all_functions(int disable)
{
    rbase.settings.heap.global_disable = disable;
}


void rldebug_heap_set_function_singleshot_limit(enum heap_function_code_t call_type, size_t limit)
{
    assert(call_type > __hfc_min && call_type < __hfc_max);
    rld_limit[call_type].heap.singleshot = limit;
}

void rldebug_heap_set_function_cumulative_limit(enum heap_function_code_t call_type, size_t limit)
{
    assert(call_type > __hfc_min && call_type < __hfc_max);
    rld_limit[call_type].heap.cumulative.limit = limit;
}


void rldebug_set_function_success_limit(enum heap_function_code_t call_type, size_t limit)
{
    assert(call_type > __hfc_min && call_type < __hfc_max);
    rld_limit[call_type].success.limit = limit;
}


void rldebug_set_reported_severity_level(enum message_severity_level_t lowest_level)
{
    rbase.settings.lowest_reported_severity = lowest_level;
}


//
//
//
//
//

void remove_single_newline(void)
{

    int old_flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, old_flags | O_NONBLOCK);

    int ch = getchar();

    fcntl(STDIN_FILENO, F_SETFL, old_flags);

    if(ch != EOF && ch != '\n')
        ungetc(ch, stdin);
}


int rdebug_call_main(int (*pmain)(int, char**, char**), int argc, char** argv, char** envp)
{
    assert(pmain != NULL);
    //jmp_buf main_return_hook;

    volatile int jstatus = setjmp(rbase.exit_hook);
    int ret_code = 0;
    if (!jstatus)
    {
        rbase.exit_hooked = 1;

        ret_code = (int8_t)pmain(argc, argv, envp);


        rbase.exit_hooked = 0;
    } else {
        rbase.exit_hooked = 0;
        assert((jstatus & 0xFF00) == 0x0100);
        ret_code = (int8_t)(jstatus & 0xFF);
    }

    // jeśli w buforze klawiatury zaplątał się znak nowej linii, to go skasuj
    remove_single_newline();
    return ret_code;
}