/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* 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.
*/
package de.codesourcery.jasm16.compiler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.IOUtils;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.Size;
import de.codesourcery.jasm16.Size.SizeInBytes;
import de.codesourcery.jasm16.ast.ASTNode;
import de.codesourcery.jasm16.ast.ASTUtils;
import de.codesourcery.jasm16.ast.ISimpleASTNodeVisitor;
import de.codesourcery.jasm16.ast.ObjectCodeOutputNode;
import de.codesourcery.jasm16.compiler.ICompiler.CompilerOption;
import de.codesourcery.jasm16.compiler.io.ByteArrayObjectCodeWriterFactory;
import de.codesourcery.jasm16.compiler.io.FileResource;
import de.codesourcery.jasm16.compiler.io.IResource.ResourceType;
import de.codesourcery.jasm16.utils.Misc;
public class Linker
{
private static final byte[] SELFRELOCATION_CODE;
public Linker() {
}
static
{
final String source = "$start:\n" +
" SET a, PC; A now points to $start\n" +
" SET j,[ a + ($relocationtable-$start)]; C = number of relocation table entries\n" +
" SET y,a; Y will become the total offset to add \n" +
" ADD y,j; add the number of relocation table entries\n" +
" ADD y, $relocationtable - $start; add size of code in front of relocation table\n" +
" SET i,y\n" +
" ADD y,1; add +1 for word that holds number of entries\n" +
"$relocationloop:\n" +
" STD x, [i]; load address to relocate into X and decrement I and J\n" +
" IFU j,0\n" +
" SET PC,y; => jump to $exit\n" +
" ADD x,y\n" +
" ADD [x] , y ; add offset\n" +
"$thisInstruction:\n" +
" SUB PC, $thisInstruction - $relocationloop\n" +
"$relocationtable:";
final ICompiler c = new Compiler();
c.setCompilerOption(CompilerOption.RELAXED_PARSING,true);
final ByteArrayObjectCodeWriterFactory factory = new ByteArrayObjectCodeWriterFactory();
c.setObjectCodeWriterFactory( factory );
final ICompilationUnit unit = CompilationUnit.createInstance( "self-relocation code" , source );
c.compile( Collections.singletonList( unit ) );
if ( unit.hasErrors() )
{
Misc.printCompilationErrors(unit, source, false );
throw new RuntimeException("Failed to compile relocator code ?");
}
SELFRELOCATION_CODE = factory.getBytes();
if ( SELFRELOCATION_CODE == null || SELFRELOCATION_CODE.length == 0 ) {
throw new RuntimeException("Failed to compile relocator code ?");
}
}
/**
* Link one or more files into an program file.
*
* @param objectFiles
* @param outputFile
* @param createSelfRelocatingCode whether to prepend the program with self-relocation code. <b>This requires that all
* compilation units where compiled with {@link CompilerOption#GENERATE_RELOCATION_INFORMATION}.</b>
* @param rewriteLabelAddresses whether symbol tables should be updated to account for the
* size of self-relocation code in front of the executable (only applicable if <code>createSelfRelocatingCode</code> was set to <code>true</code>)
* @return
* @throws IOException
*/
public Executable link(List<CompiledCode> objectFiles,
DebugInfo debugInfo,
final File outputFile,
boolean createSelfRelocatingCode,
boolean rewriteLabelAddresses) throws IOException
{
final FileResource executable = new FileResource( outputFile , ResourceType.EXECUTABLE );
final OutputStream out = executable.createOutputStream( true );
try
{
// sanity check to assert that compilation units actually
// where passed into this method in the same order their object code was generated
ICompilationUnit previous = null;
for ( CompiledCode r : objectFiles )
{
final Address currentOffset = r.getCompilationUnit().getObjectCodeStartOffset();
if ( previous == null ) {
previous = r.getCompilationUnit();
}
else
{
Address previousOffset = previous.getObjectCodeStartOffset();
if ( currentOffset.getByteAddressValue() < previousOffset.getByteAddressValue() ) {
throw new IllegalArgumentException("Bad input list, compilation unit "+r.getCompilationUnit()+" has "+
"address "+currentOffset+" but comes after "+previous+" with offset "+previousOffset);
}
}
}
final RelocationTable combined = new RelocationTable();
if ( createSelfRelocatingCode )
{
for ( CompiledCode r : objectFiles )
{
combined.merge( r.getCompilationUnit().getRelocationTable() ,
r.getCompilationUnit().getObjectCodeStartOffset() );
}
writeRelocationHeader( combined , out );
}
int currentOffset = 0;
for ( CompiledCode r : objectFiles )
{
final boolean hasAST = r.getCompilationUnit().getAST() != null;
final Address start = ASTUtils.getEarliestMemoryLocation( r.getCompilationUnit().getAST() );
final Address end = ASTUtils.getLatestMemoryLocation( r.getCompilationUnit().getAST() );
System.out.println("LINKING: [ "+Misc.toHexString( Address.byteAddress( currentOffset ) )+"] "+
r.getObjectCode()+" [ AST: "+hasAST+" , offset_from_CU: "+start+" - "+end);
// write object code
final InputStream in = r.getObjectCode().createInputStream();
int bytesWritten = 0;
try {
bytesWritten = IOUtils.copy( in , out );
} finally {
IOUtils.closeQuietly( in );
}
currentOffset += bytesWritten;
}
if ( createSelfRelocatingCode && rewriteLabelAddresses ) {
final SizeInBytes offset = Size.bytes( SELFRELOCATION_CODE.length );
final Size size = combined.getBinarySize().plus( offset );
for ( CompiledCode r : objectFiles )
{
final ISimpleASTNodeVisitor<ASTNode> visitor = new ISimpleASTNodeVisitor<ASTNode>() {
@Override
public boolean visit(ASTNode node)
{
if ( node instanceof ObjectCodeOutputNode) {
ObjectCodeOutputNode n = (ObjectCodeOutputNode) node;
if ( n.getAddress() != null ) {
n.adjustAddress( size.getSizeInWords() );
}
}
return true;
}
};
ASTUtils.visitInOrder( r.getCompilationUnit().getAST() , visitor );
for ( ISymbol s : r.getCompilationUnit().getSymbolTable().getSymbols() ) {
if ( s instanceof Label) {
final Label l = (Label) s;
if ( l.getAddress() != null ) {
l.setAddress( l.getAddress().plus( size , false ) );
}
}
}
debugInfo.relocate( Address.byteAddress( offset.getSizeInBytes() ) );
}
}
} finally {
IOUtils.closeQuietly( out );
}
return new Executable(outputFile.getAbsolutePath(),debugInfo) {
@Override
public OutputStream createOutputStream(boolean append) throws IOException {
return new FileOutputStream( outputFile , append );
}
@Override
public InputStream createInputStream() throws IOException {
return new FileInputStream( outputFile );
}
};
}
private void writeRelocationHeader(RelocationTable table, OutputStream out) throws IOException
{
out.write( SELFRELOCATION_CODE );
out.write( table.toByteArray() );
}
}