2020-07-15 16:46:50 -03:00
/*
* Copyright ( c ) 2020 , Andreas Kling < kling @ serenityos . org >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
# include "MallocTracer.h"
# include "Emulator.h"
2020-07-16 12:04:27 -03:00
# include "MmapRegion.h"
2020-07-15 16:46:50 -03:00
# include <AK/LogStream.h>
2020-10-15 15:46:52 -03:00
# include <AK/TemporaryChange.h>
2020-11-14 14:27:08 -03:00
# include <mallocdefs.h>
2020-07-20 21:29:59 -03:00
# include <string.h>
2020-07-15 16:46:50 -03:00
2020-07-16 12:16:21 -03:00
//#define REACHABLE_DEBUG
2020-07-15 16:46:50 -03:00
namespace UserspaceEmulator {
2020-11-16 10:39:05 -03:00
MallocTracer : : MallocTracer ( Emulator & emulator )
: m_emulator ( emulator )
2020-07-15 16:46:50 -03:00
{
}
2020-11-16 05:33:30 -03:00
template < typename Callback >
inline void MallocTracer : : for_each_mallocation ( Callback callback ) const
2020-11-14 19:07:07 -03:00
{
2020-11-16 10:39:05 -03:00
m_emulator . mmu ( ) . for_each_region ( [ & ] ( auto & region ) {
2020-11-16 05:33:30 -03:00
if ( region . is_mmap ( ) & & static_cast < const MmapRegion & > ( region ) . is_malloc_block ( ) ) {
auto * malloc_data = static_cast < MmapRegion & > ( region ) . malloc_metadata ( ) ;
for ( auto & mallocation : malloc_data - > mallocations ) {
if ( mallocation . used & & callback ( mallocation ) = = IterationDecision : : Break )
return IterationDecision : : Break ;
}
}
return IterationDecision : : Continue ;
} ) ;
2020-11-14 19:07:07 -03:00
}
2020-07-15 16:46:50 -03:00
void MallocTracer : : target_did_malloc ( Badge < SoftCPU > , FlatPtr address , size_t size )
{
2020-11-16 10:39:05 -03:00
auto * region = m_emulator . mmu ( ) . find_region ( { 0x20 , address } ) ;
2020-07-20 21:29:59 -03:00
ASSERT ( region ) ;
ASSERT ( region - > is_mmap ( ) ) ;
auto & mmap_region = static_cast < MmapRegion & > ( * region ) ;
2020-08-01 04:13:45 -03:00
// Mark the containing mmap region as a malloc block!
mmap_region . set_malloc ( true ) ;
2020-07-20 21:29:59 -03:00
auto * shadow_bits = mmap_region . shadow_data ( ) + address - mmap_region . base ( ) ;
memset ( shadow_bits , 0 , size ) ;
2020-07-15 16:46:50 -03:00
if ( auto * existing_mallocation = find_mallocation ( address ) ) {
ASSERT ( existing_mallocation - > freed ) ;
existing_mallocation - > size = size ;
existing_mallocation - > freed = false ;
2020-11-16 10:39:05 -03:00
existing_mallocation - > malloc_backtrace = m_emulator . raw_backtrace ( ) ;
2020-07-16 15:55:41 -03:00
existing_mallocation - > free_backtrace . clear ( ) ;
2020-07-15 16:46:50 -03:00
return ;
}
2020-11-14 14:27:08 -03:00
2020-11-16 09:18:28 -03:00
MallocRegionMetadata * malloc_data = static_cast < MmapRegion & > ( * region ) . malloc_metadata ( ) ;
if ( ! malloc_data ) {
auto new_malloc_data = make < MallocRegionMetadata > ( ) ;
malloc_data = new_malloc_data . ptr ( ) ;
static_cast < MmapRegion & > ( * region ) . set_malloc_metadata ( { } , move ( new_malloc_data ) ) ;
malloc_data - > address = region - > base ( ) ;
malloc_data - > chunk_size = mmap_region . read32 ( offsetof ( CommonHeader , m_size ) ) . value ( ) ;
bool is_chunked_block = malloc_data - > chunk_size < = size_classes [ num_size_classes - 1 ] ;
if ( is_chunked_block )
2020-11-16 05:33:30 -03:00
malloc_data - > mallocations . resize ( ( ChunkedBlock : : block_size - sizeof ( ChunkedBlock ) ) / malloc_data - > chunk_size ) ;
2020-11-16 09:18:28 -03:00
else
malloc_data - > mallocations . resize ( 1 ) ;
dbgln ( " Tracking malloc block @ {:p} with chunk_size={}, chunk_count={} " , malloc_data - > address , malloc_data - > chunk_size , malloc_data - > mallocations . size ( ) ) ;
2020-11-14 14:27:08 -03:00
}
2020-11-16 10:39:05 -03:00
malloc_data - > mallocation_for_address ( address ) = { address , size , true , false , m_emulator . raw_backtrace ( ) , Vector < FlatPtr > ( ) } ;
2020-07-15 16:46:50 -03:00
}
2020-11-16 05:33:30 -03:00
ALWAYS_INLINE Mallocation & MallocRegionMetadata : : mallocation_for_address ( FlatPtr address ) const
2020-11-14 14:36:17 -03:00
{
return const_cast < Mallocation & > ( this - > mallocations [ chunk_index_for_address ( address ) ] ) ;
}
2020-11-16 05:33:30 -03:00
ALWAYS_INLINE size_t MallocRegionMetadata : : chunk_index_for_address ( FlatPtr address ) const
2020-11-14 14:36:17 -03:00
{
2020-11-16 09:18:28 -03:00
bool is_chunked_block = chunk_size < = size_classes [ num_size_classes - 1 ] ;
if ( ! is_chunked_block ) {
// This is a BigAllocationBlock
return 0 ;
}
2020-11-14 14:36:17 -03:00
auto chunk_offset = address - ( this - > address + sizeof ( ChunkedBlock ) ) ;
return chunk_offset / this - > chunk_size ;
}
2020-07-15 16:46:50 -03:00
void MallocTracer : : target_did_free ( Badge < SoftCPU > , FlatPtr address )
{
2020-07-15 18:53:58 -03:00
if ( ! address )
return ;
2020-11-14 14:27:08 -03:00
if ( auto * mallocation = find_mallocation ( address ) ) {
if ( mallocation - > freed ) {
reportln ( " \n =={}== \033 [31;1mDouble free() \033 [0m, {:p} " , getpid ( ) , address ) ;
reportln ( " =={}== Address {} has already been passed to free() " , getpid ( ) , address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( ) ;
2020-11-14 14:27:08 -03:00
} else {
mallocation - > freed = true ;
2020-11-16 10:39:05 -03:00
mallocation - > free_backtrace = m_emulator . raw_backtrace ( ) ;
2020-07-15 16:46:50 -03:00
}
2020-11-14 14:27:08 -03:00
return ;
2020-07-15 16:46:50 -03:00
}
2020-09-30 10:30:42 -03:00
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1mInvalid free() \033 [0m, {:p} " , getpid ( ) , address ) ;
reportln ( " =={}== Address {} has never been returned by malloc() " , getpid ( ) , address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( ) ;
2020-07-15 16:46:50 -03:00
}
2020-11-08 06:10:41 -03:00
void MallocTracer : : target_did_realloc ( Badge < SoftCPU > , FlatPtr address , size_t size )
{
2020-11-16 10:39:05 -03:00
auto * region = m_emulator . mmu ( ) . find_region ( { 0x20 , address } ) ;
2020-11-08 06:10:41 -03:00
ASSERT ( region ) ;
ASSERT ( region - > is_mmap ( ) ) ;
auto & mmap_region = static_cast < MmapRegion & > ( * region ) ;
ASSERT ( mmap_region . is_malloc_block ( ) ) ;
auto * existing_mallocation = find_mallocation ( address ) ;
ASSERT ( existing_mallocation ) ;
ASSERT ( ! existing_mallocation - > freed ) ;
size_t old_size = existing_mallocation - > size ;
auto * shadow_bits = mmap_region . shadow_data ( ) + address - mmap_region . base ( ) ;
if ( size > old_size ) {
memset ( shadow_bits + old_size , 1 , size - old_size ) ;
} else {
memset ( shadow_bits + size , 1 , old_size - size ) ;
}
existing_mallocation - > size = size ;
// FIXME: Should we track malloc/realloc backtrace separately perhaps?
2020-11-16 10:39:05 -03:00
existing_mallocation - > malloc_backtrace = m_emulator . raw_backtrace ( ) ;
2020-11-08 06:10:41 -03:00
}
2020-11-16 09:18:28 -03:00
Mallocation * MallocTracer : : find_mallocation ( FlatPtr address )
{
2020-11-16 10:39:05 -03:00
auto * region = m_emulator . mmu ( ) . find_region ( { 0x23 , address } ) ;
2020-11-16 09:18:28 -03:00
if ( ! region )
return nullptr ;
return find_mallocation ( * region , address ) ;
2020-07-15 16:46:50 -03:00
}
2020-11-16 05:33:30 -03:00
Mallocation * MallocTracer : : find_mallocation_before ( FlatPtr address )
2020-08-01 04:44:19 -03:00
{
Mallocation * found_mallocation = nullptr ;
2020-11-14 14:27:08 -03:00
for_each_mallocation ( [ & ] ( auto & mallocation ) {
2020-08-01 04:44:19 -03:00
if ( mallocation . address > = address )
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
2020-08-01 04:44:19 -03:00
if ( ! found_mallocation | | ( mallocation . address > found_mallocation - > address ) )
2020-11-14 14:27:08 -03:00
found_mallocation = const_cast < Mallocation * > ( & mallocation ) ;
return IterationDecision : : Continue ;
} ) ;
2020-08-01 04:44:19 -03:00
return found_mallocation ;
}
2020-11-16 05:33:30 -03:00
Mallocation * MallocTracer : : find_mallocation_after ( FlatPtr address )
2020-11-13 06:58:31 -03:00
{
Mallocation * found_mallocation = nullptr ;
2020-11-14 14:27:08 -03:00
for_each_mallocation ( [ & ] ( auto & mallocation ) {
2020-11-13 06:58:31 -03:00
if ( mallocation . address < = address )
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
2020-11-13 06:58:31 -03:00
if ( ! found_mallocation | | ( mallocation . address < found_mallocation - > address ) )
2020-11-14 14:27:08 -03:00
found_mallocation = const_cast < Mallocation * > ( & mallocation ) ;
return IterationDecision : : Continue ;
} ) ;
2020-11-13 06:58:31 -03:00
return found_mallocation ;
}
2020-11-16 09:18:28 -03:00
void MallocTracer : : audit_read ( const Region & region , FlatPtr address , size_t size )
2020-07-15 16:46:50 -03:00
{
2020-07-16 12:04:27 -03:00
if ( ! m_auditing_enabled )
return ;
2020-11-16 10:39:05 -03:00
if ( m_emulator . is_in_malloc_or_free ( ) )
2020-07-15 16:46:50 -03:00
return ;
2020-11-16 09:18:28 -03:00
auto * mallocation = find_mallocation ( region , address ) ;
2020-08-01 04:44:19 -03:00
if ( ! mallocation ) {
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1mHeap buffer overflow \033 [0m, invalid {}-byte read at address {:p} " , getpid ( ) , size , address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( ) ;
2020-11-13 06:58:31 -03:00
auto * mallocation_before = find_mallocation_before ( address ) ;
auto * mallocation_after = find_mallocation_after ( address ) ;
size_t distance_to_mallocation_before = mallocation_before ? ( address - mallocation_before - > address - mallocation_before - > size ) : 0 ;
size_t distance_to_mallocation_after = mallocation_after ? ( mallocation_after - > address - address ) : 0 ;
if ( mallocation_before & & ( ! mallocation_after | | distance_to_mallocation_before < distance_to_mallocation_after ) ) {
reportln ( " =={}== Address is {} byte(s) after block of size {}, identity {:p}, allocated at: " , getpid ( ) , distance_to_mallocation_before , mallocation_before - > size , mallocation_before - > address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation_before - > malloc_backtrace ) ;
2020-11-13 06:58:31 -03:00
return ;
}
if ( mallocation_after & & ( ! mallocation_before | | distance_to_mallocation_after < distance_to_mallocation_before ) ) {
reportln ( " =={}== Address is {} byte(s) before block of size {}, identity {:p}, allocated at: " , getpid ( ) , distance_to_mallocation_after , mallocation_after - > size , mallocation_after - > address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation_after - > malloc_backtrace ) ;
2020-08-01 04:44:19 -03:00
}
2020-07-15 16:46:50 -03:00
return ;
2020-08-01 04:44:19 -03:00
}
2020-07-15 16:46:50 -03:00
size_t offset_into_mallocation = address - mallocation - > address ;
if ( mallocation - > freed ) {
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1mUse-after-free \033 [0m, invalid {}-byte read at address {:p} " , getpid ( ) , size , address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( ) ;
2020-10-24 04:45:51 -03:00
reportln ( " =={}== Address is {} byte(s) into block of size {}, allocated at: " , getpid ( ) , offset_into_mallocation , mallocation - > size ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation - > malloc_backtrace ) ;
2020-10-24 04:45:51 -03:00
reportln ( " =={}== Later freed at: " , getpid ( ) ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation - > free_backtrace ) ;
2020-07-15 16:46:50 -03:00
return ;
}
}
2020-11-16 09:18:28 -03:00
void MallocTracer : : audit_write ( const Region & region , FlatPtr address , size_t size )
2020-07-15 16:46:50 -03:00
{
2020-07-16 12:04:27 -03:00
if ( ! m_auditing_enabled )
return ;
2020-11-16 10:39:05 -03:00
if ( m_emulator . is_in_malloc_or_free ( ) )
2020-07-15 16:46:50 -03:00
return ;
2020-11-16 09:18:28 -03:00
auto * mallocation = find_mallocation ( region , address ) ;
2020-08-01 04:44:19 -03:00
if ( ! mallocation ) {
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1mHeap buffer overflow \033 [0m, invalid {}-byte write at address {:p} " , getpid ( ) , size , address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( ) ;
2020-11-13 06:58:31 -03:00
auto * mallocation_before = find_mallocation_before ( address ) ;
auto * mallocation_after = find_mallocation_after ( address ) ;
size_t distance_to_mallocation_before = mallocation_before ? ( address - mallocation_before - > address - mallocation_before - > size ) : 0 ;
size_t distance_to_mallocation_after = mallocation_after ? ( mallocation_after - > address - address ) : 0 ;
if ( mallocation_before & & ( ! mallocation_after | | distance_to_mallocation_before < distance_to_mallocation_after ) ) {
reportln ( " =={}== Address is {} byte(s) after block of size {}, identity {:p}, allocated at: " , getpid ( ) , distance_to_mallocation_before , mallocation_before - > size , mallocation_before - > address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation_before - > malloc_backtrace ) ;
2020-11-13 06:58:31 -03:00
return ;
}
if ( mallocation_after & & ( ! mallocation_before | | distance_to_mallocation_after < distance_to_mallocation_before ) ) {
reportln ( " =={}== Address is {} byte(s) before block of size {}, identity {:p}, allocated at: " , getpid ( ) , distance_to_mallocation_after , mallocation_after - > size , mallocation_after - > address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation_after - > malloc_backtrace ) ;
2020-08-01 04:44:19 -03:00
}
2020-07-15 16:46:50 -03:00
return ;
2020-08-01 04:44:19 -03:00
}
2020-07-15 16:46:50 -03:00
size_t offset_into_mallocation = address - mallocation - > address ;
if ( mallocation - > freed ) {
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1mUse-after-free \033 [0m, invalid {}-byte write at address {:p} " , getpid ( ) , size , address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( ) ;
2020-10-24 04:45:51 -03:00
reportln ( " =={}== Address is {} byte(s) into block of size {}, allocated at: " , getpid ( ) , offset_into_mallocation , mallocation - > size ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation - > malloc_backtrace ) ;
2020-10-24 04:45:51 -03:00
reportln ( " =={}== Later freed at: " , getpid ( ) ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation - > free_backtrace ) ;
2020-07-15 16:46:50 -03:00
return ;
}
}
2020-07-16 12:04:27 -03:00
bool MallocTracer : : is_reachable ( const Mallocation & mallocation ) const
{
ASSERT ( ! mallocation . freed ) ;
2020-11-14 14:27:08 -03:00
bool reachable = false ;
2020-07-16 12:04:27 -03:00
// 1. Search in active (non-freed) mallocations for pointers to this mallocation
2020-11-14 14:27:08 -03:00
for_each_mallocation ( [ & ] ( auto & other_mallocation ) {
2020-07-16 12:04:27 -03:00
if ( & mallocation = = & other_mallocation )
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
2020-07-16 19:24:23 -03:00
if ( other_mallocation . freed )
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
2020-07-16 12:04:27 -03:00
size_t pointers_in_mallocation = other_mallocation . size / sizeof ( u32 ) ;
for ( size_t i = 0 ; i < pointers_in_mallocation ; + + i ) {
2020-11-16 10:39:05 -03:00
auto value = m_emulator . mmu ( ) . read32 ( { 0x20 , other_mallocation . address + i * sizeof ( u32 ) } ) ;
2020-07-20 21:29:59 -03:00
if ( value . value ( ) = = mallocation . address & & ! value . is_uninitialized ( ) ) {
2020-07-16 12:16:21 -03:00
# ifdef REACHABLE_DEBUG
2020-10-24 04:45:51 -03:00
reportln ( " mallocation {:p} is reachable from other mallocation {:p} " , mallocation . address , other_mallocation . address ) ;
2020-07-16 12:16:21 -03:00
# endif
2020-11-14 14:27:08 -03:00
reachable = true ;
return IterationDecision : : Break ;
2020-07-16 12:04:27 -03:00
}
}
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
} ) ;
2020-07-16 12:04:27 -03:00
2020-11-14 14:27:08 -03:00
if ( reachable )
return true ;
2020-07-16 14:14:54 -03:00
2020-07-16 12:04:27 -03:00
// 2. Search in other memory regions for pointers to this mallocation
2020-11-16 10:39:05 -03:00
m_emulator . mmu ( ) . for_each_region ( [ & ] ( auto & region ) {
2020-07-16 12:04:27 -03:00
// Skip the stack
if ( region . is_stack ( ) )
2020-07-16 14:14:54 -03:00
return IterationDecision : : Continue ;
2020-07-16 14:27:03 -03:00
if ( region . is_text ( ) )
return IterationDecision : : Continue ;
2020-07-16 12:04:27 -03:00
// Skip malloc blocks
if ( region . is_mmap ( ) & & static_cast < const MmapRegion & > ( region ) . is_malloc_block ( ) )
2020-07-16 14:14:54 -03:00
return IterationDecision : : Continue ;
2020-07-16 12:04:27 -03:00
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 ) ) ;
2020-07-20 21:29:59 -03:00
if ( value . value ( ) = = mallocation . address & & ! value . is_uninitialized ( ) ) {
2020-07-16 12:16:21 -03:00
# ifdef REACHABLE_DEBUG
2020-10-24 04:45:51 -03:00
reportln ( " mallocation {:p} is reachable from region {:p}-{:p} " , mallocation . address , region . base ( ) , region . end ( ) - 1 ) ;
2020-07-16 12:16:21 -03:00
# endif
2020-07-16 14:14:54 -03:00
reachable = true ;
return IterationDecision : : Break ;
2020-07-16 12:04:27 -03:00
}
}
2020-07-16 14:14:54 -03:00
return IterationDecision : : Continue ;
} ) ;
return reachable ;
2020-07-16 12:04:27 -03:00
}
void MallocTracer : : dump_leak_report ( )
{
TemporaryChange change ( m_auditing_enabled , false ) ;
2020-07-16 14:15:38 -03:00
size_t bytes_leaked = 0 ;
2020-07-16 12:04:27 -03:00
size_t leaks_found = 0 ;
2020-11-14 14:27:08 -03:00
for_each_mallocation ( [ & ] ( auto & mallocation ) {
2020-07-16 12:04:27 -03:00
if ( mallocation . freed )
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
2020-07-16 12:04:27 -03:00
if ( is_reachable ( mallocation ) )
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
2020-07-16 12:04:27 -03:00
+ + leaks_found ;
2020-07-16 14:15:38 -03:00
bytes_leaked + = mallocation . size ;
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1mLeak \033 [0m, {}-byte allocation at address {:p} " , getpid ( ) , mallocation . size , mallocation . address ) ;
2020-11-16 10:39:05 -03:00
m_emulator . dump_backtrace ( mallocation . malloc_backtrace ) ;
2020-11-14 14:27:08 -03:00
return IterationDecision : : Continue ;
} ) ;
2020-07-16 12:04:27 -03:00
2020-07-16 14:15:38 -03:00
if ( ! leaks_found )
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [32;1mNo leaks found! \033 [0m " , getpid ( ) ) ;
2020-07-16 14:15:38 -03:00
else
2020-10-24 04:45:51 -03:00
reportln ( " \n =={}== \033 [31;1m{} leak(s) found: {} byte(s) leaked \033 [0m " , getpid ( ) , leaks_found , bytes_leaked ) ;
2020-07-16 12:04:27 -03:00
}
2020-07-15 16:46:50 -03:00
}