#include <vector>
#include <map>
#include <set>
#include <cassert>
#define DEBUG
class InplaceBuilder
{
public:
InplaceBuilder();
~InplaceBuilder();
void RegisterPointer( void** ptr, unsigned target_size );
void UnregisterPointer( void** ptr, unsigned target_size );
template< class T >
void RegisterPointer( T*& ptr, unsigned target_size )
{
RegisterPointer( (void**)&ptr, target_size );
}
template< class T >
void UnregisterPointer( T*& ptr, unsigned target_size )
{
UnregisterPointer( (void**)&ptr, target_size );
}
template< class T >
void RegisterRootObject( T* ptr )
{
assert( m_root == NULL );
m_blocks.insert( Block( ptr, sizeof( T ) ) );
m_root = ptr;
}
void Build( std::vector< char >& out ) const;
private:
struct Block
{
Block( void* _s, unsigned _l )
: start( _s ), length( _l ) {}
Block()
: start( NULL ), length( 0 ) {}
bool operator<( Block const& b ) const
{
if( start == b.start )
return length < b.length;
else
return start < b.start;
}
void* start;
unsigned length;
};
std::multiset< Block > m_blocks;
std::set< void** > m_pointers;
void* m_root;
void GetFinalBlockList( std::vector< Block >& blocks ) const;
void* FindContainingBlockBase( void* ptr ) const;
public:
#ifdef DEBUG
void DebugDump() const;
#endif
};
void FixupInplaceImage( void* buffer, unsigned length );
#include <iostream>
using namespace std;
#define REF_UNSIGNED( p, offset ) (*(unsigned*)(((char*)p) + offset))
InplaceBuilder::InplaceBuilder()
: m_root( NULL )
{
}
InplaceBuilder::~InplaceBuilder()
{
}
void InplaceBuilder::RegisterPointer( void** ptr, unsigned target_size )
{
m_pointers.insert( ptr );
if( target_size != 0 )
m_blocks.insert( Block( *ptr, target_size ) );
}
void InplaceBuilder::UnregisterPointer( void** ptr, unsigned target_size )
{
if( target_size != 0 )
{
Block b( *ptr, target_size );
assert( m_blocks.find( b ) != m_blocks.end() );
m_blocks.erase( m_blocks.find( b ) );
}
assert( m_pointers.find( ptr ) != m_pointers.end() );
m_pointers.erase( ptr );
}
void InplaceBuilder::GetFinalBlockList( vector< Block >& blocks ) const
{
if( m_root )
blocks.push_back( Block( m_root, 0 ) );
for( set< Block >::const_iterator it = m_blocks.begin(); it != m_blocks.end(); ++it )
{
bool found = false;
for( unsigned i = 0; i < blocks.size(); ++i )
{
if( it->start == blocks[i].start &&
it->length > blocks[i].length )
{
blocks[i] = *it;
found = true;
break;
}
}
if( !found )
blocks.push_back( *it );
}
assert( blocks.size() == 0 || blocks[0].length > 0 );
}
void* InplaceBuilder::FindContainingBlockBase( void* ptr ) const
{
for( set< Block >::const_iterator it = m_blocks.begin(); it != m_blocks.end(); ++it )
{
Block const& b = *it;
if( ptr >= b.start && (char*)ptr < (char*)b.start + b.length )
return b.start;
}
return NULL;
}
void InplaceBuilder::Build( vector< char >& out ) const
{
assert( sizeof( unsigned ) == sizeof( void* ) );
vector< Block > blocks;
GetFinalBlockList( blocks ); // root guaranteed to be first
assert( blocks.size() == 0 || blocks[0].start == m_root );
map< void*, unsigned > block_offsets;
// determine file offsets for all blocks
unsigned offset = 0;
for( unsigned i = 0; i < blocks.size(); ++i )
{
block_offsets[blocks[i].start] = offset;
offset += blocks[i].length;
// align to 4 bytes
while( offset & 3 ) ++offset;
}
// offset is now the total file length
out.resize( offset + ( m_pointers.size() * sizeof( unsigned ) ) + sizeof( unsigned ) );
// add all blocks
for( unsigned i = 0; i < blocks.size(); ++i )
{
memcpy( &out[block_offsets[blocks[i].start]],
blocks[i].start,
blocks[i].length );
}
// convert pointers
vector< unsigned > pointer_fixup_table;
for( set< void** >::const_iterator it = m_pointers.begin(); it != m_pointers.end(); ++it )
{
void** pointer_itself = *it;
void* pointer_target = *pointer_itself;
// find the block the pointer target is inside
void* pointer_targets_block = FindContainingBlockBase( pointer_target );
assert( pointer_targets_block );
unsigned target_blocks_stored_offset = block_offsets[pointer_targets_block];
unsigned target_offset_within_block = (char*)pointer_target - (char*)pointer_targets_block;
unsigned target_value_to_store = target_blocks_stored_offset + target_offset_within_block;
// find the block the pointer itself is in
void* pointers_block = FindContainingBlockBase( (void*)pointer_itself );
assert( pointers_block );
unsigned pointers_offset_into_block = (char*)pointer_itself - (char*)pointers_block;
// find the pointer's stored position in the file
unsigned blocks_stored_offset = block_offsets[pointers_block];
unsigned pointers_stored_offset = blocks_stored_offset + pointers_offset_into_block;
// update the stored pointer in place
*(unsigned*)(&out[pointers_stored_offset]) = target_value_to_store;
pointer_fixup_table.push_back( pointers_stored_offset );
}
// store pointer fixup table and offset
memcpy( &out[offset], &pointer_fixup_table[0], pointer_fixup_table.size() * sizeof( unsigned ) );
unsigned table_offset = offset;
memcpy( &out[out.size()-sizeof( unsigned )], &table_offset, sizeof( unsigned ) );
}
void FixupInplaceImage( void* buffer, unsigned length )
{
unsigned ptr_table_offset = REF_UNSIGNED( buffer, length - sizeof( unsigned ) );
unsigned* ptr_table = (unsigned*)(((char*)buffer) + ptr_table_offset);
unsigned ptr_table_length = (length - ptr_table_offset) / sizeof( unsigned ) - 1;
for( unsigned i = 0; i < ptr_table_length; ++i )
{
unsigned old_ptr = REF_UNSIGNED( buffer, ptr_table[i] );
unsigned new_ptr = ((unsigned)buffer) + old_ptr;
REF_UNSIGNED( buffer, ptr_table[i] ) = new_ptr;
cerr << "DEBUG: fixup " << old_ptr << " -> " << new_ptr << " at " << ptr_table[i] << endl;
}
}
#ifdef DEBUG
void InplaceBuilder::DebugDump() const
{
cerr << "Inplace builder debug dump:" << endl;
cerr << " Blocks:" << endl;
vector< Block > blocks;
GetFinalBlockList( blocks );
for( unsigned i = 0; i < blocks.size(); ++i )
{
cerr << " " << blocks[i].start << " (" << blocks[i].length << " bytes)" << endl;
}
cerr << " Pointers:" << endl;
for( set< void** >::const_iterator it = m_pointers.begin(); it != m_pointers.end(); ++it )
{
void** addr = *it;
void* ptr = *addr;
Block* b = NULL;
for( unsigned i = 0; i < blocks.size(); ++i )
if( blocks[i].start == ptr )
{
b = &blocks[i];
break;
}
if( b )
cerr << " " << addr << " -> " << b->start << " (" << b->length << " bytes)";
else
cerr << " " << addr << " -> " << "(unknown block)";
cerr << endl;
}
}
#endif
struct Test
{
Test( char* m, Test* n )
{
msg = new char[strlen(m)+1];
strcpy( msg, m );
next = n;
}
char* msg;
Test* next;
void RegisterPointers( InplaceBuilder& b )
{
b.RegisterPointer( msg, strlen( msg )+1 );
if( next )
{
next->RegisterPointers( b );
b.RegisterPointer( next, sizeof( Test ) );
}
}
void Display()
{
cerr << "Node " << this << ": " << msg << endl;
if( next )
next->Display();
}
};
int main()
{
Test* t = new Test( "foo", new Test( "bar", new Test( "baz", NULL ) ) );
InplaceBuilder b;
t->RegisterPointers( b );
b.RegisterRootObject( t );
b.DebugDump();
vector< char > out;
b.Build( out );
cerr << "Built object of " << out.size() << " bytes" << endl;
FILE* f = fopen( "test.out", "wb" );
assert( f );
fwrite( &out[0], out.size(), 1, f );
fclose( f );
FixupInplaceImage( &out[0], out.size() );
Test* fixed_up = (Test*)&out[0];
fixed_up->Display();
return 0;
}