/*
* 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.linux;
import java.awt.*;
import java.io.*;
import java.nio.*;
import java.util.concurrent.locks.*;
import com.sun.max.*;
import com.sun.max.lang.*;
import com.sun.max.tele.data.*;
import com.sun.max.tele.debug.*;
import com.sun.max.tele.util.*;
import com.sun.max.unsafe.*;
import com.sun.max.util.*;
/**
* A native Linux task (process or thread; threads are implemented as processes on Linux) that is controlled and
* accessed via the Linux {@code ptrace} facility. The methods that interact with the traced task always execute on a
* {@linkplain SingleThread single dedicated thread} fulfilling a requirement of ptrace. Because all operations
* on the single dedicated thread are synchronized, there's no need to synchronize the methods in this class that
* delegate to it.
*/
public final class LinuxTask {
/**
* The thread group identifier (TGID) of this task. This is the process identifier shared by all tasks in a
* process and is the value returned by getpid(2) since Linux 2.4.
*/
private final int tgid;
/**
* The (system-wide) unique task identifier (TID) of this task. The first task in a thread group is
* the <i>leader</i> of the new thread group and its {@link #tgid TGID} is the same as its {@link #tid TID}.
*
* Note that this is <b>not<b> the identifier returned by pthread_create(3p).
*/
private final int tid;
/**
* Gets the thread group identifier (TGID) of this task. This is the process identifier shared by all
* tasks in a process and is the value returned by getpid(2) since Linux 2.4.
*/
public int tgid() {
return tgid;
}
private final LinuxTask leader;
/**
* Gets the (system-wide) unique task identifier (TID) of this task. The first task in a thread group is
* the <i>leader</i> of the new thread group and its {@link #tgid TGID} is the same as its {@link #tid TID}.
*
* Note that this is <b>not<b> the identifier returned by pthread_create(3p).
*/
public int tid() {
return tid;
}
/**
* Gets the leader task from the thread group this task is a member of. The leader of a thread group is
* the first task created by clone(2) in a thread group.
*/
public LinuxTask leader() {
return leader;
}
/**
* Determines if this task is the {@linkplain #leader() leader} in its thread group.
*/
public boolean isLeader() {
return leader == this;
}
/**
* Creates the <i>leader</i> task for a Linux process.
*
* @param tgid
* @param tid
*/
LinuxTask(int tgid, int tid) {
assert tgid == tid : "TGID must match TID for leader task";
this.tgid = tgid;
this.tid = tid;
this.leader = this;
}
/**
* Creates a <i>non-leader</i> task for a Linux process.
*
* @param leader the leader task for the process
* @param tid
*/
LinuxTask(LinuxTask leader, int tid) {
this.tgid = leader.tgid;
this.tid = tid;
this.leader = leader;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LinuxTask) {
final LinuxTask other = (LinuxTask) obj;
return tid() == other.tid();
}
return false;
}
@Override
public int hashCode() {
return tid();
}
private static class Monitor extends ReentrantLock {
@Override
public void lock() {
boolean isDispatchThread = EventQueue.isDispatchThread();
Thread currentThread = Thread.currentThread();
int retries = 5000;
while (!super.tryLock()) {
Thread owner = getOwner();
if (isDispatchThread) {
// It's fine to let the AWT event thread fail fast when trying to access the remote VM.
// All such accesses should be in a try block that catches DataIOError and will thus
// simply result in a repaint being missed.
throw new ConcurrentDataIOError("Failed concurrent attempt by " + currentThread + " to access VM [lock held by " + owner + "]");
} else {
// Spin for a bit
if (--retries == 0) {
// Once we've spun for a 5 seconds, this is most likely indicates a potential deadlock with another non AWT thread.
String message = "Failed concurrent attempt by " + currentThread + " to access VM [lock held by " + owner + "]";
System.err.println(message);
throw new ConcurrentDataIOError(message);
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
}
}
}
private static final Monitor processLock = new Monitor();
static <V> V execute(Function<V> function) {
processLock.lock();
try {
return SingleThread.execute(function);
} finally {
processLock.unlock();
}
}
static <V> V executeWithException(Function<V> function) throws Exception {
processLock.lock();
try {
return SingleThread.executeWithException(function);
} finally {
processLock.unlock();
}
}
private static native int nativeCreateChildProcess(long argv, int vmAgentSocketPort);
/**
* Creates a new traced task via {@code fork()} and {@code execve()}.
*
* @param argv the arguments with which to start the new process
* @param vmAgentSocketPort the port on which the debugger agent is listening for a connection from the VM.
* @return an instance of {@link LinuxTask} representing the new process or {@code null} if there was an error
* creating the new process. If a new process was created, it is paused at the call to {@code execve}
*/
public static LinuxTask createChild(final long argv, final int vmAgentSocketPort) {
return execute(new Function<LinuxTask>() {
public LinuxTask call() {
final int tgid = nativeCreateChildProcess(argv, vmAgentSocketPort);
if (tgid < 0) {
return null;
}
return new LinuxTask(tgid, tgid);
}
});
}
private static native boolean nativeDetach(int tgid, int tid);
public void detach() throws IOException {
try {
executeWithException(new Function<Void>() {
public Void call() throws Exception {
if (!nativeDetach(tgid, tid)) {
throw new IOException("Ptrace.detach");
}
return null;
}
});
} catch (Exception exception) {
throw Utils.cast(IOException.class, exception);
}
}
private static native boolean nativeSingleStep(int tgid, int tid);
public boolean singleStep() {
return execute(new Function<Boolean>() {
public Boolean call() throws Exception {
return nativeSingleStep(tgid, tid);
}
});
}
private static native boolean nativeResume(int tgid, int tid, boolean allTasks);
public void resume(final boolean allTasks) throws OSExecutionRequestException {
try {
executeWithException(new Function<Void>() {
public Void call() throws Exception {
if (!nativeResume(tgid, tid, allTasks)) {
throw new OSExecutionRequestException("Ptrace.resume");
}
return null;
}
});
} catch (Exception exception) {
throw Utils.cast(OSExecutionRequestException.class, exception);
}
}
private static native boolean nativeSuspend(int tgid, int tid, boolean allTasks);
/**
* Suspends one or more tasks.
*
* Note: This operation is not synchronized otherwise there would be no way to interrupt
* {@link #waitUntilStopped(boolean)}
*
* @param allTasks {@code true} if all tasks should be suspended, {@code false} if only this task should be suspended
* @throws OSExecutionRequestException
*/
public void suspend(boolean allTasks) throws OSExecutionRequestException {
if (!nativeSuspend(tgid, tid, allTasks)) {
throw new OSExecutionRequestException("Ptrace.suspend");
}
}
private static native int nativeWait(int tgid, int tid, boolean allTasks);
public ProcessState waitUntilStopped(final boolean allTasks) {
if (!allTasks) {
TeleError.unimplemented();
}
return execute(new Function<ProcessState>() {
public ProcessState call() throws Exception {
int result = nativeWait(tgid, tid, allTasks);
return ProcessState.VALUES[result];
}
});
}
private static native boolean nativeKill(int tgid, int tid);
/**
* Kills all tasks.
*
* Note: This operation is not synchronized otherwise there would be no way to interrupt
* {@link #waitUntilStopped(boolean)}
*
* @throws OSExecutionRequestException
*/
public void kill() throws OSExecutionRequestException {
try {
executeWithException(new Function<Void>() {
public Void call() throws Exception {
if (!nativeKill(tgid, tid)) {
throw new OSExecutionRequestException("Ptrace.kill");
}
return null;
}
});
} catch (Exception exception) {
throw Utils.cast(OSExecutionRequestException.class, exception);
}
}
/**
* The file in /proc through which the memory of this task can be read. As Linux does not
* support writing to the memory of a process via /proc, it's still necessary to use
* ptrace for {@linkplain #writeBytes(long, Object, boolean, int, int) writing}.
*/
private RandomAccessFile memory;
/**
* Copies bytes from the tele process into a given {@linkplain ByteBuffer#isDirect() direct ByteBuffer} or byte
* array.
*
* @param src the address in the tele process to copy from
* @param dst the destination of the copy operation. This is a direct {@link ByteBuffer} or {@code byte[]}
* depending on the value of {@code isDirectByteBuffer}
* @param isDirectByteBuffer
* @param dstOffset the offset in {@code dst} at which to start writing
* @param length the number of bytes to copy
* @return the number of bytes copied or -1 if there was an error
*/
private static native int nativeReadBytes(int tgid, int tid, long src, Object dst, boolean isDirectByteBuffer, int dstOffset, int length);
public int readBytes(final long src, final Object dst, final boolean isDirectByteBuffer, final int offset, final int length) {
if (!isLeader()) {
return leader().readBytes(src, dst, isDirectByteBuffer, offset, length);
}
assert src != 0;
return execute(new Function<Integer>() {
public Integer call() throws Exception {
final long addr = src;
if (addr < 0) {
// RandomAccessFile.see() can't handle unsigned long offsets: have to resort to a JNI call
return nativeReadBytes(tgid, tid, addr, dst, isDirectByteBuffer, offset, length);
} else {
if (memory == null) {
memory = new RandomAccessFile("/proc/" + tgid() + "/mem", "r");
}
try {
final ByteBuffer dstDup = dst instanceof ByteBuffer ? ((ByteBuffer) dst).duplicate() : ByteBuffer.wrap((byte[]) dst);
final ByteBuffer dstView = (ByteBuffer) dstDup.position(offset).limit(offset + length);
dstView.position(offset);
return memory.getChannel().read(dstView, addr);
} catch (IOException ioException) {
throw new DataIOError(Address.fromLong(src), ioException.toString());
}
}
}
});
}
/**
* Copies bytes from a given {@linkplain ByteBuffer#isDirect() direct ByteBuffer} or byte array into the tele process.
*
* @param dst the address in the tele process to copy to
* @param src the source of the copy operation. This is a direct {@link ByteBuffer} or {@code byte[]}
* depending on the value of {@code isDirectByteBuffer}
* @param isDirectByteBuffer
* @param srcOffset the offset in {@code src} at which to start reading
* @param length the number of bytes to copy
* @return the number of bytes copied or -1 if there was an error
*/
private static native int nativeWriteBytes(int tgid, int tid, long dst, Object src, boolean isDirectByteBuffer, int srcOffset, int length);
public int writeBytes(final long dst, final Object src, final boolean isDirectByteBuffer, final int offset, final int length) {
return execute(new Function<Integer>() {
public Integer call() throws Exception {
return nativeWriteBytes(tgid, tid, dst, src, isDirectByteBuffer, offset, length);
}
});
}
private static native boolean nativeSetInstructionPointer(int tid, long instructionPointer);
public boolean setInstructionPointer(final long instructionPointer) {
return execute(new Function<Boolean>() {
public Boolean call() throws Exception {
return nativeSetInstructionPointer(tid, instructionPointer);
}
});
}
private static native boolean nativeReadRegisters(int tid,
byte[] integerRegisters, int integerRegistersSize,
byte[] floatingPointRegisters, int floatingPointRegistersSize,
byte[] stateRegisters, int stateRegistersSize);
public boolean readRegisters(
final byte[] integerRegisters,
final byte[] floatingPointRegisters,
final byte[] stateRegisters) {
return execute(new Function<Boolean>() {
public Boolean call() throws Exception {
return nativeReadRegisters(tid, integerRegisters, integerRegisters.length,
floatingPointRegisters, floatingPointRegisters.length,
stateRegisters, stateRegisters.length);
}
});
}
public void close() {
if (memory != null) {
try {
memory.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}