Skip to content

Commit 65409da

Browse files
committed
GP-5583 fixed default switch case labels and removed default case
address from switching instruction
1 parent 7c67a10 commit 65409da

File tree

2 files changed

+389
-84
lines changed

2 files changed

+389
-84
lines changed

Ghidra/Features/Decompiler/src/main/java/ghidra/app/cmd/function/DecompilerSwitchAnalysisCmd.java

Lines changed: 162 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
7-
*
7+
*
88
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
9+
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -114,34 +114,8 @@ private void processBranchIND(Function f, HighFunction hfunction, TaskMonitor mo
114114
continue; // skip switch owned by a different defined function
115115
}
116116

117-
AddressSetView containingBody =
118-
containingFunction != null ? containingFunction.getBody() : null;
119-
120-
Reference[] referencesFrom = instr.getReferencesFrom();
121-
Address[] tableDest = table.getCases();
122-
123-
boolean foundNotThere = false;
124-
int tableIndx;
125-
for (tableIndx = 0; tableIndx < tableDest.length; tableIndx++) {
126-
monitor.checkCancelled();
127-
boolean foundit = false;
128-
if (containingBody != null && !containingBody.contains(tableDest[tableIndx])) {
129-
// switch case missing from owner function's body
130-
foundNotThere = true;
131-
break;
132-
}
133-
for (Reference element : referencesFrom) {
134-
if (element.getToAddress().equals(tableDest[tableIndx])) {
135-
foundit = true;
136-
break;
137-
}
138-
}
139-
if (!foundit) {
140-
foundNotThere = true;
141-
}
142-
}
143117
// references already there, ignore this table
144-
if (!foundNotThere) {
118+
if (hasAllReferences(monitor, table, instr, containingFunction)) {
145119
continue;
146120
}
147121

@@ -158,64 +132,156 @@ private void processBranchIND(Function f, HighFunction hfunction, TaskMonitor mo
158132
labelSwitch(table, monitor);
159133

160134
// disassemble the table
161-
// pull out the current context so we can flow anything that needs to flow
162-
ProgramContext programContext = program.getProgramContext();
163-
Register baseContextRegister = programContext.getBaseContextRegister();
164-
RegisterValue switchContext = null;
165-
if (baseContextRegister != null) {
166-
// Use disassembler context based upon context register value at switch address (i.e., computed jump)
167-
// Only use flowing context bits
168-
switchContext = programContext.getRegisterValue(baseContextRegister, switchAddr);
169-
switchContext = programContext.getFlowValue(switchContext);
135+
disassembleTable(monitor, table, instr, flowType);
136+
137+
// fixup the function body
138+
fixupFunction(f, monitor, instr);
139+
}
140+
}
141+
142+
/*
143+
* Fix the functions body with any newly reached code from the switch recovery
144+
*/
145+
private void fixupFunction(Function f, TaskMonitor monitor, Instruction instr)
146+
throws CancelledException {
147+
Function fixupFunc = f;
148+
149+
// Make sure this case isn't the result of an undefined function,
150+
// that somehow one of the cases found a real function.
151+
if (fixupFunc instanceof UndefinedFunction) {
152+
Function realFunc =
153+
program.getFunctionManager().getFunctionContaining(instr.getMinAddress());
154+
if (realFunc != null) {
155+
fixupFunc = realFunc;
170156
}
157+
}
158+
Instruction funcStartInstr =
159+
program.getListing().getInstructionAt(fixupFunc.getEntryPoint());
160+
CreateFunctionCmd.fixupFunctionBody(program, funcStartInstr, monitor);
161+
}
171162

172-
Listing listing = program.getListing();
173-
Address[] cases = table.getCases();
174-
AddressSet disSetList = new AddressSet();
175-
for (Address caseStart : cases) {
176-
monitor.checkCancelled();
177-
instr.addMnemonicReference(caseStart, flowType, SourceType.ANALYSIS);
163+
/*
164+
* Disassemble all code reached from the table.
165+
* Also adds the case flow references to the switching instruction.
166+
*/
167+
private void disassembleTable(TaskMonitor monitor, JumpTable table,
168+
Instruction instr, FlowType flowType) throws CancelledException {
169+
170+
Address switchAddr = table.getSwitchAddress();
171+
172+
// pull out the current context so we can flow anything that needs to flow
173+
ProgramContext programContext = program.getProgramContext();
174+
Register baseContextRegister = programContext.getBaseContextRegister();
175+
RegisterValue switchContext = null;
176+
if (baseContextRegister != null) {
177+
// Use disassembler context based upon context register value at switch address (i.e., computed jump)
178+
// Only use flowing context bits
179+
switchContext = programContext.getRegisterValue(baseContextRegister, switchAddr);
180+
switchContext = programContext.getFlowValue(switchContext);
181+
}
178182

179-
// if conflict skip case
180-
if (listing.getUndefinedDataAt(caseStart) == null) {
181-
continue;
182-
}
183-
// already done
184-
if (disSetList.contains(caseStart)) {
185-
continue;
186-
}
187-
try {
188-
setSwitchTargetContext(programContext, caseStart, switchContext);
189-
}
190-
catch (ContextChangeException e) {
191-
// This can occur when two or more threads are working on the same function
192-
continue;
193-
}
194-
disSetList.add(caseStart);
183+
Listing listing = program.getListing();
184+
Address[] cases = table.getCases();
185+
Integer[] caseValues = table.getLabelValues();
186+
AddressSet disSetList = new AddressSet();
187+
188+
for (int caseIndex = 0; caseIndex < cases.length; caseIndex++) {
189+
Address caseStart = cases[caseIndex];
190+
monitor.checkCancelled();
191+
192+
if (!isDefaultCase(caseValues, caseIndex)) {
193+
// only non-default cases should be added to the switching instruction
194+
instr.addMnemonicReference(caseStart, flowType, SourceType.ANALYSIS);
195195
}
196196

197-
// do all cases at one time
198-
if (!disSetList.isEmpty()) {
199-
DisassembleCommand cmd = new DisassembleCommand(disSetList, null, true);
200-
cmd.applyTo(program);
197+
// if conflict skip case
198+
if (listing.getUndefinedDataAt(caseStart) == null) {
199+
continue;
200+
}
201+
// already done
202+
if (disSetList.contains(caseStart)) {
203+
continue;
204+
}
205+
try {
206+
setSwitchTargetContext(programContext, caseStart, switchContext);
207+
}
208+
catch (ContextChangeException e) {
209+
// This can occur when two or more threads are working on the same function
210+
continue;
201211
}
212+
disSetList.add(caseStart);
213+
}
202214

203-
// fixup the function body
204-
// make sure this case isn't the result of an undefined function, that somehow one of the cases found a real function.
205-
Function fixupFunc = f;
206-
if (fixupFunc instanceof UndefinedFunction) {
207-
Function realFunc =
208-
program.getFunctionManager().getFunctionContaining(instr.getMinAddress());
209-
if (realFunc != null) {
210-
fixupFunc = realFunc;
215+
// do all cases at one time
216+
if (!disSetList.isEmpty()) {
217+
DisassembleCommand cmd = new DisassembleCommand(disSetList, null, true);
218+
cmd.applyTo(program);
219+
}
220+
}
221+
222+
/*
223+
* Check if this case index is a default case.
224+
*
225+
* In general, each case target address should have an associated caseValue.
226+
* A case is default if it is first case to not have a case value, or has a magic case value.
227+
* It is possible that there could be more than one case without a value. The code shouldn't
228+
* blow up if this is the case.
229+
*
230+
* TODO: Should this check if the default case already has a reference to it
231+
* from a conditional jump?
232+
*/
233+
private boolean isDefaultCase(Integer[] caseValues, int caseIndex) {
234+
return (caseIndex == caseValues.length) ||
235+
(caseIndex < caseValues.length && caseValues[caseIndex] == DEFAULT_CASE_VALUE);
236+
}
237+
238+
/*
239+
* Check if the switching instruction has all switch references already.
240+
* Extra check for default case target as part of the table, when it shouldn't be.
241+
*/
242+
public boolean hasAllReferences(TaskMonitor monitor, JumpTable table, Instruction instr,
243+
Function containingFunction) throws CancelledException {
244+
AddressSetView containingBody =
245+
containingFunction != null ? containingFunction.getBody() : null;
246+
247+
Reference[] referencesFrom = instr.getReferencesFrom();
248+
Address[] tableDest = table.getCases();
249+
Integer[] caseValues = table.getLabelValues();
250+
251+
boolean allRefsFound = true;
252+
int caseIndex;
253+
for (caseIndex = 0; caseIndex < tableDest.length; caseIndex++) {
254+
monitor.checkCancelled();
255+
256+
// a case is default if it is first case to not have a value, or has a magic case value
257+
boolean isDefaultCase = isDefaultCase(caseValues, caseIndex);
258+
259+
boolean foundit = false;
260+
if (containingBody != null && !containingBody.contains(tableDest[caseIndex])) {
261+
// switch case missing from owner function's body
262+
allRefsFound = false;
263+
break;
264+
}
265+
for (Reference element : referencesFrom) {
266+
if (element.getToAddress().equals(tableDest[caseIndex])) {
267+
// good only if this isn't default case
268+
// default case should not be on switching instruction
269+
foundit = !isDefaultCase;
270+
break;
271+
} else if (isDefaultCase) {
272+
foundit = true; // not finding default case is good
211273
}
212274
}
213-
Instruction funcStartInstr =
214-
program.getListing().getInstructionAt(fixupFunc.getEntryPoint());
215-
CreateFunctionCmd.fixupFunctionBody(program, funcStartInstr, monitor);
275+
if (!foundit) {
276+
allRefsFound = false;
277+
}
216278
}
279+
return allRefsFound;
217280
}
218281

282+
/*
283+
* Set the context that should flow to the target so that target instruction will disassemble correctly
284+
*/
219285
private void setSwitchTargetContext(ProgramContext programContext, Address targetStart, RegisterValue switchContext) throws ContextChangeException {
220286
if (switchContext == null) {
221287
return;
@@ -236,6 +302,9 @@ private void setSwitchTargetContext(ProgramContext programContext, Address targe
236302
program.getProgramContext().setRegisterValue(targetStart, targetStart, switchContext);
237303
}
238304

305+
/*
306+
* Label switch table, cases, default with labels in namespace of the switch
307+
*/
239308
private void labelSwitch(JumpTable table, TaskMonitor monitor) throws CancelledException {
240309
AddLabelCmd tableNameLabel =
241310
new AddLabelCmd(table.getSwitchAddress(), "switchD", SourceType.ANALYSIS);
@@ -266,27 +335,33 @@ private void labelSwitch(JumpTable table, TaskMonitor monitor) throws CancelledE
266335
tableNameLabel.applyTo(program);
267336

268337
Address[] switchCases = table.getCases();
269-
Integer[] caseLabels = table.getLabelValues();
270-
Symbol[] caseSymbols = new Symbol[caseLabels.length];
338+
Integer[] caseValues = table.getLabelValues();
339+
Symbol[] caseSymbols = new Symbol[caseValues.length];
271340
SymbolTable symTable = program.getSymbolTable();
272-
for (int i = 0; i < switchCases.length; i++) {
341+
342+
for (int caseIndex = 0; caseIndex < switchCases.length; caseIndex++) {
273343
monitor.checkCancelled();
274-
int offset = (i >= caseLabels.length ? i : caseLabels[i]);
275-
String caseName = "caseD_" + Integer.toHexString(offset);
276-
if (offset == DEFAULT_CASE_VALUE) { // magic constant to indicate default case
344+
345+
// if there are more switchCases than switch values, just use the caseIndex
346+
int caseValue = (caseIndex < caseValues.length) ? caseValues[caseIndex] : caseIndex;
347+
348+
boolean isDefaultCase = isDefaultCase(caseValues, caseIndex);
349+
350+
String caseName = "caseD_" + Integer.toHexString(caseValue);
351+
if (isDefaultCase) {
277352
caseName = "default";
278353
}
279354
AddLabelCmd lcmd =
280-
new AddLabelCmd(switchCases[i], caseName, space, SourceType.ANALYSIS);
355+
new AddLabelCmd(switchCases[caseIndex], caseName, space, SourceType.ANALYSIS);
281356

282357
Symbol oldSym = symTable.getPrimarySymbol(lcmd.getLabelAddr());
283358
if (oldSym != null && oldSym.getSource() == SourceType.ANALYSIS &&
284359
oldSym.getName().startsWith("Addr")) {
285360
// cleanup AddressTableAnalyzer label
286361
oldSym.delete();
287362
}
288-
if (lcmd.applyTo(program) && i < caseSymbols.length) {
289-
caseSymbols[i] = symTable.getSymbol(caseName, switchCases[i], space);
363+
if (lcmd.applyTo(program) && caseIndex < caseSymbols.length) {
364+
caseSymbols[caseIndex] = symTable.getSymbol(caseName, switchCases[caseIndex], space);
290365
}
291366
}
292367

@@ -338,6 +413,9 @@ private Address[] getPointerTable(JumpTable.LoadTable loadtable, Address[] switc
338413
return addresses;
339414
}
340415

416+
/*
417+
* put labels on the switch table used to compute the target addresses of the switch.
418+
*/
341419
private void labelLoadTable(JumpTable.LoadTable loadtable, Address[] switchCases,
342420
Symbol[] caseSymbols, Namespace space, TaskMonitor monitor) throws CancelledException {
343421

0 commit comments

Comments
 (0)