/** * 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]"); } }