kotcrab / ghidra-allegrex

Ghidra processor module adding support for the Allegrex CPU (PSP)
Apache License 2.0
91 stars 9 forks source link

Missing SPECIAL instructions lead to halt in decompilation #3

Closed CelesteBlue-dev closed 4 years ago

CelesteBlue-dev commented 4 years ago

Disassembly when loading sysmem.prx with ghidra-allegrex:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24              ??         24h    $
        00000899 00              ??         00h
        0000089a 03              ??         03h
        0000089b 70              ??         70h    p
        0000089c 21              ??         21h    !
        0000089d 40              ??         40h    @
        0000089e 40              ??         40h    @
        0000089f 00              ??         00h
        000008a0 26              ??         26h    &
        000008a1 00              ??         00h
        000008a2 00              ??         00h
        000008a3 70              ??         70h    p
                             DAT_000008a4                                    XREF[1]:     0000cf40(R)  
        000008a4 40 00 42 24     undefined4 24420040h

After forcing decompilation I get:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24              ??         24h    $
        00000899 00              ??         00h
        0000089a 03              ??         03h
        0000089b 70              ??         70h    p
        0000089c 21 40 40 00     move       t0,v0
        000008a0 26 00 00 70     mtic
                             DAT_000008a4                                    XREF[1]:     0000cf40(R)  
        000008a4 40 00 42 24     undefined4 24420040h

Disassembly when loading sysmem.prx with MIPS 32 bits little endian:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24 00 03 70     SPECIAL2   zero,zero,v1,0x0,0x24
        0000089c 21 40 40 00     move       t0,v0
        000008a0 26 00 00 70     SPECIAL2   zero,zero,zero,0x0,0x26
                             LAB_000008a4                                    XREF[1]:     sceSuspendForKernel_0AB0C6F3:000
        000008a4 40 00 42 24     addiu      v0,v0,0x40

Even though MIPS disassembler doesn't understand these "SPECIAL" instructions, it decompiles them seeing them as instructions. This way, the function can be decompiled to pseudo-code.

With ghidra-allegrex, the pseudo-code fails to decompile the function:

// ...
    return 0;
  }
  if ((int)(-((uint)(in_k1 << 10) >> 0x1d) & param_2) < 0) {
    return 0x80000104;
  }
                    /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

When loading with MIPS 32-bits little endian:

// ...
    return 0;
  }
  if ((int)(-((uint)(in_k1 << 10) >> 0x1d) & param_2) < 0) {
    return 0x80000104;
  }
  special2(0,in_v1,0,0x24);
  iVar2 = -0x2000;
  do {
    special2(0,0,0,0x26);
    iVar1 = iVar2 + 0x40;
    cacheOp(0x10,iVar2 + 0x2000);
    special2(0,in_v1,0,0x26);
// ...
CelesteBlue-dev commented 4 years ago

A friend told me: "kotcrab hasn't implemented interrupt controller instructions. You can fix that in allegrexinstructions.sinc line 746. https://github.com/kotcrab/ghidra-allegrex/blob/master/data/languages/allegrexInstructions.sinc#L746 Not sure he got implemented interrupt controller at all. This can be done as workaround by creating special register otherwise it will not gonna work for decompiler. Those opcodes are overally documented, so should be easy to add."

https://gigawiz.github.io/yapspd/html_chapters_split/chap4.html#sec4.8

kotcrab commented 4 years ago

Yeah, the mfic and mtic have wrong conditions. Is there just one interrupt register? That would mean for mfic rd is always $zero and for mtic rt is $zero Though if that's the case I don't understand how this works:

mtic $a0, zero
to renable based on the original mask in a0 

The MIPS 32-bit has a catch-all special2 instruction, you can add it to your files if you want:

define pcodeop special2;
:SPECIAL2 RD, RSsrc, RTsrc, sa, fct      is $(AMODE) & prime=0x1C & sa & RD & RSsrc & RTsrc & fct {
    tmp:1 = fct;
    tmp2:1 = sa;
    RD = special2(RSsrc, RTsrc, tmp2, tmp);
}
CelesteBlue-dev commented 4 years ago

Thanks. I haven't tested anything yet, but adding more info: http://lukasz.dk/mirror/forums.ps2dev.org/viewtopic9372.html?t=13034 https://github.com/kpspemu/kpspemu/blob/44af41bf2e533822e76c213b797377e45f7f54c6/kpspemu/src/commonMain/kotlin/com/soywiz/kpspemu/cpu/Instructions.kt#L148

Another person told me "Missing a RTsrc?"

ghost commented 4 years ago

HALT: opcode 0x70000000. This instructions waits for an interruption to wake it up. MFIC: opcode 0x70000024 with mask 0xFFFF07FF. It retrieves the interrupt controller state (1: interruptions enabled, 0: interruptions disabled) into the register described by mask 0x0000F800. MTIC: opcode 0x70000026 with mask 0xFFFF07FF. It sets the interrupt controller state to the value which is in the register described by mask 0x0000F800. via: https://github.com/uofw/upspd/wiki/CPU#Special_instructions_details

MFIC instruction really use only rt, MTIC use only rs. In MTIC rt seems to be always $zero, same goes for MFIC rs. So is kinda opcode that have hardcoded IC as destination/source depend on instruction, and psp seems to always use $zero for it. This can look weird as it push value to $zero, but is like a pseudo opcode that really push it to IC instead, not to real $zero. I think that best solution will be pseudo register for interrupt controller available only from mtic, halt, mfic. Games should never reach those instructions anyway, maybe kernel.

That assume "full" support for it, workaround can be just set rt/rd in those opcodes to not rX=0. But then it will be only look nice in assembly without real value in decompilation.

kotcrab commented 4 years ago

Oops this got closed after merging #5 please reopen @CelesteBlue-dev if you still have this problem. I added new prebuilt version with all the latest changes https://github.com/kotcrab/ghidra-allegrex/releases/tag/v1.0

CelesteBlue-dev commented 3 years ago

I confirm that this issue has been fixed by adding MFIC and MTIC support in #5. For comparison, now the disassembly looks like:

                             LAB_0000087c                                    XREF[1]:     00000848(j)  
        0000087c c0 14 62 7f     ext        v0,k1,0x13,0x3
        00000880 23 10 02 00     subu       v0,zero,v0
        00000884 24 10 45 00     and        v0,v0,a1
        00000888 f9 ff 40 04     bltz       v0,LAB_00000870
        0000088c 00 e0 02 24     _li        v0,-0x2000
        00000890 04 28 04 7c     ins        a0,zero,0x0,0x6
        00000894 23 28 a4 00     subu       a1,a1,a0
        00000898 24 00 03 70     mfic       v1
        0000089c 21 40 40 00     move       t0,v0
                             LAB_000008a0                                    XREF[1]:     000008e0(j)  
        000008a0 26 00 00 70     mtic       zero
                             LAB_000008a4                                    XREF[1]:     sceSuspendForKernel_0AB0C6F3:000
        000008a4 40 00 42 24     addiu      v0,v0,0x40

And decompilation does not halt anymore, giving very good results:

// ...
    return 0;
  }
  if ((int)(-((uint)(in_k1 << 10) >> 0x1d) & param_2) < 0) {
    return 0x80000104;
  }
  uVar1 = getInterruptMask();
  iVar3 = -0x2000;
  do {
    setInterruptMask(0);
    iVar2 = iVar3 + 0x40;
    cacheOp(0x10,iVar3 + 0x2000);
    getCopReg(0,TagLo);
    getCopReg(0,TagHi);
    setInterruptMask(uVar1);
// ...