/*******************************************************************************
* Copyright (c) 2014 Ericsson AB 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:
* Alvaro Sanchez-Leon (Ericsson) - First Implementation and API (Bug 235747)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.cdt.debug.core.model.IRegisterDescriptor;
import org.eclipse.cdt.debug.internal.core.RegisterGroupsPersistance;
import org.eclipse.cdt.debug.internal.core.model.IRegisterGroupDescriptor;
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.CompositeDMContext;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters;
import org.eclipse.cdt.dsf.debug.service.IRegisters2;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.mi.service.MIRegisters;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.osgi.util.NLS;
/**
* <p>An extension of MIRegisters to support management of Register Groups as per the IRegisters2 interface.</p>
* <p>The managed registered groups are user-defined subsets of the complete list of Registers reported by GDB for a specific Target</p>
* <p>This class also triggers the read/write (persistence) of the user-defined Register Groups during the start/shutdown process of a session respectively</p>
* @since 4.6
*/
public class GDBRegisters extends MIRegisters implements IRegisters2 {
/**
* Unique temporary id for a group. 0 is reserved for the root group
*/
private static int fGroupBookingCount = 1;
/**
* References to all groups related to a given context. Different programs may use different sets of registers e.g.
* 32/64 bits
*/
private final ContextToGroupsMap<IContainerDMContext, MIRegisterGroupDMC[]> fContextToGroupsMap = new ContextToGroupsMap<IContainerDMContext, MIRegisterGroupDMC[]>();
/**
* Used to save base list of Registers associated to a group, these registers can not be used as is for
* "getRegisters" since the execution context may change e.g. The current selection points to a process or a running
* thread or a different frame, all information besides the execution context is valid.
*/
private final GroupRegistersMap<MIRegisterGroupDMC, MIRegisterDMC[]> fGroupToRegistersMap = new GroupRegistersMap<MIRegisterGroupDMC, MIRegisterDMC[]>();
/**
* Saves the Group number to RegisterGroupDescriptor created from the serialized memento, The group number is used across contexts as the
* key:Integer uses a booking number incremented across container contexts
*/
private final Map<Integer, IRegisterGroupDescriptor> fGroupMementoDescriptorIndex = new HashMap<Integer, IRegisterGroupDescriptor>();
public GDBRegisters(DsfSession session) {
super(session);
}
private class ContextToGroupsMap<K, V> extends HashMap<IContainerDMContext, MIRegisterGroupDMC[]> {
private static final long serialVersionUID = 1L;
private final Map<IContainerDMContext, Map<String, MIRegisterGroupDMC>> fNameToGroupMap = new HashMap<IContainerDMContext, Map<String, MIRegisterGroupDMC>>();
@Override
public MIRegisterGroupDMC[] put(IContainerDMContext key, MIRegisterGroupDMC[] value) {
if (key == null || value == null) {
return null;
}
// Contents are updated for the given context, reset this context
// cache
// So it can be rebuilt on the next get
fNameToGroupMap.remove(key);
return super.put(key, value);
}
@Override
public void clear() {
fNameToGroupMap.clear();
fGroupMementoDescriptorIndex.clear();
fGroupToRegistersMap.clear();
super.clear();
}
@Override
public MIRegisterGroupDMC[] remove(Object key) {
fNameToGroupMap.remove(key);
return super.remove(key);
}
public Map<String, MIRegisterGroupDMC> getGroupNameMap(IContainerDMContext key) {
// validate input
if (key == null) {
return null;
}
Map<String, MIRegisterGroupDMC> nameMap = fNameToGroupMap.get(key);
if (nameMap == null) {
// cache not resolved, rebuild
nameMap = new HashMap<String, MIRegisterGroupDMC>();
MIRegisterGroupDMC[] groupsArr = super.get(key);
// If the container context exist, build the name map
if (groupsArr != null) {
for (MIRegisterGroupDMC group : groupsArr) {
nameMap.put(group.getName(), group);
}
// cache it !
fNameToGroupMap.put(key, nameMap);
}
}
return nameMap;
}
/**
* Needed when group name(s) change but the associated group objects remain the same
*/
public void resetGroupNameMap(IContainerDMContext key) {
fNameToGroupMap.remove(key);
}
/**
* The result will reflect the reverse order of creation, i.e. last created first
*/
public MIRegisterGroupDMC[] getReversed(IDMContext key) {
MIRegisterGroupDMC[] groups = get(key);
MIRegisterGroupDMC[] reversedGroups = new MIRegisterGroupDMC[groups.length];
int size = groups.length;
for (int i = 0; i < size; i++) {
reversedGroups[size - 1 - i] = groups[i];
}
return reversedGroups;
}
}
/**
* Used to associate two dependent maps, Group to ordered Register[] and Group to indexed registers (Map<String,
* Register>)
*/
private class GroupRegistersMap<K, V> extends HashMap<MIRegisterGroupDMC, MIRegisterDMC[]> {
private static final long serialVersionUID = 1L;
private final Map<MIRegisterGroupDMC, Map<String, MIRegisterDMC>> fNameToRegisterMap = new HashMap<MIRegisterGroupDMC, Map<String, MIRegisterDMC>>();
@Override
public MIRegisterDMC[] put(MIRegisterGroupDMC key, MIRegisterDMC[] value) {
// Make sure a previous entry of the key does not keep an out of
// date cache
fNameToRegisterMap.remove(key);
return super.put(key, value);
}
public Map<String, MIRegisterDMC> getIndexedRegisters(MIRegisterGroupDMC key) {
Map<String, MIRegisterDMC> nameToRegisterMap = fNameToRegisterMap.get(key);
if (nameToRegisterMap == null && get(key) != null) {
// Needs indexing
nameToRegisterMap = indexRegisters(key);
if (nameToRegisterMap != null) {
fNameToRegisterMap.put(key, nameToRegisterMap);
}
}
return nameToRegisterMap;
}
@Override
public void clear() {
fNameToRegisterMap.clear();
super.clear();
}
@Override
public MIRegisterDMC[] remove(Object key) {
fNameToRegisterMap.remove(key);
return super.remove(key);
}
private Map<String, MIRegisterDMC> indexRegisters(MIRegisterGroupDMC registerGroup) {
MIRegisterDMC[] registers = super.get(registerGroup);
if (registers == null || registers.length < 1) {
return null;
}
Map<String, MIRegisterDMC> registerNameMap = new HashMap<String, MIRegisterDMC>();
for (IRegisterDMContext register : registers) {
assert(register instanceof MIRegisterDMC);
MIRegisterDMC registerDmc = (MIRegisterDMC) register;
registerNameMap.put(registerDmc.getName(), registerDmc);
}
return registerNameMap;
}
}
private class RegisterGroupDescriptor implements IRegisterGroupDescriptor {
private final boolean fEnabled;
private final MIRegisterGroupDMC fgroup;
public RegisterGroupDescriptor(MIRegisterGroupDMC group, boolean enabled) {
fgroup = group;
fEnabled = enabled;
}
@Override
public String getName() {
return fgroup.getName();
}
@Override
public boolean isEnabled() {
return fEnabled;
}
@Override
public IRegisterDescriptor[] getChildren() throws CoreException {
IRegisterDescriptor[] regDescriptors = null;
// Get a snap shot of the current registers
MIRegisterDMC[] registers = fGroupToRegistersMap.get(fgroup);
if (registers != null && registers.length > 0) {
regDescriptors = new IRegisterDescriptor[registers.length];
for (int i = 0; i < registers.length; i++) {
regDescriptors[i] = new RegisterDescriptor(registers[i]);
}
} else {
// The registers were probably never fetched, obtain the
// original definitions from deserialized groups
IRegisterGroupDescriptor groupMementoDescriptor = fGroupMementoDescriptorIndex.get(fgroup.getGroupNo());
if (groupMementoDescriptor != null) {
regDescriptors = groupMementoDescriptor.getChildren();
}
}
return regDescriptors;
}
}
private class RegisterDescriptor implements IRegisterDescriptor {
private final MIRegisterDMC fRegister;
private final static String ORIGINAL_GROUP_NAME = "Main"; //$NON-NLS-1$
public RegisterDescriptor(MIRegisterDMC register) {
fRegister = register;
}
@Override
public String getName() {
return fRegister.getName();
}
@Override
public String getGroupName() {
// Hard coded to keep compatibility with CDI's format
return ORIGINAL_GROUP_NAME;
}
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize(
new ImmediateRequestMonitor(requestMonitor) {
@Override
public void handleSuccess() {
doInitialize(requestMonitor);
}});
}
private void doInitialize(final RequestMonitor requestMonitor) {
register(new String[]{IRegisters.class.getName(),
IRegisters2.class.getName(),
MIRegisters.class.getName(),
GDBRegisters.class.getName()}, new Hashtable<String,String>());
requestMonitor.done();
}
@Override
public void getRegisterGroups(final IDMContext ctx, final DataRequestMonitor<IRegisterGroupDMContext[]> rm) {
final IContainerDMContext contDmc = DMContexts.getAncestorOfType(ctx, IContainerDMContext.class);
if (contDmc == null) {
IStatus status = new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Container context not provided, unable to get Register Groups", null); //$NON-NLS-1$
rm.setStatus(status);
rm.done();
return;
}
if (fContextToGroupsMap.containsKey(contDmc)) {
// The groups information is already available and can be returned
rm.setData(fContextToGroupsMap.getReversed(contDmc));
rm.done();
return;
}
// The register groups information needs to be built from GDB and user-defined groups i.e. de-serialized
// from the launch configuration.
super.getRegisterGroups(ctx, new ImmediateDataRequestMonitor<IRegisterGroupDMContext[]>(rm) {
@Override
@ConfinedToDsfExecutor("fExecutor")
protected void handleSuccess() {
final IRegisterGroupDMContext[] regGroups = getData();
// only one group from MI is expected at the moment
assert (regGroups.length == 1);
assert (regGroups[0] instanceof MIRegisterGroupDMC);
final MIRegisterGroupDMC miGroup = (MIRegisterGroupDMC) regGroups[0];
// read serialized groups
MIRegisterGroupDMC[] mementoGroups = readGroupsFromMemento(contDmc);
// Track the groups associated to this context
// The root group (mi) is placed and expected at index 0 followed
// by the user groups read from the memento
MIRegisterGroupDMC[] regGroupsCtx = concatenateArr(new MIRegisterGroupDMC[] { miGroup }, mementoGroups);
// Have the information ready for subsequent request or group operations.
fContextToGroupsMap.put(contDmc, regGroupsCtx);
// Reverse the order i.e. latest on top and get back to parent monitor
rm.setData(fContextToGroupsMap.getReversed(contDmc));
rm.done();
}
});
}
@Override
public void getRegisterGroupData(final IRegisterGroupDMContext regGroupDmc,
final DataRequestMonitor<IRegisterGroupDMData> rm) {
assert (regGroupDmc instanceof MIRegisterGroupDMC);
if (regGroupDmc instanceof MIRegisterGroupDMC) {
MIRegisterGroupDMC groupDmc = (MIRegisterGroupDMC) regGroupDmc;
rm.setData(createRegisterGroupData(groupDmc));
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
"Unable to resolve Group Data, Invalid Register Group provided", null)); //$NON-NLS-1$
}
rm.done();
}
private IRegisterGroupDMData createRegisterGroupData(final MIRegisterGroupDMC groupDmc) {
IRegisterGroupDMData groupData = new IRegisterGroupDMData() {
@Override
public String getName() {
return groupDmc.getName();
}
@Override
public String getDescription() {
if (groupDmc.getName().equals(ROOT_GROUP_NAME)) {
return ROOT_GROUP_DESCRIPTION;
}
return BLANK_STRING;
}
};
// Make sure this group is available in the groups to registers map,
// as this map provides the input to save /serialize the groups
// The associated registers will be resolved upon request.
if (fGroupToRegistersMap.get(groupDmc) == null) {
fGroupToRegistersMap.put(groupDmc, new MIRegisterDMC[0]);
}
return groupData;
}
@Override
public void getRegisters(final IDMContext aCtx, final DataRequestMonitor<IRegisterDMContext[]> rm) {
findRegisterGroup(aCtx, ROOT_GROUP_NAME, new ImmediateDataRequestMonitor<IRegisterGroupDMContext>() {
@Override
protected void handleSuccess() {
//Get the root group, needed as a possible default group and to resolve target registers
IRegisterGroupDMContext rootGroup = getData();
assert (rootGroup instanceof MIRegisterGroupDMC);
final MIRegisterGroupDMC rootGroupContext = (MIRegisterGroupDMC) rootGroup;
//if the received context does not contain a register group i.e.is null, the default group to resolve registers is the root group
MIRegisterGroupDMC tGroupDmc = DMContexts.getAncestorOfType(aCtx, MIRegisterGroupDMC.class);
IDMContext tCtx = aCtx;
if (tGroupDmc == null) {
tGroupDmc = rootGroupContext;
//We need a register group as part of the context to resolve registers
tCtx = new CompositeDMContext(new IDMContext[] {aCtx, tGroupDmc});
}
final IDMContext ctx = tCtx;
final MIRegisterGroupDMC groupDmc = tGroupDmc;
// check if base registers have been loaded already
MIRegisterDMC[] baseRegisters = fGroupToRegistersMap.get(groupDmc);
if (baseRegisters != null && baseRegisters.length > 0) {
// use baseRegisters to build registers associated to the given context
buildGroupRegisters(ctx, baseRegisters, rm);
return;
}
IContainerDMContext rootGroupContainer = DMContexts.getAncestorOfType(rootGroupContext,
IContainerDMContext.class);
MIRegisterDMC[] registerBase = fGroupToRegistersMap.get(rootGroupContainer);
if (registerBase == null || registerBase.length < 1) {
// Prepare to fetch the register information from GDB (root group)
// Include the frame/execution context whenever available
IDMContext miExecDmc = DMContexts.getAncestorOfType(ctx, IFrameDMContext.class);
if (miExecDmc == null) {
miExecDmc = DMContexts.getAncestorOfType(ctx, IMIExecutionDMContext.class);
}
// if Execution context is not available return shallow registers i.e. no execution context
final CompositeDMContext compCtx;
if (miExecDmc != null) {
compCtx = new CompositeDMContext(new IDMContext[] { rootGroupContext, miExecDmc });
} else {
compCtx = new CompositeDMContext(new IDMContext[] { rootGroupContext });
}
// Fetch the register base from GDB
GDBRegisters.super.getRegisters(compCtx, new DataRequestMonitor<IRegisterDMContext[]>(getExecutor(), rm) {
@Override
@ConfinedToDsfExecutor("fExecutor")
protected void handleSuccess() {
IRegisterDMContext[] iregisters = getData();
MIRegisterDMC[] registers = Arrays.copyOf(iregisters, iregisters.length, MIRegisterDMC[].class);
// associate group to bare registers i.e. not associated to a specific execution context
fGroupToRegistersMap.put(rootGroupContext, toBareRegisters(registers));
if (groupDmc.getName().equals(ROOT_GROUP_NAME)) {
buildGroupRegisters(ctx, registers, rm);
return;
}
// Now proceed to resolve the requested user group registers
getUserGroupRegisters(ctx, rm);
}
});
} else {
if (groupDmc.getName().equals(ROOT_GROUP_NAME)) {
buildGroupRegisters(ctx, registerBase, rm);
} else {
// resolve user group registers
getUserGroupRegisters(ctx, rm);
}
}
}
});
}
@Override
public void canAddRegisterGroup(IDMContext selectionContext, DataRequestMonitor<Boolean> rm) {
// Not relevant checks at this point
rm.setData(true);
rm.done();
}
@Override
public void addRegisterGroup(final IDMContext containerContext, final String groupName, final IRegisterDMContext[] registers, RequestMonitor rm) {
if (registers == null || registers.length < 1) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
Messages.RegisterGroup_invalid_number_of_registers, null));
rm.done();
return;
}
if (groupName.trim().toLowerCase().equals(ROOT_GROUP_NAME.toLowerCase())) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
NLS.bind(Messages.RegisterGroup_name_reserved, ROOT_GROUP_NAME), null));
rm.done();
return;
}
if (!(registers[0] instanceof MIRegisterDMC)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Unexpected IRegisterDMContext input instance type", null)); //$NON-NLS-1$
rm.done();
return;
}
IContainerDMContext contDmc = DMContexts.getAncestorOfType(registers[0], IContainerDMContext.class);
if (contDmc == null) {
contDmc = DMContexts.getAncestorOfType(containerContext, IContainerDMContext.class);
if (contDmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Unable to add Register group, Invalid Container", null)); //$NON-NLS-1$
rm.done();
return;
}
}
// must be a child of an existing container, at least the root group must be present
assert (fContextToGroupsMap.containsKey(contDmc));
// Make sure the name is not currently in use
if (fContextToGroupsMap.getGroupNameMap(contDmc).get(groupName) != null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, NLS.bind(
Messages.RegisterGroup_name_used, groupName), null));
rm.done();
return;
}
//create the new group
MIRegisterGroupDMC group = new MIRegisterGroupDMC(this, contDmc, fGroupBookingCount, groupName);
fGroupBookingCount++;
// Update the context to groups map including the new group
fContextToGroupsMap.put(contDmc,
concatenateArr(fContextToGroupsMap.get(contDmc), new MIRegisterGroupDMC[] { group }));
//type adjustment
MIRegisterDMC[] miRegisters = Arrays.copyOf(registers, registers.length, MIRegisterDMC[].class);
// associate group to bare registers i.e. not associated to a specific execution context
MIRegisterDMC[] bareRegisters = toBareRegisters(miRegisters);
fGroupToRegistersMap.put(group, bareRegisters);
// Create event notification, to trigger the UI refresh
getSession().dispatchEvent(new GroupsChangedDMEvent(contDmc), null);
rm.done();
}
@Override
public void canEditRegisterGroup(IRegisterGroupDMContext group, DataRequestMonitor<Boolean> rm) {
rm.setData(canEditRegisterGroup(group));
rm.done();
}
@Override
public void editRegisterGroup(IRegisterGroupDMContext group, String newGroupName, IRegisterDMContext[] iRegisters,
RequestMonitor rm) {
if (iRegisters != null && iRegisters.length == 0) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
Messages.RegisterGroup_invalid_number_of_registers, null));
return;
}
if (!(group instanceof MIRegisterGroupDMC)) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Unknown DMC type", null)); //$NON-NLS-1$
return;
}
IContainerDMContext contDmc = DMContexts.getAncestorOfType(group, IContainerDMContext.class);
if (contDmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Unable to edit Register group, Invalid Container", null)); //$NON-NLS-1$
rm.done();
}
MIRegisterGroupDMC miGroup = ((MIRegisterGroupDMC) group);
if (!canEditRegisterGroup(group)) {
// Should not happen as canEdit is expected to be called before edit
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot currently edit register groups", null)); //$NON-NLS-1$
return;
}
if (newGroupName != null && !newGroupName.isEmpty()) {
// Make sure the new group name is not the reserved root group name
if (newGroupName.trim().toLowerCase().equals(ROOT_GROUP_NAME.toLowerCase())) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
NLS.bind(Messages.RegisterGroup_name_reserved, ROOT_GROUP_NAME), null));
rm.done();
return;
}
// Make sure the name is not currently in use
if (!miGroup.getName().equals(newGroupName)) {
// we are updating the name, lets make sure this new name is not in use
if (fContextToGroupsMap.getGroupNameMap(contDmc).get(newGroupName) != null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
NLS.bind(Messages.RegisterGroup_name_used, newGroupName), null));
rm.done();
return;
}
}
miGroup.setName(newGroupName);
//make sure we update the group name cache
fContextToGroupsMap.resetGroupNameMap(contDmc);
generateRegisterGroupChangedEvent(miGroup);
} else {
// Request to keep name the same
}
if (iRegisters != null) {
assert (iRegisters.length > 0);
// transform to MIRegistersDMC[]
MIRegisterDMC[] registers = arrangeRegisters(iRegisters);
// preserve registers in a general format not associated to a specific frame
registers = toBareRegisters(registers);
fGroupToRegistersMap.put(miGroup, registers);
// Notify of Registers changed
generateRegistersChangedEvent(miGroup);
} else {
// Request to keep register list the same
}
rm.done();
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.dsf.debug.service.IRegisters2#removeRegisterGroups(org.eclipse.cdt.dsf.debug.service.IRegisters
* .IRegisterGroupDMContext[], org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
@Override
public void removeRegisterGroups(IRegisterGroupDMContext[] groups, RequestMonitor rm) {
removeRegisterGroups(groups, false, rm);
}
@Override
public void restoreDefaultGroups(final IDMContext selectionContext, final RequestMonitor rm) {
for (IDMContext context : fContextToGroupsMap.keySet()) {
removeRegisterGroups(context);
}
// clean the serialized registers group information
save();
// Clear all global references to the contexts and groups
fContextToGroupsMap.clear();
rm.done();
}
/**
* Reset this class i.e. does not impact saved groups within launch configuration
*
* @param rm
*/
public void reset(final RequestMonitor rm) {
for (IDMContext context : fContextToGroupsMap.keySet()) {
removeRegisterGroups(context);
}
// Clear all global references to the contexts and groups
fContextToGroupsMap.clear();
rm.done();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.cdt.dsf.mi.service.MIRegisters#findRegisterGroup(org.eclipse.cdt.dsf.datamodel.IDMContext,
* java.lang.String, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
@Override
public void findRegisterGroup(final IDMContext ctx, final String name, final DataRequestMonitor<IRegisterGroupDMContext> rm) {
final IContainerDMContext contDmc = DMContexts.getAncestorOfType(ctx, IContainerDMContext.class);
if (contDmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Container context not found", null)); //$NON-NLS-1$
rm.done();
return;
}
if (fContextToGroupsMap.get(ctx) == null) {
// Need to build the list of register groups including the one from target
getRegisterGroups(contDmc, new DataRequestMonitor<IRegisterGroupDMContext[]>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
// Using the list of groups indirectly to find the one with the given name from it
findRegisterGroup(contDmc, name, rm);
}
});
} else {
// The context to groups map has been initialized and can be used
findRegisterGroup(contDmc, name, rm);
}
}
/**
* Call it only after getRegisterGroups has been called at least once, so the context to groups map is not empty
*/
private void findRegisterGroup(IContainerDMContext contDmc, String name, DataRequestMonitor<IRegisterGroupDMContext> rm) {
Map<String, MIRegisterGroupDMC> nameToGroup = fContextToGroupsMap.getGroupNameMap(contDmc);
if (nameToGroup != null) {
rm.setData(nameToGroup.get(name));
} else {
rm.setData(null);
}
rm.done();
}
@Override
public void shutdown(RequestMonitor rm) {
save();
super.shutdown(rm);
}
public void save() {
IRegisterGroupDescriptor[] groups = buildDescriptors();
ILaunchConfiguration launchConfig = getLaunchConfig();
if (launchConfig != null) {
RegisterGroupsPersistance serializer = new RegisterGroupsPersistance(launchConfig);
try {
serializer.saveGroups(groups);
} catch (CoreException e1) {
e1.printStackTrace();
}
}
}
/**
* Cast to MI and sort them ascending order by register index
*/
private MIRegisterDMC[] arrangeRegisters(IRegisterDMContext[] iRegisters) {
TreeMap<Integer, MIRegisterDMC> sortedRegisters = new TreeMap<Integer, MIRegisterDMC>();
for (int i = 0; i < iRegisters.length; i++) {
assert(iRegisters[i] instanceof MIRegisterDMC);
MIRegisterDMC register = (MIRegisterDMC) iRegisters[i];
sortedRegisters.put(register.getRegNo(), register);
}
return sortedRegisters.values().toArray(new MIRegisterDMC[sortedRegisters.size()]);
}
/**
* @param groups
* - The groups to be removed
* @param removeRoot
* - indicates if the root group needs to be removed e.g. during restore to defaults
* @param rm
*/
private void removeRegisterGroups(IRegisterGroupDMContext[] groups, boolean removeRoot, RequestMonitor rm) {
if (groups != null) {
// Save a list of updated containers to only send an update event for each of them
final Set<IContainerDMContext> updatedContainers = new HashSet<IContainerDMContext>();
for (IRegisterGroupDMContext group : groups) {
if (!removeRoot) {
// Prevent removal of the Root Group
if (!(group instanceof MIRegisterGroupDMC)) {
// All groups are expected to be instances of MIREgisterGroupDMC
assert (false);
continue;
}
if (((MIRegisterGroupDMC) group).getName().equals(ROOT_GROUP_NAME)) {
// Skip removal of a root group, except when restoring to default groups
continue;
}
}
final IContainerDMContext containerDmc = DMContexts.getAncestorOfType(group, IContainerDMContext.class);
// All given groups are expected to be part of the same Container, however it's safer to create a new list
// per context to cover the unsual case
// This could be revisited in case there is performance concerns which does not seem an issue at this
// point.
MIRegisterGroupDMC[] groupsCtx = fContextToGroupsMap.get(containerDmc);
assert(groupsCtx != null);
if (groupsCtx != null) {
List<MIRegisterGroupDMC> groupsList = new ArrayList<MIRegisterGroupDMC>(Arrays.asList(groupsCtx));
// Removing a single group
groupsList.remove(group);
// Back to context map without the given group
fContextToGroupsMap.put(containerDmc, groupsList.toArray(new MIRegisterGroupDMC[groupsList.size()]));
// Now remove the group from the groups to registers map
if (fGroupToRegistersMap.remove(group) != null) {
updatedContainers.add(containerDmc);
}
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Unable to remove Register group, Invalid Container", null)); //$NON-NLS-1$
rm.done();
return;
}
}
// Sending only one update per container
for (IContainerDMContext container : updatedContainers) {
getSession().dispatchEvent(new GroupsChangedDMEvent(container), null);
}
}
rm.done();
}
private void removeRegisterGroups(IDMContext containerDmc) {
MIRegisterGroupDMC[] groups = fContextToGroupsMap.get(containerDmc);
if (groups != null) {
removeRegisterGroups(groups, true, new RequestMonitor(getExecutor(), null) {
});
}
}
private boolean canEditRegisterGroup(IRegisterGroupDMContext group) {
if (group instanceof MIRegisterGroupDMC) {
MIRegisterGroupDMC miGroup = ((MIRegisterGroupDMC) group);
// Prevent changes to the root group
if (miGroup.getName().trim().toLowerCase().equals(ROOT_GROUP_NAME.toLowerCase())) {
return false;
}
// Expected to be on the existing groups map
if (fGroupToRegistersMap.containsKey(group)) {
return true;
}
}
return false;
}
private ILaunchConfiguration getLaunchConfig() {
ILaunch launch = (ILaunch) getSession().getModelAdapter(ILaunch.class);
if (launch == null) {
// The launch is no longer active
return null;
}
ILaunchConfiguration config = launch.getLaunchConfiguration();
return config;
}
private IRegisterGroupDescriptor[] buildDescriptors() {
// use a tree map to sort the entries by group number
TreeMap<Integer, MIRegisterGroupDMC> sortedGroups = new TreeMap<Integer, MIRegisterGroupDMC>();
for (MIRegisterGroupDMC group : fGroupToRegistersMap.keySet()) {
sortedGroups.put(Integer.valueOf(group.getGroupNo()), group);
}
// Not serializing the root group which is dynamically created from GDB
sortedGroups.remove(Integer.valueOf(0));
Set<Entry<Integer, MIRegisterGroupDMC>> groupSet = sortedGroups.entrySet();
IRegisterGroupDescriptor[] descriptors = new IRegisterGroupDescriptor[groupSet.size()];
// load group descriptors sorted in ascending order to their group
// number into the result array
int i = 0;
for (Iterator<Entry<Integer, MIRegisterGroupDMC>> iterator = groupSet.iterator(); iterator.hasNext();) {
Entry<Integer, MIRegisterGroupDMC> entry = iterator.next();
descriptors[i] = new RegisterGroupDescriptor(entry.getValue(), true);
i++;
}
return descriptors;
}
private <T> T[] concatenateArr(T[] origArr, T[] deltaArr) {
if (origArr == null) {
return deltaArr;
}
if (deltaArr == null) {
return origArr;
}
T[] newArr = Arrays.copyOf(origArr, origArr.length + deltaArr.length);
System.arraycopy(deltaArr, 0, newArr, origArr.length, deltaArr.length);
return newArr;
}
private MIRegisterGroupDMC[] readGroupsFromMemento(final IContainerDMContext contDmc) {
RegisterGroupsPersistance deserializer = new RegisterGroupsPersistance(getLaunchConfig());
IRegisterGroupDescriptor[] groupDescriptions = deserializer.parseGroups();
List<MIRegisterGroupDMC> groups = new ArrayList<MIRegisterGroupDMC>();
for (IRegisterGroupDescriptor group : groupDescriptions) {
fGroupMementoDescriptorIndex.put(fGroupBookingCount, group);
groups.add(new MIRegisterGroupDMC(this, contDmc, fGroupBookingCount, group.getName()));
fGroupBookingCount++;
}
return groups.toArray(new MIRegisterGroupDMC[groups.size()]);
}
private void getUserGroupRegisters(IDMContext ctx, final DataRequestMonitor<IRegisterDMContext[]> rm) {
final MIRegisterGroupDMC groupDmc = DMContexts.getAncestorOfType(ctx, MIRegisterGroupDMC.class);
// Need to build the corresponding register[] from the memento descriptors
IRegisterGroupDescriptor grpDescriptor = fGroupMementoDescriptorIndex.get(groupDmc.getGroupNo());
if (grpDescriptor == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
"The Register Group Descriptor does not exist for group: " + groupDmc.getName(), null)); //$NON-NLS-1$
rm.done();
return;
}
MIRegisterDMC[] registers;
try {
// Resolve bare registers from the memento descriptors
registers = resolveRegisters(grpDescriptor, ctx);
} catch (CoreException e) {
rm.setStatus(e.getStatus());
rm.done();
return;
}
// update internal data
fGroupToRegistersMap.put(groupDmc, registers);
// now resolve to context specific registers
buildGroupRegisters(ctx, registers, rm);
}
/**
* Resolve register dmcs from de-serialized memento descriptors
*/
private MIRegisterDMC[] resolveRegisters(IRegisterGroupDescriptor grpDescriptor, IDMContext ctx)
throws CoreException {
final List<MIRegisterDMC> registerContexts = new ArrayList<MIRegisterDMC>();
final IContainerDMContext containerDmc = DMContexts.getAncestorOfType(ctx, IContainerDMContext.class);
final MIRegisterGroupDMC groupDmc = DMContexts.getAncestorOfType(ctx, MIRegisterGroupDMC.class);
IRegisterDescriptor[] registerDescriptions = grpDescriptor.getChildren();
MIRegisterGroupDMC[] groupContexts = fContextToGroupsMap.get(containerDmc);
if (groupContexts != null && groupContexts.length > 0) {
// Get the General Group (base) at index 0,
// Registers map indexed by name
Map<String, MIRegisterDMC> indexedRegisterBase = fGroupToRegistersMap.getIndexedRegisters(groupContexts[0]);
// For each descriptors find its corresponding MIRegisterDMC
for (IRegisterDescriptor registerDescription : registerDescriptions) {
MIRegisterDMC registerDmc = indexedRegisterBase.get(registerDescription.getName());
if (registerDmc == null) {
// The Register is not present from the base received from GDB
// Create a register DMC with no execution dmc and invalid
// register number e.g. not mapped to a gdb register.
registerDmc = new MIRegisterDMC(this, groupDmc, -1, registerDescription.getName());
}
registerContexts.add(registerDmc);
}
}
return registerContexts.toArray(new MIRegisterDMC[registerContexts.size()]);
}
@Override
protected void generateRegisterChangedEvent(final IRegisterDMContext dmc) {
// notify the register value change
getSession().dispatchEvent(new RegisterChangedDMEvent(dmc), getProperties());
// Propagate notification to all groups.
// A change of a single register needs to be propagated to all groups within the same Container/Process
// I.e. Some registers are dependent on the value of others and these dependent registers could be
// associated to different groups.
IContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IContainerDMContext.class);
generateRegistersChangedEvent(containerDmc);
}
private void generateRegistersChangedEvent(IContainerDMContext containerDmc) {
//resolve the groups for the current container (process) context
final MIRegisterGroupDMC[] groups = fContextToGroupsMap.get(containerDmc);
//trigger notification to all groups in the container
for (int i = 0; i < groups.length; i++) {
//We need final locals variables from the loop. Use a method call for this
generateRegistersChangedEvent(groups[i]);
}
}
private void generateRegistersChangedEvent(final MIRegisterGroupDMC groupDmc) {
IRegistersChangedDMEvent event = new IRegistersChangedDMEvent() {
@Override
public IRegisterGroupDMContext getDMContext() {
return groupDmc;
}
};
getSession().dispatchEvent(event, getProperties());
}
private void generateRegisterGroupChangedEvent(final MIRegisterGroupDMC groupDmc) {
IGroupChangedDMEvent event = new IGroupChangedDMEvent() {
@Override
public IRegisterGroupDMContext getDMContext() {
return groupDmc;
}
};
getSession().dispatchEvent(event, getProperties());
}
/**
* Create Registers from specific execution context to a generic register context, e.g. not associated to a specific
* execution context.
*/
private MIRegisterDMC[] toBareRegisters(MIRegisterDMC[] registers) {
MIRegisterDMC[] bareRegisters = new MIRegisterDMC[registers.length];
for (int i = 0; i < registers.length; i++) {
// only one parent i.e. group context
MIRegisterGroupDMC groupDmc = DMContexts.getAncestorOfType(registers[i], MIRegisterGroupDMC.class);
assert (groupDmc != null);
bareRegisters[i] = new MIRegisterDMC(this, groupDmc, registers[i].getRegNo(), registers[i].getName());
}
return bareRegisters;
}
private void buildGroupRegisters(final IDMContext ctx, final MIRegisterDMC[] baseRegisters,
final DataRequestMonitor<IRegisterDMContext[]> rm) {
final MIRegisterGroupDMC groupDmc = DMContexts.getAncestorOfType(ctx, MIRegisterGroupDMC.class);
assert (groupDmc != null);
final IFrameDMContext frameDmc = DMContexts.getAncestorOfType(ctx, IFrameDMContext.class);
if (frameDmc == null) {
// The selection does not provide a specific frame, then resolve the top frame on the current thread
// if the execution frame is not available proceed with no frame context i.e. will not be able to resolve
// values.
IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(ctx, IMIExecutionDMContext.class);
if (execDmc != null) {
IStack stackService = getServicesTracker().getService(IStack.class);
if (stackService != null) {
stackService.getTopFrame(execDmc, new ImmediateDataRequestMonitor<IStack.IFrameDMContext>(rm) {
@Override
protected void handleSuccess() {
cloneRegistersToContext(groupDmc, getData(), baseRegisters, rm);
}
@Override
protected void handleFailure() {
// Unable to resolve top frame on current thread.
// The thread could e.g. be in running state,
// we return register instances with no associated execution context
// i.e. unable to resolve its associated value.
cloneRegistersToContext(groupDmc, null, baseRegisters, rm);
}
});
return;
}
}
}
cloneRegistersToContext(groupDmc, frameDmc, baseRegisters, rm);
}
/**
* Create a new array of register instances with the given context
*/
private void cloneRegistersToContext(MIRegisterGroupDMC groupDmc, IFrameDMContext frameDmc,
MIRegisterDMC[] baseRegisters, DataRequestMonitor<IRegisterDMContext[]> rm) {
MIRegisterDMC[] registers = new MIRegisterDMC[baseRegisters.length];
if (frameDmc != null) {
// build to valid stack frame context
for (int i = 0; i < registers.length; i++) {
registers[i] = new MIRegisterDMC(this, groupDmc, frameDmc, baseRegisters[i].getRegNo(),
baseRegisters[i].getName());
}
} else {
// build with no execution context, normal case if a selection is pointing to
// e.g. a running thread, a process.. i.e. not able to associate register values.
for (int i = 0; i < registers.length; i++) {
registers[i] = new MIRegisterDMC(this, groupDmc, baseRegisters[i].getRegNo(),
baseRegisters[i].getName());
}
}
// return the registers
rm.setData(registers);
rm.done();
}
@Override
public void canRemoveRegisterGroups(IRegisterGroupDMContext[] groups, DataRequestMonitor<Boolean> rm) {
if (groups == null || groups.length < 1) {
rm.setData(false);
rm.done();
return;
}
for(IRegisterGroupDMContext group : groups) {
assert(group instanceof MIRegisterGroupDMC);
MIRegisterGroupDMC miGroup = (MIRegisterGroupDMC) group;
if (miGroup.getName().equals(ROOT_GROUP_NAME)) {
// Not allowed to remove the root group
rm.setData(false);
rm.done();
return;
}
}
rm.setData(true);
rm.done();
}
@Override
public void canRestoreDefaultGroups(IDMContext selectionContext, DataRequestMonitor<Boolean> rm) {
// Not relevant checks at this point
rm.setData(true);
rm.done();
}
}