/**
* 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.emulator;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.Register;
import de.codesourcery.jasm16.ast.ASTNode;
import de.codesourcery.jasm16.ast.ExpressionNode;
import de.codesourcery.jasm16.ast.TermNode;
import de.codesourcery.jasm16.compiler.CompilationUnit;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.ICompilationUnitResolver;
import de.codesourcery.jasm16.compiler.ISymbolTable;
import de.codesourcery.jasm16.compiler.SymbolTable;
import de.codesourcery.jasm16.compiler.io.FileResourceResolver;
import de.codesourcery.jasm16.compiler.io.IResource;
import de.codesourcery.jasm16.compiler.io.IResource.ResourceType;
import de.codesourcery.jasm16.compiler.io.IResourceResolver;
import de.codesourcery.jasm16.compiler.io.StringResource;
import de.codesourcery.jasm16.exceptions.ParseException;
import de.codesourcery.jasm16.lexer.ILexer;
import de.codesourcery.jasm16.lexer.Lexer;
import de.codesourcery.jasm16.parser.IParseContext;
import de.codesourcery.jasm16.parser.IParser.ParserOption;
import de.codesourcery.jasm16.parser.ParseContext;
import de.codesourcery.jasm16.scanner.Scanner;
import de.codesourcery.jasm16.utils.Misc;
/**
* Debugger breakpoint.
*
* <p>Implementations MUST be thread-safe.</p>
*
* @author tobias.gierke@code-sourcery.de
*/
public class Breakpoint
{
private static final Logger LOG = Logger.getLogger(Breakpoint.class);
// Address MUST be immutable !!!
private final Address address;
private volatile boolean enabled = true;
private String condition;
public Breakpoint(Address address) {
if (address == null) {
throw new IllegalArgumentException("address must not be NULL.");
}
this.address = address;
}
public Breakpoint(Address address,String condition) throws ParseException
{
if (address == null) {
throw new IllegalArgumentException("address must not be NULL.");
}
if (StringUtils.isBlank(condition)) {
throw new IllegalArgumentException(
"condition must not be blank/null");
}
setCondition( condition );
this.address = address;
this.condition = condition;
}
public Address getAddress()
{
return address;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean hasCondition() {
return StringUtils.isNotBlank( condition );
}
public boolean isOneShotBreakpoint() {
return false;
}
public void setCondition(String newCondition) throws ParseException
{
if ( StringUtils.isBlank( newCondition ) ) {
this.condition = null;
}
else
{
final String oldValue = this.condition;
this.condition = newCondition;
boolean success = false;
try
{
calculateConditionValue( new Emulator() );
success = true;
}
finally {
if ( ! success ) {
this.condition = oldValue;
}
}
}
}
public String getCondition() {
return condition;
}
public boolean matches(IEmulator emulator)
{
if ( ! enabled ) {
return false;
}
if ( hasCondition() ) {
return conditionMatches( emulator );
}
return true;
}
private boolean conditionMatches(IEmulator emulator)
{
try {
return calculateConditionValue( emulator ) != 0;
} catch (ParseException e) {
LOG.error("conditionMatches(): Failed to evaluate condition '"+condition+"'",e);
return false;
}
}
private long calculateConditionValue(IEmulator emulator) throws ParseException
{
final StringBuilder trimmed = new StringBuilder();
for ( char c : condition.toCharArray() ) {
if ( ! Character.isWhitespace( c ) ) {
trimmed.append( c );
}
}
final String expanded = substitutePlaceholders( emulator , trimmed.toString() );
final TermNode expression = parseCondition( expanded );
final Long value = expression.calculate( new SymbolTable("calculateConditionValue(IEmulator)") );
if ( value == null ) {
throw new ParseException("Failed to evaluate condition '"+expanded+"'",0,expanded.length());
}
return value.longValue();
}
private String substitutePlaceholders(final IEmulator emulator, String condition)
{
final String[] registers = new String[] { "pc" , "ex" , "sp" , "a","b","c","x","y","z","i","j"};
String registerExpression = "";
for ( int i = 0 ; i < registers.length ; i++)
{
final String reg = registers[i];
registerExpression+= reg;
if ( (i+1) < registers.length ) {
registerExpression+="|";
}
}
final Pattern registerIndirectRegEx = Pattern.compile("\\[("+registerExpression+")\\]",
Pattern.CASE_INSENSITIVE);
final Pattern registerImmediateRegEx = Pattern.compile("("+ registerExpression+")",
Pattern.CASE_INSENSITIVE);
final Pattern memoryIndirectRegEx = Pattern.compile("(\\[[ ]*(0x[0-9a-f]+)[ ]*\\])");
final Pattern hexPattern = Pattern.compile("(0x[a-f0-9]+)",Pattern.CASE_INSENSITIVE);
final StringBuilder result = new StringBuilder( condition );
// first, replace all memory references with the memory's value
// at the specified address
final IPatternReplacer replacer1 = new IPatternReplacer() {
@Override
public String replace(Matcher matcher, String context)
{
final String hexString = matcher.group(2);
final int address = (int) Misc.parseHexString( hexString );
@SuppressWarnings("deprecation")
final int decValue = emulator.getMemory().read( address );
return context.replaceAll( Pattern.quote( matcher.group(1) ) , Integer.toString( decValue ) );
}
};
substitutePatterns( result , memoryIndirectRegEx, replacer1 );
// second, substitute all hexadecimal values (0x1234) with their
// decimal counterparse so we don't accidently replace a,b,c with their
// register values
final IPatternReplacer replacer2 = new IPatternReplacer() {
@Override
public String replace(Matcher matcher, String context)
{
final String hexString = matcher.group(1);
final long decValue = Misc.parseHexString( hexString );
return context.replaceAll( Pattern.quote( hexString ) , Long.toString( decValue ) );
}
};
substitutePatterns( result , hexPattern, replacer2 );
// third, replace all register indirect [ <REG> ] expressions with their respective
// memory value
final IPatternReplacer replacer3 = new IPatternReplacer() {
@Override
public String replace(Matcher matcher, String context)
{
final String register = matcher.group(1);
final Register reg = Register.fromString( register );
final int registerValue = emulator.getCPU().getRegisterValue( reg );
@SuppressWarnings("deprecation")
final int memoryValue = emulator.getMemory().read( registerValue );
final String toReplace = Pattern.quote("["+register+"]");
return context.replaceAll( toReplace , Integer.toString( memoryValue ) );
}
};
substitutePatterns( result , registerIndirectRegEx , replacer3 );
// fourth , replace all register immediate values with their respective
// register values
final IPatternReplacer replacer4 = new IPatternReplacer() {
@Override
public String replace(Matcher matcher, String context)
{
final String register = matcher.group(1);
final Register reg = Register.fromString( register );
final int decValue = emulator.getCPU().getRegisterValue( reg );
final String toReplace = Pattern.quote( register );
return context.replaceAll( toReplace , Integer.toString( decValue ) );
}
};
substitutePatterns( result , registerImmediateRegEx, replacer4 );
return result.toString();
}
private void substitutePatterns(StringBuilder result,Pattern pattern,IPatternReplacer replacer)
{
boolean replaced = false;
do {
replaced = false;
final Matcher m = pattern.matcher( result.toString() );
if ( m.find() )
{
final String newString = replacer.replace( m , result.toString() );
result.setLength( 0 );
result.append( newString );
replaced = true;
}
} while ( replaced );
}
protected interface IPatternReplacer
{
public String replace(Matcher matcher , String context);
}
private TermNode parseCondition(String condition) throws ParseException
{
final ICompilationUnit unit = CompilationUnit.createInstance("dummy" ,
new StringResource( "dummy", condition , ResourceType.SOURCE_CODE ) );
final ISymbolTable symbolTable = new SymbolTable("parseCondition(String) in IEmulator");
final ILexer lexer = new Lexer( new Scanner( condition ) );
final IResourceResolver resourceResolver=new FileResourceResolver() {
@Override
protected ResourceType determineResourceType(File file) {
return ResourceType.UNKNOWN;
}
};
final ICompilationUnitResolver unitResolver = new ICompilationUnitResolver() {
@Override
public ICompilationUnit getOrCreateCompilationUnit(
IResource resource) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public ICompilationUnit getCompilationUnit(IResource resource)
throws IOException
{
throw new UnsupportedOperationException();
}
};
final Set<ParserOption> parserOptions = new HashSet<ParserOption>();
final IParseContext context = new ParseContext(unit,symbolTable,lexer,resourceResolver,unitResolver,parserOptions,null);
final ASTNode node = new ExpressionNode().parse( context );
if ( node.hasErrors() )
{
throw new ParseException("Invalid condition: '"+this.condition+"'",0,this.condition.length());
}
return (TermNode) node;
}
@Override
public String toString() {
return getAddress()+( hasCondition() ? ", "+getCondition()+" " : "" )+( isEnabled() ? "" : "[DISABLED]");
}
}