Skip to content

Commit 23ccf4c

Browse files
committed
Coalesce Thumb IT blocks with trailing conditional branches
1 parent f0d2df0 commit 23ccf4c

1 file changed

Lines changed: 219 additions & 72 deletions

File tree

arch/armv7/thumb2_disasm/arch_thumb2.cpp

Lines changed: 219 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,55 @@ using namespace std;
2020
#define snprintf _snprintf
2121
#endif
2222

23+
static bool IsConditionalBranch(const decomp_result& decomp)
24+
{
25+
return (decomp.mnem == ARMV7_B) && (decomp.format->operationFlags & INSTR_FORMAT_FLAG_CONDITIONAL)
26+
&& (decomp.fields[FIELD_cond] != COND_AL);
27+
}
28+
29+
static bool IsSameOrInvertedCondition(uint32_t lhs, uint32_t rhs)
30+
{
31+
return (lhs == rhs) || ((lhs < COND_AL) && (rhs < COND_AL) && ((lhs ^ 1) == rhs));
32+
}
33+
34+
static bool ThumbITInstructionWritesAPSR(const decomp_result& decomp)
35+
{
36+
switch (decomp.mnem)
37+
{
38+
case ARMV7_CMN:
39+
case ARMV7_CMP:
40+
case ARMV7_TEQ:
41+
case ARMV7_TST:
42+
return true;
43+
default:
44+
return false;
45+
}
46+
}
47+
48+
static uint32_t GetConditionalBranchTarget(const decomp_result& decomp)
49+
{
50+
return decomp.pc + decomp.fields[decomp.format->operands[0].field0];
51+
}
52+
53+
static void EmitDirectThumbJump(Architecture* arch, LowLevelILFunction& il, uint32_t target)
54+
{
55+
BNLowLevelILLabel* label = il.GetLabelForAddress(arch, target);
56+
if (label)
57+
il.AddInstruction(il.Goto(*label));
58+
else
59+
il.AddInstruction(il.Jump(il.ConstPointer(4, target)));
60+
}
61+
62+
struct ThumbITSlot
63+
{
64+
uint32_t addr = 0;
65+
decomp_result decomp = {};
66+
bool thenSlot = false;
67+
bool lift = false;
68+
bool directBranch = false;
69+
uint32_t branchTarget = 0;
70+
};
71+
2372
static Ref<Enumeration> get_msr_op_enum()
2473
{
2574
EnumerationBuilder builder;
@@ -167,7 +216,9 @@ class Thumb2Architecture: public ArmCommonArchitecture
167216

168217
virtual size_t GetMaxInstructionLength() const override
169218
{
170-
return 18; // IT blocks can have up to four following associated instructions
219+
// IT blocks can have up to four following associated instructions, and
220+
// may be coalesced with a following conditional branch.
221+
return 22;
171222
}
172223

173224
virtual size_t GetInstructionAlignment() const override
@@ -232,6 +283,8 @@ class Thumb2Architecture: public ArmCommonArchitecture
232283
bool falseBranched = false;
233284
bool trueReturned = false;
234285
bool falseReturned = false;
286+
bool trueWroteFlags = false;
287+
bool falseWroteFlags = false;
235288

236289
uint64_t trueBranchTargetAddr = 0;
237290
uint64_t falseBranchTargetAddr = 0;
@@ -240,56 +293,95 @@ class Thumb2Architecture: public ArmCommonArchitecture
240293
{
241294
bool isTrue = (i == 0) || (((mask >> (4 - i)) & 1) == (cond & 1));
242295

243-
InstructionInfo innerResult;
244-
if (!GetInstructionInfo(data + offset, addr + offset, maxLen - offset, innerResult))
296+
if (offset >= maxLen || (maxLen - offset) < 2)
297+
break;
298+
299+
decomp_result innerDecomp;
300+
size_t remainingLen = maxLen - offset;
301+
bool decoded = populateDecomposeRequest(&request, data + offset, remainingLen, addr + offset,
302+
IFTHEN_YES, ((i + 1) >= instrCount) ? IFTHENLAST_YES : IFTHENLAST_NO)
303+
&& (thumb_decompose(&request, &innerDecomp) == STATUS_OK)
304+
&& !(innerDecomp.status & STATUS_UNDEFINED) && innerDecomp.format;
305+
if (!decoded)
245306
break;
246-
if ((offset + innerResult.length) > maxLen)
307+
size_t innerLen = innerDecomp.instrSize / 8;
308+
if ((innerLen == 0) || (innerLen > remainingLen))
247309
break;
248310

249311
bool& terminated = isTrue ? trueTerminated : falseTerminated;
250312
bool& branched = isTrue ? trueBranched : falseBranched;
251313
bool& returned = isTrue ? trueReturned : falseReturned;
314+
bool& wroteFlags = isTrue ? trueWroteFlags : falseWroteFlags;
252315
uint64_t& branchTarget = isTrue ? trueBranchTargetAddr : falseBranchTargetAddr;
253316

254317
// Only process if the conditional branch we're following isn't terminated
255318
// Otherwise, just track if the arch is switching and the offset
256319
if (!terminated)
257320
{
258-
for (size_t j = 0; j < innerResult.branchCount; j++)
321+
wroteFlags |= ThumbITInstructionWritesAPSR(innerDecomp);
322+
323+
InstructionInfo innerResult;
324+
if (GetInstructionInfo(data + offset, addr + offset, remainingLen, innerResult))
259325
{
260-
switch (innerResult.branchType[j])
326+
for (size_t j = 0; j < innerResult.branchCount; j++)
261327
{
262-
case UnconditionalBranch:
263-
case TrueBranch:
264-
case FalseBranch:
265-
branched = true;
266-
terminated = true;
267-
branchTarget = innerResult.branchTarget[j];
268-
break;
269-
case FunctionReturn:
270-
returned = true;
271-
terminated = true;
272-
break;
273-
case CallDestination:
274-
result.AddBranch(CallDestination, innerResult.branchTarget[j],
275-
innerResult.branchArch[j] ? m_armArch : this);
276-
break;
277-
case UnresolvedBranch:
278-
case IndirectBranch:
279-
case ExceptionBranch:
280-
// We don't know the branch target so just set terminated
281-
terminated = true;
282-
break;
283-
default:
284-
break;
328+
switch (innerResult.branchType[j])
329+
{
330+
case UnconditionalBranch:
331+
case TrueBranch:
332+
case FalseBranch:
333+
branched = true;
334+
terminated = true;
335+
branchTarget = innerResult.branchTarget[j];
336+
break;
337+
case FunctionReturn:
338+
returned = true;
339+
terminated = true;
340+
break;
341+
case CallDestination:
342+
result.AddBranch(CallDestination, innerResult.branchTarget[j],
343+
innerResult.branchArch[j] ? m_armArch : this);
344+
break;
345+
case UnresolvedBranch:
346+
case IndirectBranch:
347+
case ExceptionBranch:
348+
// We don't know the branch target so just set terminated
349+
terminated = true;
350+
break;
351+
default:
352+
break;
353+
}
285354
}
355+
356+
if (innerResult.archTransitionByTargetAddr)
357+
result.archTransitionByTargetAddr = true;
286358
}
287359
}
288360

289-
if (innerResult.archTransitionByTargetAddr)
290-
result.archTransitionByTargetAddr = true;
361+
offset += innerLen;
362+
}
363+
364+
decomp_result branchDecomp;
365+
if ((offset < maxLen) && ((maxLen - offset) >= 2)
366+
&& populateDecomposeRequest(&request, data + offset, maxLen - offset, addr + offset, IFTHEN_NO, IFTHENLAST_NO)
367+
&& (thumb_decompose(&request, &branchDecomp) == STATUS_OK)
368+
&& ((offset + (branchDecomp.instrSize / 8)) <= maxLen)
369+
&& !(branchDecomp.status & STATUS_UNDEFINED) && branchDecomp.format && IsConditionalBranch(branchDecomp)
370+
&& IsSameOrInvertedCondition(branchDecomp.fields[FIELD_cond], cond))
371+
{
372+
bool branchOnTrue = branchDecomp.fields[FIELD_cond] == cond;
373+
bool& terminated = branchOnTrue ? trueTerminated : falseTerminated;
374+
bool& branched = branchOnTrue ? trueBranched : falseBranched;
375+
bool& wroteFlags = branchOnTrue ? trueWroteFlags : falseWroteFlags;
376+
uint64_t& branchTarget = branchOnTrue ? trueBranchTargetAddr : falseBranchTargetAddr;
291377

292-
offset += innerResult.length;
378+
if (!terminated && !wroteFlags)
379+
{
380+
branched = true;
381+
terminated = true;
382+
branchTarget = GetConditionalBranchTarget(branchDecomp);
383+
offset += branchDecomp.instrSize / 8;
384+
}
293385
}
294386

295387
result.length = offset;
@@ -2748,82 +2840,137 @@ class Thumb2Architecture: public ArmCommonArchitecture
27482840
uint32_t mask = decomp.fields[FIELD_mask];
27492841
uint32_t cond = decomp.fields[FIELD_firstcond];
27502842

2751-
// Calculate number of instructions
27522843
size_t instrCount;
2753-
if (decomp.fields[FIELD_mask] & 1)
2844+
if (mask & 1)
27542845
instrCount = 4;
2755-
else if (decomp.fields[FIELD_mask] & 2)
2846+
else if (mask & 2)
27562847
instrCount = 3;
2757-
else if (decomp.fields[FIELD_mask] & 4)
2848+
else if (mask & 4)
27582849
instrCount = 2;
27592850
else
27602851
instrCount = 1;
27612852

2762-
// decompose all instructions in the if-then block
2763-
vector<uint32_t> addrsTrue, addrsFalse;
2764-
vector<decomp_result> decompsTrue, decompsFalse;
2853+
// Decompose all instructions in the IT block and keep their original
2854+
// mask slot. A path may skip later slots once it has branched/returned.
2855+
vector<ThumbITSlot> slots;
2856+
bool pathTerminated[2] = {false, false};
2857+
bool pathHasBody[2] = {false, false};
2858+
bool pathWroteFlags[2] = {false, false};
27652859

27662860
for (size_t i = 0; i < instrCount; i++)
27672861
{
27682862
if (offset >= len || (len - offset) < 2)
27692863
return false;
27702864

2771-
bool isTrue = (i == 0) || (((mask >> (4 - i)) & 1) == (cond & 1));
2865+
bool thenSlot = (i == 0) || (((mask >> (4 - i)) & 1) == (cond & 1));
2866+
size_t stateIdx = thenSlot ? 0 : 1;
27722867
size_t remainingLen = len - offset;
27732868

2774-
if (!populateDecomposeRequest(&request, data+offset, remainingLen, addr+offset,
2775-
IFTHEN_YES, ((i + 1) >= instrCount) ? IFTHENLAST_YES : IFTHENLAST_NO))
2776-
return false;
2777-
2778-
if (thumb_decompose(&request, &decomp) != STATUS_OK)
2869+
bool decoded = populateDecomposeRequest(&request, data + offset, remainingLen, addr + offset,
2870+
IFTHEN_YES, ((i + 1) >= instrCount) ? IFTHENLAST_YES : IFTHENLAST_NO)
2871+
&& (thumb_decompose(&request, &decomp) == STATUS_OK)
2872+
&& !(decomp.status & STATUS_UNDEFINED) && decomp.format;
2873+
if (!decoded)
27792874
return false;
27802875
if ((decomp.instrSize / 8) > remainingLen)
27812876
return false;
2782-
if ((decomp.status & STATUS_UNDEFINED) || (!decomp.format))
2783-
return false;
27842877

2785-
if (isTrue) {
2786-
addrsTrue.push_back(request.addr);
2787-
decompsTrue.push_back(decomp);
2788-
}
2789-
else {
2790-
addrsFalse.push_back(request.addr);
2791-
decompsFalse.push_back(decomp);
2878+
ThumbITSlot slot;
2879+
slot.addr = request.addr;
2880+
slot.decomp = decomp;
2881+
slot.thenSlot = thenSlot;
2882+
slot.lift = !pathTerminated[stateIdx];
2883+
slots.push_back(slot);
2884+
pathHasBody[stateIdx] |= slot.lift;
2885+
2886+
if (slot.lift)
2887+
{
2888+
pathWroteFlags[stateIdx] |= ThumbITInstructionWritesAPSR(decomp);
2889+
2890+
InstructionInfo innerResult;
2891+
if (GetInstructionInfo(data + offset, addr + offset, remainingLen, innerResult))
2892+
{
2893+
for (size_t j = 0; j < innerResult.branchCount; j++)
2894+
{
2895+
switch (innerResult.branchType[j])
2896+
{
2897+
case UnconditionalBranch:
2898+
case TrueBranch:
2899+
case FalseBranch:
2900+
case FunctionReturn:
2901+
case UnresolvedBranch:
2902+
case IndirectBranch:
2903+
case ExceptionBranch:
2904+
pathTerminated[stateIdx] = true;
2905+
break;
2906+
default:
2907+
break;
2908+
}
2909+
}
2910+
}
27922911
}
27932912

27942913
offset += decomp.instrSize / 8;
27952914
}
27962915

2916+
decomp_result branchDecomp;
2917+
2918+
if ((offset < len) && ((len - offset) >= 2)
2919+
&& populateDecomposeRequest(&request, data + offset, len - offset, addr + offset, IFTHEN_NO, IFTHENLAST_NO)
2920+
&& (thumb_decompose(&request, &branchDecomp) == STATUS_OK)
2921+
&& ((offset + (branchDecomp.instrSize / 8)) <= len)
2922+
&& !(branchDecomp.status & STATUS_UNDEFINED) && branchDecomp.format && IsConditionalBranch(branchDecomp)
2923+
&& IsSameOrInvertedCondition(branchDecomp.fields[FIELD_cond], cond))
2924+
{
2925+
bool branchOnTrue = branchDecomp.fields[FIELD_cond] == cond;
2926+
size_t stateIdx = branchOnTrue ? 0 : 1;
2927+
if (!pathTerminated[stateIdx] && !pathWroteFlags[stateIdx])
2928+
{
2929+
ThumbITSlot slot;
2930+
slot.addr = request.addr;
2931+
slot.thenSlot = branchOnTrue;
2932+
slot.lift = true;
2933+
slot.directBranch = true;
2934+
slot.branchTarget = GetConditionalBranchTarget(branchDecomp);
2935+
slots.push_back(slot);
2936+
pathHasBody[stateIdx] = true;
2937+
pathTerminated[stateIdx] = true;
2938+
offset += branchDecomp.instrSize / 8;
2939+
}
2940+
}
2941+
27972942
// generate IL
27982943
LowLevelILLabel labelTrue, labelFalse, labelDone;
27992944

28002945
il.AddInstruction(il.If(GetCondition(il, cond), labelTrue, labelFalse));
28012946

2802-
// generate IL for "true" if-else members
2803-
il.MarkLabel(labelTrue);
2804-
2805-
for (size_t i = 0; i < decompsTrue.size(); i++)
2947+
auto liftPath = [&](bool thenPath, LowLevelILLabel& label)
28062948
{
2807-
il.SetCurrentAddress(this, addrsTrue[i]);
2808-
GetLowLevelILForThumbInstruction(this, il, &(decompsTrue[i]), true);
2809-
}
2949+
size_t stateIdx = thenPath ? 0 : 1;
28102950

2811-
if (decompsFalse.empty()) {
2812-
il.MarkLabel(labelFalse);
2813-
}
2814-
else {
2815-
il.AddInstruction(il.Goto(labelDone));
2816-
il.MarkLabel(labelFalse);
2951+
il.MarkLabel(label);
28172952

2818-
// generate IL for "false" if-else members
2819-
for (int i = 0; i < decompsFalse.size(); i++)
2953+
for (auto& slot : slots)
28202954
{
2821-
il.SetCurrentAddress(this, addrsFalse[i]);
2822-
GetLowLevelILForThumbInstruction(this, il, &(decompsFalse[i]), true);
2955+
if (!slot.lift || (slot.thenSlot != thenPath))
2956+
continue;
2957+
2958+
il.SetCurrentAddress(this, slot.addr);
2959+
if (slot.directBranch)
2960+
EmitDirectThumbJump(this, il, slot.branchTarget);
2961+
else
2962+
GetLowLevelILForThumbInstruction(this, il, &slot.decomp, true);
28232963
}
28242964

2965+
if (thenPath && pathHasBody[1] && !pathTerminated[stateIdx])
2966+
il.AddInstruction(il.Goto(labelDone));
2967+
};
2968+
2969+
liftPath(true, labelTrue);
2970+
liftPath(false, labelFalse);
2971+
2972+
if (pathHasBody[1])
28252973
il.MarkLabel(labelDone);
2826-
}
28272974

28282975
len = offset;
28292976
return true;

0 commit comments

Comments
 (0)