Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions backend/coreapp/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
COMMON_MWCC_WII_GC_FLAGS,
COMMON_WATCOM_FLAGS,
COMMON_BORLAND_FLAGS,
CompilerType,
Flags,
Language,
)
Expand Down Expand Up @@ -54,13 +55,6 @@
COMPILER_BASE_PATH: Path = settings.COMPILER_BASE_PATH


class CompilerType(enum.Enum):
GCC = "gcc"
IDO = "ido"
MWCC = "mwcc"
OTHER = "other"


@dataclass(frozen=True)
class Compiler:
id: str
Expand Down
16 changes: 12 additions & 4 deletions backend/coreapp/decompiler_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
from coreapp import compilers

from coreapp.flags import Language
from coreapp.compilers import Compiler
from coreapp.decompilers import DecompilerSpec, M2C

from coreapp.m2c_wrapper import M2CError, M2CWrapper
from coreapp.platforms import Platform
Expand All @@ -21,27 +23,33 @@ def decompile(
asm: str,
context: str,
compiler: Compiler,
decompiler_flags: str,
language: Language,
) -> str:
if compiler == compilers.DUMMY:
return f"decompiled({asm})"

ret = default_source_code
if platform.arch in ["mips", "mipsee", "mipsel", "mipsel:4000", "ppc"]:
if DecompilerSpec(platform.arch, compiler.type, language) in M2C.specs:
if len(asm.splitlines()) > MAX_M2C_ASM_LINES:
return "/* Too many lines to decompile; please run m2c manually */"
try:
ret = M2CWrapper.decompile(asm, context, compiler, platform.arch)
ret = M2CWrapper.decompile(
asm, context, compiler, platform.arch, decompiler_flags, language
)
except M2CError as e:
# Attempt to decompile the source without context as a last-ditch effort
try:
ret = M2CWrapper.decompile(asm, "", compiler, platform.arch)
ret = M2CWrapper.decompile(
asm, "", compiler, platform.arch, decompiler_flags, language
)
ret = f"{e}\n{DECOMP_WITH_CONTEXT_FAILED_PREAMBLE}\n{ret}"
except M2CError as e:
ret = f"{e}\n{default_source_code}"
except Exception:
logger.exception("Error running m2c")
ret = f"/* Internal error while running m2c */\n{default_source_code}"
else:
ret = f"/* No decompiler yet implemented for {platform.arch} */\n{default_source_code}"
ret = f"/* No decompiler yet implemented for the combination {platform.arch}, {compiler.type.value}, {language.value} */\n{default_source_code}"

return ret
213 changes: 213 additions & 0 deletions backend/coreapp/decompilers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import logging
from dataclasses import dataclass
from functools import cache
from typing import List, OrderedDict, Dict

from coreapp.flags import (
CompilerType,
Checkbox,
FlagSet,
Flags,
Language,
)

from rest_framework import status
from rest_framework.exceptions import APIException

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class DecompilerSpec:
arch: str
compiler_type: CompilerType
language: Language

def to_json(self) -> Dict[str, str]:
return {
"arch": self.arch,
"compilerType": self.compiler_type.value,
"language": self.language.value,
}


@dataclass(frozen=True)
class Decompiler:
id: str
specs: List[DecompilerSpec]
flags: Flags

def available(self) -> bool:
# TODO
return True


def from_id(decompiler_id: str) -> Decompiler:
if decompiler_id not in _decompilers:
raise APIException(
f"Unknown decompiler: {decompiler_id}",
str(status.HTTP_400_BAD_REQUEST),
)
return _decompilers[decompiler_id]


@cache
def available_decompilers() -> List[Decompiler]:
return list(_decompilers.values())


@cache
def available_specs() -> List[DecompilerSpec]:
s_set = set(
spec for decompiler in available_decompilers() for spec in decompiler.specs
)

# TODO
return sorted(s_set, key=lambda s: str(s))


M2C = Decompiler(
id="m2c",
specs=[
DecompilerSpec("mips", CompilerType.GCC, Language.C),
DecompilerSpec("mips", CompilerType.GCC, Language.CXX),
DecompilerSpec("mips", CompilerType.IDO, Language.C),
DecompilerSpec("mips", CompilerType.IDO, Language.CXX),
DecompilerSpec("mipsee", CompilerType.GCC, Language.C),
DecompilerSpec("mipsee", CompilerType.GCC, Language.CXX),
DecompilerSpec("mipsee", CompilerType.IDO, Language.C),
DecompilerSpec("mipsee", CompilerType.IDO, Language.CXX),
DecompilerSpec("mipsel", CompilerType.GCC, Language.C),
DecompilerSpec("mipsel", CompilerType.GCC, Language.CXX),
DecompilerSpec("mipsel", CompilerType.IDO, Language.C),
DecompilerSpec("mipsel", CompilerType.IDO, Language.CXX),
DecompilerSpec("mipsel:4000", CompilerType.GCC, Language.C),
DecompilerSpec("mipsel:4000", CompilerType.GCC, Language.CXX),
DecompilerSpec("mipsel:4000", CompilerType.IDO, Language.C),
DecompilerSpec("mipsel:4000", CompilerType.IDO, Language.CXX),
DecompilerSpec("ppc", CompilerType.MWCC, Language.C),
DecompilerSpec("mips", CompilerType.MWCC, Language.CXX),
],
flags=[
FlagSet(
id="m2c_comment_style",
flags=[
"--comment-style=multiline",
"--comment-style=oneline",
"--comment-style=none",
],
),
Checkbox(
id="m2c_allman",
flag="--allman",
),
Checkbox(
id="m2c_knr",
flag="--knr",
),
FlagSet(
id="m2c_comment_alignment",
flags=[
"--comment-column=0",
"--comment-column=52",
],
),
Checkbox(
id="m2c_indent_switch_contents",
flag="--indent-switch-contents",
),
Checkbox(
id="m2c_leftptr",
flag="--pointer-style left",
),
Checkbox(
id="m2c_zfill_constants",
flag="--zfill-constants",
),
Checkbox(
id="m2c_unk_underscore",
flag="--unk-underscore",
),
Checkbox(
id="m2c_hex_case",
flag="--hex-case",
),
Checkbox(
id="m2c_force_decimal",
flag="--force-decimal",
),
FlagSet(
id="m2c_global_decl",
flags=[
"--globals used",
"--globals all",
"--globals none",
],
),
Checkbox(
id="m2c_sanitize_tracebacks",
flag="--sanitize-tracebacks",
),
Checkbox(
id="m2c_valid_syntax",
flag="--valid-syntax",
),
FlagSet(
id="m2c_reg_vars",
flags=[
"--reg-vars saved",
"--reg-vars most",
"--reg-vars all",
"--reg-vars r29,r30,r31",
],
),
Checkbox(
id="m2c_void",
flag="--void",
),
Checkbox(
id="m2c_debug",
flag="--debug",
),
Checkbox(
id="m2c_no_andor",
flag="--no-andor",
),
Checkbox(
id="m2c_no_casts",
flag="--no-casts",
),
Checkbox(
id="m2c_no_ifs",
flag="--no-ifs",
),
Checkbox(
id="m2c_no_switches",
flag="--no-switches",
),
Checkbox(
id="m2c_no_unk_inference",
flag="--no-unk-inference",
),
Checkbox(
id="m2c_heuristic_strings",
flag="--heuristic-strings",
),
Checkbox(
id="m2c_stack_structs",
flag="--stack-structs",
),
Checkbox(
id="m2c_deterministic_vars",
flag="--deterministic-vars",
),
],
)

_all_decompilers: List[Decompiler] = [M2C]

_decompilers = OrderedDict({d.id: d for d in _all_decompilers if d.available()})

logger.info(
f"Enabled {len(_decompilers)} decompiler(s): {', '.join(_decompilers.keys())}"
)
8 changes: 8 additions & 0 deletions backend/coreapp/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
ASMDIFF_FLAG_PREFIX = "-DIFF"


# Moved here to avoid circular import
class CompilerType(enum.Enum):
GCC = "gcc"
IDO = "ido"
MWCC = "mwcc"
OTHER = "other"


class Language(enum.Enum):
C = "C"
OLD_CXX = "C++"
Expand Down
18 changes: 14 additions & 4 deletions backend/coreapp/m2c_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from m2c.main import parse_flags, run

from coreapp.flags import Language
from coreapp.compilers import Compiler, CompilerType

from coreapp.sandbox import Sandbox
Expand All @@ -17,7 +18,7 @@ class M2CError(Exception):

class M2CWrapper:
@staticmethod
def get_triple(compiler: Compiler, arch: str) -> str:
def get_triple(compiler: Compiler, arch: str, language: Language) -> str:
if "mipse" in arch:
t_arch = "mipsel"
elif "mips" in arch:
Expand All @@ -32,14 +33,23 @@ def get_triple(compiler: Compiler, arch: str) -> str:
else:
raise M2CError(f"Unsupported compiler '{compiler}'")

return f"{t_arch}-{t_compiler}"
if language == Language.C:
t_language = "c"
elif language == Language.CXX:
t_language = "c++"
else:
raise M2CError(f"Unsupported language `{language}`")

return f"{t_arch}-{t_compiler}-{t_language}"

@staticmethod
def decompile(asm: str, context: str, compiler: Compiler, arch: str) -> str:
def decompile(
asm: str, context: str, compiler: Compiler, arch: str, decompiler_flags: str, language: Language
) -> str:
with Sandbox() as sandbox:
flags = ["--stop-on-error", "--pointer-style=left"]

flags.append(f"--target={M2CWrapper.get_triple(compiler, arch)}")
flags.append(f"--target={M2CWrapper.get_triple(compiler, arch, language)}")

# Create temp asm file
asm_path = sandbox.path / "asm.s"
Expand Down
18 changes: 18 additions & 0 deletions backend/coreapp/migrations/0059_scratch_decompiler_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-25 20:05

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("coreapp", "0058_rename_gcc272sn_to_gcc272sn0004"),
]

operations = [
migrations.AddField(
model_name="scratch",
name="decompiler_flags",
field=models.TextField(blank=True, default="", max_length=1000),
),
]
1 change: 1 addition & 0 deletions backend/coreapp/models/scratch.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class Scratch(models.Model):
platform = models.CharField(max_length=100, blank=True)
compiler_flags = models.TextField(max_length=1000, default="", blank=True)
diff_flags = models.JSONField(default=list, blank=True)
decompiler_flags = models.TextField(max_length=1000, default="", blank=True)
preset = models.ForeignKey(
"Preset", null=True, blank=True, on_delete=models.SET_NULL
)
Expand Down
Loading
Loading