I made the first version of the rw_alloc() / rw_free().

  Memory leak checking is not implemented.

  The files are attached.

------------------------------------------------------------

  tests/include/rw_alloc.h
  tests/src/alloc.cpp

enum {
    RW_PROT_NONE  = 0,
    RW_PROT_READ  = 1 << 0,
    RW_PROT_WRITE = 1 << 1,
    RW_PROT_RDWR  = RW_PROT_READ | RW_PROT_WRITE,
    RW_PROT_EXEC  = 1 << 2,
    RW_PROT_BELOW = 1 << 3
};

// if flags == -1 memory will be allocated by malloc();
// if flags != -1 memory will be allocated by system call and
// additional guard page with PAGE_NOACCESS protection will be allocated
// if RW_PROT_BELOW & flags != 0 then guard page will be located right
// before the user data, otherwise - right after the user data
// if RWSTD_ALLOC_FLAGS environment variable is defined and != 0
// then rw_alloc will use value of RWSTD_ALLOC_FLAGS instead of flags variable
_TEST_EXPORT void*
rw_alloc(size_t, int /*flags*/ = -1);

// free the memory block, allocated by rw_alloc()
_TEST_EXPORT void
rw_free(void*);

Farid.

/************************************************************************
 *
 * rw_alloc.h - definitions of rw_alloc and rw_free
 *
 * $Id: rw_alloc.h
 *
 ***************************************************************************
 *
 * Copyright 2005-2006 The Apache Software Foundation or its licensors,
 * as applicable.
 *
 * Copyright 2004-2006 Rogue Wave Software.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **************************************************************************/

#ifndef RW_ALLOC_H_INCLUDED
#define RW_ALLOC_H_INCLUDED


#include <testdefs.h>   // for test config macros
#include <stddef.h>     // for size_t

enum {
    RW_PROT_NONE  = 0,
    RW_PROT_READ  = 1 << 0,
    RW_PROT_WRITE = 1 << 1,
    RW_PROT_RDWR  = RW_PROT_READ | RW_PROT_WRITE,
    RW_PROT_EXEC  = 1 << 2,
    RW_PROT_BELOW = 1 << 3
};

// if flags == -1 memory will be allocated by malloc();
// if flags != -1 memory will be allocated by system call and
// additional guard page with PAGE_NOACCESS protection will be allocated
// if RW_PROT_BELOW & flags != 0 then guard page will be located right
// before the user data, otherwise - right after the user data
// if RWSTD_ALLOC_FLAGS environment variable is defined and != 0
// then rw_alloc will use value of RWSTD_ALLOC_FLAGS instead of flags variable
_TEST_EXPORT void*
rw_alloc(size_t, int /*flags*/ = -1);

// free the memory block, allocated by rw_alloc()
_TEST_EXPORT void
rw_free(void*);

#endif   // RW_ALLOC_H_INCLUDED
/************************************************************************
 *
 * alloc.cpp - definitions of rw_alloc and rw_free
 *
 * $Id: alloc.cpp
 *
 ************************************************************************
 *
 * Copyright 2005 - 2006 The Apache Software Foundation or its licensors,
 * as applicable.
 *
 * Copyright 2003 - 2006 Rogue Wave Software.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **************************************************************************/

// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC

#include <assert.h>   // for assert()
#include <stdlib.h>   // for atoi(), getenv()
#include <string.h>   // for memset()
#include <malloc.h>   // for malloc()

#ifdef __CYGWIN__
// use the Windows API on Cygwin
#  define _WIN32
#endif

#if !defined (_WIN32) && !defined (_WIN64)
#  ifdef __SUNPRO_CC
// working around SunOS bug #568
#    include <time.h>
#  endif
#  include <unistd.h>     // for getpagesize(), sysconf()
#  include <sys/mman.h>   // for mmap()
#  include <sys/types.h>

#  ifndef _SC_PAGE_SIZE
// fall back on the alternative macro if it exists,
// or use getpagesize() otherwise
#    ifndef _SC_PAGESIZE
#      define GETPAGESIZE()   getpagesize ()
#    else
#      define GETPAGESIZE()   sysconf (_SC_PAGESIZE)
#    endif
#  else
#      define GETPAGESIZE()   sysconf (_SC_PAGE_SIZE)
#  endif   // _SC_PAGE_SIZE

#else   // defined (_WIN32) || defined (_WIN64)

#  include <windows.h>    // for everything (ugh)
#  include <sys/types.h>  // for off_t
#  include <errno.h>      // for errno

static long getpagesize ()
{
    static long pagesize_ = 0;

    if (0 == pagesize_) {
        SYSTEM_INFO info;
        ::GetSystemInfo (&info);
        pagesize_ = long (info.dwPageSize);
    }

    return pagesize_;
}

enum {
    PROT_NONE  = 0,
    PROT_READ  = 1 << 0,
    PROT_WRITE = 1 << 1,
    PROT_RDWR  = PROT_READ | PROT_WRITE,
    PROT_EXEC  = 1 << 2,
};

#define MAP_PRIVATE   0
#define MAP_ANONYMOUS 0

static void* const MAP_FAILED = (void*)-1;

static DWORD translate_prot(int prot)
{
    static DWORD prots[8] = {
        PAGE_NOACCESS,
        PAGE_READONLY,
        PAGE_READWRITE,
        PAGE_READWRITE,
        PAGE_EXECUTE,
        PAGE_EXECUTE_READ,
        PAGE_EXECUTE_READWRITE,
        PAGE_EXECUTE_READWRITE
    };

    if ((0 <= prot) && (sizeof(prots) / sizeof(prots[0]) > prot))
        return prots[prot];

    return PAGE_NOACCESS;
}

static inline void* mmap(void* addr, size_t len, int prot, int, int, off_t)
{
    DWORD flProt = translate_prot (prot);

    if (addr = ::VirtualAlloc (addr, len, MEM_RESERVE | MEM_COMMIT, flProt))
        return addr;

    errno = EINVAL;
    return MAP_FAILED;
}

static inline int munmap(void* addr, size_t)
{
    return ::VirtualFree (addr, 0, MEM_RELEASE) ? 0 : (errno = EINVAL, -1);
}

static inline int mprotect(void *addr, size_t len, int prot)
{
    DWORD flOldProt;
    return ::VirtualProtect (addr, len, translate_prot (prot),
        &flOldProt) ? 0 : (errno = EINVAL, -1);
}

#endif   // _WIN{32,64}

#include <rw/_defs.h>
#include <rw_alloc.h>

/************************************************************************/

struct BlockInfo
{
    bool   used_;   // true is block is used
    void*  addr_;   // address of the allocated block
    size_t size_;   // size of the allocated block
    void*  data_;   // address of the user data
    size_t udsz_;   // size of the user data
    int    flags_;  // memory protection flags
};

struct Stats
{
    size_t blocks_;     // number of the current allocated blocks
    size_t maxblocks_;  // max number of the allocated blocks
};

static Stats stats_;

struct Blocks
{
    Blocks*   next_;
    size_t    nblocks_;
    BlockInfo blocks_[1];
};

static Blocks* first_ = 0;  // pointer to the first Blocks in list
static Blocks* last_ = 0;   // pointer to the last Blocks in list

/************************************************************************/

// constructor sets r/w access to the specified memory pages
// destructor sets r/o access to the specified memory pages
class MemRWGuard
{
private:
    void*  addr_;
    size_t size_;

public:
    MemRWGuard (void* addr, size_t size)
    {
        size_t pagesize = getpagesize ();

        size_t off = size_t (addr) % pagesize;
        // addr_ should be aligned to memory page boundary
        addr_ = _RWSTD_STATIC_CAST(char*, addr) - off;
        size_ = size + off;

        int res = mprotect (addr_, size, PROT_READ | PROT_WRITE);
        assert (0 == res);
    }

    ~MemRWGuard ()
    {
        int res = mprotect (addr_, size_, PROT_READ);
        assert (0 == res);
    }

private:
    // not defined
    MemRWGuard (const MemRWGuard&);
    MemRWGuard& operator= (const MemRWGuard&);
};

/************************************************************************/

// allocate more blocks (allocates one memory page)
static bool _rw_allocate_blocks()
{
    // count of the blocks per memory page
    static size_t blocks_per_page = 0;

    size_t pagesize = getpagesize ();

    if (0 == blocks_per_page)
        blocks_per_page = (pagesize - sizeof (Blocks)) / sizeof (BlockInfo) + 1;

    void* buf = mmap (0, pagesize, PROT_READ,
        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (MAP_FAILED != buf) {

        MemRWGuard guard (buf, pagesize);

        memset (buf, 0, pagesize);

        Blocks* blocks = _RWSTD_STATIC_CAST(Blocks*, buf);
        blocks->nblocks_ = blocks_per_page;

        if (0 == first_)
            first_ = last_ = blocks;
        else
            last_ = last_->next_ = blocks;

        return true;
    }

    return false;
}

// free allocated blocks
// should be called when all user memory allocated
// by rw_alloc were freed by rw_free
static void _rw_free_blocks ()
{
    assert (0 == stats_.blocks_);

    size_t pagesize = getpagesize ();

    while (first_) {
        Blocks* it = first_;
        first_ = first_->next_;
        int res = munmap (it, pagesize);
        assert (0 == res);
    }

    last_ = 0;
}

template <class Fun>
static BlockInfo * _rw_find_if (Fun fun)
{
    for (Blocks* it = first_; it; it = it->next_)
        for (size_t i = 0; i < it->nblocks_; ++i) {
            BlockInfo & info = it->blocks_ [i];
            if (fun (info))
                return &info;
        }

    return 0;
}

static inline bool is_unused (BlockInfo const & info)
{
    return !info.used_;
}

// returns pointer to the first unused BlockInfo
// if none unused items tries allocate more blocks
// returns 0 if no memory
static BlockInfo * _rw_find_unused ()
{
    BlockInfo * res = _rw_find_if (&is_unused);

    if (!res && _rw_allocate_blocks ())
        res = _rw_find_if (&is_unused);

    return res;
}

struct is_addr
{
    void* const addr_;

    is_addr (void* addr) : addr_ (addr) {}

    bool operator() (BlockInfo const & info) const
    {
        return info.used_ && addr_ == info.data_;
    }
};

// returns pointer to the BlockInfo which corresponds to addr
// returns 0 if addr is not valid pointer, returned by rw_alloc
static BlockInfo * _rw_find_by_addr (void* addr)
{
    return _rw_find_if (is_addr (addr));
}

static inline int _rw_get_prot (int flags)
{
    return (flags & RW_PROT_READ  ? PROT_READ  : 0)
         | (flags & RW_PROT_WRITE ? PROT_WRITE : 0)
         | (flags & RW_PROT_EXEC  ? PROT_EXEC  : 0);
}

static inline int getenvflags ()
{
    if (const char * envvar = getenv ("RWSTD_ALLOC_FLAGS"))
        return atoi (envvar);

    return 0;
}

/************************************************************************/

_TEST_EXPORT void*
rw_alloc(size_t nbytes, int flags/* = -1*/)
{
    static const int RWSTD_ALLOC_FLAGS = getenvflags ();

    // redefine flags if environment variable was set
    if (0 != RWSTD_ALLOC_FLAGS)
        flags = RWSTD_ALLOC_FLAGS;

    if (BlockInfo * info = _rw_find_unused ()) {

        BlockInfo newinfo;

        newinfo.used_ = true;
        newinfo.udsz_ = nbytes;
        newinfo.flags_ = flags;

        if (-1 == flags) {

            newinfo.addr_ = malloc (nbytes);

            if (newinfo.addr_) {
                newinfo.size_ = nbytes;
                newinfo.data_ = newinfo.addr_;
            }
        }
        else {

            size_t page_size = getpagesize ();

            size_t size = nbytes + page_size;

            size_t offset = size % page_size;

            if (offset) {
                offset = page_size - offset;
                size += offset;
            }

            newinfo.addr_ = mmap (0, size, _rw_get_prot(flags),
                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
            if (MAP_FAILED != newinfo.addr_) {

                char* data = _RWSTD_STATIC_CAST (char*, newinfo.addr_);
                char* guard = data;
                
                if (RW_PROT_BELOW & flags)
                    offset = page_size;
                else
                    guard += size - page_size;

                // deny access to the guard page
                int res = mprotect (guard, page_size, PROT_NONE);
                assert (0 == res);

                newinfo.size_ = size;
                newinfo.data_ = data + offset;
            }
        }

        if (newinfo.data_) {

            MemRWGuard guard (info, sizeof (*info));
            *info = newinfo;

            ++stats_.blocks_;
            if (stats_.blocks_ > stats_.maxblocks_)
                stats_.maxblocks_ = stats_.blocks_;

            return info->data_;
        }
    }

    return 0;
}

_TEST_EXPORT void
rw_free(void* addr)
{
    if (BlockInfo * info = _rw_find_by_addr (addr)) {

        if (-1 == info->flags_)
            free (addr);
        else {
            int res = munmap (info->addr_, info->size_);
            assert (0 == res);
        }

        {
            MemRWGuard guard (info, sizeof (*info));
            *info = BlockInfo ();
        }

        if (0 == --stats_.blocks_)
            _rw_free_blocks ();
    }
    else
        assert (!"Invalid addr passed to the rw_free");
}

Reply via email to