mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
GP-3771: Add mask to the unwind analyzer (Fixes unwind with ARM/THUMB)
This commit is contained in:
parent
d23e67a088
commit
655082ecb5
10 changed files with 387 additions and 89 deletions
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -52,6 +52,7 @@ public class ComputeUnwindInfoScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
println("Stack depth at " + currentAddress + ": " + info.depth());
|
println("Stack depth at " + currentAddress + ": " + info.depth());
|
||||||
println("Return address address: " + addressToString(info.ofReturn()));
|
println("Return address address: " + addressToString(info.ofReturn()));
|
||||||
|
println("Return address mask: 0x" + Long.toHexString(info.maskOfReturn()));
|
||||||
println("Saved registers:");
|
println("Saved registers:");
|
||||||
for (Entry<Register, Address> entry : info.saved().entrySet()) {
|
for (Entry<Register, Address> entry : info.saved().entrySet()) {
|
||||||
println(" " + entry);
|
println(" " + entry);
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -261,8 +261,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
record MappedLocation(Program stProg, Address stAddr, Address dynAddr) {
|
record MappedLocation(Program stProg, Address stAddr, Address dynAddr) {}
|
||||||
}
|
|
||||||
|
|
||||||
protected MappedLocation mapLocation(Program programOrView, Address address) {
|
protected MappedLocation mapLocation(Program programOrView, Address address) {
|
||||||
if (programOrView instanceof TraceProgramView view) {
|
if (programOrView instanceof TraceProgramView view) {
|
||||||
|
@ -400,7 +399,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||||
public CompletableFuture<VariableValueTable> fillOperand(OperandFieldLocation opLoc,
|
public CompletableFuture<VariableValueTable> fillOperand(OperandFieldLocation opLoc,
|
||||||
Instruction ins) {
|
Instruction ins) {
|
||||||
RefType refType = ins.getOperandRefType(opLoc.getOperandIndex());
|
RefType refType = ins.getOperandRefType(opLoc.getOperandIndex());
|
||||||
if (refType.isFlow()) {
|
if (refType != null && refType.isFlow()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Object operand = ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex())
|
Object operand = ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex())
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -71,6 +71,11 @@ public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
|
||||||
return this.warnings.equals(that.warnings);
|
return this.warnings.equals(that.warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.warnings.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return warnings.size();
|
return warnings.size();
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -96,6 +96,15 @@ sealed interface Sym {
|
||||||
*/
|
*/
|
||||||
Sym twosComp();
|
Sym twosComp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical bitwise and this and another symbol with the given compiler context
|
||||||
|
*
|
||||||
|
* @param cSpec the compiler specification
|
||||||
|
* @param in2 the second symbol
|
||||||
|
* @return the resulting symbol
|
||||||
|
*/
|
||||||
|
Sym and(CompilerSpec cSpec, Sym in2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the size of this symbol with the given compiler for context
|
* Get the size of this symbol with the given compiler for context
|
||||||
*
|
*
|
||||||
|
@ -144,6 +153,11 @@ sealed interface Sym {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Sym twosComp() {
|
public Sym twosComp() {
|
||||||
return this;
|
return this;
|
||||||
|
@ -166,19 +180,23 @@ sealed interface Sym {
|
||||||
public record ConstSym(long value, int size) implements Sym {
|
public record ConstSym(long value, int size) implements Sym {
|
||||||
@Override
|
@Override
|
||||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||||
if (in2 instanceof ConstSym const2) {
|
return switch (in2) {
|
||||||
return new ConstSym(value + const2.value, size);
|
case ConstSym const2 -> new ConstSym(value + const2.value, size);
|
||||||
}
|
case RegisterSym reg2 when reg2.register() == cSpec
|
||||||
if (in2 instanceof RegisterSym reg2) {
|
.getStackPointer() -> new StackOffsetSym(value);
|
||||||
if (reg2.register() == cSpec.getStackPointer()) {
|
case StackOffsetSym off2 -> new StackOffsetSym(value + off2.offset);
|
||||||
return new StackOffsetSym(value);
|
default -> Sym.opaque();
|
||||||
}
|
};
|
||||||
return Sym.opaque();
|
}
|
||||||
}
|
|
||||||
if (in2 instanceof StackOffsetSym off2) {
|
@Override
|
||||||
return new StackOffsetSym(value + off2.offset);
|
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||||
}
|
return switch (in2) {
|
||||||
return Sym.opaque();
|
case ConstSym const2 -> new ConstSym(value & const2.value, size);
|
||||||
|
case RegisterSym reg2 -> reg2.withAppliedMask(value);
|
||||||
|
case StackDerefSym deref2 -> deref2.withAppliedMask(value);
|
||||||
|
default -> Sym.opaque();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -203,13 +221,25 @@ sealed interface Sym {
|
||||||
/**
|
/**
|
||||||
* A register symbol
|
* A register symbol
|
||||||
*/
|
*/
|
||||||
public record RegisterSym(Register register) implements Sym {
|
public record RegisterSym(Register register, long mask) implements Sym {
|
||||||
@Override
|
@Override
|
||||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||||
if (in2 instanceof ConstSym const2) {
|
return switch (in2) {
|
||||||
return const2.add(cSpec, this);
|
case ConstSym const2 -> const2.add(cSpec, this);
|
||||||
}
|
default -> Sym.opaque();
|
||||||
return Sym.opaque();
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||||
|
return switch (in2) {
|
||||||
|
case ConstSym const2 -> const2.and(cSpec, this);
|
||||||
|
default -> Sym.opaque();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegisterSym withAppliedMask(long mask) {
|
||||||
|
return new RegisterSym(register, this.mask & mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -244,10 +274,18 @@ sealed interface Sym {
|
||||||
public record StackOffsetSym(long offset) implements Sym {
|
public record StackOffsetSym(long offset) implements Sym {
|
||||||
@Override
|
@Override
|
||||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||||
if (in2 instanceof ConstSym const2) {
|
return switch (in2) {
|
||||||
return new StackOffsetSym(offset + const2.value());
|
case ConstSym const2 -> const2.add(cSpec, this);
|
||||||
}
|
default -> Sym.opaque();
|
||||||
return Sym.opaque();
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||||
|
return switch (in2) {
|
||||||
|
case ConstSym const2 -> const2.and(cSpec, this);
|
||||||
|
default -> Sym.opaque();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -276,10 +314,25 @@ sealed interface Sym {
|
||||||
* This represents a dereferenced {@link StackOffsetSym} (or the dereferenced stack pointer
|
* This represents a dereferenced {@link StackOffsetSym} (or the dereferenced stack pointer
|
||||||
* register, in which is treated as a stack offset of 0).
|
* register, in which is treated as a stack offset of 0).
|
||||||
*/
|
*/
|
||||||
public record StackDerefSym(long offset, int size) implements Sym {
|
public record StackDerefSym(long offset, long mask, int size) implements Sym {
|
||||||
@Override
|
@Override
|
||||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||||
return Sym.opaque();
|
return switch (in2) {
|
||||||
|
case ConstSym const2 -> const2.add(cSpec, this);
|
||||||
|
default -> Sym.opaque();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||||
|
return switch (in2) {
|
||||||
|
case ConstSym const2 -> const2.and(cSpec, this);
|
||||||
|
default -> Sym.opaque();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackDerefSym withAppliedMask(long mask) {
|
||||||
|
return new StackDerefSym(offset, this.mask & mask, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -65,6 +65,8 @@ class SymPcodeArithmetic implements PcodeArithmetic<Sym> {
|
||||||
return in1.add(cSpec, in2);
|
return in1.add(cSpec, in2);
|
||||||
case PcodeOp.INT_SUB:
|
case PcodeOp.INT_SUB:
|
||||||
return in1.sub(cSpec, in2);
|
return in1.sub(cSpec, in2);
|
||||||
|
case PcodeOp.INT_AND:
|
||||||
|
return in1.and(cSpec, in2);
|
||||||
default:
|
default:
|
||||||
return Sym.opaque();
|
return Sym.opaque();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -79,16 +79,16 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("""
|
return String.format("""
|
||||||
%s[
|
%s[
|
||||||
cSpec=%s
|
cSpec=%s
|
||||||
stack=%s
|
stack=%s
|
||||||
registers=%s
|
registers=%s
|
||||||
unique=%s
|
unique=%s
|
||||||
]
|
]
|
||||||
""", getClass().getSimpleName(),
|
""", getClass().getSimpleName(),
|
||||||
cSpec.toString(),
|
cSpec.toString(),
|
||||||
stackSpace.toString(" ", language),
|
stackSpace.toString(" ", language),
|
||||||
registerSpace.toString(" ", language),
|
registerSpace.toString(" ", language),
|
||||||
uniqueSpace.toString(" ", language));
|
uniqueSpace.toString(" ", language));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -218,17 +218,37 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
|
||||||
* <li>PC:Deref => location is [Stack]:PC.offset
|
* <li>PC:Deref => location is [Stack]:PC.offset
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @return
|
* @return the address (stack offset or register) of the return address
|
||||||
*/
|
*/
|
||||||
public Address computeAddressOfReturn() {
|
public Address computeAddressOfReturn() {
|
||||||
Sym expr = getVar(language.getProgramCounter(), Reason.INSPECT);
|
return switch (getVar(language.getProgramCounter(), Reason.INSPECT)) {
|
||||||
if (expr instanceof StackDerefSym stackVar) {
|
case StackDerefSym stackVar -> cSpec.getStackSpace().getAddress(stackVar.offset());
|
||||||
return cSpec.getStackSpace().getAddress(stackVar.offset());
|
case RegisterSym regVar -> regVar.register().getAddress();
|
||||||
}
|
default -> null;
|
||||||
if (expr instanceof RegisterSym regVar) {
|
};
|
||||||
return regVar.register().getAddress();
|
}
|
||||||
}
|
|
||||||
return null;
|
/**
|
||||||
|
* Examine this state's PC to determine how the return address is masked
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is only applicable in cases where {@link #computeAddressOfReturn()} returns a non-null
|
||||||
|
* address. This is to handle architectures where the low bits indicate an ISA mode, and the
|
||||||
|
* higher bits form the actual address. Often, the sleigh specifications for these processors
|
||||||
|
* will mask off those low bits when setting the PC. If that has happened, and the symbolic
|
||||||
|
* expression stored in the PC is otherwise understood to come from the stack or a register,
|
||||||
|
* this will return that mask. Most often, this will return -1, indicating that all bits are
|
||||||
|
* relevant to the actual address. If the symbolic expression does not indicate the stack or a
|
||||||
|
* register, this still returns -1.
|
||||||
|
*
|
||||||
|
* @return the mask, often -1
|
||||||
|
*/
|
||||||
|
public long computeMaskOfReturn() {
|
||||||
|
return switch (getVar(language.getProgramCounter(), Reason.INSPECT)) {
|
||||||
|
case StackDerefSym stackVar -> stackVar.mask();
|
||||||
|
case RegisterSym regVar -> regVar.mask();
|
||||||
|
default -> -1;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -211,7 +211,10 @@ public class SymStateSpace {
|
||||||
return map.values()
|
return map.values()
|
||||||
.stream()
|
.stream()
|
||||||
.map(se -> se.toString(language))
|
.map(se -> se.toString(language))
|
||||||
.collect(Collectors.joining("\n" + indent, indent + "{", "\n" + indent + "}"));
|
.collect(Collectors.joining(
|
||||||
|
"\n" + indent + indent,
|
||||||
|
"{\n" + indent + indent,
|
||||||
|
"\n" + indent + "}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private NavigableMap<Address, SymEntry> subMap(Address lower, Address upper) {
|
private NavigableMap<Address, SymEntry> subMap(Address lower, Address upper) {
|
||||||
|
@ -281,10 +284,10 @@ public class SymStateSpace {
|
||||||
Msg.warn(this, "Could not figure register: address=" + address + ",size=" + size);
|
Msg.warn(this, "Could not figure register: address=" + address + ",size=" + size);
|
||||||
return Sym.opaque();
|
return Sym.opaque();
|
||||||
}
|
}
|
||||||
return new RegisterSym(register);
|
return new RegisterSym(register, -1);
|
||||||
}
|
}
|
||||||
if (address.isStackAddress()) {
|
if (address.isStackAddress()) {
|
||||||
return new StackDerefSym(address.getOffset(), size);
|
return new StackDerefSym(address.getOffset(), -1, size);
|
||||||
}
|
}
|
||||||
return Sym.opaque();
|
return Sym.opaque();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -98,8 +98,7 @@ public class UnwindAnalysis {
|
||||||
/**
|
/**
|
||||||
* Wrap a {@link CodeBlock}
|
* Wrap a {@link CodeBlock}
|
||||||
*/
|
*/
|
||||||
record BlockVertex(CodeBlock block) {
|
record BlockVertex(CodeBlock block) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a {@link CodeBlockReference}
|
* Wrap a {@link CodeBlockReference}
|
||||||
|
@ -492,6 +491,7 @@ public class UnwindAnalysis {
|
||||||
new UnwindException("Cannot determine address of return pointer");
|
new UnwindException("Cannot determine address of return pointer");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
long maskOfReturn = exitState.computeMaskOfReturn();
|
||||||
Long adjust = exitState.computeStackDepth();
|
Long adjust = exitState.computeStackDepth();
|
||||||
if (adjust == null) {
|
if (adjust == null) {
|
||||||
lastError = new UnwindException("Cannot determine stack adjustment");
|
lastError = new UnwindException("Cannot determine stack adjustment");
|
||||||
|
@ -501,8 +501,8 @@ public class UnwindAnalysis {
|
||||||
warnings.addAll(exitState.warnings);
|
warnings.addAll(exitState.warnings);
|
||||||
Map<Register, Address> mapByExit = exitState.computeMapUsingRegisters();
|
Map<Register, Address> mapByExit = exitState.computeMapUsingRegisters();
|
||||||
mapByExit.entrySet().retainAll(mapByEntry.entrySet());
|
mapByExit.entrySet().retainAll(mapByEntry.entrySet());
|
||||||
return new UnwindInfo(function, depth, adjust, addressOfReturn, mapByExit,
|
return new UnwindInfo(function, depth, adjust, addressOfReturn, maskOfReturn,
|
||||||
new StackUnwindWarningSet(warnings), null);
|
mapByExit, new StackUnwindWarningSet(warnings), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lastSuccessfulEntryState != null) {
|
if (lastSuccessfulEntryState != null) {
|
||||||
|
@ -510,16 +510,16 @@ public class UnwindAnalysis {
|
||||||
try {
|
try {
|
||||||
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
|
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
|
||||||
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
||||||
adjust, null, lastSuccessfulEntryState.computeMapUsingStack(),
|
adjust, null, -1, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||||
new StackUnwindWarningSet(warnings), lastError);
|
new StackUnwindWarningSet(warnings), lastError);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
||||||
null, null, lastSuccessfulEntryState.computeMapUsingStack(),
|
null, null, -1, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||||
new StackUnwindWarningSet(warnings), e);
|
new StackUnwindWarningSet(warnings), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new UnwindInfo(function, null, null, null, null,
|
return new UnwindInfo(function, null, null, null, -1, null,
|
||||||
new StackUnwindWarningSet(warnings), new UnwindException(
|
new StackUnwindWarningSet(warnings), new UnwindException(
|
||||||
"Could not analyze any path from %s entry to %s.\n%s".formatted(function, pc,
|
"Could not analyze any path from %s entry to %s.\n%s".formatted(function, pc,
|
||||||
lastError.getMessage()),
|
lastError.getMessage()),
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -31,12 +31,28 @@ import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information for interpreting the current stack frame and unwinding to the next
|
* Information for interpreting the current stack frame and unwinding to the next
|
||||||
|
*
|
||||||
|
* @param function see {@link #function()}
|
||||||
|
* @param depth see {@link #depth()}
|
||||||
|
* @param adjust see {@link #adjust()}
|
||||||
|
* @param ofReturn see {@link #ofReturn()}
|
||||||
|
* @param maskOfReturn see {@link #maskOfReturn()}
|
||||||
|
* @param saved see {@link #saved()}
|
||||||
|
* @param warnings see {@link #warnings()}
|
||||||
|
* @param error see {@link #error()}
|
||||||
*/
|
*/
|
||||||
public record UnwindInfo(Function function, Long depth, Long adjust, Address ofReturn,
|
public record UnwindInfo(Function function, Long depth, Long adjust, Address ofReturn,
|
||||||
Map<Register, Address> saved, StackUnwindWarningSet warnings, Exception error) {
|
long maskOfReturn, Map<Register, Address> saved, StackUnwindWarningSet warnings,
|
||||||
|
Exception error) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an error-only info
|
||||||
|
*
|
||||||
|
* @param error the error
|
||||||
|
* @return the info containing only the error
|
||||||
|
*/
|
||||||
public static UnwindInfo errorOnly(Exception error) {
|
public static UnwindInfo errorOnly(Exception error) {
|
||||||
return new UnwindInfo(null, null, null, null, null, new StackUnwindWarningSet(), error);
|
return new UnwindInfo(null, null, null, null, -1, null, new StackUnwindWarningSet(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,6 +104,29 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR
|
||||||
return ofReturn;
|
return ofReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mask applied to the return address
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is to handle ISAs that use the low bits of addresses in jumps to indicate an ISA switch.
|
||||||
|
* Often, the code that returns from a function will apply a mask. If that is the case, this
|
||||||
|
* returns that mask. In most cases, this returns -1, which when applied as a mask has no
|
||||||
|
* effect.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE</b>: There is currently no tracking of the ISA mode by the stack unwinder. First, the
|
||||||
|
* conventions for tracking that in the Sleigh specification varies from processor to processor.
|
||||||
|
* There is often custom-made handling of that bit programmed in Java for the emulator, but it's
|
||||||
|
* not generally accessible for static analysis. Second, for stack unwinding purposes, we use
|
||||||
|
* the statically disassembled code at the return address, anyway. That should already be of the
|
||||||
|
* correct ISA; if not, then we are already lost.
|
||||||
|
*
|
||||||
|
* @return the mask, often -1
|
||||||
|
*/
|
||||||
|
public long maskOfReturn() {
|
||||||
|
return maskOfReturn;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The <em>address of</em> the return address, given a stack base
|
* The <em>address of</em> the return address, given a stack base
|
||||||
*
|
*
|
||||||
|
@ -172,7 +211,7 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR
|
||||||
* Add register map entries for the saved registers in this frame
|
* Add register map entries for the saved registers in this frame
|
||||||
*
|
*
|
||||||
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}
|
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}
|
||||||
* @param registerMap the register map of the stack to this point, to be modified
|
* @param map the register map of the stack to this point, to be modified
|
||||||
*/
|
*/
|
||||||
public void mapSavedRegisters(Address base, SavedRegisterMap map) {
|
public void mapSavedRegisters(Address base, SavedRegisterMap map) {
|
||||||
for (Entry<Register, Address> ent : saved.entrySet()) {
|
for (Entry<Register, Address> ent : saved.entrySet()) {
|
||||||
|
@ -221,7 +260,8 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR
|
||||||
AddressSpace codeSpace, Register pc) {
|
AddressSpace codeSpace, Register pc) {
|
||||||
T value = computeNextPc(base, state, pc);
|
T value = computeNextPc(base, state, pc);
|
||||||
long concrete = state.getArithmetic().toLong(value, Purpose.INSPECT);
|
long concrete = state.getArithmetic().toLong(value, Purpose.INSPECT);
|
||||||
return codeSpace.getAddress(concrete);
|
long masked = concrete & maskOfReturn;
|
||||||
|
return codeSpace.getAddress(masked);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,6 +38,7 @@ import ghidra.app.decompiler.*;
|
||||||
import ghidra.app.decompiler.component.*;
|
import ghidra.app.decompiler.component.*;
|
||||||
import ghidra.app.plugin.assembler.*;
|
import ghidra.app.plugin.assembler.*;
|
||||||
import ghidra.app.plugin.assembler.sleigh.sem.*;
|
import ghidra.app.plugin.assembler.sleigh.sem.*;
|
||||||
|
import ghidra.app.plugin.core.analysis.*;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
|
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
|
@ -81,6 +82,7 @@ import ghidra.trace.model.TraceLocation;
|
||||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||||
import ghidra.trace.model.listing.TraceData;
|
import ghidra.trace.model.listing.TraceData;
|
||||||
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.Scheduler;
|
import ghidra.trace.model.time.schedule.Scheduler;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -587,6 +589,59 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Function createInfiniteRecursionProgramArm() throws Throwable {
|
||||||
|
createProgram("ARM:LE:32:v8", "default");
|
||||||
|
intoProject(program);
|
||||||
|
try (Transaction tx = program.openTransaction("Assemble")) {
|
||||||
|
Address entry = addr(program, 0x00400000);
|
||||||
|
program.getMemory()
|
||||||
|
.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
|
||||||
|
|
||||||
|
Assembler asm = Assemblers.getAssembler(program.getLanguage());
|
||||||
|
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
|
||||||
|
|
||||||
|
Language language = asm.getLanguage();
|
||||||
|
Register regCtx = language.getContextBaseRegister();
|
||||||
|
Register regT = language.getRegister("T");
|
||||||
|
RegisterValue rvDefault = new RegisterValue(regCtx,
|
||||||
|
asm.getContextAt(entry).toBigInteger(regCtx.getNumBytes()));
|
||||||
|
RegisterValue rvThumb = rvDefault.assign(regT, BigInteger.ONE);
|
||||||
|
AssemblyPatternBlock ctxThumb = AssemblyPatternBlock.fromRegisterValue(rvThumb);
|
||||||
|
|
||||||
|
buf.emit(NumericUtilities.convertStringToBytes("00 b5")); // push {lr}
|
||||||
|
buf.emit(NumericUtilities.convertStringToBytes("00 04")); // lsl r0,r0,#0x10
|
||||||
|
buf.emit(NumericUtilities.convertStringToBytes("00 0c")); // lsr r0,r0,#0x10
|
||||||
|
buf.assemble("bl 0x%s".formatted(entry), ctxThumb);
|
||||||
|
bodyInstr = buf.getNext();
|
||||||
|
buf.assemble("pop {r0}", ctxThumb);
|
||||||
|
buf.assemble("bx r0", ctxThumb);
|
||||||
|
|
||||||
|
byte[] bytes = buf.getBytes();
|
||||||
|
program.getMemory().setBytes(entry, bytes);
|
||||||
|
|
||||||
|
Disassembler dis = Disassembler.getDisassembler(program, monitor, null);
|
||||||
|
dis.disassemble(entry, null, rvThumb, false);
|
||||||
|
|
||||||
|
Function func = program.getFunctionManager()
|
||||||
|
.createFunction("recurseInfinitely", entry,
|
||||||
|
new AddressSet(entry, entry.add(bytes.length - 1)),
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void openAndAnalyze(Function function) {
|
||||||
|
GhidraProgramUtilities.markProgramNotToAskToAnalyze(program);
|
||||||
|
programManager.openProgram(program);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
AutoAnalysisManager analysisManager = AutoAnalysisManager.getAnalysisManager(program);
|
||||||
|
tool.executeBackgroundCommand(new AnalysisBackgroundCommand(analysisManager, true),
|
||||||
|
program);
|
||||||
|
analysisManager.reAnalyzeAll(function.getBody());
|
||||||
|
waitForTasks();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testComputeUnwindInfoX86_32() throws Throwable {
|
public void testComputeUnwindInfoX86_32() throws Throwable {
|
||||||
addPlugin(tool, CodeBrowserPlugin.class);
|
addPlugin(tool, CodeBrowserPlugin.class);
|
||||||
|
@ -599,13 +654,14 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||||
|
|
||||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||||
assertEquals(
|
assertEquals(new UnwindInfo(function, 0L, 4L, stack(0), -1,
|
||||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
Map.of(), new StackUnwindWarningSet(), null),
|
||||||
infoAtEntry);
|
infoAtEntry);
|
||||||
|
|
||||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0), -1,
|
||||||
Map.of(register("EBP"), stack(-4)), new StackUnwindWarningSet(), null), infoAtBody);
|
Map.of(register("EBP"), stack(-4)), new StackUnwindWarningSet(), null),
|
||||||
|
infoAtBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -623,16 +679,17 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||||
|
|
||||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||||
assertEquals(
|
assertEquals(new UnwindInfo(function, 0L, 4L, stack(0), -1,
|
||||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
Map.of(), new StackUnwindWarningSet(), null),
|
||||||
infoAtEntry);
|
infoAtEntry);
|
||||||
|
|
||||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||||
assertEquals(
|
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0), -1,
|
||||||
new UnwindInfo(function, -20L, 4L, stack(0), Map.of(register("EBP"), stack(-4)),
|
Map.of(register("EBP"), stack(-4)),
|
||||||
new StackUnwindWarningSet(new UnspecifiedConventionStackUnwindWarning(myExtern),
|
new StackUnwindWarningSet(
|
||||||
new UnknownPurgeStackUnwindWarning(myExtern)),
|
new UnspecifiedConventionStackUnwindWarning(myExtern),
|
||||||
null),
|
new UnknownPurgeStackUnwindWarning(myExtern)),
|
||||||
|
null),
|
||||||
infoAtBody);
|
infoAtBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,18 +706,47 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||||
|
|
||||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||||
assertEquals(
|
assertEquals(new UnwindInfo(function, 0L, 4L, stack(0), -1,
|
||||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
Map.of(), new StackUnwindWarningSet(), null),
|
||||||
infoAtEntry);
|
infoAtEntry);
|
||||||
|
|
||||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||||
DataType ptr2Undef = new PointerDataType(DataType.DEFAULT, program.getDataTypeManager());
|
DataType ptr2Undef = new PointerDataType(DataType.DEFAULT, program.getDataTypeManager());
|
||||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0), -1,
|
||||||
Map.of(register("EBP"), stack(-4)),
|
Map.of(register("EBP"), stack(-4)),
|
||||||
new StackUnwindWarningSet(new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef)), null),
|
new StackUnwindWarningSet(new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef)), null),
|
||||||
infoAtBody);
|
infoAtBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComputeUnwindInfoWithArmBx() throws Throwable {
|
||||||
|
addPlugin(tool, CodeBrowserPlugin.class);
|
||||||
|
addPlugin(tool, AutoAnalysisPlugin.class);
|
||||||
|
addPlugin(tool, DecompilePlugin.class);
|
||||||
|
|
||||||
|
Function function = createInfiniteRecursionProgramArm();
|
||||||
|
openAndAnalyze(function);
|
||||||
|
Address entry = function.getEntryPoint();
|
||||||
|
|
||||||
|
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||||
|
|
||||||
|
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||||
|
assertEquals(new UnwindInfo(function, 0L, 0L, register("lr").getAddress(), 0xfffffffeL,
|
||||||
|
Map.of(), new StackUnwindWarningSet(
|
||||||
|
new UnspecifiedConventionStackUnwindWarning(function),
|
||||||
|
new UnknownPurgeStackUnwindWarning(function)),
|
||||||
|
null),
|
||||||
|
infoAtEntry);
|
||||||
|
|
||||||
|
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||||
|
assertEquals(new UnwindInfo(function, -4L, 0L, stack(-4), 0xfffffffeL,
|
||||||
|
Map.of(), new StackUnwindWarningSet(
|
||||||
|
new UnspecifiedConventionStackUnwindWarning(function),
|
||||||
|
new UnknownPurgeStackUnwindWarning(function)),
|
||||||
|
null),
|
||||||
|
infoAtBody);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnwindTopFrameX86_32() throws Throwable {
|
public void testUnwindTopFrameX86_32() throws Throwable {
|
||||||
addPlugin(tool, CodeBrowserPlugin.class);
|
addPlugin(tool, CodeBrowserPlugin.class);
|
||||||
|
@ -814,6 +900,95 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
assertEquals(BigInteger.valueOf(34), retVal.toBigInteger(false));
|
assertEquals(BigInteger.valueOf(34), retVal.toBigInteger(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnwindRecursiveArmThumb() throws Throwable {
|
||||||
|
addPlugin(tool, CodeBrowserPlugin.class);
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
addPlugin(tool, DisassemblerPlugin.class);
|
||||||
|
addPlugin(tool, AutoAnalysisPlugin.class);
|
||||||
|
addPlugin(tool, DecompilePlugin.class);
|
||||||
|
DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
|
||||||
|
|
||||||
|
Function function = createInfiniteRecursionProgramArm();
|
||||||
|
openAndAnalyze(function);
|
||||||
|
Address entry = function.getEntryPoint();
|
||||||
|
|
||||||
|
useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this));
|
||||||
|
tb.trace.release(this);
|
||||||
|
TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads());
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateThread(thread);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
DebuggerCoordinates atSetup = traceManager.getCurrent();
|
||||||
|
StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform());
|
||||||
|
|
||||||
|
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||||
|
Register sp = program.getCompilerSpec().getStackPointer();
|
||||||
|
long spAtSetup = regs.getValue(0, sp).getUnsignedValue().longValueExact();
|
||||||
|
|
||||||
|
TraceBreakpoint bptUnwind;
|
||||||
|
try (Transaction tx = tb.startTransaction()) {
|
||||||
|
bptUnwind = tb.trace.getBreakpointManager()
|
||||||
|
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), entry, Set.of(),
|
||||||
|
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack");
|
||||||
|
bptUnwind.setEmuSleigh("""
|
||||||
|
if (%s >= 0x%x) goto <skip>;
|
||||||
|
emu_swi();
|
||||||
|
<skip>
|
||||||
|
emu_exec_decoded();
|
||||||
|
""".formatted(sp, spAtSetup - 0xc));
|
||||||
|
}
|
||||||
|
|
||||||
|
EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor,
|
||||||
|
Scheduler.oneThread(thread));
|
||||||
|
Msg.debug(this, "Broke after " + result.schedule());
|
||||||
|
|
||||||
|
traceManager.activateTime(result.schedule());
|
||||||
|
waitForTasks();
|
||||||
|
DebuggerCoordinates broke = traceManager.getCurrent();
|
||||||
|
|
||||||
|
AnalysisUnwoundFrame<WatchValue> frameBroke = unwinder.start(broke, monitor);
|
||||||
|
assertEquals(tb.addr(0x00004ff0), frameBroke.getStackPointer());
|
||||||
|
assertEquals(tb.addr(0x00004ff0), frameBroke.getBasePointer());
|
||||||
|
assertEquals(tb.addr(0x00400000), frameBroke.getProgramCounter());
|
||||||
|
|
||||||
|
frameBroke = frameBroke.unwindNext(monitor); // Innermost hadn't pushed anything, yet
|
||||||
|
assertEquals(tb.addr(0x00004ff0), frameBroke.getStackPointer());
|
||||||
|
assertEquals(tb.addr(0x00004ff4), frameBroke.getBasePointer());
|
||||||
|
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||||
|
|
||||||
|
frameBroke = frameBroke.unwindNext(monitor);
|
||||||
|
assertEquals(tb.addr(0x00004ff4), frameBroke.getStackPointer());
|
||||||
|
assertEquals(tb.addr(0x00004ff8), frameBroke.getBasePointer());
|
||||||
|
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||||
|
|
||||||
|
frameBroke = frameBroke.unwindNext(monitor);
|
||||||
|
assertEquals(tb.addr(0x00004ff8), frameBroke.getStackPointer());
|
||||||
|
assertEquals(tb.addr(0x00004ffc), frameBroke.getBasePointer());
|
||||||
|
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||||
|
|
||||||
|
frameBroke = frameBroke.unwindNext(monitor);
|
||||||
|
assertEquals(tb.addr(0x00004ffc), frameBroke.getStackPointer());
|
||||||
|
assertEquals(tb.addr(0x00005000), frameBroke.getBasePointer());
|
||||||
|
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||||
|
|
||||||
|
frameBroke = frameBroke.unwindNext(monitor);
|
||||||
|
assertEquals(tb.addr(0x00005000), frameBroke.getStackPointer());
|
||||||
|
assertNull(frameBroke.getBasePointer());
|
||||||
|
assertEquals(tb.addr(0x00000000), frameBroke.getProgramCounter());
|
||||||
|
|
||||||
|
assertEquals(5, frameBroke.getLevel());
|
||||||
|
|
||||||
|
try {
|
||||||
|
frameBroke.unwindNext(monitor);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (NoSuchElementException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateFramesAtEntryX86_32() throws Throwable {
|
public void testCreateFramesAtEntryX86_32() throws Throwable {
|
||||||
addPlugin(tool, CodeBrowserPlugin.class);
|
addPlugin(tool, CodeBrowserPlugin.class);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue