This replaces the breakpoint mechanism with a more efficient approach.
We now insert breakpoint instructions into the bytecode stream instead of
maintaining a table. This requires mapping DEX files as private instead
of shared, which allows copy-on-write to work. mprotect() is used to
guard the pages against inadvertent writes.
Unused opcode EC is now OP_BREAKPOINT. It's not recognized by dexdump or
any interpreter except portdbg, but it can be encountered by the bytecode
verifier (the debugger can request breakpoints in unverified code).
Breakpoint changes are blocked while the verifier runs to avoid races.
This eliminates method->debugBreakpointCount, which is no longer needed.
(Also, it clashed with LinearAlloc's read-only mode.)
The deferred verification error mechanism was using a code-copying
approach to modify the bytecode stream. That has been changed to use
the same copy-on-write modification mechanism.
Also, normalized all PAGE_SIZE/PAGESIZE references to a single
SYSTEM_PAGE_SIZE define.
Simple Fibonacci computation test times (opal-eng):
JIT, no debugger: 10.6ms
Fast interp, no debugger: 36ms
Portable interp, no debugger: 43.8ms
ORIG debug interp, no breakpoints set: 458ms
ORIG debug interp, breakpoint set nearby: 697ms
NEW debug interp, no breakpoints set: 341ms
NEW debug interp, breakpoints set nearby: 341ms
Where "nearby" means there's a breakpoint in the method doing the
computation that isn't actually hit -- the VM had an optimization where
it flagged methods with breakpoints and skipped some of the processing
when possible.
The bottom line is that code should run noticeably faster while a
debugger is attached.
"UNUSED",
"UNUSED",
"UNUSED",
- "UNUSED",
- "UNUSED",
+ "^breakpoint", // does not appear in DEX files
+ "^throw-verification-error", // does not appear in DEX files
"+execute-inline",
"UNUSED",
width = -3;
break;
- /* these should never appear */
+ /* these should never appear when scanning bytecode */
case OP_UNUSED_3E:
case OP_UNUSED_3F:
case OP_UNUSED_40:
case OP_UNUSED_E9:
case OP_UNUSED_EA:
case OP_UNUSED_EB:
- case OP_UNUSED_EC:
+ case OP_BREAKPOINT:
case OP_UNUSED_EF:
case OP_UNUSED_F1:
case OP_UNUSED_FC:
flags = kInstrCanContinue | kInstrCanThrow | kInstrInvoke;
break;
- /* these should never appear */
+ /* these should never appear when scanning code */
case OP_UNUSED_3E:
case OP_UNUSED_3F:
case OP_UNUSED_40:
case OP_UNUSED_E9:
case OP_UNUSED_EA:
case OP_UNUSED_EB:
- case OP_UNUSED_EC:
+ case OP_BREAKPOINT:
case OP_UNUSED_EF:
case OP_UNUSED_F1:
case OP_UNUSED_FC:
fmt = kFmt35c;
break;
- /* these should never appear */
+ /* these should never appear when scanning code */
case OP_UNUSED_3E:
case OP_UNUSED_3F:
case OP_UNUSED_40:
case OP_UNUSED_E9:
case OP_UNUSED_EA:
case OP_UNUSED_EB:
- case OP_UNUSED_EC:
+ case OP_BREAKPOINT:
case OP_UNUSED_EF:
case OP_UNUSED_F1:
case OP_UNUSED_FC:
OP_UNUSED_E9 = 0xe9,
OP_UNUSED_EA = 0xea,
OP_UNUSED_EB = 0xeb,
- OP_UNUSED_EC = 0xec,
+
+ /*
+ * The "breakpoint" instruction is special, in that it should never
+ * be seen by anything but the debug interpreter. During debugging
+ * it takes the place of an arbitrary opcode, which means operations
+ * like "tell me the opcode width so I can find the next instruction"
+ * aren't possible. (This is correctable, but probably not useful.)
+ */
+ OP_BREAKPOINT = 0xec,
/* optimizer output -- these are never generated by "dx" */
OP_THROW_VERIFICATION_ERROR = 0xed,
#define kNumDalvikInstructions 256
+
/*
* Switch-statement signatures are a "NOP" followed by a code. (A true NOP
* is 0x0000.)
H(OP_UNUSED_E9), \
H(OP_UNUSED_EA), \
H(OP_UNUSED_EB), \
- H(OP_UNUSED_EC), \
+ H(OP_BREAKPOINT), \
H(OP_THROW_VERIFICATION_ERROR), \
H(OP_EXECUTE_INLINE), \
H(OP_UNUSED_EF), \
/*
* Map a file (from fd's current offset) into a shared, read-only memory
- * segment. The file offset must be a multiple of the page size.
+ * segment. The file offset must be a multiple of the system page size.
*
* On success, returns 0 and fills out "pMap". On failure, returns a nonzero
* value and does not disturb "pMap".
if (getFileStartAndLength(fd, &start, &length) < 0)
return -1;
- memPtr = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, start);
+ /*
+ * This was originally (PROT_READ, MAP_SHARED), but we want to be able
+ * to make local edits for verification errors and debugger breakpoints.
+ * So we map it read-write and private, but use mprotect to mark the
+ * pages read-only. This should yield identical results so long as the
+ * pages are left read-only.
+ */
+ memPtr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE,
+ fd, start);
if (memPtr == MAP_FAILED) {
- LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n", (int) length,
+ LOGW("mmap(%d, R/W, FILE|PRIVATE, %d, %d) failed: %s\n", (int) length,
fd, (int) start, strerror(errno));
return -1;
}
+ if (mprotect(memPtr, length, PROT_READ) < 0) {
+ LOGW("mprotect(%p, %d, PROT_READ) failed: %s\n",
+ memPtr, length, strerror(errno));
+ (void) munmap(memPtr, length);
+ return -1;
+ }
pMap->baseAddr = pMap->addr = memPtr;
pMap->baseLength = pMap->length = length;
}
/*
+ * Change the access rights on one or more pages to read-only or read-write.
+ *
+ * Returns 0 on success.
+ */
+int sysChangeMapAccess(void* addr, size_t length, int wantReadWrite,
+ MemMapping* pMap)
+{
+ /*
+ * Verify that "addr" is part of this mapping file.
+ */
+ if (addr < pMap->baseAddr ||
+ (u1*)addr >= (u1*)pMap->baseAddr + pMap->baseLength)
+ {
+ LOGE("Attempted to change %p; map is %p - %p\n",
+ addr, pMap->baseAddr, (u1*)pMap->baseAddr + pMap->baseLength);
+ return -1;
+ }
+
+ /*
+ * Align "addr" to a page boundary and adjust "length" appropriately.
+ * (The address must be page-aligned, the length doesn't need to be,
+ * but we do need to ensure we cover the same range.)
+ */
+ u1* alignAddr = (u1*) ((int) addr & ~(SYSTEM_PAGE_SIZE-1));
+ size_t alignLength = length + ((u1*) addr - alignAddr);
+
+ //LOGI("%p/%zd --> %p/%zd\n", addr, length, alignAddr, alignLength);
+ int prot = wantReadWrite ? (PROT_READ|PROT_WRITE) : (PROT_READ);
+ if (mprotect(alignAddr, alignLength, prot) != 0) {
+ int err = errno;
+ LOGW("mprotect (%p,%zd,%d) failed: %s\n",
+ alignAddr, alignLength, prot, strerror(errno));
+ return (errno != 0) ? errno : -1;
+ }
+
+ return 0;
+}
+
+/*
* Release a memory mapping.
*/
void sysReleaseShmem(MemMapping* pMap)
#include <sys/types.h>
/*
+ * System page size. Normally you're expected to get this from
+ * sysconf(_SC_PAGESIZE) or some system-specific define (usually PAGESIZE
+ * or PAGE_SIZE). If we use a simple #define the compiler can generate
+ * appropriate masks directly, so we define it here and verify it as the
+ * VM is starting up.
+ *
+ * Must be a power of 2.
+ */
+#define SYSTEM_PAGE_SIZE 4096
+
+/*
* Use this to keep track of mapped segments.
*/
typedef struct MemMapping {
int sysCreatePrivateMap(size_t length, MemMapping* pMap);
/*
+ * Change the access rights on one or more pages. If "wantReadWrite" is
+ * zero, the pages will be made read-only; otherwise they will be read-write.
+ *
+ * Returns 0 on success.
+ */
+int sysChangeMapAccess(void* addr, size_t length, int wantReadWrite,
+ MemMapping* pmap);
+
+/*
* Release the pages associated with a shared memory segment.
*
* This does not free "pMap"; it just releases the memory.
*/
bool dvmDebuggerStartup(void)
{
+ if (!dvmBreakpointStartup())
+ return false;
+
gDvm.dbgRegistry = dvmHashTableCreate(1000, NULL);
return (gDvm.dbgRegistry != NULL);
}
{
dvmHashTableFree(gDvm.dbgRegistry);
gDvm.dbgRegistry = NULL;
+ dvmBreakpointShutdown();
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* Dalvik-specific side of debugger support. (The JDWP code is intended to
* be relatively generic.)
struct Thread;
/*
- * used by StepControl to track a set of addresses associated with
+ * Used by StepControl to track a set of addresses associated with
* a single line.
*/
typedef struct AddressSet {
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* VM-specific state associated with a DEX file.
*/
#include "Dalvik.h"
+
/*
* Create auxillary data structures.
*
free(pDvmDex);
}
+
+/*
+ * Change the byte at the specified address to a new value. If the location
+ * already has the new value, do nothing.
+ *
+ * This requires changing the access permissions to read-write, updating
+ * the value, and then resetting the permissions.
+ *
+ * This does not make any synchronization guarantees. It's important for the
+ * caller(s) to work out mutual exclusion, at least on a page granularity,
+ * to avoid a race where one threads sets read-write, another thread sets
+ * read-only, and then the first thread does a write.
+ *
+ * TODO: if we're back to the original state of the page, use
+ * madvise(MADV_DONTNEED) to release the private/dirty copy.
+ *
+ * Returns "true" on success.
+ */
+bool dvmDexChangeDex1(DvmDex* pDvmDex, u1* addr, u1 newVal)
+{
+ if (*addr == newVal) {
+ LOGV("+++ byte at %p is already 0x%02x\n", addr, newVal);
+ return true;
+ }
+
+ LOGV("+++ change byte at %p from 0x%02x to 0x%02x\n", addr, *addr, newVal);
+ if (sysChangeMapAccess(addr, 1, true, &pDvmDex->memMap) < 0) {
+ LOGE("access change failed\n");
+ return false;
+ }
+
+ *addr = newVal;
+
+ if (sysChangeMapAccess(addr, 1, false, &pDvmDex->memMap) < 0) {
+ LOGW("WARNING: unable to restore read-only access on mapping\n");
+ /* not fatal, keep going */
+ }
+
+ return true;
+}
+
+/*
+ * Change the 2-byte value at the specified address to a new value. If the
+ * location already has the new value, do nothing.
+ *
+ * Otherwise works like dvmDexChangeDex1.
+ */
+bool dvmDexChangeDex2(DvmDex* pDvmDex, u2* addr, u2 newVal)
+{
+ if (*addr == newVal) {
+ LOGV("+++ value at %p is already 0x%04x\n", addr, newVal);
+ return true;
+ }
+
+ LOGV("+++ change 2byte at %p from 0x%04x to 0x%04x\n", addr, *addr, newVal);
+ if (sysChangeMapAccess(addr, 2, true, &pDvmDex->memMap) < 0) {
+ LOGE("access change failed\n");
+ return false;
+ }
+
+ *addr = newVal;
+
+ if (sysChangeMapAccess(addr, 2, false, &pDvmDex->memMap) < 0) {
+ LOGW("WARNING: unable to restore read-only access on mapping\n");
+ /* not fatal, keep going */
+ }
+
+ return true;
+}
+
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* The VM wraps some additional data structures around the DexFile. These
* are defined here.
void dvmDexFileFree(DvmDex* pDvmDex);
+/*
+ * Change the 1- or 2-byte value at the specified address to a new value. If
+ * the location already has the new value, do nothing.
+ *
+ * This does not make any synchronization guarantees. The caller must
+ * ensure exclusivity vs. other callers.
+ *
+ * For the 2-byte call, the pointer should have 16-bit alignment.
+ *
+ * Returns "true" on success.
+ */
+bool dvmDexChangeDex1(DvmDex* pDvmDex, u1* addr, u1 newVal);
+bool dvmDexChangeDex2(DvmDex* pDvmDex, u2* addr, u2 newVal);
+
+
#if DVM_RESOLVER_CACHE == DVM_RC_DISABLED
/* 1:1 mapping */
#define MAX_BREAKPOINTS 20 /* used for a debugger optimization */
-// fwd
-typedef struct GcHeap GcHeap; /* heap internal structure */
+/* private structures */
+typedef struct GcHeap GcHeap;
+typedef struct BreakpointSet BreakpointSet;
/*
* One of these for each -ea/-da/-esa/-dsa on the command line.
HashTable* dbgRegistry;
/*
- * Breakpoint optimization table. This is global and NOT explicitly
- * synchronized, but all operations that modify the table are made
- * from relatively-synchronized functions. False-positives are
- * possible, false-negatives (i.e. missing a breakpoint) should not be.
+ * Debugger breakpoint table.
*/
- const u2* debugBreakAddr[MAX_BREAKPOINTS];
+ BreakpointSet* breakpointSet;
/*
* Single-step control struct. We currently only allow one thread to
if (!gDvm.reduceSignals)
blockSignals();
+ /* verify system page size */
+ if (sysconf(_SC_PAGESIZE) != SYSTEM_PAGE_SIZE) {
+ LOGE("ERROR: expected page size %d, got %d\n",
+ SYSTEM_PAGE_SIZE, (int) sysconf(_SC_PAGESIZE));
+ goto fail;
+ }
+
/* mterp setup */
LOGV("Using executionMode %d\n", gDvm.executionMode);
dvmCheckAsmConstants();
#else
dvmClearReferenceTable(&gDvm.jniGlobalRefTable);
#endif
+ dvmClearReferenceTable(&gDvm.jniPinRefTable);
}
#define LENGTHFLAG_RW 0x40000000
#define LENGTHFLAG_MASK (~(LENGTHFLAG_FREE|LENGTHFLAG_RW))
-/* in case limits.h doesn't have it; must be a power of 2 */
-#ifndef PAGESIZE
-# define PAGESIZE 4096
-#endif
-
/* fwd */
static void checkAllFree(Object* classLoader);
* chunk of data will be properly aligned.
*/
assert(BLOCK_ALIGN >= HEADER_EXTRA);
- pHdr->curOffset = pHdr->firstOffset = (BLOCK_ALIGN-HEADER_EXTRA) + PAGESIZE;
+ pHdr->curOffset = pHdr->firstOffset =
+ (BLOCK_ALIGN-HEADER_EXTRA) + SYSTEM_PAGE_SIZE;
pHdr->mapLength = DEFAULT_MAX_LENGTH;
#ifdef USE_ASHMEM
#endif /*USE_ASHMEM*/
/* region expected to begin on a page boundary */
- assert(((int) pHdr->mapAddr & (PAGESIZE-1)) == 0);
+ assert(((int) pHdr->mapAddr & (SYSTEM_PAGE_SIZE-1)) == 0);
/* the system should initialize newly-mapped memory to zero */
assert(*(u4*) (pHdr->mapAddr + pHdr->curOffset) == 0);
free(pHdr);
return NULL;
}
- if (mprotect(pHdr->mapAddr + PAGESIZE, PAGESIZE,
+ if (mprotect(pHdr->mapAddr + SYSTEM_PAGE_SIZE, SYSTEM_PAGE_SIZE,
ENFORCE_READ_ONLY ? PROT_READ : PROT_READ|PROT_WRITE) != 0)
{
LOGW("LinearAlloc init mprotect #2 failed: %s\n", strerror(errno));
if (ENFORCE_READ_ONLY) {
/* allocate the per-page ref count */
- int numPages = (pHdr->mapLength+PAGESIZE-1) / PAGESIZE;
+ int numPages = (pHdr->mapLength+SYSTEM_PAGE_SIZE-1) / SYSTEM_PAGE_SIZE;
pHdr->writeRefCount = calloc(numPages, sizeof(short));
if (pHdr->writeRefCount == NULL) {
free(pHdr);
* See if we are starting on or have crossed into a new page. If so,
* call mprotect on the page(s) we're about to write to. We have to
* page-align the start address, but don't have to make the length a
- * PAGESIZE multiple (but we do it anyway).
+ * SYSTEM_PAGE_SIZE multiple (but we do it anyway).
*
* Note that "startOffset" is not the last *allocated* byte, but rather
* the offset of the first *unallocated* byte (which we are about to
* If ENFORCE_READ_ONLY is enabled, we have to call mprotect even if
* we've written to this page before, because it might be read-only.
*/
- lastGoodOff = (startOffset-1) & ~(PAGESIZE-1);
- firstWriteOff = startOffset & ~(PAGESIZE-1);
- lastWriteOff = (nextOffset-1) & ~(PAGESIZE-1);
+ lastGoodOff = (startOffset-1) & ~(SYSTEM_PAGE_SIZE-1);
+ firstWriteOff = startOffset & ~(SYSTEM_PAGE_SIZE-1);
+ lastWriteOff = (nextOffset-1) & ~(SYSTEM_PAGE_SIZE-1);
LOGVV("--- lastGood=0x%04x firstWrite=0x%04x lastWrite=0x%04x\n",
lastGoodOff, firstWriteOff, lastWriteOff);
if (lastGoodOff != lastWriteOff || ENFORCE_READ_ONLY) {
start = firstWriteOff;
assert(start <= nextOffset);
- len = (lastWriteOff - firstWriteOff) + PAGESIZE;
+ len = (lastWriteOff - firstWriteOff) + SYSTEM_PAGE_SIZE;
LOGVV("--- calling mprotect(start=%d len=%d RW)\n", start, len);
cc = mprotect(pHdr->mapAddr + start, len, PROT_READ | PROT_WRITE);
if (ENFORCE_READ_ONLY) {
int i, start, end;
- start = firstWriteOff / PAGESIZE;
- end = lastWriteOff / PAGESIZE;
+ start = firstWriteOff / SYSTEM_PAGE_SIZE;
+ end = lastWriteOff / SYSTEM_PAGE_SIZE;
LOGVV("--- marking pages %d-%d RW (alloc %d at %p)\n",
start, end, size, pHdr->mapAddr + startOffset + HEADER_EXTRA);
u4 len = *pLen & LENGTHFLAG_MASK;
int firstPage, lastPage;
- firstPage = ((u1*)pLen - (u1*)pHdr->mapAddr) / PAGESIZE;
- lastPage = ((u1*)mem - (u1*)pHdr->mapAddr + (len-1)) / PAGESIZE;
+ firstPage = ((u1*)pLen - (u1*)pHdr->mapAddr) / SYSTEM_PAGE_SIZE;
+ lastPage = ((u1*)mem - (u1*)pHdr->mapAddr + (len-1)) / SYSTEM_PAGE_SIZE;
LOGVV("--- updating pages %d-%d (%d)\n", firstPage, lastPage, direction);
int i, cc;
pHdr->writeRefCount[i]--;
if (pHdr->writeRefCount[i] == 0) {
LOGVV("--- prot page %d RO\n", i);
- cc = mprotect(pHdr->mapAddr + PAGESIZE * i, PAGESIZE, PROT_READ);
+ cc = mprotect(pHdr->mapAddr + SYSTEM_PAGE_SIZE * i,
+ SYSTEM_PAGE_SIZE, PROT_READ);
assert(cc == 0);
}
} else {
}
if (pHdr->writeRefCount[i] == 0) {
LOGVV("--- prot page %d RW\n", i);
- cc = mprotect(pHdr->mapAddr + PAGESIZE * i, PAGESIZE,
- PROT_READ | PROT_WRITE);
+ cc = mprotect(pHdr->mapAddr + SYSTEM_PAGE_SIZE * i,
+ SYSTEM_PAGE_SIZE, PROT_READ | PROT_WRITE);
assert(cc == 0);
}
pHdr->writeRefCount[i]++;
& ~(BLOCK_ALIGN-1));
LOGI(" %p (%3d): %clen=%d%s\n", pHdr->mapAddr + off + HEADER_EXTRA,
- (int) ((off + HEADER_EXTRA) / PAGESIZE),
+ (int) ((off + HEADER_EXTRA) / SYSTEM_PAGE_SIZE),
(rawLen & LENGTHFLAG_FREE) != 0 ? '*' : ' ',
rawLen & LENGTHFLAG_MASK,
(rawLen & LENGTHFLAG_RW) != 0 ? " [RW]" : "");
if (ENFORCE_READ_ONLY) {
LOGI("writeRefCount map:\n");
- int numPages = (pHdr->mapLength+PAGESIZE-1) / PAGESIZE;
+ int numPages = (pHdr->mapLength+SYSTEM_PAGE_SIZE-1) / SYSTEM_PAGE_SIZE;
int zstart = 0;
int i;
/*
* If this is set, we create additional data structures and make many
* additional mprotect() calls.
- * (this breaks the debugger because the debugBreakpointCount cannot be updated)
*/
#define ENFORCE_READ_ONLY false
#ifdef HAVE_ANDROID_OS
# define UPDATE_MAGIC_PAGE 1
-# ifndef PAGESIZE
-# define PAGESIZE 4096
-# endif
#endif
/*
if (fd < 0) {
LOGV("Unable to open /dev/qemu_trace\n");
} else {
- gDvm.emulatorTracePage = mmap(0, PAGESIZE, PROT_READ|PROT_WRITE,
+ gDvm.emulatorTracePage = mmap(0, SYSTEM_PAGE_SIZE, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (gDvm.emulatorTracePage == MAP_FAILED) {
{
#ifdef UPDATE_MAGIC_PAGE
if (gDvm.emulatorTracePage != NULL)
- munmap(gDvm.emulatorTracePage, PAGESIZE);
+ munmap(gDvm.emulatorTracePage, SYSTEM_PAGE_SIZE);
#endif
free(gDvm.executedInstrCounts);
}
#define HB_ASHMEM_NAME "dalvik-heap-bitmap"
-#ifndef PAGE_SIZE
-#define PAGE_SIZE 4096
-#endif
#define ALIGN_UP_TO_PAGE_SIZE(p) \
- (((size_t)(p) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1))
+ (((size_t)(p) + (SYSTEM_PAGE_SIZE - 1)) & ~(SYSTEM_PAGE_SIZE - 1))
#define LIKELY(exp) (__builtin_expect((exp) != 0, true))
#define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))
static void snapIdealFootprint(void);
static void setIdealFootprint(size_t max);
-#ifndef PAGE_SIZE
-#define PAGE_SIZE 4096
-#endif
#define ALIGN_UP_TO_PAGE_SIZE(p) \
- (((size_t)(p) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1))
+ (((size_t)(p) + (SYSTEM_PAGE_SIZE - 1)) & ~(SYSTEM_PAGE_SIZE - 1))
#define ALIGN_DOWN_TO_PAGE_SIZE(p) \
- ((size_t)(p) & ~(PAGE_SIZE - 1))
+ ((size_t)(p) & ~(SYSTEM_PAGE_SIZE - 1))
#define HEAP_UTILIZATION_MAX 1024
#define DEFAULT_HEAP_UTILIZATION 512 // Range 1..HEAP_UTILIZATION_MAX
* We also align the end address.
*/
start = (void *)ALIGN_UP_TO_PAGE_SIZE(start);
- end = (void *)((size_t)end & ~(PAGE_SIZE - 1));
+ end = (void *)((size_t)end & ~(SYSTEM_PAGE_SIZE - 1));
if (start < end) {
size_t length = (char *)end - (char *)start;
madvise(start, length, MADV_DONTNEED);
#define LOGV_SWEEP(...) LOGVV_GC("SWEEP: " __VA_ARGS__)
#define LOGV_REF(...) LOGVV_GC("REF: " __VA_ARGS__)
-#ifndef PAGE_SIZE
-#define PAGE_SIZE 4096
-#endif
#define ALIGN_UP_TO_PAGE_SIZE(p) \
- (((size_t)(p) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1))
+ (((size_t)(p) + (SYSTEM_PAGE_SIZE - 1)) & ~(SYSTEM_PAGE_SIZE - 1))
/* Do not cast the result of this to a boolean; the only set bit
* may be > 1<<8.
* receive a "nop". The instruction's length will be left unchanged
* in "insnFlags".
*
+ * The verifier explicitly locks out breakpoint activity, so there should
+ * be no clashes with the debugger.
+ *
* IMPORTANT: this may replace meth->insns with a pointer to a new copy of
* the instructions.
*
u2 oldInsn = *oldInsns;
bool result = false;
- dvmMakeCodeReadWrite(meth);
+ //dvmMakeCodeReadWrite(meth);
//LOGD(" was 0x%04x\n", oldInsn);
u2* newInsns = (u2*) meth->insns + insnIdx;
/* nothing to do */
break;
case 3:
- newInsns[2] = OP_NOP;
+ dvmDexChangeDex2(meth->clazz->pDvmDex, newInsns+2, OP_NOP);
+ //newInsns[2] = OP_NOP;
break;
default:
/* whoops */
}
/* encode the opcode, with the failure code in the high byte */
- newInsns[0] = OP_THROW_VERIFICATION_ERROR |
+ u2 newVal = OP_THROW_VERIFICATION_ERROR |
(failure << 8) | (refType << (8 + kVerifyErrorRefTypeShift));
+ //newInsns[0] = newVal;
+ dvmDexChangeDex2(meth->clazz->pDvmDex, newInsns, newVal);
result = true;
bail:
- dvmMakeCodeReadOnly(meth);
+ //dvmMakeCodeReadOnly(meth);
return result;
}
failure = VERIFY_ERROR_GENERIC;
break;
- /* these should never appear */
+ /* these should never appear during verification */
case OP_UNUSED_3E:
case OP_UNUSED_3F:
case OP_UNUSED_40:
case OP_UNUSED_E9:
case OP_UNUSED_EA:
case OP_UNUSED_EB:
- case OP_UNUSED_EC:
+ case OP_BREAKPOINT:
case OP_UNUSED_EF:
case OP_UNUSED_F1:
case OP_UNUSED_FC:
}
/*
+ * Temporarily "undo" any breakpoints found in this method. There is no risk
+ * of confusing the interpreter, because unverified code cannot be executed.
+ *
+ * Breakpoints can be set after a class is loaded but before it has been
+ * verified.
+ *
+ * The "breakpoint" opcode can replace any other opcode, leaving no
+ * indication of the original instruction's width or purpose in the
+ * instruction stream. We either have to quietly undo the breakpoints
+ * before verification, or look up the original opcode whenever we need it.
+ * The latter is more efficient since we only slow down on code that
+ * actually has breakpoints, but it requires explicit handling in every
+ * function that examines the instruction stream.
+ *
+ * We need to ensure that the debugger doesn't insert any additional
+ * breakpoints while we work. This either requires holding a lock on the
+ * breakpoint set throughout the verification of this method, or adding a
+ * "do not touch anything on these pages" list to the set. Either way,
+ * the caller of this method must ensure that it calls "redo" to release
+ * state.
+ *
+ * A debugger could connect while we work, so we return without doing
+ * anything if a debugger doesn't happen to be connected now. We can only
+ * avoid doing work if the debugger thread isn't running (dexopt, zygote,
+ * or debugging not configured).
+ *
+ * Returns "false" if we did nothing, "true" if we did stuff (and, hence,
+ * need to call "redo" at some point).
+ */
+static bool undoBreakpoints(Method* meth)
+{
+#ifdef WITH_DEBUGGER
+ if (gDvm.optimizing || gDvm.zygote || !gDvm.jdwpConfigured)
+ return false;
+ dvmUndoBreakpoints(meth);
+ return true;
+#else
+ return false;
+#endif
+}
+
+/*
+ * Restore any breakpoints we undid previously. Also has to update the
+ * stored "original opcode" value for any instruction that we replaced
+ * with a throw-verification-error op.
+ */
+static void redoBreakpoints(Method* meth)
+{
+#ifdef WITH_DEBUGGER
+ if (gDvm.optimizing || gDvm.zygote || !gDvm.jdwpConfigured) {
+ /* should not be here */
+ assert(false);
+ return;
+ }
+ dvmRedoBreakpoints(meth);
+#else
+ assert(false);
+#endif
+}
+
+/*
* Perform verification on a single method.
*
* We do this in three passes:
UninitInstanceMap* uninitMap = NULL;
InsnFlags* insnFlags = NULL;
int i, newInstanceCount;
+ bool undidBreakpoints;
+
+ undidBreakpoints = undoBreakpoints(meth);
/*
* If there aren't any instructions, make sure that's expected, then
result = true;
bail:
+ if (undidBreakpoints)
+ redoBreakpoints(meth);
dvmFreeUninitInstanceMap(uninitMap);
free(insnFlags);
return result;
case OP_UNUSED_E9:
case OP_UNUSED_EA:
case OP_UNUSED_EB:
- case OP_UNUSED_EC:
+ case OP_BREAKPOINT:
case OP_UNUSED_ED:
case OP_UNUSED_EF:
case OP_UNUSED_F1:
// EB OP_UNUSED_EB
DF_NOP,
- // EC OP_UNUSED_EC
+ // EC OP_BREAKPOINT
DF_NOP,
// ED OP_THROW_VERIFICATION_ERROR
{
OpCode dalvikOpCode = mir->dalvikInsn.opCode;
if (((dalvikOpCode >= OP_UNUSED_3E) && (dalvikOpCode <= OP_UNUSED_43)) ||
- ((dalvikOpCode >= OP_UNUSED_E3) && (dalvikOpCode <= OP_UNUSED_EC))) {
+ ((dalvikOpCode >= OP_UNUSED_E3) && (dalvikOpCode <= OP_UNUSED_EB))) {
LOGE("Codegen: got unused opcode 0x%x\n",dalvikOpCode);
return true;
}
* ===========================================================================
*/
+// fwd
+static BreakpointSet* dvmBreakpointSetAlloc(void);
+static void dvmBreakpointSetFree(BreakpointSet* pSet);
+
+/*
+ * Initialize global breakpoint structures.
+ */
+bool dvmBreakpointStartup(void)
+{
+#ifdef WITH_DEBUGGER
+ gDvm.breakpointSet = dvmBreakpointSetAlloc();
+ return (gDvm.breakpointSet != NULL);
+#else
+ return true;
+#endif
+}
+
+/*
+ * Free resources.
+ */
+void dvmBreakpointShutdown(void)
+{
+#ifdef WITH_DEBUGGER
+ dvmBreakpointSetFree(gDvm.breakpointSet);
+#endif
+}
+
+
+#ifdef WITH_DEBUGGER
+/*
+ * This represents a breakpoint inserted in the instruction stream.
+ *
+ * The debugger may ask us to create the same breakpoint multiple times.
+ * We only remove the breakpoint when the last instance is cleared.
+ */
+typedef struct {
+ u2* addr; /* absolute memory address */
+ u1 originalOpCode; /* original 8-bit opcode value */
+ int setCount; /* #of times this breakpoint was set */
+} Breakpoint;
+
+/*
+ * Set of breakpoints.
+ */
+struct BreakpointSet {
+ /* grab lock before reading or writing anything else in here */
+ pthread_mutex_t lock;
+
+ /* vector of breakpoint structures */
+ int alloc;
+ int count;
+ Breakpoint* breakpoints;
+};
+
+/*
+ * Initialize a BreakpointSet. Initially empty.
+ */
+static BreakpointSet* dvmBreakpointSetAlloc(void)
+{
+ BreakpointSet* pSet = (BreakpointSet*) calloc(1, sizeof(*pSet));
+
+ dvmInitMutex(&pSet->lock);
+ /* leave the rest zeroed -- will alloc on first use */
+
+ return pSet;
+}
+
+/*
+ * Free storage associated with a BreakpointSet.
+ */
+static void dvmBreakpointSetFree(BreakpointSet* pSet)
+{
+ if (pSet == NULL)
+ return;
+
+ free(pSet->breakpoints);
+ free(pSet);
+}
+
+/*
+ * Lock the breakpoint set.
+ */
+static void dvmBreakpointSetLock(BreakpointSet* pSet)
+{
+ dvmLockMutex(&pSet->lock);
+}
+
+/*
+ * Unlock the breakpoint set.
+ */
+static void dvmBreakpointSetUnlock(BreakpointSet* pSet)
+{
+ dvmUnlockMutex(&pSet->lock);
+}
+
+/*
+ * Return the #of breakpoints.
+ */
+static int dvmBreakpointSetCount(const BreakpointSet* pSet)
+{
+ return pSet->count;
+}
+
+/*
+ * See if we already have an entry for this address.
+ *
+ * The BreakpointSet's lock must be acquired before calling here.
+ *
+ * Returns the index of the breakpoint entry, or -1 if not found.
+ */
+static int dvmBreakpointSetFind(const BreakpointSet* pSet, const u2* addr)
+{
+ int i;
+
+ for (i = 0; i < pSet->count; i++) {
+ Breakpoint* pBreak = &pSet->breakpoints[i];
+ if (pBreak->addr == addr)
+ return i;
+ }
+
+ return -1;
+}
+
+/*
+ * Retrieve the opcode that was originally at the specified location.
+ *
+ * The BreakpointSet's lock must be acquired before calling here.
+ *
+ * Returns "true" with the opcode in *pOrig on success.
+ */
+static bool dvmBreakpointSetOriginalOpCode(const BreakpointSet* pSet,
+ const u2* addr, u1* pOrig)
+{
+ int idx = dvmBreakpointSetFind(pSet, addr);
+ if (idx < 0)
+ return false;
+
+ *pOrig = pSet->breakpoints[idx].originalOpCode;
+ return true;
+}
+
+/*
+ * Add a breakpoint at a specific address. If the address is already
+ * present in the table, this just increments the count.
+ *
+ * For a new entry, this will extract and preserve the current opcode from
+ * the instruction stream, and replace it with a breakpoint opcode.
+ *
+ * The BreakpointSet's lock must be acquired before calling here.
+ *
+ * Returns "true" on success.
+ */
+static bool dvmBreakpointSetAdd(BreakpointSet* pSet, Method* method,
+ unsigned int instrOffset)
+{
+ const int kBreakpointGrowth = 10;
+ const u2* addr = method->insns + instrOffset;
+ int idx = dvmBreakpointSetFind(pSet, addr);
+ Breakpoint* pBreak;
+
+ if (idx < 0) {
+ if (pSet->count == pSet->alloc) {
+ int newSize = pSet->alloc + kBreakpointGrowth;
+ Breakpoint* newVec;
+
+ LOGV("+++ increasing breakpoint set size to %d\n", newSize);
+
+ /* pSet->breakpoints will be NULL on first entry */
+ newVec = realloc(pSet->breakpoints, newSize * sizeof(Breakpoint));
+ if (newVec == NULL)
+ return false;
+
+ pSet->breakpoints = newVec;
+ pSet->alloc = newSize;
+ }
+
+ pBreak = &pSet->breakpoints[pSet->count++];
+ pBreak->addr = (u2*)addr;
+ pBreak->originalOpCode = *(u1*)addr;
+ pBreak->setCount = 1;
+
+ /*
+ * Change the opcode. We must ensure that the BreakpointSet
+ * updates happen before we change the opcode.
+ */
+ MEM_BARRIER();
+ assert(*(u1*)addr != OP_BREAKPOINT);
+ dvmDexChangeDex1(method->clazz->pDvmDex, (u1*)addr, OP_BREAKPOINT);
+ } else {
+ pBreak = &pSet->breakpoints[idx];
+ pBreak->setCount++;
+
+ /* verify instruction stream has break op */
+ assert(*(u1*)addr == OP_BREAKPOINT);
+ }
+
+ return true;
+}
+
/*
- * Initialize the breakpoint address lookup table when the debugger attaches.
+ * Remove one instance of the specified breakpoint. When the count
+ * reaches zero, the entry is removed from the table, and the original
+ * opcode is restored.
*
- * This shouldn't be necessary -- the global area is initially zeroed out,
- * and the events should be cleaning up after themselves.
+ * The BreakpointSet's lock must be acquired before calling here.
+ */
+static void dvmBreakpointSetRemove(BreakpointSet* pSet, Method* method,
+ unsigned int instrOffset)
+{
+ const u2* addr = method->insns + instrOffset;
+ int idx = dvmBreakpointSetFind(pSet, addr);
+
+ if (idx < 0) {
+ /* breakpoint not found in set -- unexpected */
+ if (*(u1*)addr == OP_BREAKPOINT) {
+ LOGE("Unable to restore breakpoint opcode (%s.%s +%u)\n",
+ method->clazz->descriptor, method->name, instrOffset);
+ dvmAbort();
+ } else {
+ LOGW("Breakpoint was already restored? (%s.%s +%u)\n",
+ method->clazz->descriptor, method->name, instrOffset);
+ }
+ } else {
+ Breakpoint* pBreak = &pSet->breakpoints[idx];
+ if (pBreak->setCount == 1) {
+ /*
+ * Must restore opcode before removing set entry.
+ */
+ dvmDexChangeDex1(method->clazz->pDvmDex, (u1*)addr,
+ pBreak->originalOpCode);
+ MEM_BARRIER();
+
+ if (idx != pSet->count-1) {
+ /* shift down */
+ memmove(&pSet->breakpoints[idx], &pSet->breakpoints[idx+1],
+ (pSet->count-1 - idx) * sizeof(pSet->breakpoints[0]));
+ }
+ pSet->count--;
+ pSet->breakpoints[pSet->count].addr = (u2*) 0xdecadead; // debug
+ } else {
+ pBreak->setCount--;
+ assert(pBreak->setCount > 0);
+ }
+ }
+}
+
+/*
+ * Restore the original opcode on any breakpoints that are in the specified
+ * method. The breakpoints are NOT removed from the set.
+ *
+ * The BreakpointSet's lock must be acquired before calling here.
+ */
+static void dvmBreakpointSetUndo(BreakpointSet* pSet, Method* method)
+{
+ const u2* start = method->insns;
+ const u2* end = method->insns + dvmGetMethodInsnsSize(method);
+
+ int i;
+ for (i = 0; i < pSet->count; i++) {
+ Breakpoint* pBreak = &pSet->breakpoints[i];
+ if (pBreak->addr >= start && pBreak->addr < end) {
+ LOGV("UNDO %s.%s [%d]\n",
+ method->clazz->descriptor, method->name, i);
+ dvmDexChangeDex1(method->clazz->pDvmDex, (u1*)pBreak->addr,
+ pBreak->originalOpCode);
+ }
+ }
+}
+
+/*
+ * Put the breakpoint opcode back into the instruction stream, and check
+ * to see if the original opcode has changed.
+ *
+ * The BreakpointSet's lock must be acquired before calling here.
+ */
+static void dvmBreakpointSetRedo(BreakpointSet* pSet, Method* method)
+{
+ const u2* start = method->insns;
+ const u2* end = method->insns + dvmGetMethodInsnsSize(method);
+
+ int i;
+ for (i = 0; i < pSet->count; i++) {
+ Breakpoint* pBreak = &pSet->breakpoints[i];
+ if (pBreak->addr >= start && pBreak->addr < end) {
+ LOGV("REDO %s.%s [%d]\n",
+ method->clazz->descriptor, method->name, i);
+ u1 currentOpCode = *(u1*)pBreak->addr;
+ if (pBreak->originalOpCode != currentOpCode) {
+ /* verifier can drop in a throw-verification-error */
+ LOGD("NOTE: updating originalOpCode from 0x%02x to 0x%02x\n",
+ pBreak->originalOpCode, currentOpCode);
+ pBreak->originalOpCode = currentOpCode;
+ }
+ dvmDexChangeDex1(method->clazz->pDvmDex, (u1*)pBreak->addr,
+ OP_BREAKPOINT);
+ }
+ }
+}
+
+#endif /*WITH_DEBUGGER*/
+
+
+/*
+ * Do any debugger-attach-time initialization.
*/
void dvmInitBreakpoints(void)
{
#ifdef WITH_DEBUGGER
- memset(gDvm.debugBreakAddr, 0, sizeof(gDvm.debugBreakAddr));
+ /* quick sanity check */
+ BreakpointSet* pSet = gDvm.breakpointSet;
+ dvmBreakpointSetLock(pSet);
+ if (dvmBreakpointSetCount(pSet) != 0) {
+ LOGW("WARNING: %d leftover breakpoints\n", dvmBreakpointSetCount(pSet));
+ /* generally not good, but we can keep going */
+ }
+ dvmBreakpointSetUnlock(pSet);
#else
assert(false);
#endif
*
* "addr" is the absolute address of the breakpoint bytecode.
*/
-void dvmAddBreakAddr(Method* method, int instrOffset)
+void dvmAddBreakAddr(Method* method, unsigned int instrOffset)
{
#ifdef WITH_DEBUGGER
- const u2* addr = method->insns + instrOffset;
- const u2** ptr = gDvm.debugBreakAddr;
- int i;
-
- LOGV("BKP: add %p %s.%s (%s:%d)\n",
- addr, method->clazz->descriptor, method->name,
- dvmGetMethodSourceFile(method), dvmLineNumFromPC(method, instrOffset));
-
- method->debugBreakpointCount++;
- for (i = 0; i < MAX_BREAKPOINTS; i++, ptr++) {
- if (*ptr == NULL) {
- *ptr = addr;
- break;
- }
- }
- if (i == MAX_BREAKPOINTS) {
- /* no room; size is too small or we're not cleaning up properly */
- LOGE("ERROR: max breakpoints exceeded\n");
- assert(false);
- }
+ BreakpointSet* pSet = gDvm.breakpointSet;
+ dvmBreakpointSetLock(pSet);
+ dvmBreakpointSetAdd(pSet, method, instrOffset);
+ dvmBreakpointSetUnlock(pSet);
#else
assert(false);
#endif
* synchronized, so it should not be possible for two threads to be
* updating breakpoints at the same time.
*/
-void dvmClearBreakAddr(Method* method, int instrOffset)
+void dvmClearBreakAddr(Method* method, unsigned int instrOffset)
{
#ifdef WITH_DEBUGGER
- const u2* addr = method->insns + instrOffset;
- const u2** ptr = gDvm.debugBreakAddr;
- int i;
-
- LOGV("BKP: clear %p %s.%s (%s:%d)\n",
- addr, method->clazz->descriptor, method->name,
- dvmGetMethodSourceFile(method), dvmLineNumFromPC(method, instrOffset));
+ BreakpointSet* pSet = gDvm.breakpointSet;
+ dvmBreakpointSetLock(pSet);
+ dvmBreakpointSetRemove(pSet, method, instrOffset);
+ dvmBreakpointSetUnlock(pSet);
- method->debugBreakpointCount--;
- assert(method->debugBreakpointCount >= 0);
- for (i = 0; i < MAX_BREAKPOINTS; i++, ptr++) {
- if (*ptr == addr) {
- *ptr = NULL;
- break;
- }
- }
- if (i == MAX_BREAKPOINTS) {
- /* didn't find it */
- LOGE("ERROR: breakpoint on %p not found\n", addr);
- assert(false);
- }
#else
assert(false);
#endif
}
+#ifdef WITH_DEBUGGER
+/*
+ * Get the original opcode from under a breakpoint.
+ */
+u1 dvmGetOriginalOpCode(const u2* addr)
+{
+ BreakpointSet* pSet = gDvm.breakpointSet;
+ u1 orig = 0;
+
+ dvmBreakpointSetLock(pSet);
+ if (!dvmBreakpointSetOriginalOpCode(pSet, addr, &orig)) {
+ orig = *(u1*)addr;
+ if (orig == OP_BREAKPOINT) {
+ LOGE("GLITCH: can't find breakpoint, opcode is still set\n");
+ dvmAbort();
+ }
+ }
+ dvmBreakpointSetUnlock(pSet);
+
+ return orig;
+}
+
+/*
+ * Temporarily "undo" any breakpoints set in a specific method. Used
+ * during verification.
+ *
+ * Locks the breakpoint set, and leaves it locked.
+ */
+void dvmUndoBreakpoints(Method* method)
+{
+ BreakpointSet* pSet = gDvm.breakpointSet;
+
+ dvmBreakpointSetLock(pSet);
+ dvmBreakpointSetUndo(pSet, method);
+ /* lock remains held */
+}
+
+/*
+ * "Redo" the breakpoints cleared by a previous "undo", re-inserting the
+ * breakpoint opcodes and updating the "original opcode" values.
+ *
+ * Unlocks the breakpoint set, which must be held by a previous "undo".
+ */
+void dvmRedoBreakpoints(Method* method)
+{
+ BreakpointSet* pSet = gDvm.breakpointSet;
+
+ /* lock already held */
+ dvmBreakpointSetRedo(pSet, method);
+ dvmBreakpointSetUnlock(pSet);
+}
+#endif
+
/*
* Add a single step event. Currently this is a global item.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* Dalvik interpreter public definitions.
*/
void dvmThrowVerificationError(const Method* method, int kind, int ref);
/*
- * Breakpoint optimization table.
+ * One-time initialization and shutdown.
+ */
+bool dvmBreakpointStartup(void);
+void dvmBreakpointShutdown(void);
+
+/*
+ * Breakpoint implementation.
*/
void dvmInitBreakpoints();
-void dvmAddBreakAddr(Method* method, int instrOffset);
-void dvmClearBreakAddr(Method* method, int instrOffset);
+void dvmAddBreakAddr(Method* method, unsigned int instrOffset);
+void dvmClearBreakAddr(Method* method, unsigned int instrOffset);
bool dvmAddSingleStep(Thread* thread, int size, int depth);
void dvmClearSingleStep(Thread* thread);
+#ifdef WITH_DEBUGGER
+/*
+ * Recover the opcode that was replaced by a breakpoint.
+ */
+u1 dvmGetOriginalOpCode(const u2* addr);
+
+/*
+ * Temporarily "undo" any breakpoints set in a specific method. Used
+ * during verification.
+ *
+ * Locks the breakpoint set, and leaves it locked.
+ */
+void dvmUndoBreakpoints(Method* method);
+
+/*
+ * "Redo" the breakpoints cleared by a previous "undo", re-inserting the
+ * breakpoint opcodes and updating the "original opcode" values.
+ *
+ * Unlocks the breakpoint set, which must be held by a previous "undo".
+ */
+void dvmRedoBreakpoints(Method* method);
+#endif
+
#endif /*_DALVIK_INTERP_INTERP*/
--- /dev/null
+HANDLE_OPCODE(OP_BREAKPOINT)
+#if (INTERP_TYPE == INTERP_DBG) && defined(WITH_DEBUGGER)
+ {
+ /*
+ * Restart this instruction with the original opcode. We do
+ * this by simply jumping to the handler.
+ *
+ * It's probably not necessary to update "inst", but we do it
+ * for the sake of anything that needs to do disambiguation in a
+ * common handler with INST_INST.
+ *
+ * The breakpoint itself is handled over in updateDebugger(),
+ * because we need to detect other events (method entry, single
+ * step) and report them in the same event packet, and we're not
+ * yet handling those through breakpoint instructions. By the
+ * time we get here, the breakpoint has already been handled and
+ * the thread resumed.
+ */
+ u1 originalOpCode = dvmGetOriginalOpCode(pc);
+ LOGV("+++ break 0x%02x (0x%04x -> 0x%04x)\n", originalOpCode, inst,
+ INST_REPLACE_OP(inst, originalOpCode));
+ inst = INST_REPLACE_OP(inst, originalOpCode);
+ FINISH_BKPT(originalOpCode);
+ }
+#else
+ LOGE("Breakpoint hit in non-debug interpreter\n");
+ dvmAbort();
+#endif
+OP_END
+++ /dev/null
-HANDLE_OPCODE(OP_UNUSED_EC)
-OP_END
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
/* ------------------------------ */
.balign 64
-.L_OP_UNUSED_EC: /* 0xec */
-/* File: armv5te/OP_UNUSED_EC.S */
+.L_OP_BREAKPOINT: /* 0xec */
+/* File: armv5te/OP_BREAKPOINT.S */
/* File: armv5te/unused.S */
bl common_abort
/* ------------------------------ */
.balign 64
-.L_OP_UNUSED_EC: /* 0xec */
-/* File: armv5te/OP_UNUSED_EC.S */
+.L_OP_BREAKPOINT: /* 0xec */
+/* File: armv5te/OP_BREAKPOINT.S */
/* File: armv5te/unused.S */
bl common_abort
/* ------------------------------ */
.balign 64
-.L_OP_UNUSED_EC: /* 0xec */
-/* File: armv5te/OP_UNUSED_EC.S */
+.L_OP_BREAKPOINT: /* 0xec */
+/* File: armv5te/OP_BREAKPOINT.S */
/* File: armv5te/unused.S */
bl common_abort
/* ------------------------------ */
.balign 64
-.L_OP_UNUSED_EC: /* 0xec */
-/* File: armv5te/OP_UNUSED_EC.S */
+.L_OP_BREAKPOINT: /* 0xec */
+/* File: armv5te/OP_BREAKPOINT.S */
/* File: armv5te/unused.S */
bl common_abort
/* ------------------------------ */
.balign 64
-.L_OP_UNUSED_EC: /* 0xec */
-/* File: x86/OP_UNUSED_EC.S */
+.L_OP_BREAKPOINT: /* 0xec */
+/* File: x86/OP_BREAKPOINT.S */
/* File: x86/unused.S */
jmp common_abort
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
HANDLE_OPCODE(OP_UNUSED_EB)
OP_END
-/* File: c/OP_UNUSED_EC.c */
-HANDLE_OPCODE(OP_UNUSED_EC)
+/* File: c/OP_BREAKPOINT.c */
+HANDLE_OPCODE(OP_BREAKPOINT)
+#if (INTERP_TYPE == INTERP_DBG) && defined(WITH_DEBUGGER)
+ {
+ /*
+ * Restart this instruction with the original opcode. We do
+ * this by simply jumping to the handler.
+ *
+ * It's probably not necessary to update "inst", but we do it
+ * for the sake of anything that needs to do disambiguation in a
+ * common handler with INST_INST.
+ *
+ * The breakpoint itself is handled over in updateDebugger(),
+ * because we need to detect other events (method entry, single
+ * step) and report them in the same event packet, and we're not
+ * yet handling those through breakpoint instructions. By the
+ * time we get here, the breakpoint has already been handled and
+ * the thread resumed.
+ */
+ u1 originalOpCode = dvmGetOriginalOpCode(pc);
+ LOGV("+++ break 0x%02x (0x%04x -> 0x%04x)\n", originalOpCode, inst,
+ INST_REPLACE_OP(inst, originalOpCode));
+ inst = INST_REPLACE_OP(inst, originalOpCode);
+ FINISH_BKPT(originalOpCode);
+ }
+#else
+ LOGE("Breakpoint hit in non-debug interpreter\n");
+ dvmAbort();
+#endif
OP_END
/* File: c/OP_THROW_VERIFICATION_ERROR.c */
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
*
* Assumes the existence of "const u2* pc" and (for threaded operation)
* "u2 inst".
+ *
+ * TODO: remove "switch" version.
*/
#ifdef THREADED_INTERP
# define H(_op) &&op_##_op
if (CHECK_JIT()) GOTO_bail_switch(); \
goto *handlerTable[INST_INST(inst)]; \
}
+# define FINISH_BKPT(_opcode) { \
+ goto *handlerTable[_opcode]; \
+ }
#else
# define HANDLE_OPCODE(_op) case _op:
# define FINISH(_offset) { ADJUST_PC(_offset); break; }
+# define FINISH_BKPT(opcode) { > not implemented < }
#endif
#define OP_END
/* code in here is only included in portable-debug interpreter */
/*
- * Determine if an address is "interesting" to the debugger. This allows
- * us to avoid scanning the entire event list before every instruction.
- *
- * The "debugBreakAddr" table is global and not synchronized.
- */
-static bool isInterestingAddr(const u2* pc)
-{
- const u2** ptr = gDvm.debugBreakAddr;
- int i;
-
- for (i = 0; i < MAX_BREAKPOINTS; i++, ptr++) {
- if (*ptr == pc) {
- LOGV("BKP: hit on %p\n", pc);
- return true;
- }
- }
- return false;
-}
-
-/*
* Update the debugger on interesting events, such as hitting a breakpoint
* or a single-step point. This is called from the top of the interpreter
* loop, before the current instruction is processed.
*
* Depending on the "mods" associated with event(s) on this address,
* we may or may not actually send a message to the debugger.
- *
- * Checking method->debugBreakpointCount is slower on the device than
- * just scanning the table (!). We could probably work something out
- * where we just check it on method entry/exit and remember the result,
- * but that's more fragile and requires passing more stuff around.
*/
#ifdef WITH_DEBUGGER
- if (method->debugBreakpointCount > 0 && isInterestingAddr(pc)) {
+ if (INST_INST(*pc) == OP_BREAKPOINT) {
+ LOGV("+++ breakpoint hit at %p\n", pc);
eventFlags |= DBG_BREAKPOINT;
}
#endif
HANDLE_OPCODE(OP_UNUSED_EB)
OP_END
-/* File: c/OP_UNUSED_EC.c */
-HANDLE_OPCODE(OP_UNUSED_EC)
+/* File: c/OP_BREAKPOINT.c */
+HANDLE_OPCODE(OP_BREAKPOINT)
+#if (INTERP_TYPE == INTERP_DBG) && defined(WITH_DEBUGGER)
+ {
+ /*
+ * Restart this instruction with the original opcode. We do
+ * this by simply jumping to the handler.
+ *
+ * It's probably not necessary to update "inst", but we do it
+ * for the sake of anything that needs to do disambiguation in a
+ * common handler with INST_INST.
+ *
+ * The breakpoint itself is handled over in updateDebugger(),
+ * because we need to detect other events (method entry, single
+ * step) and report them in the same event packet, and we're not
+ * yet handling those through breakpoint instructions. By the
+ * time we get here, the breakpoint has already been handled and
+ * the thread resumed.
+ */
+ u1 originalOpCode = dvmGetOriginalOpCode(pc);
+ LOGV("+++ break 0x%02x (0x%04x -> 0x%04x)\n", originalOpCode, inst,
+ INST_REPLACE_OP(inst, originalOpCode));
+ inst = INST_REPLACE_OP(inst, originalOpCode);
+ FINISH_BKPT(originalOpCode);
+ }
+#else
+ LOGE("Breakpoint hit in non-debug interpreter\n");
+ dvmAbort();
+#endif
OP_END
/* File: c/OP_THROW_VERIFICATION_ERROR.c */
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
*
* Assumes the existence of "const u2* pc" and (for threaded operation)
* "u2 inst".
+ *
+ * TODO: remove "switch" version.
*/
#ifdef THREADED_INTERP
# define H(_op) &&op_##_op
if (CHECK_JIT()) GOTO_bail_switch(); \
goto *handlerTable[INST_INST(inst)]; \
}
+# define FINISH_BKPT(_opcode) { \
+ goto *handlerTable[_opcode]; \
+ }
#else
# define HANDLE_OPCODE(_op) case _op:
# define FINISH(_offset) { ADJUST_PC(_offset); break; }
+# define FINISH_BKPT(opcode) { > not implemented < }
#endif
#define OP_END
HANDLE_OPCODE(OP_UNUSED_EB)
OP_END
-/* File: c/OP_UNUSED_EC.c */
-HANDLE_OPCODE(OP_UNUSED_EC)
+/* File: c/OP_BREAKPOINT.c */
+HANDLE_OPCODE(OP_BREAKPOINT)
+#if (INTERP_TYPE == INTERP_DBG) && defined(WITH_DEBUGGER)
+ {
+ /*
+ * Restart this instruction with the original opcode. We do
+ * this by simply jumping to the handler.
+ *
+ * It's probably not necessary to update "inst", but we do it
+ * for the sake of anything that needs to do disambiguation in a
+ * common handler with INST_INST.
+ *
+ * The breakpoint itself is handled over in updateDebugger(),
+ * because we need to detect other events (method entry, single
+ * step) and report them in the same event packet, and we're not
+ * yet handling those through breakpoint instructions. By the
+ * time we get here, the breakpoint has already been handled and
+ * the thread resumed.
+ */
+ u1 originalOpCode = dvmGetOriginalOpCode(pc);
+ LOGV("+++ break 0x%02x (0x%04x -> 0x%04x)\n", originalOpCode, inst,
+ INST_REPLACE_OP(inst, originalOpCode));
+ inst = INST_REPLACE_OP(inst, originalOpCode);
+ FINISH_BKPT(originalOpCode);
+ }
+#else
+ LOGE("Breakpoint hit in non-debug interpreter\n");
+ dvmAbort();
+#endif
OP_END
/* File: c/OP_THROW_VERIFICATION_ERROR.c */
#define INST_INST(_inst) ((_inst) & 0xff)
/*
+ * Replace the opcode (used when handling breakpoints). _opcode is a u1.
+ */
+#define INST_REPLACE_OP(_inst, _opcode) (((_inst) & 0xff00) | _opcode)
+
+/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
/* code in here is only included in portable-debug interpreter */
/*
- * Determine if an address is "interesting" to the debugger. This allows
- * us to avoid scanning the entire event list before every instruction.
- *
- * The "debugBreakAddr" table is global and not synchronized.
- */
-static bool isInterestingAddr(const u2* pc)
-{
- const u2** ptr = gDvm.debugBreakAddr;
- int i;
-
- for (i = 0; i < MAX_BREAKPOINTS; i++, ptr++) {
- if (*ptr == pc) {
- LOGV("BKP: hit on %p\n", pc);
- return true;
- }
- }
- return false;
-}
-
-/*
* Update the debugger on interesting events, such as hitting a breakpoint
* or a single-step point. This is called from the top of the interpreter
* loop, before the current instruction is processed.
*
* Depending on the "mods" associated with event(s) on this address,
* we may or may not actually send a message to the debugger.
- *
- * Checking method->debugBreakpointCount is slower on the device than
- * just scanning the table (!). We could probably work something out
- * where we just check it on method entry/exit and remember the result,
- * but that's more fragile and requires passing more stuff around.
*/
#ifdef WITH_DEBUGGER
- if (method->debugBreakpointCount > 0 && isInterestingAddr(pc)) {
+ if (INST_INST(*pc) == OP_BREAKPOINT) {
+ LOGV("+++ breakpoint hit at %p\n", pc);
eventFlags |= DBG_BREAKPOINT;
}
#endif
*
* Assumes the existence of "const u2* pc" and (for threaded operation)
* "u2 inst".
+ *
+ * TODO: remove "switch" version.
*/
#ifdef THREADED_INTERP
# define H(_op) &&op_##_op
if (CHECK_JIT()) GOTO_bail_switch(); \
goto *handlerTable[INST_INST(inst)]; \
}
+# define FINISH_BKPT(_opcode) { \
+ goto *handlerTable[_opcode]; \
+ }
#else
# define HANDLE_OPCODE(_op) case _op:
# define FINISH(_offset) { ADJUST_PC(_offset); break; }
+# define FINISH_BKPT(opcode) { > not implemented < }
#endif
#define OP_END
}
}
+#if 0 /* replaced with private/read-write mapping */
/*
* We usually map bytecode directly out of the DEX file, which is mapped
* shared read-only. If we want to be able to modify it, we have to make
LOGV("+++ marking %p read-only\n", methodDexCode);
dvmLinearReadOnly(meth->clazz->classLoader, methodDexCode);
}
+#endif
/*
#ifdef WITH_PROFILER
bool inProfile;
#endif
-#ifdef WITH_DEBUGGER
- short debugBreakpointCount;
-#endif
};
/*