google-site-verification: google61fe8ba583a51912.html
Truffle Instrument API for Debugging in Java

The Truffle Instrument API provides a powerful framework for building debugging tools for languages implemented on the Truffle platform. This comprehensive guide covers creating custom debuggers, profilers, and monitoring tools.

Understanding Truffle Instrument Architecture

Key Components:

  • Instruments: Debugging and profiling tools
  • Probes: Code instrumentation points
  • Events: Execution events (breakpoints, stepping, etc.)
  • Contexts: Language execution contexts
  • Sources: Source code representation

Project Setup and Dependencies

<properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- GraalVM Truffle API --> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-api</artifactId> <version>23.1.0</version> </dependency> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-dsl-processor</artifactId> <version>23.1.0</version> <scope>provided</scope> </dependency> <!-- JSON for debug protocol --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> </dependency> <!-- WebSocket for debug adapter protocol --> <dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.4</version> </dependency> </dependencies>

Basic Instrument Foundation

package com.example.truffle.instrument; import com.oracle.truffle.api.instrumentation.*; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Base class for Truffle instruments */ public abstract class BaseDebugInstrument implements Instrumenter { protected final Set<EventBinding<?>> bindings = ConcurrentHashMap.newKeySet(); protected final Map<Object, List<EventListener>> eventListeners = new ConcurrentHashMap<>(); @Override public <T extends EventContext> EventBinding<T> attachListener( SourceSectionFilter sourceSectionFilter, EventBinding.ExecutionEventNodeFactory<T> factory) { EventBinding<T> binding = Instrumenter.super.attachListener(sourceSectionFilter, factory); bindings.add(binding); return binding; } @Override public <T extends EventContext> EventBinding<T> attachExecutionEventFactory( SourceSectionFilter sourceSectionFilter, EventBinding.ExecutionEventNodeFactory<T> factory) { EventBinding<T> binding = Instrumenter.super.attachExecutionEventFactory(sourceSectionFilter, factory); bindings.add(binding); return binding; } public void dispose() { // Clean up all bindings for (EventBinding<?> binding : bindings) { binding.dispose(); } bindings.clear(); eventListeners.clear(); } protected void addEventListener(Object key, EventListener listener) { eventListeners.computeIfAbsent(key, k -> new ArrayList<>()).add(listener); } protected void removeEventListeners(Object key) { eventListeners.remove(key); } protected void notifyEventListeners(Object key, DebugEvent event) { List<EventListener> listeners = eventListeners.get(key); if (listeners != null) { for (EventListener listener : listeners) { listener.onEvent(event); } } } public interface EventListener { void onEvent(DebugEvent event); } public static class DebugEvent { public enum Type { BREAKPOINT_HIT, STEP_COMPLETE, EXCEPTION, THREAD_START, THREAD_END, VARIABLE_CHANGE, FRAME_ENTER, FRAME_EXIT, SOURCE_LOADED } private final Type type; private final Object data; private final long timestamp; public DebugEvent(Type type, Object data) { this.type = type; this.data = data; this.timestamp = System.currentTimeMillis(); } // Getters public Type getType() { return type; } public Object getData() { return data; } public long getTimestamp() { return timestamp; } } }

Breakpoint Manager Instrument

package com.example.truffle.instrument; import com.oracle.truffle.api.instrumentation.*; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; /** * Advanced breakpoint management instrument */ @TruffleInstrument.Registration( id = "debug-breakpoints", name = "Debug Breakpoint Manager", version = "1.0", services = BreakpointManager.class ) public class BreakpointInstrument extends TruffleInstrument { private Env env; private BreakpointManager breakpointManager; @Override protected void onCreate(Env env) { this.env = env; this.breakpointManager = new BreakpointManager(env); env.registerService(breakpointManager); // Register as instrumenter env.getInstrumenter().attachLoadSourceListener( SourceSectionFilter.ANY, new LoadSourceListener(), true ); } public static class BreakpointManager { private final Env env; private final Map<String, Set<Breakpoint>> breakpointsBySource = new ConcurrentHashMap<>(); private final Map<Integer, Breakpoint> breakpointsById = new ConcurrentHashMap<>(); private final Set<BreakpointListener> listeners = new CopyOnWriteArraySet<>(); private int nextBreakpointId = 1; public BreakpointManager(Env env) { this.env = env; } public Breakpoint setBreakpoint(Source source, int line) { return setBreakpoint(source, line, null); } public Breakpoint setBreakpoint(Source source, int line, String condition) { Breakpoint breakpoint = new Breakpoint(nextBreakpointId++, source, line, condition); breakpointsBySource.computeIfAbsent(source.getName(), k -> new HashSet<>()).add(breakpoint); breakpointsById.put(breakpoint.getId(), breakpoint); // Notify listeners for (BreakpointListener listener : listeners) { listener.onBreakpointSet(breakpoint); } return breakpoint; } public boolean removeBreakpoint(int breakpointId) { Breakpoint breakpoint = breakpointsById.remove(breakpointId); if (breakpoint != null) { Set<Breakpoint> sourceBreakpoints = breakpointsBySource.get(breakpoint.getSource().getName()); if (sourceBreakpoints != null) { sourceBreakpoints.remove(breakpoint); if (sourceBreakpoints.isEmpty()) { breakpointsBySource.remove(breakpoint.getSource().getName()); } } for (BreakpointListener listener : listeners) { listener.onBreakpointRemoved(breakpoint); } return true; } return false; } public List<Breakpoint> getBreakpoints() { return new ArrayList<>(breakpointsById.values()); } public List<Breakpoint> getBreakpointsForSource(Source source) { Set<Breakpoint> sourceBreakpoints = breakpointsBySource.get(source.getName()); return sourceBreakpoints != null ? new ArrayList<>(sourceBreakpoints) : new ArrayList<>(); } public void addBreakpointListener(BreakpointListener listener) { listeners.add(listener); } public void removeBreakpointListener(BreakpointListener listener) { listeners.remove(listener); } public void clearAllBreakpoints() { List<Breakpoint> allBreakpoints = new ArrayList<>(breakpointsById.values()); for (Breakpoint breakpoint : allBreakpoints) { removeBreakpoint(breakpoint.getId()); } } public Breakpoint findBreakpointAt(Source source, int line) { Set<Breakpoint> sourceBreakpoints = breakpointsBySource.get(source.getName()); if (sourceBreakpoints != null) { for (Breakpoint breakpoint : sourceBreakpoints) { if (breakpoint.getLine() == line) { return breakpoint; } } } return null; } } private class LoadSourceListener implements LoadSourceListener { @Override public void onLoad(LoadSourceEvent event) { Source source = event.getSource(); Set<Breakpoint> breakpoints = breakpointManager.breakpointsBySource.get(source.getName()); if (breakpoints != null && !breakpoints.isEmpty()) { // Instrument the source with breakpoints for (Breakpoint breakpoint : breakpoints) { instrumentBreakpoint(breakpoint); } } } private void instrumentBreakpoint(Breakpoint breakpoint) { SourceSectionFilter filter = SourceSectionFilter.newBuilder() .sourceIs(breakpoint.getSource()) .lineIs(breakpoint.getLine()) .build(); env.getInstrumenter().attachExecutionEventFactory(filter, new BreakpointExecutionFactory(breakpoint)); } } private static class BreakpointExecutionFactory implements EventBinding.ExecutionEventNodeFactory<BreakpointContext> { private final Breakpoint breakpoint; BreakpointExecutionFactory(Breakpoint breakpoint) { this.breakpoint = breakpoint; } @Override public BreakpointContext create(EventContext context) { return new BreakpointContext(context, breakpoint); } } private static class BreakpointContext extends EventContext { private final Breakpoint breakpoint; BreakpointContext(EventContext context, Breakpoint breakpoint) { super(context.getInstrumentedNode(), context.getInstrumentedSourceSection()); this.breakpoint = breakpoint; } @TruffleInstrumentation.Insert(value = TruffleInstrumentation.Insert.Before.class) public void onBreakpoint() { if (breakpoint.isEnabled()) { // Check condition if present if (breakpoint.getCondition() != null) { // Evaluate condition (simplified) // In real implementation, you'd parse and evaluate the condition if (!evaluateCondition(breakpoint.getCondition())) { return; } } // Trigger breakpoint breakpoint.hit(); } } private boolean evaluateCondition(String condition) { // Simplified condition evaluation // In practice, you'd use the language's expression evaluator try { // This is a placeholder - real implementation would be language-specific return true; // Assume condition is true for demo } catch (Exception e) { return false; } } } public static class Breakpoint { private final int id; private final Source source; private final int line; private final String condition; private boolean enabled; private int hitCount; private String logMessage; public Breakpoint(int id, Source source, int line, String condition) { this.id = id; this.source = source; this.line = line; this.condition = condition; this.enabled = true; this.hitCount = 0; } public void hit() { hitCount++; // Notify breakpoint listeners // In practice, this would pause execution and notify debugger } // Getters and setters public int getId() { return id; } public Source getSource() { return source; } public int getLine() { return line; } public String getCondition() { return condition; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public int getHitCount() { return hitCount; } public String getLogMessage() { return logMessage; } public void setLogMessage(String logMessage) { this.logMessage = logMessage; } } public interface BreakpointListener { void onBreakpointSet(Breakpoint breakpoint); void onBreakpointRemoved(Breakpoint breakpoint); void onBreakpointHit(Breakpoint breakpoint); } }

Execution Tracer Instrument

package com.example.truffle.instrument; import com.oracle.truffle.api.instrumentation.*; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** * Execution tracing and profiling instrument */ @TruffleInstrument.Registration( id = "execution-tracer", name = "Execution Tracer", version = "1.0", services = ExecutionTracer.class ) public class ExecutionTracerInstrument extends TruffleInstrument { private Env env; private ExecutionTracer tracer; @Override protected void onCreate(Env env) { this.env = env; this.tracer = new ExecutionTracer(env); env.registerService(tracer); // Attach to all execution events attachExecutionTracer(); } private void attachExecutionTracer() { SourceSectionFilter filter = SourceSectionFilter.newBuilder() .tagIs(StandardTags.StatementTag.class, StandardTags.ExpressionTag.class) .build(); env.getInstrumenter().attachExecutionEventFactory(filter, new ExecutionTraceFactory()); } private class ExecutionTraceFactory implements EventBinding.ExecutionEventNodeFactory<ExecutionTraceContext> { @Override public ExecutionTraceContext create(EventContext context) { return new ExecutionTraceContext(context, tracer); } } public static class ExecutionTracer { private final Env env; private final Map<Source, SourceExecutionStats> sourceStats = new ConcurrentHashMap<>(); private final AtomicLong totalExecutions = new AtomicLong(); private final Set<ExecutionListener> listeners = new HashSet<>(); private boolean enabled = true; private OutputStream traceOutput; public ExecutionTracer(Env env) { this.env = env; } public void recordExecution(SourceSection sourceSection, long durationNanos) { if (!enabled) return; totalExecutions.incrementAndGet(); Source source = sourceSection.getSource(); SourceExecutionStats stats = sourceStats.computeIfAbsent(source, k -> new SourceExecutionStats(source)); stats.recordExecution(sourceSection, durationNanos); // Notify listeners ExecutionEvent event = new ExecutionEvent(sourceSection, durationNanos); for (ExecutionListener listener : listeners) { listener.onExecution(event); } // Write to trace output if configured if (traceOutput != null) { writeTraceEvent(event); } } public void setTraceOutput(OutputStream output) { this.traceOutput = output; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public void addListener(ExecutionListener listener) { listeners.add(listener); } public void removeListener(ExecutionListener listener) { listeners.remove(listener); } public ExecutionReport generateReport() { return new ExecutionReport(new HashMap<>(sourceStats), totalExecutions.get()); } public void clearStats() { sourceStats.clear(); totalExecutions.set(0); } private void writeTraceEvent(ExecutionEvent event) { if (traceOutput != null) { try { String traceLine = String.format("%d,%s,%d,%d%n", System.currentTimeMillis(), event.getSourceSection().getSource().getName(), event.getSourceSection().getStartLine(), event.getDurationNanos()); traceOutput.write(traceLine.getBytes()); } catch (IOException e) { // Ignore write errors } } } } private static class ExecutionTraceContext extends EventContext { private final ExecutionTracer tracer; private long startTime; ExecutionTraceContext(EventContext context, ExecutionTracer tracer) { super(context.getInstrumentedNode(), context.getInstrumentedSourceSection()); this.tracer = tracer; } @TruffleInstrumentation.Insert(value = TruffleInstrumentation.Insert.Before.class) public void onEnter() { startTime = System.nanoTime(); } @TruffleInstrumentation.Insert(value = TruffleInstrumentation.Insert.After.class) public void onExit() { long duration = System.nanoTime() - startTime; tracer.recordExecution(getInstrumentedSourceSection(), duration); } } public static class SourceExecutionStats { private final Source source; private final Map<SourceSection, LineExecutionStats> lineStats = new ConcurrentHashMap<>(); private long totalExecutions; private long totalTimeNanos; public SourceExecutionStats(Source source) { this.source = source; } public void recordExecution(SourceSection sourceSection, long durationNanos) { LineExecutionStats stats = lineStats.computeIfAbsent(sourceSection, k -> new LineExecutionStats(sourceSection.getStartLine())); stats.recordExecution(durationNanos); totalExecutions++; totalTimeNanos += durationNanos; } // Getters public Source getSource() { return source; } public Map<SourceSection, LineExecutionStats> getLineStats() { return lineStats; } public long getTotalExecutions() { return totalExecutions; } public long getTotalTimeNanos() { return totalTimeNanos; } } public static class LineExecutionStats { private final int lineNumber; private long executionCount; private long totalTimeNanos; private long minTimeNanos = Long.MAX_VALUE; private long maxTimeNanos = Long.MIN_VALUE; public LineExecutionStats(int lineNumber) { this.lineNumber = lineNumber; } public void recordExecution(long durationNanos) { executionCount++; totalTimeNanos += durationNanos; minTimeNanos = Math.min(minTimeNanos, durationNanos); maxTimeNanos = Math.max(maxTimeNanos, durationNanos); } public double getAverageTimeNanos() { return executionCount > 0 ? (double) totalTimeNanos / executionCount : 0; } // Getters public int getLineNumber() { return lineNumber; } public long getExecutionCount() { return executionCount; } public long getTotalTimeNanos() { return totalTimeNanos; } public long getMinTimeNanos() { return minTimeNanos; } public long getMaxTimeNanos() { return maxTimeNanos; } } public static class ExecutionReport { private final Map<Source, SourceExecutionStats> sourceStats; private final long totalExecutions; public ExecutionReport(Map<Source, SourceExecutionStats> sourceStats, long totalExecutions) { this.sourceStats = sourceStats; this.totalExecutions = totalExecutions; } public void printReport(PrintWriter writer) { writer.printf("Execution Report - Total Executions: %,d%n", totalExecutions); writer.println("=============================================="); for (SourceExecutionStats sourceStats : sourceStats.values()) { writer.printf("Source: %s%n", sourceStats.getSource().getName()); writer.printf(" Total Executions: %,d%n", sourceStats.getTotalExecutions()); writer.printf(" Total Time: %,d ns%n", sourceStats.getTotalTimeNanos()); for (LineExecutionStats lineStats : sourceStats.getLineStats().values()) { writer.printf(" Line %d: %,d executions, avg: %.2f ns%n", lineStats.getLineNumber(), lineStats.getExecutionCount(), lineStats.getAverageTimeNanos()); } writer.println(); } } } public interface ExecutionListener { void onExecution(ExecutionEvent event); } public static class ExecutionEvent { private final SourceSection sourceSection; private final long durationNanos; public ExecutionEvent(SourceSection sourceSection, long durationNanos) { this.sourceSection = sourceSection; this.durationNanos = durationNanos; } // Getters public SourceSection getSourceSection() { return sourceSection; } public long getDurationNanos() { return durationNanos; } } }

Variable Inspector Instrument

package com.example.truffle.instrument; import com.oracle.truffle.api.instrumentation.*; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.frame.*; import com.oracle.truffle.api.source.SourceSection; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Variable inspection and watch expression instrument */ @TruffleInstrument.Registration( id = "variable-inspector", name = "Variable Inspector", version = "1.0", services = VariableInspector.class ) public class VariableInspectorInstrument extends TruffleInstrument { private Env env; private VariableInspector inspector; @Override protected void onCreate(Env env) { this.env = env; this.inspector = new VariableInspector(env); env.registerService(inspector); // Attach frame instrumentation attachFrameListener(); } private void attachFrameListener() { env.getInstrumenter().attachContextsListener(new ContextsListener() { @Override public void onContextCreated(TruffleContext context) { inspector.onContextCreated(context); } @Override public void onContextClosed(TruffleContext context) { inspector.onContextClosed(context); } }, true); } public static class VariableInspector { private final Env env; private final Map<TruffleContext, ContextInspector> contextInspectors = new ConcurrentHashMap<>(); private final Set<VariableChangeListener> listeners = new HashSet<>(); private final Set<WatchExpression> watchExpressions = new HashSet<>(); public VariableInspector(Env env) { this.env = env; } public void onContextCreated(TruffleContext context) { ContextInspector contextInspector = new ContextInspector(context); contextInspectors.put(context, contextInspector); // Instrument frames for this context instrumentFrames(context); } public void onContextClosed(TruffleContext context) { contextInspectors.remove(context); } private void instrumentFrames(TruffleContext context) { // This would attach to frame events in the context // Implementation depends on language-specific frame access } public List<Variable> getVariables(TruffleContext context, Frame frame) { ContextInspector inspector = contextInspectors.get(context); return inspector != null ? inspector.getVariables(frame) : new ArrayList<>(); } public void setVariableValue(TruffleContext context, Frame frame, String name, Object value) { ContextInspector inspector = contextInspectors.get(context); if (inspector != null) { inspector.setVariableValue(frame, name, value); } } public void addWatchExpression(String expression) { WatchExpression watch = new WatchExpression(expression); watchExpressions.add(watch); // Evaluate watch expression on relevant events } public void removeWatchExpression(String expression) { watchExpressions.removeIf(w -> w.getExpression().equals(expression)); } public List<WatchResult> evaluateWatchExpressions(TruffleContext context, Frame frame) { List<WatchResult> results = new ArrayList<>(); for (WatchExpression watch : watchExpressions) { try { Object value = evaluateExpression(context, frame, watch.getExpression()); results.add(new WatchResult(watch.getExpression(), value, null)); } catch (Exception e) { results.add(new WatchResult(watch.getExpression(), null, e.getMessage())); } } return results; } private Object evaluateExpression(TruffleContext context, Frame frame, String expression) { // This would use the language's expression evaluator // Simplified implementation return "evaluated: " + expression; } public void addVariableChangeListener(VariableChangeListener listener) { listeners.add(listener); } public void removeVariableChangeListener(VariableChangeListener listener) { listeners.remove(listener); } private void notifyVariableChanged(TruffleContext context, Frame frame, String name, Object oldValue, Object newValue) { VariableChangeEvent event = new VariableChangeEvent(context, frame, name, oldValue, newValue); for (VariableChangeListener listener : listeners) { listener.onVariableChanged(event); } } } private static class ContextInspector { private final TruffleContext context; private final Map<Frame, FrameSnapshot> frameSnapshots = new ConcurrentHashMap<>(); public ContextInspector(TruffleContext context) { this.context = context; } public List<Variable> getVariables(Frame frame) { FrameSnapshot snapshot = frameSnapshots.computeIfAbsent(frame, k -> new FrameSnapshot(frame)); return snapshot.getVariables(); } public void setVariableValue(Frame frame, String name, Object value) { FrameSnapshot snapshot = frameSnapshots.get(frame); if (snapshot != null) { snapshot.setVariableValue(name, value); } } public void updateFrame(Frame frame) { FrameSnapshot snapshot = frameSnapshots.get(frame); if (snapshot != null) { snapshot.update(); } } } private static class FrameSnapshot { private final Frame frame; private final Map<String, Variable> variables = new ConcurrentHashMap<>(); private long lastUpdate; public FrameSnapshot(Frame frame) { this.frame = frame; update(); } public void update() { // Capture current frame state // This is language-specific and would need adaptation try { FrameDescriptor descriptor = frame.getFrameDescriptor(); for (FrameSlot slot : descriptor.getSlots()) { Object value = frame.getValue(slot); String name = slot.getIdentifier().toString(); Variable oldVar = variables.get(name); Object oldValue = oldVar != null ? oldVar.getValue() : null; if (!Objects.equals(oldValue, value)) { variables.put(name, new Variable(name, value, slot.getKind())); // Notify about change } } lastUpdate = System.currentTimeMillis(); } catch (Exception e) { // Frame might be invalid } } public List<Variable> getVariables() { return new ArrayList<>(variables.values()); } public void setVariableValue(String name, Object value) { Variable variable = variables.get(name); if (variable != null) { // This would need language-specific frame manipulation // variable.setValue(value); } } } public static class Variable { private final String name; private final Object value; private final FrameSlotKind kind; public Variable(String name, Object value, FrameSlotKind kind) { this.name = name; this.value = value; this.kind = kind; } // Getters public String getName() { return name; } public Object getValue() { return value; } public FrameSlotKind getKind() { return kind; } public String getType() { return value != null ? value.getClass().getSimpleName() : "null"; } } public static class WatchExpression { private final String expression; private final String id; public WatchExpression(String expression) { this.expression = expression; this.id = UUID.randomUUID().toString(); } // Getters public String getExpression() { return expression; } public String getId() { return id; } } public static class WatchResult { private final String expression; private final Object value; private final String error; public WatchResult(String expression, Object value, String error) { this.expression = expression; this.value = value; this.error = error; } // Getters public String getExpression() { return expression; } public Object getValue() { return value; } public String getError() { return error; } public boolean hasError() { return error != null; } } public interface VariableChangeListener { void onVariableChanged(VariableChangeEvent event); } public static class VariableChangeEvent { private final TruffleContext context; private final Frame frame; private final String variableName; private final Object oldValue; private final Object newValue; public VariableChangeEvent(TruffleContext context, Frame frame, String variableName, Object oldValue, Object newValue) { this.context = context; this.frame = frame; this.variableName = variableName; this.oldValue = oldValue; this.newValue = newValue; } // Getters public TruffleContext getContext() { return context; } public Frame getFrame() { return frame; } public String getVariableName() { return variableName; } public Object getOldValue() { return oldValue; } public Object getNewValue() { return newValue; } } }

Debug Adapter Protocol Implementation

package com.example.truffle.instrument; import com.fasterxml.jackson.databind.ObjectMapper; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.*; /** * Debug Adapter Protocol server for Truffle languages */ public class DebugAdapterServer extends WebSocketServer { private final ObjectMapper mapper = new ObjectMapper(); private final Map<Integer, WebSocket> sessions = new ConcurrentHashMap<>(); private final DebugSessionManager sessionManager; private final ExecutorService messageProcessor = Executors.newCachedThreadPool(); public DebugAdapterServer(int port) { super(new InetSocketAddress(port)); this.sessionManager = new DebugSessionManager(); } @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { int sessionId = conn.hashCode(); sessions.put(sessionId, conn); sessionManager.createSession(sessionId); System.out.println("New debug session connected: " + sessionId); } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { int sessionId = conn.hashCode(); sessions.remove(sessionId); sessionManager.closeSession(sessionId); System.out.println("Debug session closed: " + sessionId); } @Override public void onMessage(WebSocket conn, String message) { messageProcessor.submit(() -> processMessage(conn, message)); } @Override public void onError(WebSocket conn, Exception ex) { System.err.println("WebSocket error: " + ex.getMessage()); ex.printStackTrace(); } @Override public void onStart() { System.out.println("Debug Adapter Server started on port: " + getPort()); } private void processMessage(WebSocket conn, String message) { try { DAPMessage request = mapper.readValue(message, DAPMessage.class); int sessionId = conn.hashCode(); DAPResponse response = sessionManager.handleRequest(sessionId, request); if (response != null) { String responseJson = mapper.writeValueAsString(response); conn.send(responseJson); } } catch (Exception e) { System.err.println("Error processing message: " + e.getMessage()); e.printStackTrace(); } } public void sendEvent(int sessionId, DAPEvent event) { WebSocket conn = sessions.get(sessionId); if (conn != null && conn.isOpen()) { try { String eventJson = mapper.writeValueAsString(event); conn.send(eventJson); } catch (Exception e) { System.err.println("Error sending event: " + e.getMessage()); } } } // DAP Message classes public static class DAPMessage { private String type; private int seq; private String command; private Map<String, Object> arguments; // Getters and setters public String getType() { return type; } public void setType(String type) { this.type = type; } public int getSeq() { return seq; } public void setSeq(int seq) { this.seq = seq; } public String getCommand() { return command; } public void setCommand(String command) { this.command = command; } public Map<String, Object> getArguments() { return arguments; } public void setArguments(Map<String, Object> arguments) { this.arguments = arguments; } } public static class DAPResponse { private String type = "response"; private int request_seq; private boolean success; private String command; private String message; private Map<String, Object> body; public DAPResponse(int requestSeq, String command, boolean success) { this.request_seq = requestSeq; this.command = command; this.success = success; } // Getters and setters public String getType() { return type; } public int getRequest_seq() { return request_seq; } public void setRequest_seq(int request_seq) { this.request_seq = request_seq; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getCommand() { return command; } public void setCommand(String command) { this.command = command; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Map<String, Object> getBody() { return body; } public void setBody(Map<String, Object> body) { this.body = body; } } public static class DAPEvent { private String type = "event"; private String event; private Map<String, Object> body; public DAPEvent(String event) { this.event = event; } // Getters and setters public String getType() { return type; } public String getEvent() { return event; } public void setEvent(String event) { this.event = event; } public Map<String, Object> getBody() { return body; } public void setBody(Map<String, Object> body) { this.body = body; } } private static class DebugSessionManager { private final Map<Integer, DebugSession> sessions = new ConcurrentHashMap<>(); public void createSession(int sessionId) { sessions.put(sessionId, new DebugSession(sessionId)); } public void closeSession(int sessionId) { DebugSession session = sessions.remove(sessionId); if (session != null) { session.close(); } } public DAPResponse handleRequest(int sessionId, DAPMessage request) { DebugSession session = sessions.get(sessionId); if (session != null) { return session.handleRequest(request); } return createErrorResponse(request, "Session not found"); } private DAPResponse createErrorResponse(DAPMessage request, String message) { DAPResponse response = new DAPResponse(request.getSeq(), request.getCommand(), false); response.setMessage(message); return response; } } private static class DebugSession { private final int sessionId; private final Map<String, Object> capabilities; public DebugSession(int sessionId) { this.sessionId = sessionId; this.capabilities = initializeCapabilities(); } public DAPResponse handleRequest(DAPMessage request) { return switch (request.getCommand()) { case "initialize" -> handleInitialize(request); case "launch" -> handleLaunch(request); case "attach" -> handleAttach(request); case "setBreakpoints" -> handleSetBreakpoints(request); case "setExceptionBreakpoints" -> handleSetExceptionBreakpoints(request); case "configurationDone" -> handleConfigurationDone(request); case "threads" -> handleThreads(request); case "stackTrace" -> handleStackTrace(request); case "scopes" -> handleScopes(request); case "variables" -> handleVariables(request); case "continue" -> handleContinue(request); case "next" -> handleNext(request); case "stepIn" -> handleStepIn(request); case "stepOut" -> handleStepOut(request); case "pause" -> handlePause(request); case "disconnect" -> handleDisconnect(request); default -> createErrorResponse(request, "Unsupported command: " + request.getCommand()); }; } private DAPResponse handleInitialize(DAPMessage request) { DAPResponse response = new DAPResponse(request.getSeq(), request.getCommand(), true); response.setBody(capabilities); return response; } private DAPResponse handleSetBreakpoints(DAPMessage request) { // Implementation for setting breakpoints DAPResponse response = new DAPResponse(request.getSeq(), request.getCommand(), true); response.setBody(Map.of("breakpoints", new ArrayList<>())); return response; } // Other handler methods would be implemented similarly public void close() { // Clean up session resources } private Map<String, Object> initializeCapabilities() { Map<String, Object> caps = new HashMap<>(); caps.put("supportsConfigurationDoneRequest", true); caps.put("supportsEvaluateForHovers", true); caps.put("supportsStepBack", false); caps.put("supportsSetVariable", true); caps.put("supportsRestartFrame", false); caps.put("supportsGotoTargetsRequest", false); caps.put("supportsStepInTargetsRequest", false); caps.put("supportsCompletionsRequest", true); caps.put("supportsModulesRequest", false); caps.put("supportsRestartRequest", false); caps.put("supportsExceptionOptions", false); caps.put("supportsValueFormattingOptions", false); caps.put("supportsExceptionInfoRequest", true); caps.put("supportTerminateDebuggee", false); caps.put("supportsDelayedStackTraceLoading", false); caps.put("supportsLoadedSourcesRequest", false); caps.put("supportsLogPoints", true); caps.put("supportsTerminateThreadsRequest", false); caps.put("supportsSetExpression", false); caps.put("supportsTerminateRequest", false); caps.put("supportsDataBreakpoints", false); caps.put("supportsReadMemoryRequest", false); caps.put("supportsDisassembleRequest", false); caps.put("supportsCancelRequest", false); caps.put("supportsBreakpointLocationsRequest", true); caps.put("supportsClipboardContext", true); caps.put("supportsSteppingGranularity", false); caps.put("supportsInstructionBreakpoints", false); caps.put("supportsExceptionFilterOptions", false); return caps; } private DAPResponse createErrorResponse(DAPMessage request, String message) { DAPResponse response = new DAPResponse(request.getSeq(), request.getCommand(), false); response.setMessage(message); return response; } } }

Usage Examples and Integration

package com.example.truffle.instrument; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.nodes.RootNode; /** * Example usage of Truffle debugging instruments */ public class DebuggingExamples { /** * Example of using the breakpoint instrument */ public static class BreakpointExample { public void demonstrateBreakpoints() { // In a real implementation, you'd get the instrument from the environment // BreakpointInstrument.BreakpointManager breakpointManager = // env.getInstrumenter().lookup(BreakpointInstrument.BreakpointManager.class); // Set breakpoints // breakpointManager.setBreakpoint(source, 10); // breakpointManager.setBreakpoint(source, 20, "x > 5"); // Add breakpoint listener // breakpointManager.addBreakpointListener(new BreakpointInstrument.BreakpointListener() { // @Override // public void onBreakpointSet(BreakpointInstrument.Breakpoint breakpoint) { // System.out.println("Breakpoint set at line " + breakpoint.getLine()); // } // // @Override // public void onBreakpointHit(BreakpointInstrument.Breakpoint breakpoint) { // System.out.println("Breakpoint hit at line " + breakpoint.getLine()); // // Pause execution and start debugging session // } // }); } } /** * Example of using the execution tracer */ public static class TracingExample { public void demonstrateTracing() { // ExecutionTracerInstrument.ExecutionTracer tracer = // env.getInstrumenter().lookup(ExecutionTracerInstrument.ExecutionTracer.class); // Configure tracing // tracer.setEnabled(true); // tracer.setTraceOutput(System.out); // Add execution listener // tracer.addListener(new ExecutionTracerInstrument.ExecutionListener() { // @Override // public void onExecution(ExecutionTracerInstrument.ExecutionEvent event) { // SourceSection section = event.getSourceSection(); // System.out.printf("Executed %s:%d in %d ns%n", // section.getSource().getName(), // section.getStartLine(), // event.getDurationNanos()); // } // }); // Generate report after execution // ExecutionTracerInstrument.ExecutionReport report = tracer.generateReport(); // report.printReport(new PrintWriter(System.out)); } } /** * Example of using the variable inspector */ public static class VariableInspectionExample { public void demonstrateVariableInspection() { // VariableInspectorInstrument.VariableInspector inspector = // env.getInstrumenter().lookup(VariableInspectorInstrument.VariableInspector.class); // Add watch expressions // inspector.addWatchExpression("x + y"); // inspector.addWatchExpression("calculateTotal()"); // Add variable change listener // inspector.addVariableChangeListener( // new VariableInspectorInstrument.VariableChangeListener() { // @Override // public void onVariableChanged( // VariableInspectorInstrument.VariableChangeEvent event) { // System.out.printf("Variable %s changed from %s to %s%n", // event.getVariableName(), // event.getOldValue(), // event.getNewValue()); // } // }); } } /** * Integration with a Truffle language */ @TruffleLanguage.Registration( id = "example-lang", name = "Example Language", version = "1.0", characterMimeTypes = "application/x-example" ) public static class ExampleLanguage extends TruffleLanguage<ExampleContext> { @Override protected ExampleContext createContext(Env env) { // Initialize debugging instruments initializeInstruments(env); return new ExampleContext(); } private void initializeInstruments(Env env) { // The instruments are automatically loaded by Truffle // We can access them through the environment // Example: Set up breakpoints for commonly used files // BreakpointInstrument.BreakpointManager breakpointManager = // env.getInstrumenter().lookup(BreakpointInstrument.BreakpointManager.class); // // if (breakpointManager != null) { // // Set initial breakpoints based on configuration // breakpointManager.addBreakpointListener(new DebugEventListener()); // } } @Override protected CallTarget parse(ParsingRequest request) throws Exception { // Parse the source code and return call target Source source = request.getSource(); RootNode rootNode = new ExampleRootNode(this, source); return Truffle.getRuntime().createCallTarget(rootNode); } @Override protected Object getLanguageGlobal(ExampleContext context) { return context.getGlobalScope(); } @Override protected boolean isObjectOfLanguage(Object object) { return object instanceof ExampleObject; } // Debug event listener for the language private static class DebugEventListener implements BreakpointInstrument.BreakpointListener { @Override public void onBreakpointSet(BreakpointInstrument.Breakpoint breakpoint) { System.out.println("Language: Breakpoint set at " + breakpoint.getSource().getName() + ":" + breakpoint.getLine()); } @Override public void onBreakpointRemoved(BreakpointInstrument.Breakpoint breakpoint) { System.out.println("Language: Breakpoint removed from " + breakpoint.getSource().getName() + ":" + breakpoint.getLine()); } @Override public void onBreakpointHit(BreakpointInstrument.Breakpoint breakpoint) { System.out.println("Language: Breakpoint hit at " + breakpoint.getSource().getName() + ":" + breakpoint.getLine()); // Implement language-specific breakpoint handling } } } // Supporting classes for the example language public static class ExampleContext { private final Map<String, Object> globalScope = new HashMap<>(); public Map<String, Object> getGlobalScope() { return globalScope; } } public static class ExampleRootNode extends RootNode { private final Source source; protected ExampleRootNode(ExampleLanguage language, Source source) { super(language); this.source = source; } @Override public Object execute(VirtualFrame frame) { // Execute the program return "Program result"; } } public static class ExampleObject { // Example language object } }

Best Practices

  1. Performance Considerations:
  • Use SourceSectionFilter to limit instrumentation to relevant code
  • Cache event bindings and reuse them when possible
  • Use asynchronous processing for expensive operations
  1. Memory Management:
  • Always dispose of event bindings when no longer needed
  • Use weak references for long-lived listeners
  • Clean up resources in onContextClosed
  1. Error Handling:
  • Handle exceptions gracefully in event listeners
  • Use appropriate logging for debugging the debugger itself
  • Validate inputs in public API methods
  1. Integration:
  • Follow the Debug Adapter Protocol for IDE compatibility
  • Provide comprehensive capabilities reporting
  • Support both launch and attach debugging modes

Conclusion

The Truffle Instrument API provides a powerful foundation for building sophisticated debugging tools for Truffle languages. Key advantages include:

  • Language-Agnostic: Works with any language implemented on Truffle
  • High Performance: Minimal overhead through selective instrumentation
  • Comprehensive Access: Full access to execution state, frames, and variables
  • Protocol Support: Easy integration with standard debugging protocols like DAP
  • Extensible Architecture: Easy to add new debugging features and instruments

By leveraging these instruments, you can create professional-grade debugging experiences that integrate seamlessly with existing IDEs and debugging tools while providing language-specific insights and capabilities.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper