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");
}