blob: c93b236fe42227a2961e6b8e1b8de755d06cbec8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file jvmti_break_intf.cpp
* @brief JVMTI native breakpoints API implementation
*/
#define LOG_DOMAIN "jvmti.break"
#include "cxxlog.h"
#include "open/vm_method_access.h"
#include "environment.h"
#include "interpreter.h"
#include "interpreter_exports.h"
#include "jit_intf_cpp.h"
#include "exceptions.h"
#include "m2n.h"
#include "stack_iterator.h"
#include "open/bytecodes.h"
#include "cci.h"
#include "ncai_internal.h"
#include "jvmti_break_intf.h"
#include "cci.h"
#include "port_thread.h"
#include "port_crash_handler.h"
#include "open/vm_class_info.h"
#if (defined _IA32_) || (defined _EM64T_)
#include "encoder.h"
// Forward declarations
static ConditionCode
get_condition_code(InstructionDisassembler::CondJumpType jump_type);
#endif
static bool set_jit_mode_breakpoint(VMBreakPoint* bp);
static bool set_native_breakpoint(VMBreakPoint* bp);
static bool clear_native_breakpoint(VMBreakPoint* bp);
//////////////////////////////////////////////////////////////////////////////
// VMBreakPoints implementation
VMBreakPoints::~VMBreakPoints()
{
lock();
for(unsigned index = 0; index < PRIORITY_NUMBER; index++ ) {
while (m_intf[index]) {
VMBreakInterface* intf = m_intf[index];
m_intf[index] = intf->m_next;
delete intf;
}
}
assert(m_break == NULL);
unlock();
}
VMBreakInterface*
VMBreakPoints::new_intf(TIEnv *env,
BPInterfaceCallBack callback,
unsigned priority,
bool is_interp)
{
assert(callback);
assert(priority < PRIORITY_NUMBER);
VMBreakInterface* intf = new VMBreakInterface(env, callback, priority, is_interp);
assert(intf);
lock();
TRACE2("jvmti.break", "Create breakpoint interface: " << intf );
// add interface to the top of list
intf->m_next = m_intf[priority];
m_intf[priority] = intf;
unlock();
return intf;
}
void
VMBreakPoints::release_intf(VMBreakInterface* intf)
{
assert(intf);
assert(intf->get_priority() < PRIORITY_NUMBER);
LMAutoUnlock lock(get_lock());
TRACE2("jvmti.break", "Release breakpoint interface: " << intf );
// correct thread processing breakpoints
for(VMLocalBreak *local = m_local; local; local = local->next) {
if( local->intf == intf ) {
TRACE2("jvmti.break", "Set local thread interface: "
<< local << ", intf: " << intf->m_next );
local->intf = intf->m_next;
}
}
// release interface
for (VMBreakInterface** cur_ptr = &m_intf[intf->get_priority()];
*cur_ptr;
cur_ptr = &((*cur_ptr)->m_next))
{
if (*cur_ptr == intf)
{
*cur_ptr = (*cur_ptr)->m_next;
delete intf;
return;
}
}
LDIE(23, "{0} try to release unknown interface" << "VMBreakPoints::release_intf:");
}
VMBreakInterface*
VMBreakPoints::get_next_intf(VMBreakInterface *intf)
{
return intf->m_next;
}
VMBreakPoint*
VMBreakPoints::get_next_breakpoint(VMBreakPoint* prev)
{
return prev->next;
}
inline bool
VMBreakPoints::check_insert_breakpoint(VMBreakPoint* bp)
{
#ifndef NDEBUG
if( !bp ) {
return false;
} else if( bp->method ) {
TRACE2("jvmti.break", "Try to insert breakpoint: "
<< bp->method->get_class()->get_name()->bytes << "."
<< bp->method->get_name()->bytes
<< bp->method->get_descriptor()->bytes
<< " :" << bp->location << " :" << bp->addr);
VMBreakPoint* another = find_breakpoint(bp->method, bp->location);
if( !another ) {
return true;
}
TRACE2("jvmti.break", "Before inserting found another breakpoint: "
<< (another->method
? another->method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (another->method ? another->method->get_name()->bytes : "(nil)")
<< (another->method ? another->method->get_descriptor()->bytes : "")
<< " :" << another->location << " :" << another->addr);
if( bp->addr == another->addr) {
return false;
}
} else if( bp->addr ) {
TRACE2("jvmti.break", "Try to insert breakpoint: native address:"
<< bp->addr);
VMBreakPoint* another = find_breakpoint(bp->addr);
if( !another ) {
return true;
}
TRACE2("jvmti.break", "Before inserting found another breakpoint: "
<< (another->method
? another->method->get_class()->get_name()->bytes :"(nil)")
<< "."
<< (another->method ? another->method->get_name()->bytes :"(nil)")
<< (another->method ? another->method->get_descriptor()->bytes :"")
<< " :" << another->location << " :" << another->addr);
if(another->method) {
return false;
}
} else {
return false;
}
#endif // !NDEBUG
return true;
}
inline void
VMBreakPoints::insert_breakpoint(VMBreakPoint* bp)
{
TRACE2("jvmti.break", "Insert breakpoint: "
<< (bp->method
? bp->method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)")
<< (bp->method ? bp->method->get_descriptor()->bytes : "")
<< " :" << bp->location << " :" << bp->addr);
// add breakpoint to the top of list
bp->next = m_break;
m_break = bp;
return;
}
bool
VMBreakPoints::insert_native_breakpoint(VMBreakPoint* bp)
{
LMAutoUnlock lock(get_lock());
bool UNREF check = check_insert_breakpoint(bp);
assert(check);
if (bp->method != NULL)
{ // JIT breakpoint
Method *m = (Method *)bp->method;
if (m->get_state() == Method::ST_Compiled)
{
if (!set_jit_mode_breakpoint(bp))
return false;
}
else
{
assert(bp->addr == NULL);
TRACE2("jvmti.break.intf", "Skipping setting breakpoint in method "
<< m->get_class()->get_name()->bytes << "."
<< m->get_name()->bytes
<< m->get_descriptor()->bytes
<< " because it is not compiled yet");
m->insert_pending_breakpoint();
}
}
else
{
if (!set_native_breakpoint(bp))
return false;
}
insert_breakpoint(bp);
return true;
}
bool
VMBreakPoints::insert_interpreter_breakpoint(VMBreakPoint* bp)
{
LMAutoUnlock lock(get_lock());
assert(interpreter_enabled());
bool UNREF check = check_insert_breakpoint(bp);
assert(check);
bp->saved_byte =
interpreter.interpreter_ti_set_breakpoint(bp->method, bp->location);
insert_breakpoint(bp);
return true;
}
inline void
VMBreakPoints::remove_breakpoint(VMBreakPoint* bp)
{
TRACE2("jvmti.break.intf", "Remove breakpoint: "
<< (bp->method ? bp->method->get_class()->get_name()->bytes : "(nil)" )
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)" )
<< (bp->method ? bp->method->get_descriptor()->bytes : "" )
<< " :" << bp->location << " :" << bp->addr);
// remove breakpoint from list
for (VMBreakPoint** plast = &m_break; *plast;
plast = &((*plast)->next))
{
if (*plast == bp)
{
*plast = (*plast)->next;
break;
}
}
// correct thread processing breakpoints
for(VMLocalBreak *local = m_local; local; local = local->next) {
if( local->bp == bp ) {
// processed breakpoint was removed
TRACE2("jvmti.break", "Remove local thread breakpoint: "
<< local << ", bp: "
<< (bp->method
? bp->method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)")
<< (bp->method ? bp->method->get_descriptor()->bytes : "")
<< " :" << bp->location << " :" << bp->addr);
local->bp = NULL;
} else if( local->bp_next == bp ) {
// set new next breakpoint
TRACE2("jvmti.break", "Set local thread next breakpoint: "
<< local << ", next: "
<< (bp->method
? bp->method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)")
<< (bp->method ? bp->method->get_descriptor()->bytes : "")
<< " :" << bp->location << " :" << bp->addr);
local->bp_next = bp->next;
}
}
return;
}
bool
VMBreakPoints::remove_native_breakpoint(VMBreakPoint* bp)
{
assert(bp);
assert(!bp->method || find_breakpoint(bp->method, bp->location));
assert(bp->method || find_breakpoint(bp->addr));
LMAutoUnlock lock(get_lock());
remove_breakpoint(bp);
if (bp->addr)
{
assert(!bp->method || (((Method*)bp->method)->get_state() == Method::ST_Compiled));
return clear_native_breakpoint(bp);
}
else
{
assert(bp->method && (((Method*)bp->method)->get_state() != Method::ST_Compiled));
Method *m = (Method *)bp->method;
m->remove_pending_breakpoint();
}
return true;
}
bool
VMBreakPoints::remove_interpreter_breakpoint(VMBreakPoint* bp)
{
assert(bp);
assert(bp->method);
assert(find_breakpoint(bp->method, bp->location));
assert(interpreter_enabled());
LMAutoUnlock lock(get_lock());
remove_breakpoint(bp);
interpreter.interpreter_ti_clear_breakpoint(bp->method,
bp->location, bp->saved_byte);
return true;
}
VMBreakPoint*
VMBreakPoints::find_breakpoint(jmethodID method, jlocation location)
{
for (VMBreakPoint* brpt = m_break; brpt; brpt = brpt->next)
{
if (brpt->method == method &&
brpt->location == location)
return brpt;
}
return NULL;
}
VMBreakPoint*
VMBreakPoints::find_breakpoint(NativeCodePtr addr)
{
assert(addr);
for (VMBreakPoint* brpt = m_break; brpt; brpt = brpt->next)
{
if (brpt->addr == addr)
return brpt;
}
return NULL;
}
VMBreakPoint*
VMBreakPoints::find_other_breakpoint_with_same_addr(VMBreakPoint* bp)
{
assert(bp);
for (VMBreakPoint* other = m_break; other; other = other->next)
{
if (other != bp && other->addr == bp->addr)
return other;
}
return NULL;
}
VMBreakPoint*
VMBreakPoints::find_next_breakpoint(VMBreakPoint* prev, NativeCodePtr addr)
{
assert(addr && prev);
for (VMBreakPoint* brpt = prev->next; brpt; brpt = brpt->next)
{
if (brpt->addr == addr)
return brpt;
}
return NULL;
}
VMBreakPoint*
VMBreakPoints::find_next_breakpoint(VMBreakPoint* prev,
jmethodID method,
jlocation location)
{
assert(prev);
assert(method);
for (VMBreakPoint* bp = prev->next; bp; bp = bp->next) {
if (bp->method == method && bp->location == location) {
return bp;
}
}
return NULL;
}
VMBreakPoint*
VMBreakPoints::find_method_breakpoint(jmethodID method)
{
assert(method);
for (VMBreakPoint* brpt = m_break; brpt; brpt = brpt->next)
{
if (brpt->method &&
brpt->method == method)
return brpt;
}
return NULL;
}
VMBreakPoint*
VMBreakPoints::find_next_method_breakpoint(VMBreakPoint* prev, jmethodID method)
{
assert(prev);
for (VMBreakPoint* brpt = prev->next; brpt; brpt = brpt->next)
{
if (brpt->method == method)
return brpt;
}
return NULL;
}
VMBreakPointRef*
VMBreakPoints::find_other_reference(VMBreakInterface* intf,
jmethodID method,
jlocation location)
{
assert(intf);
for( unsigned index = 0; index < PRIORITY_NUMBER; index++ ) {
for (VMBreakInterface* cur = m_intf[index]; cur; cur = cur->m_next) {
if (cur == intf)
continue;
VMBreakPointRef* ref = cur->find_reference(method, location);
if (ref)
return ref;
}
}
return NULL;
}
VMBreakPointRef*
VMBreakPoints::find_other_reference(VMBreakInterface* intf,
NativeCodePtr addr)
{
assert(intf);
for( unsigned index = 0; index < PRIORITY_NUMBER; index++ ) {
for (VMBreakInterface* cur = m_intf[index]; cur; cur = cur->m_next) {
if (cur == intf)
continue;
VMBreakPointRef* ref = cur->find_reference(addr);
if (ref)
return ref;
}
}
return NULL;
}
VMBreakPointRef*
VMBreakPoints::find_other_reference(VMBreakInterface* intf,
VMBreakPoint* brpt)
{
assert(intf);
for( unsigned index = 0; index < PRIORITY_NUMBER; index++ ) {
for (VMBreakInterface* cur = m_intf[index]; cur; cur = cur->m_next) {
if (cur == intf)
continue;
VMBreakPointRef* ref = cur->find_reference(brpt);
if (ref)
return ref;
}
}
return NULL;
}
void
VMBreakPoints::set_thread_local_break(VMLocalBreak *local)
{
local->next = m_local;
m_local = local;
TRACE2( "jvmti.break", "Set local thread structure: " << local);
}
void
VMBreakPoints::remove_thread_local_break(VMLocalBreak *local)
{
TRACE2( "jvmti.break", "Remove local thread structure: " << local);
VMLocalBreak *last = NULL;
for( VMLocalBreak *index = m_local;
index;
last = index, index = index->next )
{
if(index == local) {
if(last) {
last->next = index->next;
} else {
m_local = index->next;
}
return;
}
}
assert(false);
}
static char *gen_push(char *code_addr, POINTER_SIZE_INT value)
{
#ifdef _IA32_
return push(code_addr, Imm_Opnd(size_32, value));
#elif defined _EM64T_
I_32 high = (I_32)((U_32)(value >> 32));
I_32 low = (I_32)((U_32)value);
code_addr = alu(code_addr, sub_opc, rsp_opnd, Imm_Opnd(size_8, 8));
code_addr = mov(code_addr, M_Base_Opnd(rsp_reg, 4), Imm_Opnd(size_32, high), size_32);
return mov(code_addr, M_Base_Opnd(rsp_reg, 0), Imm_Opnd(size_32, low), size_32);
#else
assert(0);
return NULL;
#endif
}
static char *gen_jump(char *code_addr, char *target_addr)
{
#ifdef _IA32_
return jump(code_addr, target_addr);
#elif defined _EM64T_
code_addr = gen_push(code_addr, (POINTER_SIZE_INT)target_addr);
return ret(code_addr);
#else
assert(0);
return NULL;
#endif
}
VMLocalBreak*
VMBreakPoints::find_thread_local_break(VM_thread* vmthread)
{
assert(vmthread);
TRACE2( "jvmti.break", "Find local structure for thread: " << vmthread);
for (VMLocalBreak* cur = m_local; cur; cur = cur->next)
{
if (cur->vmthread == vmthread)
return cur;
}
return NULL;
}
void
VMBreakPoints::process_native_breakpoint(Registers* regs)
{
#if (defined _IA32_) || (defined _EM64T_)
// When we get here we know already that breakpoint occurred in JITted code,
// JVMTI handles it, and registers context is saved for us in TLS
VM_thread *vm_thread = p_TLS_vmthread;
lock();
NativeCodePtr addr = (NativeCodePtr)regs->get_ip();
TRACE2("jvmti.break", "Native breakpoint occured: " << addr);
M2nFrame* m2nf = m2n_push_suspended_frame(regs);
VMBreakPoint* bp = find_breakpoint(addr);
if (NULL == bp) {
// breakpoint could be deleted by another thread
assert(!port_is_breakpoint_set(addr));
unlock();
// Transfer execution back to the original register
// context. In case the target location happens to be
// instrumented, it means that another breakpoint has been set
// there right after unlock was done.
m2n_set_last_frame(m2n_get_previous_frame(m2nf));
return;
}
assert(bp->addr == addr);
TRACE2("jvmti.break", "Process native breakpoint: "
<< (bp->method ? bp->method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)")
<< (bp->method ? bp->method->get_descriptor()->bytes : "")
<< " : " << bp->location << " : " << bp->addr);
// Save registers in TLS - mainly for single stepping in virtual methods
vm_set_jvmti_saved_exception_registers(vm_thread, regs);
jbyte *instruction_buffer;
BEGIN_RAISE_AREA;
// need to be able to pop the frame
frame_type m2nf_type = m2n_get_frame_type(m2nf);
m2nf_type = (frame_type) (m2nf_type | FRAME_POPABLE);
m2n_set_frame_type(m2nf, m2nf_type);
jbyte orig_byte = bp->saved_byte;
// Copy disassembler instance in case a breakpoint is deleted
// inside of callbacks
InstructionDisassembler idisasm(*bp->disasm);
bool is_enabled = VM_Global_State::loader_env->TI->shouldReportLocally();
// if is_enabled is false then we should continue execution without
// reporting breakpoint event
for (unsigned priority = 0;
is_enabled && priority < PRIORITY_NUMBER;
priority++)
{
bp = find_breakpoint(addr);
assert(!bp || bp->addr == addr);
VMLocalBreak local;
local.priority = priority;
local.vmthread = vm_thread;
while( bp )
{
assert(bp->addr == addr);
// copy breakpoint to local thread variable
local.bp = bp;
local.bp_next = find_next_breakpoint(bp, addr);
VMBreakInterface *intf = get_first_intf(priority);
while( intf )
{
VMBreakPointRef* ref = intf->find_reference(bp);
assert(!ref || ref->bp->addr == addr);
if (ref && intf->breakpoint_event_callback != NULL)
{
local.intf = intf->m_next;
VMBreakPoint local_bp = *bp;
local_bp.regs = *regs;
local.local_bp = &local_bp;
// Set local copy's pointer to local copy of disassembler
local_bp.disasm = &idisasm;
POINTER_SIZE_INT data = ref->data;
Method *method = (Method*)bp->method;
jlocation location = bp->location;
NativeCodePtr addr = bp->addr;
TRACE2("jvmti.break",
"Calling native breakpoint callback function: "
<< (method
? method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (method ? method->get_name()->bytes : "(nil)")
<< (method ? method->get_descriptor()->bytes : "")
<< " :" << location << " :" << addr);
set_thread_local_break(&local);
unlock();
// call event breakpoint callback
intf->breakpoint_event_callback(intf->get_env(), &local_bp, data);
lock();
remove_thread_local_break(&local);
TRACE2("jvmti.break",
"Finished native breakpoint callback function: "
<< (method
? method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (method ? method->get_name()->bytes : "(nil)")
<< (method ? method->get_descriptor()->bytes : "")
<< " :" << location << " :" << addr);
if( !local.bp ) {
// breakpoint was removed, no need report it anymore
break;
}
intf = local.intf;
} else {
intf = intf->m_next;
}
}
bp = local.bp_next;
if( bp && bp->addr != addr ) {
bp = find_next_breakpoint(bp, addr);
}
}
}
unlock();
// Now we need to return back to normal code execution, it is
// necessary to execute the original instruction The idea is to
// recreate the original instruction in a special thread local
// buffer followed by a jump to an instruction next after it. In
// case the instruction was a relative jump or call it requires
// special handling.
InstructionDisassembler::Type type = idisasm.get_type();
instruction_buffer =
vm_thread->jvmti_thread.jvmti_jit_breakpoints_handling_buffer;
jbyte *interrupted_instruction = (jbyte *)addr;
jint instruction_length = idisasm.get_length_with_prefix();
switch(type)
{
case InstructionDisassembler::UNKNOWN:
case InstructionDisassembler::RET:
{
char *next_instruction = (char *)interrupted_instruction +
instruction_length;
// Copy original instruction to the execution buffer
*instruction_buffer = orig_byte;
memcpy(instruction_buffer + 1, interrupted_instruction + 1,
instruction_length - 1);
// Create JMP $next_instruction instruction in the execution buffer
gen_jump((char *)instruction_buffer + instruction_length,
next_instruction);
break;
}
case InstructionDisassembler::RELATIVE_JUMP:
{
char *jump_target = (char *)idisasm.get_jump_target_address();
// Create JMP to the absolute address which conditional jump
// had in the execution buffer
gen_jump((char *)instruction_buffer, jump_target);
break;
}
case InstructionDisassembler::RELATIVE_COND_JUMP:
{
char *code = (char *)instruction_buffer;
InstructionDisassembler::CondJumpType jump_type =
idisasm.get_cond_jump_type();
char *next_instruction = (char *)interrupted_instruction +
instruction_length;
char *jump_target = (char *)idisasm.get_jump_target_address();
// Create a conditional JMP of the same type over 1
// instruction forward, the next instruction is JMP to the
// $next_instruction
code = branch8(code, get_condition_code(jump_type), Imm_Opnd(size_8, 0));
char *branch_address = code - 1;
code = gen_jump(code, next_instruction);
jint offset = (jint)(code - branch_address - 1);
*branch_address = offset;
gen_jump(code, jump_target);
break;
}
case InstructionDisassembler::RELATIVE_CALL:
{
jbyte *next_instruction = interrupted_instruction + instruction_length;
char *jump_target = (char *)idisasm.get_jump_target_address();
char *code = (char *)instruction_buffer;
// Push "return address" to the $next_instruction
code = gen_push(code, (POINTER_SIZE_INT)next_instruction);
// Jump to the target address of the call instruction
gen_jump(code, jump_target);
break;
}
case InstructionDisassembler::INDIRECT_JUMP:
{
char *jump_target = (char *)idisasm.get_target_address_from_context(regs);
// Create JMP to the absolute address which conditional jump
// had in the execution buffer
gen_jump((char *)instruction_buffer, jump_target);
break;
}
case InstructionDisassembler::INDIRECT_CALL:
{
jbyte *next_instruction = interrupted_instruction + instruction_length;
char *jump_target = (char *)idisasm.get_target_address_from_context(regs);
char *code = (char *)instruction_buffer;
// Push "return address" to the $next_instruction
code = gen_push(code, (POINTER_SIZE_INT)next_instruction);
// Jump to the target address of the call instruction
gen_jump(code, jump_target);
break;
}
}
END_RAISE_AREA;
// This function does not return. It restores register context and
// transfers execution control to the instruction buffer to
// execute the original instruction with the registers which it
// had before breakpoint happened
m2n_set_last_frame(m2n_get_previous_frame(m2nf));
regs->set_ip(instruction_buffer);
return; // We'll go to updated regs
#else
// PLATFORM dependent code
LDIE(45, "Not supported on this platform");
#endif
}
jbyte
VMBreakPoints::process_interpreter_breakpoint(jmethodID method, jlocation location)
{
TRACE2("jvmti.break.intf", "Interpreter breakpoint occured: "
<< method->get_class()->get_name()->bytes << "."
<< method->get_name()->bytes
<< method->get_descriptor()->bytes
<< " : " << location );
assert(interpreter_enabled());
lock();
VMBreakPoint* bp = find_breakpoint(method, location);
if(NULL == bp) {
// breakpoint could be deleted by another thread
unlock();
return (jbyte)OPCODE_COUNT;
}
assert(bp->method == method);
assert(bp->location == location);
TRACE2("jvmti.break", "Process interpreter breakpoint: "
<< method->get_class()->get_name()->bytes << "."
<< method->get_name()->bytes
<< method->get_descriptor()->bytes
<< " :" << location );
jbyte orig_byte = bp->saved_byte;
bool is_enabled = VM_Global_State::loader_env->TI->shouldReportLocally();
// if is_enabled is false then we should continue execution without
// reporting breakpoint event
for (unsigned priority = 0;
is_enabled && priority < PRIORITY_NUMBER;
priority++)
{
bp = find_breakpoint(method, location);;
assert(bp->method == method);
assert(bp->location == location);
VMLocalBreak local;
local.priority = priority;
while( bp )
{
assert(bp->method == method);
assert(bp->location == location);
// copy breakpoint to local thread variable
local.bp = bp;
local.bp_next = find_next_breakpoint(bp, method, location);
VMBreakInterface *intf = get_first_intf(priority);
while( intf )
{
VMBreakPointRef* ref = intf->find_reference(bp);
assert(!ref || ref->bp->method == method);
assert(!ref || ref->bp->location == location);
if (ref && intf->breakpoint_event_callback != NULL)
{
local.intf = intf->m_next;
VMBreakPoint local_bp = *bp;
POINTER_SIZE_INT data = ref->data;
TRACE2("jvmti.break.intf",
"Calling interpreter breakpoint callback function: "
<< method->get_class()->get_name()->bytes << "."
<< method->get_name()->bytes
<< method->get_descriptor()->bytes
<< " :" << location );
set_thread_local_break(&local);
unlock();
// call event breakpoint callback
intf->breakpoint_event_callback(intf->get_env(), &local_bp, data);
lock();
remove_thread_local_break(&local);
TRACE2("jvmti.break",
"Finished interpreter breakpoint callback function: "
<< method->get_class()->get_name()->bytes << "."
<< method->get_name()->bytes
<< method->get_descriptor()->bytes
<< " :" << location );
if( !local.bp ) {
// breakpoint was removed, no need report it anymore
break;
}
intf = local.intf;
} else {
intf = intf->m_next;
}
}
bp = local.bp_next;
if( bp && !(bp->method == method && bp->location == location) ) {
bp = find_next_breakpoint(bp, method, location);
}
}
}
unlock();
return orig_byte;
}
//////////////////////////////////////////////////////////////////////////////
// VMBreakInterface implementation
static bool insert_native_breakpoint(VMBreakPoint *bp)
{
return VM_Global_State::loader_env->
TI->vm_brpt->insert_native_breakpoint(bp);
}
static bool insert_interpreter_breakpoint(VMBreakPoint *bp)
{
return VM_Global_State::loader_env->
TI->vm_brpt->insert_interpreter_breakpoint(bp);
}
static bool remove_native_breakpoint(VMBreakPoint *bp)
{
return VM_Global_State::loader_env->
TI->vm_brpt->remove_native_breakpoint(bp);
}
static bool remove_interpreter_breakpoint(VMBreakPoint *bp)
{
return VM_Global_State::loader_env->
TI->vm_brpt->remove_interpreter_breakpoint(bp);
}
VMBreakInterface::VMBreakInterface(TIEnv *env,
BPInterfaceCallBack callback,
unsigned priority,
bool is_interp)
: m_next(NULL), breakpoint_event_callback(callback), m_list(NULL),
m_env(env), m_priority(priority)
{
if(is_interp) {
breakpoint_insert = &insert_interpreter_breakpoint;
breakpoint_remove = &remove_interpreter_breakpoint;
} else {
breakpoint_insert = &insert_native_breakpoint;
breakpoint_remove = &remove_native_breakpoint;
}
}
inline VMBreakPointRef*
VMBreakInterface::add_reference_internal(VMBreakPoint *bp, POINTER_SIZE_INT data)
{
VMBreakPointRef* bp_ref =
(VMBreakPointRef*)STD_MALLOC(sizeof(VMBreakPointRef));
assert(bp_ref);
bp_ref->bp = bp;
bp_ref->data = data;
bp_ref->next = m_list;
m_list = bp_ref;
TRACE2("jvmti.break.intf", "Added ref on breakpoint: "
<< (bp->method
? bp->method->get_class()->get_name()->bytes : "(nil)" )
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)")
<< (bp->method ? bp->method->get_descriptor()->bytes : "")
<< " :" << bp->location << " :" << bp->addr << ", data: " << data);
return bp_ref;
}
VMBreakPointRef*
VMBreakInterface::add_reference(jmethodID method, jlocation location, POINTER_SIZE_INT data)
{
assert(method);
VMBreakPoints* vm_brpt = VM_Global_State::loader_env->TI->vm_brpt;
LMAutoUnlock lock(vm_brpt->get_lock());
// find existing reference
VMBreakPointRef *ref = find_reference(method, location);
if( ref && ref->data == data ) {
return ref;
}
VMBreakPoint* brpt = vm_brpt->find_breakpoint(method, location);
if (!brpt)
{
brpt = (VMBreakPoint*)STD_MALLOC(sizeof(VMBreakPoint));
assert(brpt);
brpt->addr = NULL;
brpt->method = method;
brpt->location = location;
brpt->saved_byte = 0;
brpt->disasm = NULL;
// Insert breakpoint, possibly to the same native address
if (!breakpoint_insert(brpt))
{
STD_FREE(brpt);
return false;
}
}
return add_reference_internal( brpt, data );
}
VMBreakPointRef*
VMBreakInterface::add_reference(jmethodID method, jlocation location,
NativeCodePtr addr, POINTER_SIZE_INT data)
{
assert(method);
VMBreakPoints* vm_brpt = VM_Global_State::loader_env->TI->vm_brpt;
LMAutoUnlock lock(vm_brpt->get_lock());
// find existing reference
VMBreakPointRef *ref = find_reference(method, location);
if( ref && (!addr || addr == ref->bp->addr) && data == ref->data ) {
return ref;
}
VMBreakPoint* brpt = vm_brpt->find_breakpoint(method, location);
// If breakpoint with the same method location is not found or
// given native address is differ with obtained breakpoint.
// The last case could be if the same method location points
// to different native address.
if ( !brpt || brpt->addr != addr )
{
brpt = (VMBreakPoint*)STD_MALLOC(sizeof(VMBreakPoint));
assert(brpt);
brpt->addr = addr;
brpt->method = method;
brpt->location = location;
brpt->saved_byte = 0;
brpt->disasm = NULL;
if (!breakpoint_insert(brpt))
{
STD_FREE(brpt);
return false;
}
}
return add_reference_internal( brpt, data );
}
VMBreakPointRef*
VMBreakInterface::add_reference(NativeCodePtr addr, POINTER_SIZE_INT data)
{
assert(addr);
VMBreakPoints* vm_brpt = VM_Global_State::loader_env->TI->vm_brpt;
LMAutoUnlock lock(vm_brpt->get_lock());
// find existing reference
VMBreakPointRef *ref = find_reference(addr);
if( ref && ref->data == data ) {
return ref;
}
VMBreakPoint* brpt = vm_brpt->find_breakpoint(addr);
if (!brpt)
{
brpt = (VMBreakPoint*)STD_MALLOC(sizeof(VMBreakPoint));
assert(brpt);
brpt->addr = addr;
brpt->method = NULL;
brpt->location = 0;
brpt->saved_byte = 0;
brpt->disasm = NULL;
// Insert breakpoint, possibly duplicating breakpoint with method != NULL
if (!breakpoint_insert(brpt))
{
STD_FREE(brpt);
return false;
}
}
return add_reference_internal( brpt, data );
}
bool
VMBreakInterface::remove_reference(VMBreakPointRef* bp_ref)
{
assert(bp_ref);
VMBreakPoints* vm_brpt = VM_Global_State::loader_env->TI->vm_brpt;
LMAutoUnlock lock(vm_brpt->get_lock());
TRACE2("jvmti.break.intf", "Remove reference on breakpoint: "
<< (bp_ref->bp->method
? bp_ref->bp->method->get_class()->get_name()->bytes : "(nil)")
<< "."
<< (bp_ref->bp->method ? bp_ref->bp->method->get_name()->bytes : "(nil)")
<< (bp_ref->bp->method ? bp_ref->bp->method->get_descriptor()->bytes : "")
<< " :" << bp_ref->bp->location << " :" << bp_ref->bp->addr
<< ", data: " << bp_ref->data );
VMBreakPointRef* found = NULL;
for (VMBreakPointRef** cur_ptr = &m_list;
*cur_ptr; cur_ptr = &(*cur_ptr)->next)
{
if (*cur_ptr == bp_ref)
{
found = *cur_ptr;
*cur_ptr = (*cur_ptr)->next;
break;
}
}
assert(found);
VMBreakPoint* brpt = found->bp;
assert(brpt);
STD_FREE(found);
if (vm_brpt->find_other_reference(this, brpt))
return true; // There are some other references to the same breakpoint
if (!breakpoint_remove(brpt))
return false;
STD_FREE(brpt);
return true;
}
VMBreakPointRef*
VMBreakInterface::find_reference(jmethodID method, jlocation location)
{
assert(method);
for (VMBreakPointRef* ref = m_list; ref; ref = ref->next)
{
if (ref->bp->method &&
ref->bp->method == method &&
ref->bp->location == location)
{
return ref;
}
}
return NULL;
}
VMBreakPointRef*
VMBreakInterface::find_reference(NativeCodePtr addr)
{
assert(addr);
for (VMBreakPointRef* ref = m_list; ref; ref = ref->next)
{
if (ref->bp->addr == addr)
{
return ref;
}
}
return NULL;
}
VMBreakPointRef*
VMBreakInterface::find_reference(VMBreakPoint* brpt)
{
assert(brpt);
for (VMBreakPointRef* ref = m_list; ref; ref = ref->next)
{
if (ref->bp == brpt)
{
return ref;
}
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////////
// Helper functions
#if (defined _IA32_) || (defined _EM64T_)
static inline ConditionCode
get_condition_code(InstructionDisassembler::CondJumpType jump_type)
{
// Right now InstructionDisassembler::CondJumpType enum values are
// equal to enums in ia32/em64t encoder, so this statement is ok
return (ConditionCode)jump_type;
}
#endif
static bool set_jit_mode_breakpoint(VMBreakPoint* bp)
{
assert(bp);
// Find native location in the method code
Method *m = (Method *)bp->method;
assert(m);
assert( m->get_state() == Method::ST_Compiled );
NativeCodePtr np = bp->addr;
if (!np)
{
OpenExeJpdaError res = EXE_ERROR_NONE;
for (CodeChunkInfo* cci = m->get_first_JIT_specific_info();
cci; cci = cci->_next)
{
JIT *jit = cci->get_jit();
res = jit->get_native_location_for_bc(m, (uint16)bp->location, &np);
if (res == EXE_ERROR_NONE)
break;
}
if (NULL == np)
return false;
bp->addr = np;
}
TRACE2("jvmti.break.intf", "Set JIT breakpoint: "
<< bp->method->get_class()->get_name()->bytes << "."
<< bp->method->get_name()->bytes
<< bp->method->get_descriptor()->bytes
<< " :" << bp->location << " :" << bp->addr);
return set_native_breakpoint(bp);
}
static bool set_native_breakpoint(VMBreakPoint* bp)
{
#if (defined _IA32_) || (defined _EM64T_)
assert(bp);
assert(bp->addr);
TRACE2("jvmti.break.intf", "Instrumenting native: "
<< (bp->method ? bp->method->get_class()->get_name()->bytes : "(nil)" )
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "(nil)" )
<< (bp->method ? bp->method->get_descriptor()->bytes : "" )
<< " :" << bp->location << " :" << bp->addr);
VMBreakPoints* vm_brpt = VM_Global_State::loader_env->TI->vm_brpt;
// Look for breakpoint with identical addr
VMBreakPoint* other_bp = vm_brpt->find_other_breakpoint_with_same_addr(bp);
if (other_bp)
{
assert(other_bp->disasm);
bp->disasm = new InstructionDisassembler(*other_bp->disasm);
assert(bp->disasm);
bp->saved_byte = other_bp->saved_byte;
}
else
{
bp->disasm = new InstructionDisassembler(bp->addr);
assert(bp->disasm);
// code instrumentation
if (port_set_breakpoint(bp->addr, (unsigned char*)&bp->saved_byte) != 0)
return false;
}
return true;
#else
return false;
#endif
}
static bool clear_native_breakpoint(VMBreakPoint* bp)
{
assert(bp);
assert(bp->addr);
VMBreakPoints* vm_brpt = VM_Global_State::loader_env->TI->vm_brpt;
// Looking for another breakpoint with the same address,
// current breakpoint is already removed from breakpoint list.
if (!vm_brpt->find_breakpoint(bp->addr))
{
TRACE2("jvmti.break.intf", "Deinstrumentation native: "
<< (bp->method ? bp->method->get_class()->get_name()->bytes : "" )
<< "."
<< (bp->method ? bp->method->get_name()->bytes : "" )
<< (bp->method ? bp->method->get_descriptor()->bytes : "" )
<< " :" << bp->location << " :" << bp->addr);
if (port_clear_breakpoint(bp->addr, bp->saved_byte) != 0)
return false;
}
delete bp->disasm;
return true;
}
//////////////////////////////////////////////////////////////////////////////
// Native breakpoints
//////////////////////////////////////////////////////////////////////////////
bool jvmti_jit_breakpoint_handler(Registers *regs)
{
NativeCodePtr native_location = (NativeCodePtr)regs->get_ip();
TRACE2("jvmti.break", "BREAKPOINT occured: " << native_location);
DebugUtilsTI *ti = VM_Global_State::loader_env->TI;
if (ti->getPhase() != JVMTI_PHASE_LIVE)
return false;
// Now it is necessary to set up a transition to
// process_native_breakpoint_event from the exception/signal handler
VM_thread *vm_thread = p_TLS_vmthread;
#if 0
if (!vm_thread) // FIXME ?????
{
VMBreakPoints* vm_brpt = ti->vm_brpt;
vm_brpt->lock();
VMBreakPoint* bp;
for (unsigned priority = 0; priority < PRIORITY_NUMBER; priority++)
{
bp = vm_brpt->find_breakpoint(native_location);
assert(!bp || bp->addr == native_location);
while (bp)
{
VMBreakPoint* next_bp =
vm_brpt->find_next_breakpoint(bp, native_location);
for (VMBreakInterface* intf = vm_brpt->get_first_intf(priority);
intf; intf = vm_brpt->get_next_intf(intf))
{
VMBreakPointRef* ref = intf->find_reference(bp);
if (ref)
intf->remove_reference(ref);
}
bp = next_bp;
}
}
vm_brpt->unlock();
}
#endif
ti->vm_brpt->process_native_breakpoint(regs);
return true;
}
// Called when method compilation has completed
void jvmti_set_pending_breakpoints(Method *method)
{
DebugUtilsTI *ti = VM_Global_State::loader_env->TI;
if (!ti->isEnabled())
return;
VMBreakPoints* vm_brpt = ti->vm_brpt;
LMAutoUnlock lock(vm_brpt->get_lock());
if( !method->get_pending_breakpoints() )
return;
VMBreakPoint* bp = vm_brpt->find_method_breakpoint((jmethodID)method);
assert(bp);
jlocation *locations = (jlocation *)STD_MALLOC(sizeof(jlocation) *
method->get_pending_breakpoints());
assert(locations);
U_32 location_count = 0;
do
{
// It is necessary to set breakpoints only once for each
// location, so we need to filter out duplicate breakpoints
for (U_32 iii = 0; iii < location_count; iii++)
if (bp->location == locations[iii])
continue;
set_jit_mode_breakpoint(bp);
locations[location_count++] = bp->location;
method->remove_pending_breakpoint();
bp = vm_brpt->find_next_method_breakpoint(bp, (jmethodID)method);
}
while (NULL != bp);
assert(method->get_pending_breakpoints() == 0);
STD_FREE(locations);
return;
}
//////////////////////////////////////////////////////////////////////////////
// Interpreter breakpoints
//////////////////////////////////////////////////////////////////////////////
VMEXPORT jbyte
jvmti_process_interpreter_breakpoint_event(jmethodID method, jlocation location)
{
DebugUtilsTI *ti = VM_Global_State::loader_env->TI;
if (!ti->isEnabled())
return false;
return ti->vm_brpt->process_interpreter_breakpoint(method, location);
}