Commit cb0bf6ab authored by Noah Orensa's avatar Noah Orensa
Browse files

track allocations created by mmap()

parent 4f65ad86
......@@ -2,6 +2,7 @@
#include <dtest_core/call_stack.h>
#include <mutex>
#include <map>
#include <unordered_map>
#include <string>
#include <dlfcn.h>
......@@ -22,6 +23,7 @@ private:
volatile bool _track = false;
std::unordered_map<void *, Allocation> _blocks;
std::map<char *, Allocation> _orderedBlocks;
size_t _allocateSize = 0;
size_t _freeSize = 0;
......@@ -64,10 +66,16 @@ public:
void track(void *ptr, size_t size);
void track_mapped(char *ptr, size_t size);
void retrack(void *oldPtr, void *newPtr, size_t newSize);
void retrack_mapped(char *oldPtr, size_t oldSize, char *newPtr, size_t newSize);
void remove(void *ptr);
void remove_mapped(char *ptr, size_t size);
void clear();
std::string report();
......
......@@ -157,6 +157,10 @@ struct LibC {
void * (*reallocarray)(void *, size_t, size_t) = nullptr;
void (*free)(void *) = nullptr;
void * (*mmap)(void *, size_t, int, int, int, __off_t) = nullptr;
void * (*mremap)(void *, size_t, size_t, int, ...) = nullptr;
int (*munmap)(void *, size_t) = nullptr;
ssize_t (*send)(int, const void *, size_t, int) = nullptr;
ssize_t (*sendto)(int, const void *, size_t, int, const struct sockaddr *, socklen_t) = nullptr;
ssize_t (*recv)(int, void *, size_t, int) = nullptr;
......
......@@ -132,6 +132,20 @@ void Memory::track(void *ptr, size_t size) {
_exit();
}
void Memory::track_mapped(char *ptr, size_t size) {
if (! _enter()) return;
auto callstack = CallStack::trace(2);
if (_canTrackAlloc(callstack)) {
_mtx.lock();
_orderedBlocks.insert({ ptr + size - 1, { size, std::move(callstack) } });
_allocateSize += size;
_mtx.unlock();
}
_exit();
}
void Memory::retrack(void *oldPtr, void *newPtr, size_t newSize) {
if (! _enter()) return;
_mtx.lock();
......@@ -169,6 +183,94 @@ void Memory::retrack(void *oldPtr, void *newPtr, size_t newSize) {
_exit();
}
void Memory::retrack_mapped(char *oldPtr, size_t oldSize, char *newPtr, size_t newSize) {
if (! _enter()) return;
_mtx.lock();
while (oldSize > 0) {
auto it = _orderedBlocks.lower_bound(oldPtr);
if (it == _orderedBlocks.end()) break;
auto p = it->first - it->second.size + 1;
if (p > oldPtr) break;
auto alloc = std::move(it->second);
_orderedBlocks.erase(it);
if (oldPtr == p) {
if (alloc.size > oldSize) {
p += oldSize;
alloc.size -= oldSize;
_orderedBlocks.insert({ p + alloc.size - 1, std::move(alloc) });
_freeSize += oldSize;
oldSize = 0;
}
else {
_freeSize += alloc.size;
oldPtr += alloc.size;
oldSize -= alloc.size;
}
}
else {
size_t off = oldPtr - p;
size_t rem = alloc.size - off;
if (rem > oldSize) {
alloc.size = off;
_orderedBlocks.insert({ p + alloc.size - 1, alloc});
p = oldPtr + oldSize;
alloc.size = rem - oldSize;
_orderedBlocks.insert({ p + alloc.size - 1, std::move(alloc) });
_freeSize += oldSize;
oldSize = 0;
}
else {
_freeSize += rem;
oldPtr += rem;
oldSize -= rem;
alloc.size -= rem;
_orderedBlocks.insert({ p + alloc.size - 1, std::move(alloc) });
}
}
}
if (oldSize > 0) {
_mtx.unlock();
bool error = _canTrackDealloc(CallStack::trace(2));
_exit();
if (error) {
sandbox().exitAll();
char buf[64];
snprintf(buf, sizeof(buf), "no valid memory block at %p", oldPtr);
throw SandboxFatalException(
FatalError::MEMORY_BLOCK_DOES_NOT_EXIST,
buf,
2
);
}
return;
}
auto callstack = CallStack::trace(2);
if (_canTrackAlloc(callstack)) {
_orderedBlocks.insert({ newPtr + newSize - 1, { newSize, std::move(callstack) } });
_allocateSize += newSize;
}
_mtx.unlock();
_exit();
}
void Memory::remove(void *ptr) {
if (! _enter()) return;
_mtx.lock();
......@@ -203,6 +305,88 @@ void Memory::remove(void *ptr) {
_exit();
}
void Memory::remove_mapped(char *ptr, size_t size) {
if (! _enter()) return;
_mtx.lock();
while (size > 0) {
auto it = _orderedBlocks.lower_bound(ptr);
if (it == _orderedBlocks.end()) break;
auto p = it->first - it->second.size + 1;
if (p > ptr) break;
auto alloc = std::move(it->second);
_orderedBlocks.erase(it);
if (ptr == p) {
if (alloc.size > size) {
p += size;
alloc.size -= size;
_orderedBlocks.insert({ p + alloc.size - 1, std::move(alloc) });
_freeSize += size;
size = 0;
}
else {
_freeSize += alloc.size;
ptr += alloc.size;
size -= alloc.size;
}
}
else {
size_t off = ptr - p;
size_t rem = alloc.size - off;
if (rem > size) {
alloc.size = off;
_orderedBlocks.insert({ p + alloc.size - 1, alloc});
p = ptr + size;
alloc.size = rem - size;
_orderedBlocks.insert({ p + alloc.size - 1, std::move(alloc) });
_freeSize += size;
size = 0;
}
else {
_freeSize += rem;
ptr += rem;
size -= rem;
alloc.size -= rem;
_orderedBlocks.insert({ p + alloc.size - 1, std::move(alloc) });
}
}
}
if (size > 0) {
_mtx.unlock();
bool error = _canTrackDealloc(CallStack::trace(2));
_exit();
if (error) {
sandbox().exitAll();
char buf[64];
snprintf(buf, sizeof(buf), "no valid memory block at %p", ptr);
throw SandboxFatalException(
FatalError::MEMORY_BLOCK_DOES_NOT_EXIST,
buf,
2
);
}
return;
}
_mtx.unlock();
_exit();
}
void Memory::clear() {
_enter();
_mtx.lock();
......@@ -214,6 +398,12 @@ void Memory::clear() {
}
_blocks.clear();
for (const auto &block : _orderedBlocks) {
_freeSize += block.second.size;
libc().munmap(block.first - block.second.size + 1, block.second.size);
}
_orderedBlocks.clear();
_mtx.unlock();
_exit();
}
......@@ -225,7 +415,12 @@ std::string Memory::report() {
std::stringstream s;
for (const auto & block : _blocks) {
s << "\nBlock @ " << block.first << " allocated from:\n" << block.second.callstack.toString();;
s << "\nBlock @ " << block.first << " allocated from:\n" << block.second.callstack.toString();
}
for (const auto & block : _orderedBlocks) {
s << "\nBlock @ " << (void *) (block.first - block.second.size + 1)
<< " allocated from:\n" << block.second.callstack.toString();
}
_mtx.unlock();
......
#include <dtest_core/memory.h>
#include <dtest_core/sandbox.h>
#include <malloc.h>
#include <sys/mman.h>
#include <stdarg.h>
using namespace dtest;
......@@ -104,6 +106,45 @@ void free(void *__ptr) {
libc().free(__ptr);
}
// mmap & friends
void * mmap(void *__addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset) {
void *ptr;
ptr = libc().mmap(__addr, __len, __prot, __flags, __fd, __offset);
if (ptr != MAP_FAILED && _mmgr_instance) {
_mmgr_instance->track_mapped((char *) ptr, __len);
}
return ptr;
}
void * mremap(void *__addr, size_t __old_len, size_t __new_len, int __flags, ...) {
void *ptr;
va_list args;
va_start(args, __flags);
if ((__flags & MREMAP_FIXED) != 0) {
ptr = libc().mremap(__addr, __old_len, __new_len, __flags, va_arg(args, void *));
}
else {
ptr = libc().mremap(__addr, __old_len, __new_len, __flags);
}
va_end(args);
if (ptr != MAP_FAILED && _mmgr_instance) {
_mmgr_instance->retrack_mapped((char *) __addr, __old_len, (char *) ptr, __new_len);
}
return ptr;
}
int munmap(void *__addr, size_t __len) {
if (__addr && _mmgr_instance) _mmgr_instance->remove_mapped((char *) __addr, __len);
return libc().munmap(__addr, __len);
}
// operator new overrides /////////////////////////////////////////////////////
void * operator new(size_t count) {
......
......@@ -360,6 +360,12 @@ void LibC::_init() {
free = (void (*)(void *)) dlsym(RTLD_NEXT, "free");
mmap = (void *(*)(void *, size_t, int, int, int, __off_t)) dlsym(RTLD_NEXT, "mmap");
mremap = (void *(*)(void *, size_t, size_t, int, ...)) dlsym(RTLD_NEXT, "mremap");
munmap = (int (*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap");
send = (ssize_t (*)(int, const void *, size_t, int)) dlsym(RTLD_NEXT, "send");
sendto = (ssize_t (*)(int, const void *, size_t, int, const struct sockaddr *, socklen_t)) dlsym(RTLD_NEXT, "sendto");
......
......@@ -83,22 +83,22 @@ void UnitTest::_driverRun() {
}
bool UnitTest::_hasMemoryReport() {
return _usedResources.memory.allocate.count > 0
|| _usedResources.memory.deallocate.count > 0;
return _usedResources.memory.allocate.size > 0
|| _usedResources.memory.deallocate.size > 0;
}
std::string UnitTest::_memoryReport() {
std::stringstream s;
if (_usedResources.memory.allocate.count > 0) {
if (_usedResources.memory.allocate.size > 0) {
s << "\"allocated\": {";
s << "\n \"size\": " << _usedResources.memory.allocate.size;
s << ",\n \"blocks\": " << _usedResources.memory.allocate.count;
s << "\n}";
if (_usedResources.memory.deallocate.count > 0) s << ",\n";
if (_usedResources.memory.deallocate.size > 0) s << ",\n";
}
if (_usedResources.memory.deallocate.count > 0) {
if (_usedResources.memory.deallocate.size > 0) {
s << "\"freed\": {";
s << "\n \"size\": " << _usedResources.memory.deallocate.size;
s << ",\n \"blocks\": " << _usedResources.memory.deallocate.count;
......
#include <dtest.h>
#include <iostream>
#include <thread>
#include <sys/mman.h>
#include <unistd.h>
unit("root-test")
.body([] {
......@@ -23,7 +25,7 @@ unit("unit-test", "timeout")
.body([] {
});
unit("unit-test", "mem-leak")
unit("unit-test", "malloc-mem-leak")
.expect(Status::PASS_WITH_MEMORY_LEAK)
.body([] {
#pragma GCC diagnostic push
......@@ -41,6 +43,67 @@ unit("unit-test", "invalid-free")
#pragma GCC diagnostic pop
});
unit("unit-test", "mmap")
.body([] {
size_t sz = getpagesize();
void *ptr = mmap(nullptr, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(ptr != MAP_FAILED);
assert(munmap(ptr, sz) == 0);
});
unit("unit-test", "mmap-munmap-multiple")
.body([] {
size_t sz = getpagesize();
void *ptr = mmap(nullptr, 2 * sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(ptr != MAP_FAILED);
assert(munmap(ptr, sz) == 0);
assert(munmap((char *) ptr + sz, sz) == 0);
});
unit("unit-test", "mmap-mem-leak")
.expect(Status::PASS_WITH_MEMORY_LEAK)
.body([] {
size_t sz = getpagesize();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
mmap(nullptr, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#pragma GCC diagnostic pop
});
unit("unit-test", "invalid-munmap")
.expect(Status::FAIL)
.body([] {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
munmap((void *) 0xdead, 1);
#pragma GCC diagnostic pop
});
unit("unit-test", "mmap-munmap-partial")
.expect(Status::PASS_WITH_MEMORY_LEAK)
.body([] {
size_t sz = getpagesize();
void *ptr = mmap(nullptr, 3 * sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(ptr != MAP_FAILED);
assert(munmap((char *) ptr + sz, sz) == 0);
});
unit("unit-test", "mmap-mremap")
.expect(Status::PASS_WITH_MEMORY_LEAK)
.body([] {
size_t sz = getpagesize();
void *ptr = mmap(nullptr, 3 * sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(ptr != MAP_FAILED);
mremap((char *) ptr + sz, sz, 2 * sz, MREMAP_MAYMOVE);
mremap(ptr, sz, sz * 2, MREMAP_MAYMOVE);
});
unit("unit-test", "error-message")
.body([] {
err("error");
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment