GP-2966: Consider aliases in debugger register mapping.

This commit is contained in:
Dan
2023-01-19 12:48:01 -05:00
parent 942d60eeac
commit a9c1949742
13 changed files with 276 additions and 55 deletions

View File

@@ -51,16 +51,30 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
collectFilteredLanguageRegs(); collectFilteredLanguageRegs();
} }
protected boolean testTraceRegister(Register lReg) { protected boolean includeTraceRegister(Register lReg) {
return lReg.isBaseRegister(); return lReg.isBaseRegister();
} }
protected synchronized void collectFilteredLanguageRegs() { protected synchronized void collectFilteredLanguageRegs() {
for (Register lReg : language.getRegisters()) { for (Register lReg : language.getRegisters()) {
if (!testTraceRegister(lReg)) { if (!includeTraceRegister(lReg)) {
continue; continue;
} }
filtLanguageRegs.put(normalizeName(lReg.getName()), lReg); putLanguageRegister(filtLanguageRegs, lReg);
}
}
protected synchronized void putLanguageRegister(Map<String, Register> map, Register lReg) {
map.put(normalizeName(lReg.getName()), lReg);
for (String alias : lReg.getAliases()) {
map.put(normalizeName(alias), lReg);
}
}
protected synchronized void removeLanguageRegister(Map<String, Register> map, Register lReg) {
map.remove(normalizeName(lReg.getName()));
for (String alias : lReg.getAliases()) {
map.remove(normalizeName(alias));
} }
} }
@@ -70,7 +84,7 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
if (lReg == null) { if (lReg == null) {
return null; return null;
} }
languageRegs.put(name, lReg); putLanguageRegister(languageRegs, lReg);
return lReg; return lReg;
} }
@@ -81,7 +95,7 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
return null; return null;
} }
targetRegs.put(name, tReg); targetRegs.put(name, tReg);
languageRegs.put(name, lReg); putLanguageRegister(languageRegs, lReg);
return lReg; return lReg;
} }
@@ -92,7 +106,7 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
return null; return null;
} }
targetRegs.remove(name); targetRegs.remove(name);
languageRegs.remove(name); removeLanguageRegister(languageRegs, lReg);
return lReg; return lReg;
} }
@@ -115,7 +129,17 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
@Override @Override
public synchronized TargetRegister traceToTarget(Register lReg) { public synchronized TargetRegister traceToTarget(Register lReg) {
return targetRegs.get(normalizeName(lReg.getName())); TargetRegister tReg = targetRegs.get(normalizeName(lReg.getName()));
if (tReg != null) {
return tReg;
}
for (String alias : lReg.getAliases()) {
tReg = targetRegs.get(normalizeName(alias));
if (tReg != null) {
return tReg;
}
}
return null;
} }
@Override @Override

View File

@@ -40,7 +40,7 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
} }
@Override @Override
protected boolean testTraceRegister(Register lReg) { protected boolean includeTraceRegister(Register lReg) {
return true; return true;
} }
@@ -123,7 +123,7 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
return null; return null;
} }
Register lReg = subs.last(); // largest Register lReg = subs.last(); // largest
return targetRegs.get(normalizeName(lReg.getName())); return super.traceToTarget(lReg);
} }
@Override @Override
@@ -156,7 +156,7 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
Msg.warn(this, "Potential register cache aliasing: " + lReg + " vs " + subs.last()); Msg.warn(this, "Potential register cache aliasing: " + lReg + " vs " + subs.last());
/** /**
* After testing, there is a problem with data truncation. If, e.g., EAX is sent, * After testing, there is a problem with data truncation. If, e.g., EAX is sent,
* followed by AX, and we expand to fill RAX, the we're going to truncate the upper 16 * followed by AX, and we expand to fill RAX, then we're going to truncate the upper 16
* bits of EAX :( . To avoid this, we'll only map the largest register from target to * bits of EAX :( . To avoid this, we'll only map the largest register from target to
* trace. We'll still warn as a courtesy, but we'll abort the mapping. * trace. We'll still warn as a courtesy, but we'll abort the mapping.
*/ */
@@ -164,7 +164,7 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
} }
/** /**
* TODO: A mapping with masks may be useful in the future, but as it is, the trace database * TODO: A mapping with masks may be useful in the future, but as it is, the trace database
* will reject it. Furthermore, we'd still want to mark the whole base register is known, * will reject it. Furthermore, we'd still want to mark the whole base register as known,
* which is not intuitive, and makes me worry about that method. * which is not intuitive, and makes me worry about that method.
*/ */
// return new RegisterValue(lReg, new BigInteger(1,value)).getBaseRegisterValue(); // return new RegisterValue(lReg, new BigInteger(1,value)).getBaseRegisterValue();

View File

@@ -543,9 +543,9 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
} }
protected TargetRegisterBank isExactRegisterOnTarget(TracePlatform platform, protected TargetRegisterBank isExactRegisterOnTarget(TracePlatform platform,
TargetRegisterContainer regContainer, String name) { TargetRegisterContainer regContainer, Register register) {
PathMatcher matcher = PathMatcher matcher =
platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), name); platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), register);
for (TargetObject targetObject : matcher.getCachedSuccessors(regContainer).values()) { for (TargetObject targetObject : matcher.getCachedSuccessors(regContainer).values()) {
if (!(targetObject instanceof TargetRegister targetRegister)) { if (!(targetObject instanceof TargetRegister targetRegister)) {
continue; continue;
@@ -568,28 +568,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
if (regContainer == null) { if (regContainer == null) {
return null; return null;
} }
TargetRegisterBank result; return isExactRegisterOnTarget(platform, regContainer, register);
String name = platform.getConventionalRegisterObjectName(register);
result = isExactRegisterOnTarget(platform, regContainer, name);
if (result != null) {
return result;
}
// Not totally case insensitive, but the sane cases
String upperName = name.toUpperCase();
if (!name.equals(upperName)) {
result = isExactRegisterOnTarget(platform, regContainer, upperName);
if (result != null) {
return result;
}
}
String lowerName = name.toLowerCase();
if (!name.equals(lowerName)) {
result = isExactRegisterOnTarget(platform, regContainer, lowerName);
if (result != null) {
return result;
}
}
return null;
} }
@Override @Override

View File

@@ -0,0 +1,132 @@
/* ###
* IP: GHIDRA
*
* Licensed 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.
*/
package ghidra.app.plugin.core.debug.mapping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Objects;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetRegister;
import ghidra.dbg.model.TestTargetRegisterValue;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegister;
import ghidra.program.model.lang.*;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.model.thread.TraceThread;
public class DefaultDebuggerRegisterMapperTest extends AbstractGhidraHeadedDebuggerGUITest {
static class TestTargetMapper extends DefaultDebuggerTargetTraceMapper {
public TestTargetMapper(TargetObject target)
throws LanguageNotFoundException, CompilerSpecNotFoundException {
super(target, new LanguageID(ToyProgramBuilder._TOY64_BE),
new CompilerSpecID("default"), Set.of());
}
}
protected static void assertSameRegister(TargetRegister expected, TargetObject actual) {
if (actual instanceof TestTargetRegister tr) {
assertEquals(expected, tr);
}
else if (actual instanceof TestTargetRegisterValue rv) {
assertEquals(expected, rv.desc);
}
else {
fail();
}
}
@Before
public void setUpMapperTest() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.createTestThreadRegisterBanks();
}
protected DebuggerRegisterMapper doGetRegisterMapper(String toConfirm)
throws Throwable {
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
new TestTargetMapper(mb.testProcess1), ActionSource.AUTOMATIC);
TraceThread thread1 = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
DebuggerRegisterMapper rm = waitForValue(() -> recorder.getRegisterMapper(thread1));
waitForValue(() -> rm.getTargetRegister(toConfirm));
return rm;
}
protected DebuggerRegisterMapper getRegisterMapper() throws Throwable {
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), r -> true);
return doGetRegisterMapper("r0");
}
protected DebuggerRegisterMapper getRegisterMapperAliased() throws Throwable {
Register r0 = getToyBE64Language().getRegister("r0");
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), r -> r != r0);
mb.testProcess1.regs.addRegister("a0", r0);
return doGetRegisterMapper("r1");
}
@Test
public void testTraceToTargetRegCanonical() throws Throwable {
DebuggerRegisterMapper rm = getRegisterMapper();
TestTargetRegister tR0 =
Objects.requireNonNull(mb.testProcess1.regs.getCachedElements().get("r0"));
Register lR0 = Objects.requireNonNull(getToyBE64Language().getRegister("r0"));
TargetRegister tReg = waitForValue(() -> rm.traceToTarget(lR0));
assertSameRegister(tR0, tReg);
}
@Test
public void testTargetToTraceRegCanonical() throws Throwable {
DebuggerRegisterMapper rm = getRegisterMapper();
TestTargetRegister tR0 =
Objects.requireNonNull(mb.testProcess1.regs.getCachedElements().get("r0"));
Register lR0 = Objects.requireNonNull(getToyBE64Language().getRegister("r0"));
Register lReg = waitForValue(() -> rm.targetToTrace(tR0));
assertEquals(lR0, lReg);
}
@Test
public void testTraceToTargetRegAlias() throws Throwable {
DebuggerRegisterMapper rm = getRegisterMapperAliased();
TestTargetRegister tA0 =
Objects.requireNonNull(mb.testProcess1.regs.getCachedElements().get("a0"));
Register lR0 = Objects.requireNonNull(getToyBE64Language().getRegister("r0"));
TargetRegister tReg = waitForValue(() -> rm.traceToTarget(lR0));
assertSameRegister(tA0, tReg);
}
@Test
public void testTargetToTraceRegAlias() throws Throwable {
DebuggerRegisterMapper rm = getRegisterMapperAliased();
TestTargetRegister tA0 =
Objects.requireNonNull(mb.testProcess1.regs.getCachedElements().get("a0"));
Register lR0 = Objects.requireNonNull(getToyBE64Language().getRegister("r0"));
Register lReg = waitForValue(() -> rm.targetToTrace(tA0));
assertEquals(lR0, lReg);
}
}

View File

@@ -33,6 +33,10 @@ public class PathMatcher implements PathPredicates {
patterns.add(pattern); patterns.add(pattern);
} }
public void addAll(PathMatcher matcher) {
patterns.addAll(matcher.patterns);
}
@Override @Override
public String toString() { public String toString() {
return String.format("<PathMatcher\n %s\n>", StringUtils.join(patterns, "\n ")); return String.format("<PathMatcher\n %s\n>", StringUtils.join(patterns, "\n "));

View File

@@ -156,6 +156,11 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
return TestTargetRegister.fromLanguageRegister(container, register); return TestTargetRegister.fromLanguageRegister(container, register);
} }
protected TestTargetRegister newTestTargetRegister(TestTargetRegisterContainer container,
String index, Register register) {
return TestTargetRegister.fromLanguageRegister(container, index, register);
}
protected TestTargetThreadContainer newTestTargetThreadContainer(TestTargetProcess process) { protected TestTargetThreadContainer newTestTargetThreadContainer(TestTargetProcess process) {
return new TestTargetThreadContainer(process); return new TestTargetThreadContainer(process);
} }

View File

@@ -27,10 +27,15 @@ public class TestTargetRegister
extends DefaultTestTargetObject<TestTargetObject, TestTargetRegisterContainer> extends DefaultTestTargetObject<TestTargetObject, TestTargetRegisterContainer>
implements TargetRegister { implements TargetRegister {
public static TestTargetRegister fromLanguageRegister(TestTargetRegisterContainer parent,
String index, Register register) {
return new TestTargetRegister(parent, PathUtils.makeKey(index),
(register.getBitLength() + 7) / 8, register.isProgramCounter());
}
public static TestTargetRegister fromLanguageRegister( public static TestTargetRegister fromLanguageRegister(
TestTargetRegisterContainer parent, Register register) { TestTargetRegisterContainer parent, Register register) {
return new TestTargetRegister(parent, PathUtils.makeKey(register.getName()), return fromLanguageRegister(parent, register.getName(), register);
(register.getBitLength() + 7) / 8, register.isProgramCounter());
} }
protected final int byteLength; protected final int byteLength;

View File

@@ -61,6 +61,11 @@ public class TestTargetRegisterContainer
public TestTargetRegister addRegister(Register register) { public TestTargetRegister addRegister(Register register) {
TestTargetRegister tr = TestTargetRegister tr =
getModel().newTestTargetRegister(this, Objects.requireNonNull(register)); getModel().newTestTargetRegister(this, Objects.requireNonNull(register));
doAddRegister(tr, register);
return tr;
}
protected void doAddRegister(TestTargetRegister tr, Register register) {
String reason = "Added " + register + " from Ghidra language"; String reason = "Added " + register + " from Ghidra language";
changeElements(List.of(), List.of(tr), reason); changeElements(List.of(), List.of(tr), reason);
List<AbstractTestTargetRegisterBank<?>> banks; List<AbstractTestTargetRegisterBank<?>> banks;
@@ -70,6 +75,12 @@ public class TestTargetRegisterContainer
for (AbstractTestTargetRegisterBank<?> bank : banks) { for (AbstractTestTargetRegisterBank<?> bank : banks) {
bank.addRegisterDescs(List.of(tr), reason); bank.addRegisterDescs(List.of(tr), reason);
} }
}
public TestTargetRegister addRegister(String index, Register register) {
TestTargetRegister tr =
getModel().newTestTargetRegister(this, index, Objects.requireNonNull(register));
doAddRegister(tr, register);
return tr; return tr;
} }

View File

@@ -15,8 +15,7 @@
*/ */
package ghidra.trace.database.guest; package ghidra.trace.database.guest;
import java.util.Collection; import java.util.*;
import java.util.List;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegister; import ghidra.dbg.target.TargetRegister;
@@ -80,41 +79,58 @@ public interface InternalTracePlatform extends TracePlatform {
return result; return result;
} }
default List<String> listRegNames(Register register) {
Set<String> result = new LinkedHashSet<>();
result.add(register.getName());
result.add(register.getName().toUpperCase());
result.add(register.getName().toLowerCase());
for (String alias : register.getAliases()) {
result.add(alias);
result.add(alias.toUpperCase());
result.add(alias.toLowerCase());
}
return List.copyOf(result);
}
@Override @Override
default String getConventionalRegisterObjectName(Register register) { default Collection<String> getConventionalRegisterObjectNames(Register register) {
Address pmin = mapGuestToHost(register.getAddress()); Address pmin = mapGuestToHost(register.getAddress());
if (pmin == null) { if (pmin == null) {
return register.getName(); return listRegNames(register);
} }
TraceSymbolManager symbolManager = getTrace().getSymbolManager(); TraceSymbolManager symbolManager = getTrace().getSymbolManager();
TraceNamespaceSymbol nsRegMap = symbolManager.namespaces().getGlobalNamed(regMap(register)); TraceNamespaceSymbol nsRegMap = symbolManager.namespaces().getGlobalNamed(regMap(register));
Collection<? extends TraceLabelSymbol> labels = symbolManager.labels() Collection<String> labels = symbolManager.labels()
.getAt(0, null, pmin, false) .getAt(0, null, pmin, false)
.stream() .stream()
.filter(s -> s.getParentNamespace() == nsRegMap) .filter(s -> s.getParentNamespace() == nsRegMap)
.map(TraceSymbol::getName)
.toList(); .toList();
if (labels.isEmpty()) { if (!labels.isEmpty()) {
return register.getName(); return labels;
} }
// primary is listed first, so take it return listRegNames(register);
return labels.iterator().next().getName();
} }
@Override @Override
default PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List<String> path, default PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List<String> path,
String name) { Collection<String> names) {
PathMatcher matcher = schema.searchFor(TargetRegister.class, path, true); PathMatcher matcher = schema.searchFor(TargetRegister.class, path, true);
if (matcher.isEmpty()) { if (matcher.isEmpty()) {
return matcher; return matcher;
} }
return matcher.applyKeys(Align.RIGHT, List.of(name)); PathMatcher result = new PathMatcher();
for (String name:names) {
result.addAll(matcher.applyKeys(Align.RIGHT, List.of(name)));
}
return result;
} }
@Override @Override
default PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List<String> path, default PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List<String> path,
Register register) { Register register) {
return getConventionalRegisterPath(schema, path, return getConventionalRegisterPath(schema, path,
getConventionalRegisterObjectName(register)); getConventionalRegisterObjectNames(register));
} }
@Override @Override
@@ -156,6 +172,11 @@ public interface InternalTracePlatform extends TracePlatform {
if (nsRegMap == null) { if (nsRegMap == null) {
nsRegMap = namespaces.add(regMap, globals, SourceType.USER_DEFINED); nsRegMap = namespaces.add(regMap, globals, SourceType.USER_DEFINED);
} }
TraceLabelSymbol exists = symbolManager.labels()
.getChildWithNameAt(objectName, getIntKey(), null, hostAddr, nsRegMap);
if (exists != null) {
return exists;
}
return symbolManager.labels() return symbolManager.labels()
.create(0, null, hostAddr, objectName, nsRegMap, SourceType.USER_DEFINED); .create(0, null, hostAddr, objectName, nsRegMap, SourceType.USER_DEFINED);
} }

View File

@@ -15,6 +15,7 @@
*/ */
package ghidra.trace.model.guest; package ghidra.trace.model.guest;
import java.util.Collection;
import java.util.List; import java.util.List;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
@@ -171,27 +172,29 @@ public interface TracePlatform {
AddressRange getConventionalRegisterRange(AddressSpace overlay, Register register); AddressRange getConventionalRegisterRange(AddressSpace overlay, Register register);
/** /**
* Get the name or index of the register object for the given platform register * Get the names or indices of the register object for the given platform register
* *
* <p> * <p>
* This will check for a label in the host physical space, allowing a mapper to specify an * This will check for a label in the host physical space, allowing a mapper to specify an
* alternative register object name. See {@link #addRegisterMapOverride(Register, String)}. * alternative register object name. See {@link #addRegisterMapOverride(Register, String)}. If
* one exists, then only that name is returned. Otherwise, the given register's names and
* aliases are all returned as defined and in all-upper and all-lower case.
* *
* @param register the platform register * @param register the platform register
* @return the mapped name * @return the mapped name
*/ */
String getConventionalRegisterObjectName(Register register); Collection<String> getConventionalRegisterObjectNames(Register register);
/** /**
* Get the expected path where an object defining the register value would be * Get the expected path where an object defining the register value would be
* *
* @param schema the schema of the register container * @param schema the schema of the register container
* @param path the path to the register container * @param path the path to the register container
* @param name the name of the register on the target * @param names the possible names of the register on the target
* @return the path matcher, possibly empty * @return the path matcher, possibly empty
*/ */
PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List<String> path, PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List<String> path,
String name); Collection<String> names);
/** /**
* Get the expected path where an object defining the register value would be * Get the expected path where an object defining the register value would be

View File

@@ -459,4 +459,23 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte
assertMatches("Targets[0].Threads[0].Registers.User[r0]", assertMatches("Targets[0].Threads[0].Registers.User[r0]",
b.host.getConventionalRegisterPath(overlay, b.reg("r0"))); b.host.getConventionalRegisterPath(overlay, b.reg("r0")));
} }
@Test
public void testPlatformGetConventionalRegisterPathAlias() throws Throwable {
AddressSpace registers = b.trace.getBaseAddressFactory().getRegisterSpace();
AddressSpace overlay;
Register r0;
try (UndoableTransaction tid = b.startTransaction()) {
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
r0 = b.language.getRegister("r0");
overlay = b.trace.getMemoryManager()
.createOverlayAddressSpace("Targets[0].Threads[0].Registers", registers);
}
PathMatcher matcher = b.host.getConventionalRegisterPath(overlay, r0);
assertMatches("Targets[0].Threads[0].Registers.User[r0]", matcher);
assertMatches("Targets[0].Threads[0].Registers.User[a0]", matcher);
assertMatches("Targets[0].Threads[0].Registers.User[R0]", matcher);
assertMatches("Targets[0].Threads[0].Registers.User[A0]", matcher);
}
} }

View File

@@ -12,7 +12,7 @@ data/languages/old/v01stuff/toyInstructions.sinc||GHIDRA||||END|
data/languages/old/v01stuff/toyPosStack.cspec||GHIDRA||||END| data/languages/old/v01stuff/toyPosStack.cspec||GHIDRA||||END|
data/languages/toy.cspec||GHIDRA||||END| data/languages/toy.cspec||GHIDRA||||END|
data/languages/toy.ldefs||GHIDRA||||END| data/languages/toy.ldefs||GHIDRA||||END|
data/languages/toy.pspec||GHIDRA||reviewed||END| data/languages/toy.pspec||GHIDRA||||END|
data/languages/toy.sinc||GHIDRA||||END| data/languages/toy.sinc||GHIDRA||||END|
data/languages/toy64-long8.cspec||GHIDRA||||END| data/languages/toy64-long8.cspec||GHIDRA||||END|
data/languages/toy64.cspec||GHIDRA||||END| data/languages/toy64.cspec||GHIDRA||||END|

View File

@@ -2,4 +2,22 @@
<processor_spec> <processor_spec>
<programcounter register="pc"/> <programcounter register="pc"/>
<register_data>
<register name="r0" alias="a0"/>
<register name="r1" alias="a1"/>
<register name="r2" alias="a2"/>
<register name="r3" alias="a3"/>
<register name="r4" alias="a4"/>
<register name="r5" alias="a5"/>
<register name="r6" alias="a6"/>
<register name="r7" alias="a7"/>
<register name="r8" alias="a8"/>
<register name="r9" alias="a9"/>
<register name="r10" alias="a10"/>
<register name="r11" alias="a11"/>
<register name="r12" alias="a12"/>
<register name="sp" alias="a13"/>
<register name="lr" alias="a14"/>
<register name="pc" alias="a15"/>
</register_data>
</processor_spec> </processor_spec>