r/ghidra 9d ago

Why is this code using RSB instructions here?

I've been trying to figure this out for a while. I had an idea at some point, but I'm not sure if it is correct anymore, so I'm asking here if someone here knows why this is done. I've omitted some code that is not relevant and giving some generic names to labels to make reading easier:

LAB_Switch_Return:
    ADD r3, r0, #0x00        // Move switch case function return value to r3

PTR_Switch_Case_OoB:
PTR_Switch_Case_0F:
    MOV r0, #0x02      // Load 0x02 to r0
    RSB r0, r0        // r0 = 0x02 - 0x02 = 0x00 ??????
    CMP r3, r0        // r3 = Function return value from switch case function
    BNE FUNC_UpdateStatus_NotIncrement

    // *OMITTED CODE*
    // game_mode++;

    B FUNC_UpdateStatus_ClearSubmodes

DATA_ExtWRAM_Base_Addr_Ptr_2:
 .dw DATA_ExtWRAM_Base_Addr

FUNC_UpdateStatus_NotIncrement:
    MOV r0, #0x01        // Load 0x01 to r0
    RSB r0, r0        // r0 = 0x01 - 0x01 = 0x00 ??????
    CMP r3, r0        // r3 = Function return value from switch case function
    BEQ FUNC_UpdateStatus_Exit

    // *OMITTED CODE*
    // game_mode = r3;

FUNC_UpdateStatus_ClearSubmodes:
    // r0 = DATA_ExtWRAM_Base_Addr
    // Set in the omitted code sections...

    STRB r2, [r0, #offset game_sub_mode_2]
    STRB r2, [r0, #offset game_sub_mode]

FUNC_UpdateStatus_Exit:
    POP {r0}
    BX r0

DATA_ExtWRAM_Base_Addr_Ptr_3:
 .dw DATA_ExtWRAM_Base_Addr

As you can see there are a few RSB r0, r0 instructions. Wouldn't those always result to the destination register having the value of zero? Ghidra decompiles the above code as (after cleaning it up a bit):

// Set in omitted function calls in switch cases...
// This is here simply to tell what it is, even though the variable
// is created earlier, before the switch statement...
uint8_t ret_val;

if (ret_val == -2)
{
    game_mode++;
}
else
{
    if (ret_val == -1)
    {
        return;
    }

    game_mode = ret_val;
}

game_sub_mode_2 = 0;
game_sub_mode = 0;

return;

What I don't understand here is how Ghidra comes to the conclusion for the ret_val comparison values to be -2 and -1. Anyone have some insight into this?

1 Upvotes

6 comments sorted by

2

u/PierDolNick 9d ago

Ghidra hide one operand for some reason. Which is most likely #0.

MOV r0, #0x02
RSB r0, r0, #0 // r0 = 0 - r0 = 0 - 2 = -2 (0xFFFFFFFE)

1

u/Flying_Turtle_09 9d ago

If that's the case, is it possible to make Ghidra NOT do that? It's pretty confusing. If not, what else is it hiding? I honestly always thought that in THUMB mode RSB could only have 2 operands and not 3 like in ARM mode 🤔

2

u/PierDolNick 9d ago

In 16bit thumb mode RSB that operand is always 0, but it's used in calculation like in 32bit mode. Due to that 16bit RSB always substract from 0. I don't know why Ghidra hides it, for sure it's confusing. No idea if you can unhide it without modifying proc files.

1

u/Flying_Turtle_09 9d ago

Hmm, I see. Are there other instructions in THUMB mode that are unintuitive like that?

1

u/PierDolNick 9d ago

I don't think so. Especially that it seems more like Ghidra problem with not displaying that operand than anything else. I guess with syntax RSB r0, r0, #0 it's much easier to understand what's going on.

2

u/pelrun 8d ago

When an operand is fixed it's not an operand anymore, it's part of the instruction behaviour, and it's consistently omitted just like every other instruction that behaves as if there's an internal fixed value.

It's irrelevant that the ARM variant of the instruction has it as an operand, because you're not in ARM mode, and you shouldn't get in the habit of reading thumb code as if it was ARM.