/* * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.tele.debug; import static com.sun.max.tele.debug.ProcessState.*; import java.io.*; import java.nio.*; import java.util.*; import java.util.concurrent.*; import com.sun.max.*; import com.sun.max.gui.*; import com.sun.max.platform.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.data.*; import com.sun.max.tele.debug.TeleNativeThread.*; import com.sun.max.tele.memory.*; import com.sun.max.tele.method.*; import com.sun.max.tele.method.CodeLocation.*; import com.sun.max.tele.page.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.thread.*; /** * A model of the remote process in which the VM is running, * which includes access to the memory state and debugging * actions that control execution. */ public abstract class TeleProcess extends AbstractVmHolder implements TeleVMCache, TeleIO { private static final int TRACE_VALUE = 1; // Detailed tracing is done at TRACE_VALUE + 1 private final TimedTrace updateTracer; // Standard names for process control actions. private static final String RUN_TO_INSTRUCTION = "runToInstruction"; private static final String TERMINATE = "terminate"; private static final String PAUSE = "pause"; private static final String RESUME = "resume"; private static final String SINGLE_STEP = "singleStep"; private static final String STEP_OVER = "stepOver"; private static final List<TeleNativeThread> EMPTY_THREAD_LIST = Collections.emptyList(); private static final List<TeleBreakpointEvent> EMPTY_BREAKPOINTEVENT_LIST = Collections.emptyList(); public static final String[] EMPTY_COMMAND_LINE_ARGUMENTS = {}; /** * Exception thrown in the event handling loop when * the VM process is discovered to have died during * an execution. */ private final class ProcessDied extends Exception { public ProcessDied(String message) { super(message); } } /** * Allocates and initializes a buffer in native memory to hold a given set of command line arguments. De-allocating * the memory for the buffer is the responsibility of the caller. * * @param programFile the executable that will be copied into element 0 of the returned buffer * @param commandLineArguments * @return a native buffer than can be cast to the C type {@code char**} and used as the first argument to a C * {@code main} function */ public static long createCommandLineArgumentsBuffer(File programFile, String[] commandLineArguments) { final String[] strings = new String[commandLineArguments.length + 1]; strings[0] = programFile.getAbsolutePath(); System.arraycopy(commandLineArguments, 0, strings, 1, commandLineArguments.length); return CString.utf8ArrayFromStringArray(strings, true, true); } /** * A thread on which local VM execution requests are made, and on which follow-up actions are * performed when the VM process (or thread) stops. */ private class RequestHandlingThread extends Thread { /** * The action to be performed when the process (or a thread in the process) stops as a result of the * currently executing process request. */ private BlockingDeque<TeleEventRequest> requests = new LinkedBlockingDeque<TeleEventRequest>(10); RequestHandlingThread() { super("RequestHandlingThread"); setDaemon(true); } /** * Waits until the VM process has stopped after it has been issued an execution request. * Carry out any special processing needed in case of triggered watchpoint or breakpoint. * The request's post-execution action is then performed. * Finally, any threads waiting for an execution request to complete are notified. * * @param request the intended VM action assumed to be running in the VM. */ private void waitUntilProcessStopped(TeleEventRequest request) { assert requestHandlingThread == Thread.currentThread(); Trace.begin(TRACE_VALUE + 1, tracePrefix() + "waiting for execution to stop: " + request); try { final List<TeleBreakpointEvent> teleBreakpointEvents = new ArrayList<TeleBreakpointEvent>(); VmWatchpointEvent watchpointEvent = null; // Keep resuming the process, after dealing with event handlers, as long as this is true. boolean resumeExecution = true; do { // while resumeExecution = true assert teleBreakpointEvents.isEmpty(); assert watchpointEvent == null; // There are five legitimate cases where execution should halt (i.e. not be resumed), // although there may be more than one simultaneously . // Case 1. The process has died // Case 2. A thread was just single stepped // Case 3. At least one thread is at a breakpoint that specifies that execution should halt // Case 4. At least one thread is at a memory watchpoint that specifies that execution should halt // Case 5. There is a "pause" request pending // Check for the special case where we can't determine what caused the VM to stop boolean eventCauseFound = false; assert vm().lockHeldByCurrentThread(); // Wait here for VM process to stop ProcessState newState = waitUntilStopped(); ++epoch; // Case 1. The process has died; throw an exception. if (newState != ProcessState.STOPPED) { if (newState != ProcessState.TERMINATED) { throw new ProcessDied("unexpected state [" + newState + "]"); } throw new ProcessDied("normal exit"); } processState = newState; Trace.line(TRACE_VALUE + 1, tracePrefix() + "Execution stopped: " + request); if (lastSingleStepThread != null) { // Case 2. A thread was just single stepped; do not continue execution. eventCauseFound = true; resumeExecution = false; } // Clear all breakpoints before we refresh, so the breakpoints don't show up as real patches to compiled code breakpointManager().targetBreakpoints().setActiveAll(false); // Read VM memory and update various bits of cached state about the VM state updateVMCaches(request, epoch); updateCache(request); int newlystarted = 0; int newlydetached = 0; // Look through all the threads to see which, if any, have events triggered that caused the stop for (TeleNativeThread thread : threads()) { switch(thread.state()) { case BREAKPOINT: eventCauseFound = true; final VmTargetBreakpoint breakpoint = thread.breakpoint(); if (breakpoint.handleTriggerEvent(thread)) { Trace.line(TRACE_VALUE + 1, tracePrefix() + " stopping thread [id=" + thread.id() + "] after triggering breakpoint"); // Case 3. At least one thread is at a breakpoint that specifies that execution should halt; record it and do not continue. // At a breakpoint where we should really stop; create a record teleBreakpointEvents.add(new TeleBreakpointEvent(breakpoint, thread)); resumeExecution = false; } else { if (breakpoint.codeLocation().equals(methods().vmThreadRunMethodLocation())) { newlystarted++; } else if (breakpoint.codeLocation().equals(methods().vmThreadDetachedMethodLocation())) { newlydetached++; } Trace.line(TRACE_VALUE, tracePrefix() + (resumeExecution ? " RESUMING" : " STOPPING") + " execution after thread [id=" + thread.id() + "] triggered breakpoint"); } break; case WATCHPOINT: eventCauseFound = true; final Address triggeredWatchpointAddress = Address.fromLong(readWatchpointAddress()); final VmWatchpoint systemWatchpoint = vm().watchpointManager().findSystemWatchpoint(triggeredWatchpointAddress); if (systemWatchpoint != null && systemWatchpoint.handleTriggerEvent(thread)) { Trace.line(TRACE_VALUE + 1, tracePrefix() + " stopping thread [id=" + thread.id() + "] after triggering system watchpoint"); // Case 4. At least one thread is at a memory watchpoint that specifies that execution should halt; record it and do not continue. final int triggeredWatchpointCode = readWatchpointAccessCode(); watchpointEvent = new VmWatchpointEvent(systemWatchpoint, thread, triggeredWatchpointAddress, triggeredWatchpointCode); resumeExecution = false; break; } final VmWatchpoint clientWatchpoint = vm().watchpointManager().findClientWatchpointContaining(triggeredWatchpointAddress); if (clientWatchpoint != null && clientWatchpoint.handleTriggerEvent(thread)) { Trace.line(TRACE_VALUE + 1, tracePrefix() + " stopping thread [id=" + thread.id() + "] after triggering client watchpoint"); // Case 4. At least one thread is at a memory watchpoint that specifies that execution should halt; record it and do not continue. final int triggeredWatchpointCode = readWatchpointAccessCode(); watchpointEvent = new VmWatchpointEvent(clientWatchpoint, thread, triggeredWatchpointAddress, triggeredWatchpointCode); resumeExecution = false; } break; default: // This thread not stopped at breakpoint or watchpoint break; } } if (pauseRequestPending) { // Case 5. There is a "pause" request pending // Whether or not the process has threads at breakpoints or watchpoints, // we must not resume execution if a client-originated pause has been requested. eventCauseFound = true; resumeExecution = false; pauseRequestPending = false; } if (!eventCauseFound) { new Exception("Process halted for no apparent cause").printStackTrace(); } // ProgramError.check(eventCauseFound, "Process halted for no apparent cause"); // CLEANUP: debugging traces... if (newlystarted > 0 || newlydetached > 0) { Trace.line(TRACE_VALUE, tracePrefix() + " (epoch=" + epoch + ") " + "Hit " + newlystarted + " VmThread.run() breakpoints " + newlydetached + " VmThread.detached() breakpoints " + ", resume execution = " + resumeExecution + " " + request); } if (resumeExecution) { Trace.line(TRACE_VALUE, tracePrefix() + "Resuming execution after handling event triggers: " + request); restoreBreakpointsAndResume(request.withClientBreakpoints); processState = RUNNING; } } while (resumeExecution); // Finished with these now breakpointManager().targetBreakpoints().removeTransientBreakpoints(); Trace.end(TRACE_VALUE /*+ 1*/, tracePrefix() + " e(" + epoch + ") " + "waiting for execution to stop: " + request); Trace.begin(TRACE_VALUE + 1, tracePrefix() + "firing execution post-request action: " + request); request.notifyProcessStopped(); Trace.end(TRACE_VALUE + 1, tracePrefix() + "firing execution post-request action: " + request); updateState(STOPPED, teleBreakpointEvents, watchpointEvent); } catch (ProcessDied processDied) { Trace.line(TRACE_VALUE + 1, tracePrefix() + "VM process terminated: " + processDied.getMessage()); updateState(TERMINATED); } catch (Throwable throwable) { throwable.printStackTrace(); ThrowableDialog.showLater(throwable, null, tracePrefix() + "Uncaught exception while processing " + request); } finally { Trace.begin(TRACE_VALUE + 1, tracePrefix() + "notifying completion of request: " + request); request.notifyOfCompletion(); Trace.end(TRACE_VALUE + 1, tracePrefix() + "notifying completion of request: " + request); } } private void updateCache(TeleEventRequest request) { try { TeleProcess.this.updateCache(epoch); } catch (Throwable e) { e.printStackTrace(); ThrowableDialog.showLater(e, null, tracePrefix() + "Uncaught exception updating cache while processing " + request); } } /** * Requests the update of all state cached from the VM. * @param request * @param epoch the number of times the process has run */ private void updateVMCaches(TeleEventRequest request, long epoch) { try { vm().updateVMCaches(epoch); } catch (Throwable e) { e.printStackTrace(); ThrowableDialog.showLater(e, null, tracePrefix() + "Uncaught exception updating VM caches while processing " + request); } } private String traceSuffix(boolean synchronous) { return " (" + (synchronous ? "synchronous" : "asynchronous") + ")"; } /** * Accepts a tele process execution request and schedules it for execution on the * {@linkplain #requestHandlingThread request handling thread}. The request is executed immediately if the * {@linkplain Thread#currentThread() current} thread is the request handling thread. Otherwise, it will be executed * once any pending request has completed. * * @param request an execution request to schedule * @param isSynchronous if this value is true or the {@linkplain Thread#currentThread() current thread} is the * {@linkplain #requestHandlingThread request handling thread}, this method will block until the * request's post execution action has * completed. Otherwise, this method returns after scheduling the request and notifying the request * handling thread. */ void scheduleRequest(TeleEventRequest request, boolean isSynchronous) { final Thread currentThread = Thread.currentThread(); if (currentThread == this) { Trace.begin(TRACE_VALUE + 1, tracePrefix() + "immediate execution request: " + traceSuffix(isSynchronous)); execute(request, true); Trace.end(TRACE_VALUE + 1, tracePrefix() + "immediate execution request: " + request + traceSuffix(isSynchronous)); } else { try { Trace.begin(TRACE_VALUE + 1, tracePrefix() + "scheduled execution request: " + request + traceSuffix(isSynchronous)); requests.putFirst(request); Trace.end(TRACE_VALUE + 1, tracePrefix() + "scheduled execution request: " + request + traceSuffix(isSynchronous)); } catch (InterruptedException interruptedException) { TeleWarning.message(tracePrefix() + "Could not schedule " + request, interruptedException); return; } if (isSynchronous) { Trace.begin(TRACE_VALUE + 1, tracePrefix() + "waiting for synchronous request to complete: " + request); request.waitUntilComplete(); Trace.end(TRACE_VALUE + 1, tracePrefix() + "waiting for synchronous request to complete: " + request); } } } private void execute(TeleEventRequest request, boolean isNested) { if (!isNested && processState != STOPPED) { TeleWarning.message(tracePrefix() + "Cannot execute \"" + request + "\" unless process state is " + STOPPED); } else { try { lastSingleStepThread = null; Trace.begin(TRACE_VALUE + 1, tracePrefix() + "executing request: " + request); updateState(RUNNING); request.execute(); Trace.end(TRACE_VALUE + 1, tracePrefix() + "executing request: " + request); } catch (OSExecutionRequestException executionRequestException) { executionRequestException.printStackTrace(); return; } waitUntilProcessStopped(request); } } @Override public void run() { while (true) { try { final TeleEventRequest request = requests.takeLast(); vm().lock(); Trace.begin(TRACE_VALUE + 1, tracePrefix() + "handling execution request: " + request); execute(request, false); Trace.end(TRACE_VALUE + 1, tracePrefix() + "handling execution request: " + request); } catch (InterruptedException interruptedException) { TeleWarning.message(tracePrefix() + "Could not take request from sceduling queue", interruptedException); } catch (Throwable throwable) { TeleWarning.message(tracePrefix() + "Error on RequestHandlingThread: " + Utils.stackTraceAsString(throwable)); } finally { vm().unlock(); } } } } @Override protected String tracePrefix() { return "[TeleProcess: " + Thread.currentThread().getName() + "] "; } private final Platform platform; private final int maximumWatchpointCount; private final RequestHandlingThread requestHandlingThread; /** * The number of times that the VM's process has been run. */ private long epoch; /** * The current state of the process. */ private ProcessState processState; /** * All threads currently active in the process. */ private SortedMap<Long, TeleNativeThread> handleToThreadMap = new TreeMap<Long, TeleNativeThread>(); /** * Instruction pointers for all threads currently active in the process. */ private Set<Long> instructionPointers = new HashSet<Long>(); /** * The thread that was single-stepped in the most recent VM activation, * null if the most recent activation was not a single step. */ private TeleNativeThread lastSingleStepThread; /** * Threads newly created since the previous execution request. */ private final Set<TeleNativeThread> threadsStarted = new TreeSet<TeleNativeThread>(); /** * Threads newly died since the previous execution request (may contain newly created threads). */ private final Set<TeleNativeThread> threadsDied = new TreeSet<TeleNativeThread>(); /** * Whether a client-initiated request to pause the process is pending. */ private boolean pauseRequestPending = false; private int transportDebugLevel = 0; private final Object statsPrinter = new Object() { @Override public String toString() { final int currentThreadCount = handleToThreadMap.values().size(); final StringBuilder msg = new StringBuilder(); msg.append("#threads=(").append(currentThreadCount); msg.append(", started=").append(threadsStarted.size()); msg.append(", died=").append(threadsDied.size()).append(")"); return msg.toString(); } }; /** * @param vm the VM with which the Process will be associated * @param platform * @param initialState Initial state of the process at creation. */ protected TeleProcess(TeleVM vm, Platform platform, ProcessState initialState) { super(vm); final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " creating"); tracer.begin(); this.platform = platform; this.processState = initialState; this.epoch = 0; this.maximumWatchpointCount = platformWatchpointCount(); this.updateTracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " updating"); //Initiate the thread that continuously waits on the running process. this.requestHandlingThread = new RequestHandlingThread(); if (initialState != ProcessState.UNKNOWN) { this.requestHandlingThread.start(); } tracer.end(statsPrinter); } /** * Initializes the state history for the process. Must be called after process created, but before * any requests. */ public final void initializeState() { final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " initializing"); tracer.begin(); updateState(processState); tracer.end(statsPrinter); } /** * Initializes the state history for attach/dump modes. */ public final void initializeStateOnAttach() { final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " initializing on attach"); tracer.begin(); updateState(processState); epoch++; updateCache(epoch); // now update state to reflect the discovered threads, all of which will appear as STARTED updateState(STOPPED); tracer.end(statsPrinter); } public void updateCache(long epoch) { updateTracer.begin("epoch =" + epoch); assert vm().lockHeldByCurrentThread(); final List<TeleNativeThread> currentThreads = new ArrayList<TeleNativeThread>(handleToThreadMap.size()); gatherThreads(currentThreads); final SortedMap<Long, TeleNativeThread> newHandleToThreadMap = new TreeMap<Long, TeleNativeThread>(); final Set<Long> newInstructionPointers = new HashSet<Long>(); // List all previously live threads as possibly dead; remove the ones discovered to be current. threadsDied.addAll(handleToThreadMap.values()); for (TeleNativeThread thread : currentThreads) { // Refresh the thread thread.updateCache(epoch); newHandleToThreadMap.put(thread.localHandle(), thread); final TeleNativeThread oldThread = handleToThreadMap.get(thread.localHandle()); if (oldThread != null) { if (oldThread != thread) { threadsStarted.add(thread); } else { threadsDied.remove(thread); Trace.line(TRACE_VALUE + 1, " " + thread); } } else { threadsStarted.add(thread); Trace.line(TRACE_VALUE + 1, " " + thread + " STARTED"); } final Pointer instructionPointer = thread.registers().instructionPointer(); if (instructionPointer.isNotZero()) { newInstructionPointers.add(instructionPointer.toLong()); } } handleToThreadMap = newHandleToThreadMap; instructionPointers = newInstructionPointers; updateTracer.end(statsPrinter); } /** * Causes VM execution of a single instruction on a specified thread. * * @param thread the thread to be executed. * @param isSynchronous wait until execution is complete to return? * @throws InvalidVMRequestException * @throws OSExecutionRequestException */ public final void singleStepThread(final TeleNativeThread thread, boolean isSynchronous) throws InvalidVMRequestException, OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + SINGLE_STEP + " schedule"); final TeleEventRequest request = new TeleEventRequest(SINGLE_STEP, thread, false) { @Override public void execute() throws OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + SINGLE_STEP + " perform"); updateWatchpointCaches(); singleStep(thread, false); Trace.end(TRACE_VALUE + 1, tracePrefix() + SINGLE_STEP + " perform"); } }; requestHandlingThread.scheduleRequest(request, isSynchronous); Trace.end(TRACE_VALUE + 1, tracePrefix() + SINGLE_STEP + " schedule"); } /** * Steps a single thread to the next instruction in the current method. If the current * instruction is a call, then run until the call returns. * <br> * This is effected by first single stepping and then noticing if execution has arrived at the * next instruction (the simple case). If not, then assume that the thread stepped into a * call, set a transient breakpoint, and resume. * * @param thread the thread to step * @param synchronous wait for execution to complete before returning? * @param withClientBreakpoints should client breakpoints be enabled during execution? * @throws InvalidVMRequestException * @throws OSExecutionRequestException */ public final void stepOver(final TeleNativeThread thread, boolean synchronous, final boolean withClientBreakpoints) throws InvalidVMRequestException, OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, STEP_OVER + " schedule"); final TeleEventRequest request = new TeleEventRequest(STEP_OVER, thread, withClientBreakpoints) { private Pointer oldInstructionPointer; private Pointer oldReturnAddress; @Override public void execute() throws OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + STEP_OVER + " perform"); updateWatchpointCaches(); oldInstructionPointer = thread.registers().instructionPointer(); final CodeLocation returnLocation = thread.stack().returnLocation(); if (returnLocation == null) { TeleWarning.message("stepOver cannot determine frame's return address"); oldReturnAddress = Pointer.zero(); } else { oldReturnAddress = returnLocation.address().asPointer(); } singleStep(thread, false); Trace.end(TRACE_VALUE + 1, tracePrefix() + STEP_OVER + " perform"); } @Override public void notifyProcessStopped() { final CodeLocation stepOutLocation = getStepoutLocation(thread, oldReturnAddress, oldInstructionPointer, thread.registers().instructionPointer()); if (stepOutLocation != null) { try { runToInstruction(stepOutLocation, true, withClientBreakpoints); } catch (OSExecutionRequestException e) { e.printStackTrace(); } catch (InvalidVMRequestException e) { e.printStackTrace(); } } } }; requestHandlingThread.scheduleRequest(request, synchronous); Trace.end(TRACE_VALUE + 1, STEP_OVER + " schedule"); } /** * Resumes process to make it run until a given destination instruction is reached. * <br> * This is effected by creating a transient breakpoint and then performing an ordinary resume. * * @param codeLocation the destination instruction in compiled code * @param synchronous wait for completion before returning? * @param withClientBreakpoints enable client breakpoints during execution? * @throws OSExecutionRequestException * @throws InvalidVMRequestException */ public final void runToInstruction(final CodeLocation codeLocation, final boolean synchronous, final boolean withClientBreakpoints) throws OSExecutionRequestException, InvalidVMRequestException { assert codeLocation instanceof MachineCodeLocation; final MachineCodeLocation compiledCodeLocation = (MachineCodeLocation) codeLocation; Trace.begin(TRACE_VALUE + 1, tracePrefix() + RUN_TO_INSTRUCTION + " schedule"); final TeleEventRequest request = new TeleEventRequest(RUN_TO_INSTRUCTION, null, withClientBreakpoints) { @Override public void execute() throws OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + RUN_TO_INSTRUCTION + " perform"); updateWatchpointCaches(); // Create a temporary breakpoint if there is not already an enabled, non-persistent breakpoint for the target address: VmTargetBreakpoint breakpoint = breakpointManager().targetBreakpoints().findClientBreakpoint(compiledCodeLocation.codePointer()); if (breakpoint == null || !breakpoint.isEnabled()) { try { breakpoint = breakpointManager().targetBreakpoints().makeTransientBreakpoint(compiledCodeLocation); } catch (MaxVMBusyException e) { TeleError.unexpected("run to instruction should alwasy be executed inside VM lock on request handling thread"); } breakpoint.setDescription("transient breakpoint for low-level run-to-instruction operation"); } restoreBreakpointsAndResume(withClientBreakpoints); Trace.end(TRACE_VALUE + 1, tracePrefix() + RUN_TO_INSTRUCTION + " perform"); } }; requestHandlingThread.scheduleRequest(request, synchronous); Trace.end(TRACE_VALUE + 1, tracePrefix() + RUN_TO_INSTRUCTION + " schedule"); } /** * Resumes process execution. * * @param synchronous wait for completion before returning? * @param withClientBreakpoints enable client breakpoints during execution? * @throws OSExecutionRequestException * @throws InvalidVMRequestException */ public final void resume(final boolean synchronous, final boolean withClientBreakpoints) throws InvalidVMRequestException, OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + RESUME + " schedule"); final TeleEventRequest request = new TeleEventRequest(RESUME, null, withClientBreakpoints) { @Override public void execute() throws OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + RESUME + " perform"); updateWatchpointCaches(); restoreBreakpointsAndResume(withClientBreakpoints); Trace.end(TRACE_VALUE + 1, tracePrefix() + RESUME + " perform"); } }; requestHandlingThread.scheduleRequest(request, synchronous); Trace.end(TRACE_VALUE + 1, tracePrefix() + RESUME + " schedule"); } /** * Request that VM execution suspend as soon as possible. <br> * The suspended process may or may not have threads stopped * at breakpoints by the time execution stops completely. * * @throws InvalidVMRequestException * @throws OSExecutionRequestException */ public final void pauseProcess() throws InvalidVMRequestException, OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + PAUSE + " perform"); if (processState != RUNNING) { throw new InvalidVMRequestException("Can only suspend a running tele process, not a tele process that is " + processState.toString().toLowerCase()); } pauseRequestPending = true; suspend(); Trace.end(TRACE_VALUE + 1, tracePrefix() + PAUSE + " perform"); } /** * Kill the VM process immediately. * * @throws InvalidVMRequestException * @throws OSExecutionRequestException */ public final void terminateProcess() throws InvalidVMRequestException, OSExecutionRequestException { Trace.begin(TRACE_VALUE + 1, tracePrefix() + TERMINATE + " perform"); if (processState == TERMINATED) { throw new InvalidVMRequestException("Can only terminate a non-terminated tele process, not a tele process that is " + processState.toString().toLowerCase()); } kill(); Trace.end(TRACE_VALUE + 1, tracePrefix() + TERMINATE + " perform"); } /** * Gets the current process epoch: the number of requested execution steps of the process since it was created. * <br> * This counter is updated directly after the process halts, and so is correct throughout the VM refresh cycle, * unlike the {@linkplain TeleVM#state() VM state cache}, which is updated only at the end of the refresh cycle * when external clients are notified. * <br> * Note that this is different from the number of execution requests made by clients, since the process * may be run several times in the execution of such a request. * * @return the current process epoch */ public final long epoch() { return epoch; } public final int pageSize() { return platform.pageSize; } public final int read(Address address, ByteBuffer buffer, int offset, int length) throws DataIOError, TerminatedProcessIOException { if (processState == TERMINATED) { final StringBuilder msg = new StringBuilder(); msg.append("Memory read @ ").append(address.to0xHexString()); msg.append(" (process TERMINATED)"); throw new TerminatedProcessIOException(msg.toString()); } if (processState != STOPPED && processState != null && Thread.currentThread() != requestHandlingThread) { throw new DataIOError(address, "Reading from process memory while processed not stopped [thread: " + Thread.currentThread().getName() + "]"); // TeleWarning.message("Reading from process memory while processed not stopped [thread: " + Thread.currentThread().getName() + "]"); } DataIO.Static.checkRead(buffer, offset, length); final int bytesRead = read0(address, buffer, offset, length); if (bytesRead < 0) { throw new DataIOError(address); } return bytesRead; } public final int write(ByteBuffer buffer, int offset, int length, Address address) throws DataIOError, IndexOutOfBoundsException, TerminatedProcessIOException { if (processState == TERMINATED) { final StringBuilder msg = new StringBuilder(); msg.append("Attempt to write to memory @").append(address.to0xHexString()); msg.append("memory when the process is in state " + TERMINATED); throw new TerminatedProcessIOException(msg.toString()); } if (processState != STOPPED && processState != null && Thread.currentThread() != requestHandlingThread) { //TeleWarning.message("Writing to process memory while processed not stopped [thread: " + Thread.currentThread().getName() + "]"); /* Uncomment to trace the culprit try { throw new Exception(); } catch (Exception ex) { ex.printStackTrace(); } */ } DataIO.Static.checkWrite(buffer, offset, length); return write0(buffer, offset, length, address); } /** * @return the current state of the process */ public final ProcessState processState() { return processState; } /** * Gets the set of all the threads in this process the last time it stopped. * The returned threads are sorted in ascending order of their {@linkplain TeleNativeThread#id() identifiers}. * * @return the threads in the process */ public final Collection<TeleNativeThread> threads() { return handleToThreadMap.values(); } /** * Thread-safe. * * @return whether watchpoints are supported on this platform. */ public boolean watchpointsEnabled() { return maximumWatchpointCount > 0; } /** * @return platform-specific limit on how many memory watchpoints can be * simultaneously active; 0 if memory watchpoints are not supported on the platform. */ protected abstract int platformWatchpointCount(); /** * @return tracing level of the underlying transportation * mechanism used for communication with this process. * * @see #setTransportDebugLevel(int) */ public final int transportDebugLevel() { return transportDebugLevel; } /** * A subclass should override this method to set the tracing level of the underlying * transport mechanism used for communication with the target. The override should call * super.setTransportDebugLevel to cache the value here. * @param level new level */ public void setTransportDebugLevel(int level) { transportDebugLevel = level; } /** * @return address to data i/o from process memory; platform-specific implementation. */ public abstract DataAccess dataAccess(); /** * Gathers information about this process and posts a thread-safe record of the state change. * * @param newState the current state of this process * @param watchpointEvent description of watchpoint trigger, if just happened. */ private void updateState(ProcessState newState, List<TeleBreakpointEvent> breakpointEvents, VmWatchpointEvent watchpointEvent) { processState = newState; if (newState == TERMINATED) { this.threadsDied.addAll(handleToThreadMap.values()); handleToThreadMap.clear(); } for (TeleNativeThread thread : this.threadsDied) { thread.setDead(); Trace.line(TRACE_VALUE + 1, tracePrefix() + " " + thread.toShortString() + " DEAD"); } final List<TeleNativeThread> threadsStarted = this.threadsStarted.isEmpty() ? EMPTY_THREAD_LIST : new ArrayList<TeleNativeThread>(this.threadsStarted); final List<TeleNativeThread> threadsDied = this.threadsDied.isEmpty() ? EMPTY_THREAD_LIST : new ArrayList<TeleNativeThread>(this.threadsDied); this.threadsStarted.clear(); this.threadsDied.clear(); vm().notifyStateChange(processState, epoch, lastSingleStepThread, handleToThreadMap.values(), threadsStarted, threadsDied, breakpointEvents, watchpointEvent); } private void updateState(ProcessState newState) { updateState(newState, EMPTY_BREAKPOINTEVENT_LIST, null); } /** * Given the code location before and after a single step, this method determines if the step represents a call * from one target method to another (or a recursive call from a target method to itself) and, if so, returns the * address of the next instruction that will be executed in the target method that is the origin of the step (i.e. * the return address of the call). * * @param thread the executing thread * @param oldReturnAddress the return address of the thread just before the single step * @param oldInstructionPointer the instruction pointer of the thread just before the single step * @param newInstructionPointer the instruction pointer of the thread just after the single step * @return if {@code oldInstructionPointer} and {@code newInstructionPointer} indicate two different target methods * or a recursive call to the same target method, then the return location of the call is returned. * Otherwise, null is returned, indicating that the step over is really just a single step. */ private CodeLocation getStepoutLocation(TeleNativeThread thread, Pointer oldReturnAddress, Pointer oldInstructionPointer, Pointer newInstructionPointer) { if (newInstructionPointer.equals(oldReturnAddress)) { // Executed a return return null; } final TeleCompilation oldCompiledCode = vm().machineCode().findCompilation(oldInstructionPointer); if (oldCompiledCode == null) { // Stepped from external native code: return null; } final TeleCompilation newCompiledCode = vm().machineCode().findCompilation(newInstructionPointer); if (newCompiledCode == null) { // Stepped into external native code: return null; } if (oldCompiledCode != newCompiledCode || newCompiledCode.getCallEntryPoint().equals(newInstructionPointer)) { // Stepped into a different compilation or back into the entry of the same target method (i.e. a recursive call): return thread.stack().returnLocation(); } // Stepped over a normal, non-call instruction: return null; } /** * Re-activates breakpoints and resumes VM execution, first ensuring * that no threads are stuck at breakpoints. * * @param withClientBreakpoints should client-created breakpoints be activated? * @throws OSExecutionRequestException */ private void restoreBreakpointsAndResume(boolean withClientBreakpoints) throws OSExecutionRequestException { for (TeleNativeThread thread : threads()) { thread.evadeBreakpoint(); } if (withClientBreakpoints) { breakpointManager().targetBreakpoints().setActiveAll(true); } else { breakpointManager().targetBreakpoints().setActiveNonClient(true); } resume(); } /** * Resumes this process, platform-specific implementation. * * @throws OSExecutionRequestException if there was some problem while resuming this process */ protected abstract void resume() throws OSExecutionRequestException; /** * Suspends this process, platform-specific implementation. * * @throws OSExecutionRequestException if the request could not be performed */ protected abstract void suspend() throws OSExecutionRequestException; /** * Single steps a given thread. * * @param thread the thread to single step * @param block specifies if the current thread should wait until the single step completes * @throws OSExecutionRequestException if there was a problem issuing the single step */ protected final void singleStep(TeleNativeThread thread, boolean block) throws OSExecutionRequestException { if (!block) { lastSingleStepThread = thread; } if (!thread.singleStep()) { throw new OSExecutionRequestException("Error while single stepping thread " + thread); } if (block) { if (waitUntilStopped() != ProcessState.STOPPED) { throw new OSExecutionRequestException("Error while waiting for complete single stepping thread " + thread); } } } /** * Kills this process; platform-specific implementation. * * @throws OSExecutionRequestException */ protected abstract void kill() throws OSExecutionRequestException; /** * Waits for this process to stop. * * @return true if the process stopped, false if there was an error */ protected abstract ProcessState waitUntilStopped(); protected abstract void gatherThreads(List<TeleNativeThread> threads); /** * Creates a native thread; platform-specific implementation. * * @param params description of thread to be created. * @return the new thread */ protected abstract TeleNativeThread createTeleNativeThread(Params params); /** * Callback from JNI: creates new thread object or updates existing thread object with same thread ID. * * @param threads the sequence being used to collect the threads * @param id the {@link VmThread#id() id} of the thread. If {@code id > 0}, then this thread corresponds to a * {@link VmThread Java thread}. If {@code id == 0}, then this is the primordial thread. Otherwise, this * is a native thread or a Java thread that has not yet executed past the point in * {@link VmThread#run} where it is added to the active thread list. * @param localHandle the platform-specific process control library handle to this thread * @param handle the native thread library {@linkplain TeleNativeThread#handle() handle} to this thread * @param state * @param instructionPointer the current value of the instruction pointer * @param stackBase the lowest known address of the stack * @param stackSize the size of the stack in bytes * @param tlb the thread locals region of the thread * @param tlbSize the size of the thread locals region * @param tlaSize the size of a thread locals area */ public final void jniGatherThread(List<TeleNativeThread> threads, int id, long localHandle, long handle, int state, long instructionPointer, long stackBase, long stackSize, long tlb, long tlbSize, int tlaSize) { assert state >= 0 && state < MaxThreadState.values().length : state; TeleNativeThread thread = handleToThreadMap.get(localHandle); final TeleFixedMemoryRegion stackRegion = new TeleFixedMemoryRegion(vm(), "stack region", Address.fromLong(stackBase), stackSize); TeleFixedMemoryRegion threadLocalsRegion = (tlb == 0) ? null : new TeleFixedMemoryRegion(vm(), "thread locals region", Address.fromLong(tlb), tlbSize); Params params = new Params(); params.id = id; params.localHandle = localHandle; params.handle = handle; params.stackRegion = stackRegion; params.threadLocalsRegion = threadLocalsRegion; if (thread == null) { thread = createTeleNativeThread(params); } else { // Handle the cases where a thread was added/removed from the global thread list since the last epoch if (id > 0) { if (thread.id() != id) { // This is a Java thread that added from the global thread list since the last epoch. thread = createTeleNativeThread(params); } } else { if (thread.id() != id) { assert thread.isJava() : thread.id() + " != " + id + ": " + thread + ", params=" + params; // This is a Java thread that removed from the global thread list since the last epoch thread = createTeleNativeThread(params); } } } thread.updateAfterGather(MaxThreadState.values()[state], Pointer.fromLong(instructionPointer), threadLocalsRegion, tlaSize); threads.add(thread); } /** * Reads bytes from process memory, platform-specific implementation. * * Precondition: * {@code buffer != null && offset >= 0 && offset < buffer.capacity() && length >= 0 && offset + length <= buffer.capacity()} * * @param address the address from which reading should start * @param buffer the buffer into which the bytes are read * @param offset the offset in {@code buffer} at which the bytes are read * @param length the maximum number of bytes to be read * @return the number of bytes read into {@code buffer} * * @throws DataIOError if some IO error occurs * @throws IndexOutOfBoundsException if {@code offset} is negative, {@code length} is negative, or * {@code length > buffer.limit() - offset} * @see #read(Address, ByteBuffer, int, int) */ protected abstract int read0(Address address, ByteBuffer buffer, int offset, int length); /** * Writes bytes to process memory, platform-specific implementation. * * Precondition: * {@code buffer != null && offset >= 0 && offset < buffer.capacity() && length >= 0 && offset + length <= buffer.capacity()} * * @param buffer the buffer from which the bytes are written * @param offset the offset in {@code buffer} from which the bytes are written * @param length the maximum number of bytes to be written * @param address the address at which writing should start * @return the number of bytes written to {@code address} * * @throws DataIOError if some IO error occurs * @throws IndexOutOfBoundsException if {@code srcOffset} is negative, {@code length} is negative, or * {@code length > src.limit() - srcOffset} * @see #write(ByteBuffer, int, int, Address) */ protected abstract int write0(ByteBuffer buffer, int offset, int length, Address address); /** * Activates a watchpoint in the native process, according to the specified * watchpoint configuration. All watchpoints are by default <strong>after</strong> * watchpoints; they are intended to trigger after the specified event has taken place. * * @param watchpoint specifications for a watchpoint, assumed to be currently inactive. * @return whether the activation succeeded. */ protected boolean activateWatchpoint(VmWatchpoint watchpoint) { return false; } /** * Deactivate a watchpoint in the native process. * <br> * Deactivation depends on the location specified in the watchpoint; * * @param watchpoint a watchpoint that is currently activated, and which still * specifies the same address. * @return whether the deactivation succeeded. */ protected boolean deactivateWatchpoint(VmWatchpoint watchpoint) { return false; } protected long readWatchpointAddress() { return 0; } protected int readWatchpointAccessCode() { return 0; } private void updateWatchpointCaches() { if (vm().watchpointManager() != null) { vm().watchpointManager().updateWatchpointMemoryCaches(); } } }