1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 03:27:44 +00:00

UserspaceEmulator: Implement very basic leak checking :^)

Upon exit, the emulator will now print a leak report of any malloc
allocations that are still live and don't have pointers to their base
address anywhere in either another live mallocation, or in one of the
non-malloc-block memory regions.

Note that the malloc-block memory region check is not fully functional
and this will work even better once we get that fixed.

This is pretty cool. :^)
This commit is contained in:
Andreas Kling 2020-07-16 17:04:27 +02:00
parent 7e13244238
commit f6584bfc36
4 changed files with 78 additions and 2 deletions

View file

@ -174,6 +174,10 @@ int Emulator::exec()
if (trace) if (trace)
m_cpu.dump(); m_cpu.dump();
} }
if (auto* tracer = malloc_tracer())
tracer->dump_leak_report();
return m_exit_status; return m_exit_status;
} }

View file

@ -26,6 +26,7 @@
#include "MallocTracer.h" #include "MallocTracer.h"
#include "Emulator.h" #include "Emulator.h"
#include "MmapRegion.h"
#include <AK/LogStream.h> #include <AK/LogStream.h>
namespace UserspaceEmulator { namespace UserspaceEmulator {
@ -81,6 +82,9 @@ MallocTracer::Mallocation* MallocTracer::find_mallocation(FlatPtr address)
void MallocTracer::audit_read(FlatPtr address, size_t size) void MallocTracer::audit_read(FlatPtr address, size_t size)
{ {
if (!m_auditing_enabled)
return;
if (Emulator::the().is_in_malloc_or_free()) if (Emulator::the().is_in_malloc_or_free())
return; return;
@ -101,6 +105,9 @@ void MallocTracer::audit_read(FlatPtr address, size_t size)
void MallocTracer::audit_write(FlatPtr address, size_t size) void MallocTracer::audit_write(FlatPtr address, size_t size)
{ {
if (!m_auditing_enabled)
return;
if (Emulator::the().is_in_malloc_or_free()) if (Emulator::the().is_in_malloc_or_free())
return; return;
@ -112,11 +119,69 @@ void MallocTracer::audit_write(FlatPtr address, size_t size)
if (mallocation->freed) { if (mallocation->freed) {
dbgprintf("\n"); dbgprintf("\n");
dbgprintf("==%d== \033[31;1mUse-after-free\033[0m, invalid %zu-byte write at address %p\n", s_pid, size, address); dbgprintf("==%d== \033[31;1mUse-after-free\033[0m, invalid %zu-byte write at address %p\n", s_pid, size, address);
dbgprintf("==%d== Address is %zu bytes into freed block of size %zu\n", s_pid, offset_into_mallocation, mallocation->size); dbgprintf("==%d== Address is %zu bytes into freed block of size %zu\n", s_pid, offset_into_mallocation, mallocation->size);
Emulator::the().dump_backtrace(); Emulator::the().dump_backtrace();
return; return;
} }
} }
bool MallocTracer::is_reachable(const Mallocation& mallocation) const
{
ASSERT(!mallocation.freed);
// 1. Search in active (non-freed) mallocations for pointers to this mallocation
for (auto& other_mallocation : m_mallocations) {
if (&mallocation == &other_mallocation)
continue;
size_t pointers_in_mallocation = other_mallocation.size / sizeof(u32);
for (size_t i = 0; i < pointers_in_mallocation; ++i) {
auto value = Emulator::the().mmu().read32({ 0x20, other_mallocation.address + i * sizeof(u32) });
if (value == mallocation.address) {
dbgprintf("mallocation %p is reachable from other mallocation %p\n", mallocation.address, other_mallocation.address);
return true;
}
}
}
// 2. Search in other memory regions for pointers to this mallocation
for (auto& region : Emulator::the().mmu().regions()) {
// Skip the stack
if (region.is_stack())
continue;
// Skip malloc blocks
if (region.is_mmap() && static_cast<const MmapRegion&>(region).is_malloc_block())
continue;
size_t pointers_in_region = region.size() / sizeof(u32);
for (size_t i = 0; i < pointers_in_region; ++i) {
auto value = region.read32(i * sizeof(u32));
if (value == mallocation.address) {
dbgprintf("mallocation %p is reachable from region %p-%p\n", mallocation.address, region.base(), region.end() - 1);
return true;
}
}
}
return false;
}
void MallocTracer::dump_leak_report()
{
TemporaryChange change(m_auditing_enabled, false);
size_t leaks_found = 0;
for (auto& mallocation : m_mallocations) {
if (mallocation.freed)
continue;
if (is_reachable(mallocation))
continue;
++leaks_found;
dbgprintf("\n");
dbgprintf("==%d== \033[31;1mLeak\033[0m, %zu-byte allocation at address %p\n", s_pid, mallocation.size, mallocation.address);
}
dbgprintf("==%d== %zu leak(s) found\n", s_pid, leaks_found);
}
} }

View file

@ -44,6 +44,8 @@ public:
void audit_read(FlatPtr address, size_t); void audit_read(FlatPtr address, size_t);
void audit_write(FlatPtr address, size_t); void audit_write(FlatPtr address, size_t);
void dump_leak_report();
private: private:
struct Mallocation { struct Mallocation {
bool contains(FlatPtr a) const bool contains(FlatPtr a) const
@ -57,8 +59,11 @@ private:
}; };
Mallocation* find_mallocation(FlatPtr); Mallocation* find_mallocation(FlatPtr);
bool is_reachable(const Mallocation&) const;
Vector<Mallocation> m_mallocations; Vector<Mallocation> m_mallocations;
bool m_auditing_enabled { true };
}; };
} }

View file

@ -98,6 +98,8 @@ public:
SharedBufferRegion* shbuf_region(int shbuf_id); SharedBufferRegion* shbuf_region(int shbuf_id);
NonnullOwnPtrVector<Region>& regions() { return m_regions; }
private: private:
OwnPtr<Region> m_tls_region; OwnPtr<Region> m_tls_region;
NonnullOwnPtrVector<Region> m_regions; NonnullOwnPtrVector<Region> m_regions;