| /* |
| * Copyright (c) 2002, 2013, 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 sun.jvm.hotspot.debugger.bsd; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import sun.jvm.hotspot.debugger.Address; |
| import sun.jvm.hotspot.debugger.DebuggerBase; |
| import sun.jvm.hotspot.debugger.DebuggerException; |
| import sun.jvm.hotspot.debugger.DebuggerUtilities; |
| import sun.jvm.hotspot.debugger.MachineDescription; |
| import sun.jvm.hotspot.debugger.NotInHeapException; |
| import sun.jvm.hotspot.debugger.OopHandle; |
| import sun.jvm.hotspot.debugger.ReadResult; |
| import sun.jvm.hotspot.debugger.ThreadProxy; |
| import sun.jvm.hotspot.debugger.UnalignedAddressException; |
| import sun.jvm.hotspot.debugger.UnmappedAddressException; |
| import sun.jvm.hotspot.debugger.cdbg.CDebugger; |
| import sun.jvm.hotspot.debugger.cdbg.ClosestSymbol; |
| import sun.jvm.hotspot.debugger.cdbg.LoadObject; |
| import sun.jvm.hotspot.runtime.JavaThread; |
| import sun.jvm.hotspot.runtime.Threads; |
| import sun.jvm.hotspot.runtime.VM; |
| import sun.jvm.hotspot.utilities.PlatformInfo; |
| |
| /** <P> An implementation of the JVMDebugger interface. The basic debug |
| facilities are implemented through ptrace interface in the JNI code |
| (libsaproc.so). Library maps and symbol table management are done in |
| JNI. </P> |
| |
| <P> <B>NOTE</B> that since we have the notion of fetching "Java |
| primitive types" from the remote process (which might have |
| different sizes than we expect) we have a bootstrapping |
| problem. We need to know the sizes of these types before we can |
| fetch them. The current implementation solves this problem by |
| requiring that it be configured with these type sizes before they |
| can be fetched. The readJ(Type) routines here will throw a |
| RuntimeException if they are called before the debugger is |
| configured with the Java primitive type sizes. </P> */ |
| |
| public class BsdDebuggerLocal extends DebuggerBase implements BsdDebugger { |
| private boolean useGCC32ABI; |
| private boolean attached; |
| private long p_ps_prochandle; // native debugger handle |
| private long symbolicator; // macosx symbolicator handle |
| private long task; // macosx task handle |
| private boolean isCore; |
| private boolean isDarwin; // variant for bsd |
| |
| // CDebugger support |
| private BsdCDebugger cdbg; |
| |
| // threadList and loadObjectList are filled by attach0 method |
| private List threadList; |
| private List loadObjectList; |
| |
| // called by native method lookupByAddress0 |
| private ClosestSymbol createClosestSymbol(String name, long offset) { |
| return new ClosestSymbol(name, offset); |
| } |
| |
| // called by native method attach0 |
| private LoadObject createLoadObject(String fileName, long textsize, |
| long base) { |
| File f = new File(fileName); |
| Address baseAddr = newAddress(base); |
| return new SharedObject(this, fileName, f.length(), baseAddr); |
| } |
| |
| // native methods |
| |
| private native static void init0() |
| throws DebuggerException; |
| private native void attach0(int pid) |
| throws DebuggerException; |
| private native void attach0(String execName, String coreName) |
| throws DebuggerException; |
| private native void detach0() |
| throws DebuggerException; |
| private native long lookupByName0(String objectName, String symbol) |
| throws DebuggerException; |
| private native ClosestSymbol lookupByAddress0(long address) |
| throws DebuggerException; |
| private native long[] getThreadIntegerRegisterSet0(long unique_thread_id) |
| throws DebuggerException; |
| private native byte[] readBytesFromProcess0(long address, long numBytes) |
| throws DebuggerException; |
| public native static int getAddressSize() ; |
| |
| // Note on Bsd threads are really processes. When target process is |
| // attached by a serviceability agent thread, only that thread can do |
| // ptrace operations on the target. This is because from kernel's point |
| // view, other threads are just separate processes and they are not |
| // attached to the target. When they attempt to make ptrace calls, |
| // an ESRCH error will be returned as kernel believes target is not |
| // being traced by the caller. |
| // To work around the problem, we use a worker thread here to handle |
| // all JNI functions that are making ptrace calls. |
| |
| interface WorkerThreadTask { |
| public void doit(BsdDebuggerLocal debugger) throws DebuggerException; |
| } |
| |
| class BsdDebuggerLocalWorkerThread extends Thread { |
| BsdDebuggerLocal debugger; |
| WorkerThreadTask task; |
| DebuggerException lastException; |
| |
| public BsdDebuggerLocalWorkerThread(BsdDebuggerLocal debugger) { |
| this.debugger = debugger; |
| setDaemon(true); |
| } |
| |
| public void run() { |
| synchronized (workerThread) { |
| for (;;) { |
| if (task != null) { |
| lastException = null; |
| try { |
| task.doit(debugger); |
| } catch (DebuggerException exp) { |
| lastException = exp; |
| } |
| task = null; |
| workerThread.notifyAll(); |
| } |
| |
| try { |
| workerThread.wait(); |
| } catch (InterruptedException x) {} |
| } |
| } |
| } |
| |
| public WorkerThreadTask execute(WorkerThreadTask task) throws DebuggerException { |
| synchronized (workerThread) { |
| this.task = task; |
| workerThread.notifyAll(); |
| while (this.task != null) { |
| try { |
| workerThread.wait(); |
| } catch (InterruptedException x) {} |
| } |
| if (lastException != null) { |
| throw new DebuggerException(lastException); |
| } else { |
| return task; |
| } |
| } |
| } |
| } |
| |
| private BsdDebuggerLocalWorkerThread workerThread = null; |
| |
| //---------------------------------------------------------------------- |
| // Implementation of Debugger interface |
| // |
| |
| /** <P> machDesc may not be null. </P> |
| |
| <P> useCache should be set to true if debugging is being done |
| locally, and to false if the debugger is being created for the |
| purpose of supporting remote debugging. </P> */ |
| public BsdDebuggerLocal(MachineDescription machDesc, |
| boolean useCache) throws DebuggerException { |
| this.machDesc = machDesc; |
| utils = new DebuggerUtilities(machDesc.getAddressSize(), |
| machDesc.isBigEndian()) { |
| public void checkAlignment(long address, long alignment) { |
| // Need to override default checkAlignment because we need to |
| // relax alignment constraints on Bsd/x86 |
| if ( (address % alignment != 0) |
| &&(alignment != 8 || address % 4 != 0)) { |
| throw new UnalignedAddressException( |
| "Trying to read at address: " |
| + addressValueToString(address) |
| + " with alignment: " + alignment, |
| address); |
| } |
| } |
| }; |
| |
| if (useCache) { |
| // FIXME: re-test necessity of cache on Bsd, where data |
| // fetching is faster |
| // Cache portion of the remote process's address space. |
| // Fetching data over the socket connection to dbx is slow. |
| // Might be faster if we were using a binary protocol to talk to |
| // dbx, but would have to test. For now, this cache works best |
| // if it covers the entire heap of the remote process. FIXME: at |
| // least should make this tunable from the outside, i.e., via |
| // the UI. This is a cache of 4096 4K pages, or 16 MB. The page |
| // size must be adjusted to be the hardware's page size. |
| // (FIXME: should pick this up from the debugger.) |
| if (getCPU().equals("ia64")) { |
| initCache(16384, parseCacheNumPagesProperty(1024)); |
| } else { |
| initCache(4096, parseCacheNumPagesProperty(4096)); |
| } |
| } |
| |
| isDarwin = getOS().equals("darwin"); |
| workerThread = new BsdDebuggerLocalWorkerThread(this); |
| workerThread.start(); |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public boolean hasProcessList() throws DebuggerException { |
| return false; |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public List getProcessList() throws DebuggerException { |
| throw new DebuggerException("getProcessList not implemented yet"); |
| } |
| |
| private void checkAttached() throws DebuggerException { |
| if (attached) { |
| if (isCore) { |
| throw new DebuggerException("attached to a core dump already"); |
| } else { |
| throw new DebuggerException("attached to a process already"); |
| } |
| } |
| } |
| |
| private void requireAttach() { |
| if (! attached) { |
| throw new RuntimeException("not attached to a process or a core!"); |
| } |
| } |
| |
| /* called from attach methods */ |
| private void findABIVersion() throws DebuggerException { |
| String libjvmName = isDarwin ? "libjvm.dylib" : "libjvm.so"; |
| String javaThreadVt = isDarwin ? "_vt_10JavaThread" : "__vt_10JavaThread"; |
| if (lookupByName0(libjvmName, javaThreadVt) != 0) { |
| // old C++ ABI |
| useGCC32ABI = false; |
| } else { |
| // new C++ ABI |
| useGCC32ABI = true; |
| } |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public synchronized void attach(int processID) throws DebuggerException { |
| checkAttached(); |
| threadList = new ArrayList(); |
| loadObjectList = new ArrayList(); |
| class AttachTask implements WorkerThreadTask { |
| int pid; |
| public void doit(BsdDebuggerLocal debugger) { |
| debugger.attach0(pid); |
| debugger.attached = true; |
| debugger.isCore = false; |
| findABIVersion(); |
| } |
| } |
| |
| AttachTask task = new AttachTask(); |
| task.pid = processID; |
| workerThread.execute(task); |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public synchronized void attach(String execName, String coreName) { |
| checkAttached(); |
| threadList = new ArrayList(); |
| loadObjectList = new ArrayList(); |
| attach0(execName, coreName); |
| attached = true; |
| isCore = true; |
| findABIVersion(); |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public synchronized boolean detach() { |
| if (!attached) { |
| return false; |
| } |
| |
| threadList = null; |
| loadObjectList = null; |
| |
| if (isCore) { |
| detach0(); |
| attached = false; |
| return true; |
| } else { |
| class DetachTask implements WorkerThreadTask { |
| boolean result = false; |
| |
| public void doit(BsdDebuggerLocal debugger) { |
| debugger.detach0(); |
| debugger.attached = false; |
| result = true; |
| } |
| } |
| |
| DetachTask task = new DetachTask(); |
| workerThread.execute(task); |
| return task.result; |
| } |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public Address parseAddress(String addressString) |
| throws NumberFormatException { |
| long addr = utils.scanAddress(addressString); |
| if (addr == 0) { |
| return null; |
| } |
| return new BsdAddress(this, addr); |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public String getOS() { |
| return PlatformInfo.getOS(); |
| } |
| |
| /** From the Debugger interface via JVMDebugger */ |
| public String getCPU() { |
| return PlatformInfo.getCPU(); |
| } |
| |
| public boolean hasConsole() throws DebuggerException { |
| return false; |
| } |
| |
| public String consoleExecuteCommand(String cmd) throws DebuggerException { |
| throw new DebuggerException("No debugger console available on Bsd"); |
| } |
| |
| public String getConsolePrompt() throws DebuggerException { |
| return null; |
| } |
| |
| /* called from lookup */ |
| private long handleGCC32ABI(long addr, String symbol) throws DebuggerException { |
| if (useGCC32ABI && symbol.startsWith("_ZTV")) { |
| return addr + (2 * machDesc.getAddressSize()); |
| } else { |
| return addr; |
| } |
| } |
| |
| /** From the SymbolLookup interface via Debugger and JVMDebugger */ |
| public synchronized Address lookup(String objectName, String symbol) { |
| requireAttach(); |
| if (!attached) { |
| return null; |
| } |
| |
| if (isCore) { |
| // MacOSX symbol with "_" as leading |
| long addr = lookupByName0(objectName, isDarwin ? "_" + symbol : symbol); |
| return (addr == 0)? null : new BsdAddress(this, handleGCC32ABI(addr, symbol)); |
| } else { |
| class LookupByNameTask implements WorkerThreadTask { |
| String objectName, symbol; |
| Address result; |
| |
| public void doit(BsdDebuggerLocal debugger) { |
| long addr = debugger.lookupByName0(objectName, symbol); |
| result = (addr == 0 ? null : new BsdAddress(debugger, handleGCC32ABI(addr, symbol))); |
| } |
| } |
| |
| LookupByNameTask task = new LookupByNameTask(); |
| task.objectName = objectName; |
| task.symbol = symbol; |
| workerThread.execute(task); |
| return task.result; |
| } |
| } |
| |
| /** From the SymbolLookup interface via Debugger and JVMDebugger */ |
| public synchronized OopHandle lookupOop(String objectName, String symbol) { |
| Address addr = lookup(objectName, symbol); |
| if (addr == null) { |
| return null; |
| } |
| return addr.addOffsetToAsOopHandle(0); |
| } |
| |
| /** From the Debugger interface */ |
| public MachineDescription getMachineDescription() { |
| return machDesc; |
| } |
| |
| //---------------------------------------------------------------------- |
| // Implementation of ThreadAccess interface |
| // |
| |
| /** From the ThreadAccess interface via Debugger and JVMDebugger */ |
| public ThreadProxy getThreadForIdentifierAddress(Address threadIdAddr, Address uniqueThreadIdAddr) { |
| return new BsdThread(this, threadIdAddr, uniqueThreadIdAddr); |
| } |
| |
| @Override |
| public ThreadProxy getThreadForIdentifierAddress(Address addr) { |
| throw new RuntimeException("unimplemented"); |
| } |
| |
| /** From the ThreadAccess interface via Debugger and JVMDebugger */ |
| public ThreadProxy getThreadForThreadId(long id) { |
| return new BsdThread(this, id); |
| } |
| |
| //---------------------------------------------------------------------- |
| // Internal routines (for implementation of BsdAddress). |
| // These must not be called until the MachineDescription has been set up. |
| // |
| |
| /** From the BsdDebugger interface */ |
| public String addressValueToString(long address) { |
| return utils.addressValueToString(address); |
| } |
| |
| /** From the BsdDebugger interface */ |
| public BsdAddress readAddress(long address) |
| throws UnmappedAddressException, UnalignedAddressException { |
| long value = readAddressValue(address); |
| return (value == 0 ? null : new BsdAddress(this, value)); |
| } |
| public BsdAddress readCompOopAddress(long address) |
| throws UnmappedAddressException, UnalignedAddressException { |
| long value = readCompOopAddressValue(address); |
| return (value == 0 ? null : new BsdAddress(this, value)); |
| } |
| |
| public BsdAddress readCompKlassAddress(long address) |
| throws UnmappedAddressException, UnalignedAddressException { |
| long value = readCompKlassAddressValue(address); |
| return (value == 0 ? null : new BsdAddress(this, value)); |
| } |
| |
| /** From the BsdDebugger interface */ |
| public BsdOopHandle readOopHandle(long address) |
| throws UnmappedAddressException, UnalignedAddressException, |
| NotInHeapException { |
| long value = readAddressValue(address); |
| return (value == 0 ? null : new BsdOopHandle(this, value)); |
| } |
| public BsdOopHandle readCompOopHandle(long address) |
| throws UnmappedAddressException, UnalignedAddressException, |
| NotInHeapException { |
| long value = readCompOopAddressValue(address); |
| return (value == 0 ? null : new BsdOopHandle(this, value)); |
| } |
| |
| //---------------------------------------------------------------------- |
| // Thread context access |
| // |
| |
| public synchronized long[] getThreadIntegerRegisterSet(long unique_thread_id) |
| throws DebuggerException { |
| requireAttach(); |
| if (isCore) { |
| return getThreadIntegerRegisterSet0(unique_thread_id); |
| } else { |
| class GetThreadIntegerRegisterSetTask implements WorkerThreadTask { |
| long unique_thread_id; |
| long[] result; |
| public void doit(BsdDebuggerLocal debugger) { |
| result = debugger.getThreadIntegerRegisterSet0(unique_thread_id); |
| } |
| } |
| |
| GetThreadIntegerRegisterSetTask task = new GetThreadIntegerRegisterSetTask(); |
| task.unique_thread_id = unique_thread_id; |
| workerThread.execute(task); |
| return task.result; |
| } |
| } |
| |
| /** Need to override this to relax alignment checks on x86. */ |
| public long readCInteger(long address, long numBytes, boolean isUnsigned) |
| throws UnmappedAddressException, UnalignedAddressException { |
| // Only slightly relaxed semantics -- this is a hack, but is |
| // necessary on x86 where it seems the compiler is |
| // putting some global 64-bit data on 32-bit boundaries |
| if (numBytes == 8) { |
| utils.checkAlignment(address, 4); |
| } else { |
| utils.checkAlignment(address, numBytes); |
| } |
| byte[] data = readBytes(address, numBytes); |
| return utils.dataToCInteger(data, isUnsigned); |
| } |
| |
| // Overridden from DebuggerBase because we need to relax alignment |
| // constraints on x86 |
| public long readJLong(long address) |
| throws UnmappedAddressException, UnalignedAddressException { |
| utils.checkAlignment(address, jintSize); |
| byte[] data = readBytes(address, jlongSize); |
| return utils.dataToJLong(data, jlongSize); |
| } |
| |
| //---------------------------------------------------------------------- |
| // Address access. Can not be package private, but should only be |
| // accessed by the architecture-specific subpackages. |
| |
| /** From the BsdDebugger interface */ |
| public long getAddressValue(Address addr) { |
| if (addr == null) return 0; |
| return ((BsdAddress) addr).getValue(); |
| } |
| |
| /** From the BsdDebugger interface */ |
| public Address newAddress(long value) { |
| if (value == 0) return null; |
| return new BsdAddress(this, value); |
| } |
| |
| /** From the BsdCDebugger interface */ |
| public List/*<ThreadProxy>*/ getThreadList() { |
| requireAttach(); |
| return threadList; |
| } |
| |
| /** From the BsdCDebugger interface */ |
| public List/*<LoadObject>*/ getLoadObjectList() { |
| requireAttach(); |
| return loadObjectList; |
| } |
| |
| /** From the BsdCDebugger interface */ |
| public synchronized ClosestSymbol lookup(long addr) { |
| requireAttach(); |
| if (isCore) { |
| return lookupByAddress0(addr); |
| } else { |
| class LookupByAddressTask implements WorkerThreadTask { |
| long addr; |
| ClosestSymbol result; |
| |
| public void doit(BsdDebuggerLocal debugger) { |
| result = debugger.lookupByAddress0(addr); |
| } |
| } |
| |
| LookupByAddressTask task = new LookupByAddressTask(); |
| task.addr = addr; |
| workerThread.execute(task); |
| return task.result; |
| } |
| } |
| |
| public CDebugger getCDebugger() { |
| if (cdbg == null) { |
| String cpu = getCPU(); |
| if (cpu.equals("ia64") ) { |
| // IA-64 is not supported because of stack-walking issues |
| return null; |
| } |
| cdbg = new BsdCDebugger(this); |
| } |
| return cdbg; |
| } |
| |
| /** This reads bytes from the remote process. */ |
| public synchronized ReadResult readBytesFromProcess(long address, |
| long numBytes) throws UnmappedAddressException, DebuggerException { |
| requireAttach(); |
| if (isCore) { |
| byte[] res = readBytesFromProcess0(address, numBytes); |
| return (res != null)? new ReadResult(res) : new ReadResult(address); |
| } else { |
| class ReadBytesFromProcessTask implements WorkerThreadTask { |
| long address, numBytes; |
| ReadResult result; |
| public void doit(BsdDebuggerLocal debugger) { |
| byte[] res = debugger.readBytesFromProcess0(address, numBytes); |
| if (res != null) |
| result = new ReadResult(res); |
| else |
| result = new ReadResult(address); |
| } |
| } |
| |
| ReadBytesFromProcessTask task = new ReadBytesFromProcessTask(); |
| task.address = address; |
| task.numBytes = numBytes; |
| workerThread.execute(task); |
| return task.result; |
| } |
| } |
| |
| public void writeBytesToProcess(long address, long numBytes, byte[] data) |
| throws UnmappedAddressException, DebuggerException { |
| // FIXME |
| throw new DebuggerException("Unimplemented"); |
| } |
| |
| /** this functions used for core file reading and called from native attach0, |
| it returns an array of long integers as |
| [thread_id, stack_start, stack_end, thread_id, stack_start, stack_end, ....] for |
| all java threads recorded in Threads. Also adds the ThreadProxy to threadList */ |
| public long[] getJavaThreadsInfo() { |
| requireAttach(); |
| Threads threads = VM.getVM().getThreads(); |
| int len = threads.getNumberOfThreads(); |
| long[] result = new long[len * 3]; // triple |
| JavaThread t = threads.first(); |
| long beg, end; |
| int i = 0; |
| while (t != null) { |
| end = t.getStackBaseValue(); |
| beg = end - t.getStackSize(); |
| BsdThread bsdt = (BsdThread)t.getThreadProxy(); |
| long uid = bsdt.getUniqueThreadId(); |
| if (threadList != null) threadList.add(bsdt); |
| result[i] = uid; |
| result[i + 1] = beg; |
| result[i + 2] = end; |
| t = t.next(); |
| i += 3; |
| } |
| return result; |
| } |
| |
| static { |
| System.loadLibrary("saproc"); |
| init0(); |
| } |
| } |