/******************************************************************************* * Copyright (c) 2000, 2011 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * QNX Software Systems - Initial API and implementation * Giuseppe Montalto, STMicroelectronics - bug 174988 * Alena Laskavaia (QNX) - Bug 221224 *******************************************************************************/ package org.eclipse.cdt.debug.mi.core.cdi; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.cdt.debug.core.cdi.CDIException; import org.eclipse.cdt.debug.core.cdi.model.ICDIRegisterDescriptor; import org.eclipse.cdt.debug.core.cdi.model.ICDIRegisterGroup; import org.eclipse.cdt.debug.core.cdi.model.type.ICDIType; import org.eclipse.cdt.debug.mi.core.MIException; import org.eclipse.cdt.debug.mi.core.MISession; import org.eclipse.cdt.debug.mi.core.cdi.model.Register; import org.eclipse.cdt.debug.mi.core.cdi.model.RegisterDescriptor; import org.eclipse.cdt.debug.mi.core.cdi.model.RegisterGroup; import org.eclipse.cdt.debug.mi.core.cdi.model.StackFrame; import org.eclipse.cdt.debug.mi.core.cdi.model.Target; import org.eclipse.cdt.debug.mi.core.cdi.model.Thread; import org.eclipse.cdt.debug.mi.core.cdi.model.Variable; import org.eclipse.cdt.debug.mi.core.cdi.model.VariableDescriptor; import org.eclipse.cdt.debug.mi.core.command.CommandFactory; import org.eclipse.cdt.debug.mi.core.command.MIDataListChangedRegisters; import org.eclipse.cdt.debug.mi.core.command.MIDataListRegisterNames; import org.eclipse.cdt.debug.mi.core.command.MIVarCreate; import org.eclipse.cdt.debug.mi.core.command.MIVarDelete; import org.eclipse.cdt.debug.mi.core.command.MIVarUpdate; import org.eclipse.cdt.debug.mi.core.event.MIEvent; import org.eclipse.cdt.debug.mi.core.event.MIRegisterChangedEvent; import org.eclipse.cdt.debug.mi.core.event.MIVarChangedEvent; import org.eclipse.cdt.debug.mi.core.output.MIDataListChangedRegistersInfo; import org.eclipse.cdt.debug.mi.core.output.MIDataListRegisterNamesInfo; import org.eclipse.cdt.debug.mi.core.output.MIVar; import org.eclipse.cdt.debug.mi.core.output.MIVarChange; import org.eclipse.cdt.debug.mi.core.output.MIVarCreateInfo; import org.eclipse.cdt.debug.mi.core.output.MIVarUpdateInfo; /** */ public class RegisterManager extends Manager { final int MAX_ENTRIES = 150; /** * * LRUMap.<br> * Simple LRU cache using a LinkedHashMap */ class LRUMap extends LinkedHashMap { private static final long serialVersionUID = 1L; LRUMap() { super(MAX_ENTRIES+1, .75F, true); } /* (non-Javadoc) * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry) */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { boolean toRemove = size() > MAX_ENTRIES; if (toRemove) { ShadowRegister v = (ShadowRegister)eldest.getValue(); try { Target target = (Target)v.getTarget(); removeMIVar(target.getMISession(), v.getMIVar()); } catch (Exception e) { // ignore all } v.setMIVar(null); } return toRemove; } } /** * * ShadowRegister.<br> * To keep track of the register value we can a shadow variable. If the * the variable MIVar was destroy by the LRUCache we try to recreate it. */ class ShadowRegister extends Register { public ShadowRegister(Register reg, StackFrame frame, String n, MIVar v) { super((Target)reg.getTarget(), (Thread)frame.getThread(), frame, n, null, 0, 0, v); try { fTypename = reg.getTypeName(); } catch (CDIException e) { // ignore } try { fType = reg.getType(); } catch (CDIException e) { // ignore } } /* (non-Javadoc) * @see org.eclipse.cdt.debug.mi.core.cdi.model.Variable#getMIVar() */ @Override public MIVar getMIVar() throws CDIException { if (fMIVar == null) { fMIVar = createMiVar((StackFrame)getStackFrame(), getName()); } return fMIVar; } public void setMIVar(MIVar newMIVar) { fMIVar = newMIVar; } } Map regsMap; Map varsMap; MIVarChange[] noChanges = new MIVarChange[0]; HashMap fTypeCache; public RegisterManager(Session session) { super(session, true); regsMap = new Hashtable(); varsMap = new Hashtable(); // The register bookkeeping provides better update control. setAutoUpdate( true ); fTypeCache = new HashMap(); } public ICDIType getFromTypeCache(String typeName) { return (ICDIType)fTypeCache.get(typeName); } public void addToTypeCache(String typeName, ICDIType type) { fTypeCache.put(typeName, type); } synchronized List getRegistersList(Target target) { List regsList = (List)regsMap.get(target); if (regsList == null) { regsList = Collections.synchronizedList(new ArrayList()); regsMap.put(target, regsList); } return regsList; } synchronized Map getVariableMap(Target target) { Map varMap = (Map)varsMap.get(target); if (varMap == null) { varMap = Collections.synchronizedMap(new LRUMap()); varsMap.put(target, varMap); } return varMap; } public ICDIRegisterGroup[] getRegisterGroups(Target target) throws CDIException { RegisterGroup group = new RegisterGroup(target, "Main"); //$NON-NLS-1$ return new ICDIRegisterGroup[] { group }; } public ICDIRegisterDescriptor[] getRegisterDescriptors(RegisterGroup group) throws CDIException { Target target = (Target)group.getTarget(); return getRegisterDescriptors(target); } public ICDIRegisterDescriptor[] getRegisterDescriptors(Target target) throws CDIException { MISession mi = target.getMISession(); CommandFactory factory = mi.getCommandFactory(); MIDataListRegisterNames registers = factory.createMIDataListRegisterNames(); try { mi.postCommand(registers); MIDataListRegisterNamesInfo info = registers.getMIDataListRegisterNamesInfo(); if (info == null) { throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ } String[] names = info.getRegisterNames(); List regsList = new ArrayList(names.length); for (int i = 0; i < names.length; i++) { if (names[i].length() > 0) { regsList.add(new RegisterDescriptor(target, null, null, names[i], null, i, 0)); } } return (ICDIRegisterDescriptor[])regsList.toArray(new ICDIRegisterDescriptor[0]); } catch (MIException e) { throw new MI2CDIException(e); } finally { } } public Register createRegister(RegisterDescriptor regDesc) throws CDIException { Register reg = findRegister(regDesc); if (reg == null) { try { String name = regDesc.getQualifiedName(); Target target = (Target)regDesc.getTarget(); MISession mi = target.getMISession(); CommandFactory factory = mi.getCommandFactory(); MIVarCreate var = factory.createMIVarCreate(name); mi.postCommand(var, -1); // mi.postCommand(var); // MIVarCreateInfo info = var.getMIVarCreateInfo(); // if (info == null) { // throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ // } // reg = new Register(regDesc, info.getMIVar()); reg = new Register(regDesc, var); List regList = getRegistersList(target); regList.add(reg); } catch (MIException e) { throw new MI2CDIException(e); } } return reg; } public void destroyRegister(Register reg) { Target target = (Target)reg.getTarget(); List regList = (List)regsMap.get(target); if (regList != null) { if (regList.remove(reg)) { MISession miSession = target.getMISession(); try { removeMIVar(miSession, reg.getMIVar()); } catch (CDIException e) { // } } } } /** * Tell gdb to remove the underlying var-object also. */ void removeMIVar(MISession miSession, MIVar miVar) throws CDIException { CommandFactory factory = miSession.getCommandFactory(); MIVarDelete var = factory.createMIVarDelete(miVar.getVarName()); try { miSession.postCommand(var); var.getMIInfo(); } catch (MIException e) { throw new MI2CDIException(e); } } public MIVar createMiVar(StackFrame frame, String regName) throws CDIException { Target target = (Target)frame.getTarget(); Thread currentThread = (Thread)target.getCurrentThread(); StackFrame currentFrame = currentThread.getCurrentStackFrame(); synchronized(target.getLock()) { try { target.setCurrentThread(frame.getThread(), false); ((Thread)frame.getThread()).setCurrentStackFrame(frame, false); MISession mi = target.getMISession(); CommandFactory factory = mi.getCommandFactory(); MIVarCreate var = factory.createMIVarCreate(regName); mi.postCommand(var); MIVarCreateInfo info = var.getMIVarCreateInfo(); if (info == null) { throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ } return info.getMIVar(); } catch (MIException e) { throw new MI2CDIException(e); } finally { target.setCurrentThread(currentThread, false); currentThread.setCurrentStackFrame(currentFrame, false); } } } public MIVarChange[] updateMiVar(StackFrame frame, String varName) throws CDIException { Target target = (Target)frame.getTarget(); Thread currentThread = (Thread)target.getCurrentThread(); StackFrame currentFrame = currentThread.getCurrentStackFrame(); synchronized(target.getLock()) { try { target.setCurrentThread(frame.getThread(), false); ((Thread)frame.getThread()).setCurrentStackFrame(frame, false); MISession mi = target.getMISession(); CommandFactory factory = mi.getCommandFactory(); MIVarUpdate var = factory.createMIVarUpdate(varName); mi.postCommand(var); MIVarUpdateInfo info = var.getMIVarUpdateInfo(); if (info == null) { throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ } return info.getMIVarChanges(); } catch (MIException e) { throw new MI2CDIException(e); } finally { target.setCurrentThread(currentThread, false); currentThread.setCurrentStackFrame(currentFrame, false); } } } public Variable createShadowRegister(Register register, StackFrame frame, String regName) throws CDIException { Target target = (Target)frame.getTarget(); Map varMap = getVariableMap(target); ShadowRegister variable = null; if(varMap.containsKey(regName)) { variable = (ShadowRegister)varMap.get(regName); updateMiVar(frame,variable.getMIVar().getVarName()); } else { MIVar miVar = createMiVar(frame, regName); variable = new ShadowRegister(register, frame, regName, miVar); /* varMap seems to be unused, so I changed it... * now it helds the register name, instead of the variable name; * it now can be used to retrieve existing variables, thus avoiding * variable prolification and also reducing the number of mi commands * to be sent to the target. */ varMap.put(regName, variable); } return variable; } /** * Use by the eventManager to find the Register; */ public Register getRegister(MISession miSession, String varName) { Target target = ((Session)getSession()).getTarget(miSession); Register[] regs = getRegisters(target); for (int i = 0; i < regs.length; i++) { try { if (regs[i].getMIVar().getVarName().equals(varName)) { return regs[i]; } try { Register r = (Register)regs[i].getChild(varName); if (r != null) { return r; } } catch (ClassCastException e) { // ignore ??? } } catch (CDIException e1) { // ignore; } } return null; } /** * Use by the eventManager to find the Register; */ public Register getRegister(MISession miSession, int regno) { Target target = ((Session)getSession()).getTarget(miSession); return getRegister(target, regno); } public Register getRegister(Target target, int regno) { Register[] regs = getRegisters(target); for (int i = 0; i < regs.length; i++) { if (regs[i].getPosition() == regno) { return regs[i]; } } return null; } @Override public void update(Target target) throws CDIException { MISession mi = target.getMISession(); CommandFactory factory = mi.getCommandFactory(); MIDataListChangedRegisters changed = factory.createMIDataListChangedRegisters(); try { mi.postCommand(changed); MIDataListChangedRegistersInfo info = changed.getMIDataListChangedRegistersInfo(); if (info == null) { throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ } int[] regnos = info.getRegisterNumbers(); List eventList = new ArrayList(regnos.length); // Now that we know the registers changed // call -var-update to update the value in gdb. // And send the notification. for (int i = 0 ; i < regnos.length; i++) { Register reg = getRegister(target, regnos[i]); if (reg != null) { String varName = reg.getMIVar().getVarName(); MIVarChange[] changes = noChanges; MIVarUpdate update = factory.createMIVarUpdate(varName); try { mi.postCommand(update); MIVarUpdateInfo updateInfo = update.getMIVarUpdateInfo(); if (updateInfo == null) { throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$ } changes = updateInfo.getMIVarChanges(); } catch (MIException e) { //throw new MI2CDIException(e); //eventList.add(new MIVarDeletedEvent(varName)); } if (changes.length != 0) { for (int j = 0 ; j < changes.length; j++) { String n = changes[j].getVarName(); if (changes[j].isInScope()) { eventList.add(new MIVarChangedEvent(mi, n)); } } } else { // Fall back to the register number. eventList.add(new MIRegisterChangedEvent(mi, update.getToken(), reg.getName(), regnos[i])); } } } MIEvent[] events = (MIEvent[])eventList.toArray(new MIEvent[0]); mi.fireEvents(events); } catch (MIException e) { if (!target.isTerminated()) throw new MI2CDIException(e); } } private Register[] getRegisters(Target target) { List regsList = (List)regsMap.get(target); if (regsList != null) { return (Register[]) regsList.toArray(new Register[regsList.size()]); } return new Register[0]; } // private Variable[] getVariables(Target target) { // List varList = (List)varsMap.get(target); // if (varList != null) { // return (Variable[]) varList.toArray(new Variable[varList.size()]); // } // return new Register[0]; // } /** * Return the Element with this thread/stackframe, and with this name. * null is return if the element is not in the cache. */ private Register findRegister(RegisterDescriptor rd) throws CDIException { Target target = (Target)rd.getTarget(); String name = rd.getName(); String fullName = rd.getFullName(); int position = rd.getPosition(); Register[] regs = getRegisters(target); for (int i = 0; i < regs.length; i++) { if (regs[i].getName().equals(name) && regs[i].getFullName().equals(fullName) && regs[i].getCastingArrayStart() == rd.getCastingArrayStart() && regs[i].getCastingArrayEnd() == rd.getCastingArrayEnd() && VariableDescriptor.equalsCasting(regs[i], rd)) { // check threads if (regs[i].getPosition() == position) { return regs[i]; } } } return null; } }