Search code examples
capr

APR memory not freed after pool destroy


apr_pool_t *pool;
char *a;
char *b;

apr_pool_create(&pool, NULL);
a = (char *) apr_palloc(pool, 10);

strcpy(a, "hello");
printf("a is %s\n", a);

apr_pool_destroy(pool);
apr_terminate();
b = (char *) apr_palloc(pool, 10);

strcpy(b, "world");
printf("b is %s\n", b);

I'm new to libapr, from the document it said

Destroy the pool. This takes similar action as apr_pool_clear() and then frees all the memory.

but actually it's not, and I can still use apr_palloc to alloc memory from a destroyed pool? so my question is how can I actually free the memory and how can I actually destroy the pool object


Solution

  • You are invoking Undefined Behavior in at least two places. I compiled on my linux (Mint a derived of Ubuntu), gcc 7.4. Your program crashes at the first apr_pool_create because you did not invoke the appropriate initialization functions (e.g. apr_initialize), see https://apr.apache.org/docs/apr/1.6/group__apr__library.html . Here is the Valgrind trace:

    ==7158== Process terminating with default action of signal 11 (SIGSEGV)
    ==7158==  Access not within mapped region at address 0x30
    ==7158==    at 0x4E595F0: apr_pool_create_ex (in /usr/lib/x86_64-linux-gnu/libapr-1.so.0.6.3)
    ==7158==    by 0x1088EB: main (in /home/user/apr/test)
    =
    

    Once that issue is fixed, you get the following program (note: I do not know whether apr_initialize or apr_app_initialize is appropriate for your use case).

        apr_pool_t *pool;
        char *a;
        char *b;
    
        apr_initialize(); // You need this or apr_app_initialize
    
        apr_pool_create(&pool, NULL);
        a = (char *) apr_palloc(pool, 10);
    
        strcpy(a, "hello");
        printf("a is %s\n", a);
    
        apr_pool_destroy(pool);
        apr_terminate();
    
        b = (char *) apr_palloc(pool, 10);
    
        strcpy(b, "world");
        printf("b is %s\n", b);
    

    The above code crashes in the second apr_palloc with the following Valgrind trace because you are accessing memory that was freed most likely as a consequence of apr_pool_destroy

    a is hello
    ==7196== Invalid read of size 8
    ==7196==    at 0x4E58A62: apr_palloc (in /usr/lib/x86_64-linux-gnu/libapr-1.so.0.6.3)
    ==7196==    by 0x1089AF: main (in /home/user/apr/test)
    ==7196==  Address 0x402d080 is not stack'd, malloc'd or (recently) free'd
    ==7196== 
    

    Removing the last lines

        b = (char *) apr_palloc(pool, 10);
    
        strcpy(b, "world");
        printf("b is %s\n", b);
    

    allow the program to terminate correctly and Valgrind shows no errors.

    So it looks like apr_pool_destroy is working correctly, you were simply accessing memory you were not supposed to have access to and you did not experience a crash: Undefined Behavior is sneaky, your program can run for years without showing any problem, and then, one day it crashes.

    For completeness, I compiled with the following command (the code was in test.c):

    gcc -Wall test.c $(apr-1-config --cflags --cppflags --includes --link-ld) -o test
    

    I suggest that you should use tools like Valgrind (http://www.valgrind.org/) to detect these kind of issues.

    User Eliyahu Machluf (thanks) makes the point that APR provides facilities to debug memory allocation, from http://download.vikis.lt/manual/developer/debugging.html

    Allocation Debugging

    ALLOC_DEBUG

    Debugging support: Define this to enable code which helps detect re-use of free()d memory and other such nonsense. The theory is simple. The FILL_BYTE (0xa5) is written over all malloc'd memory as we receive it, and is written over everything that we free up during a clear_pool. We check that blocks on the free list always have the FILL_BYTE in them, and we check during palloc() that the bytes still have FILL_BYTE in them. If you ever see garbage URLs or whatnot containing lots of 0xa5s then you know something used data that's been freed or uninitialized.