Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code fixes before new release #99

Merged
merged 14 commits into from
Aug 1, 2024
83 changes: 21 additions & 62 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [windows-2019, ubuntu-22.04, macos-13]
os: [windows-2019, windows-2022, ubuntu-22.04, ubuntu-24.04, macos-13]
python-version: ['3.10', '3.11', '3.12']
name: ${{ matrix.os }} / ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
outputs:
windows-2019-3-10: ${{ join(steps.*.outputs.windows-2019-3-10,'') }}
windows-2019-3-11: ${{ join(steps.*.outputs.windows-2019-3-11,'') }}
windows-2019-3-12: ${{ join(steps.*.outputs.windows-2019-3-12,'') }}
ubuntu-22.04-3-10: ${{ join(steps.*.outputs.ubuntu-22.04-3-10,'') }}
ubuntu-22.04-3-11: ${{ join(steps.*.outputs.ubuntu-22.04-3-11,'') }}
ubuntu-22.04-3-12: ${{ join(steps.*.outputs.ubuntu-22.04-3-12,'') }}
ubuntu-22-04-3-10: ${{ join(steps.*.outputs.ubuntu-22-04-3-10,'') }}
ubuntu-22-04-3-11: ${{ join(steps.*.outputs.ubuntu-22-04-3-11,'') }}
ubuntu-22-04-3-12: ${{ join(steps.*.outputs.ubuntu-22-04-3-12,'') }}
macos-13-3-10: ${{ join(steps.*.outputs.macos-13-3-10,'') }}
macos-13-3-11: ${{ join(steps.*.outputs.macos-13-3-11,'') }}
macos-13-3-12: ${{ join(steps.*.outputs.macos-13-3-12,'') }}
Expand All @@ -47,62 +47,17 @@ jobs:
version: 'latest'

- name: "Install Pre-requisite (Linux)"
if: matrix.os == 'ubuntu-22.04'
if: startsWith(matrix.os, 'ubuntu')
shell: bash
run: |
sudo apt update
sudo apt upgrade -y
sudo apt install -y build-essential libegl1 libgl1-mesa-glx

# - name: "Install Pre-requisite (macOS)"
# if: matrix.os == 'macos-13'
# run: |
# env

# - name: "Install Pre-requisite (Windows)"
# if: matrix.os == 'windows-2019'
# shell: pwsh
# run: |
# env
sudo apt install -y build-essential libegl1

- run: rye fmt
- run: rye lint
- run: rye test
- run: rye build --wheel --out ./build


# - name: Build artifact
# shell: bash
# run: |
# mkdir build
# mkdir build/bin
# python --version
# python -m pip --version
# python -m pip install --upgrade pip setuptools wheel
# python -m pip install --user --upgrade .[all]

# - name: "Post build Cemu (Windows)"
# if: matrix.os == 'windows-2019'
# shell: pwsh
# run: |
# Copy-Item $env:APPDATA\Python\Python*\Scripts\cemu.exe build\bin\

# - name: "Post build Cemu (Linux)"
# if: matrix.os == 'ubuntu-22.04'
# shell: bash
# run: |
# cp -v ~/.local/bin/cemu build/bin/

# - name: "Post build Cemu (macOS)"
# if: matrix.os == 'macos-13'
# shell: bash
# run: |
# cp -v ~/.local/bin/cemu build/bin/ || cp -v /Users/runner/Library/Python/${{ matrix.python-version }}/bin/cemu build/bin/

# - name: "Run tests"
# run: |
# python -m pytest tests/

- name: Publish artifact
id: publish_artifact
uses: actions/upload-artifact@v4
Expand All @@ -112,35 +67,39 @@ jobs:

- name: Populate the successful output (Windows)
id: output_success_windows
if: ${{ matrix.os == 'windows-2019' && success() }}
if: ${{ startsWith(matrix.os, 'windows') && success() }}
shell: pwsh
run: |
$osVersion = "${{ matrix.os }}" -replace "\.", "-"
$pyVersion = "${{ matrix.python-version }}" -replace "\.", "-"
echo "${{ matrix.os }}-$pyVersion=✅ ${{ matrix.os }} ${{ matrix.python-version }}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
echo "${osVersion}-$pyVersion=✅ ${{ matrix.os }} ${{ matrix.python-version }}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append

- name: Populate the successful output (Other)
id: output_success_other
if: ${{matrix.os != 'windows-2019' && success() }}
if: ${{startsWith(matrix.os, 'windows') == false && success() }}
shell: bash
run: |
osVersion="$(echo -n ${{ matrix.os }} | tr . -)"
pyVersion="$(echo -n ${{ matrix.python-version }} | tr . -)"
echo "${{ matrix.os }}-${pyVersion}=✅ ${{ matrix.os }} ${{ matrix.python-version }}" >> $GITHUB_OUTPUT
echo "${osVersion}-${pyVersion}=✅ ${{ matrix.os }} ${{ matrix.python-version }}" >> $GITHUB_OUTPUT

- name: Populate the failure output (Windows)
id: output_failure_windows
if: ${{matrix.os == 'windows-2019' && failure() }}
if: ${{startsWith(matrix.os, 'windows') && failure() }}
shell: pwsh
run: |
$osVersion = "${{ matrix.os }}" -replace "\.", "-"
$pyVersion = "${{ matrix.python-version }}" -replace "\.", "-"
echo "${{ matrix.os }}-${pyVersion}=❌ ${{ matrix.os }} ${{ matrix.python-version }}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
echo "${osVersion}-${pyVersion}=❌ ${{ matrix.os }} ${{ matrix.python-version }}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append

- name: Populate the failure output (Other)
id: output_failure_other
if: ${{matrix.os != 'windows-2019' && failure() }}
if: ${{startsWith(matrix.os, 'windows') && failure() }}
shell: bash
run: |
osVersion="$(echo -n ${{ matrix.os }} | tr . -)"
pyVersion="$(echo -n ${{ matrix.python-version }} | tr . -)"
echo "${{ matrix.os }}-$pyVersion=❌ ${{ matrix.os }} ${{ matrix.python-version }}" >> $GITHUB_OUTPUT
echo "${osVersion}-$pyVersion=❌ ${{ matrix.os }} ${{ matrix.python-version }}" >> $GITHUB_OUTPUT

notify:
env:
Expand All @@ -167,9 +126,9 @@ jobs:
${{ needs.build.outputs.windows-2019-3-10 }}
${{ needs.build.outputs.windows-2019-3-11 }}
${{ needs.build.outputs.windows-2019-3-12 }}
${{ needs.build.outputs.ubuntu-22.04-3-10 }}
${{ needs.build.outputs.ubuntu-22.04-3-11 }}
${{ needs.build.outputs.ubuntu-22.04-3-12 }}
${{ needs.build.outputs.ubuntu-22-04-3-10 }}
${{ needs.build.outputs.ubuntu-22-04-3-11 }}
${{ needs.build.outputs.ubuntu-22-04-3-12 }}
${{ needs.build.outputs.macos-13-3-10 }}
${{ needs.build.outputs.macos-13-3-11 }}
${{ needs.build.outputs.macos-13-3-12 }}
Expand Down
5 changes: 2 additions & 3 deletions src/cemu/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ def setup_remote_debug(port: int = cemu.const.DEBUG_DEBUGPY_PORT):


def main(argv: list[str]):
parser = argparse.ArgumentParser(
prog=cemu.const.PROGNAME,
description=cemu.const.DESCRIPTION)
parser = argparse.ArgumentParser(prog=cemu.const.PROGNAME, description=cemu.const.DESCRIPTION)
parser.add_argument("filename")
parser.add_argument("--debug", action="store_true")
parser.add_argument("--attach", action="store_true")
Expand All @@ -46,6 +44,7 @@ def main(argv: list[str]):

if __name__ == "__main__":
import sys

path = pathlib.Path(__file__).absolute().parent.parent
sys.path.append(str(path))
main(sys.argv)
147 changes: 135 additions & 12 deletions src/cemu/arch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
from dataclasses import dataclass
import enum
import importlib
import pathlib
from typing import Optional, TYPE_CHECKING

import capstone
import keystone
import unicorn

import cemu.errors
from cemu.const import SYSCALLS_PATH
from ..ui.utils import popup, PopupType
from cemu.log import dbg, error
from cemu.utils import DISASSEMBLY_DEFAULT_BASE_ADDRESS


if TYPE_CHECKING:
import cemu.core


class Endianness(enum.Enum):
class Endianness(enum.IntEnum):
LITTLE_ENDIAN = 1
BIG_ENDIAN = 2

Expand All @@ -27,7 +32,7 @@ def __int__(self) -> int:
return self.value


class Syntax(enum.Enum):
class Syntax(enum.IntEnum):
INTEL = 1
ATT = 2

Expand Down Expand Up @@ -71,26 +76,29 @@ def syscalls(self):
if not self.__context:
import cemu.core

assert cemu.core.context
self.__context = cemu.core.context
assert isinstance(self.__context, cemu.core.GlobalContext)

if not self.__syscalls:
syscall_dir = SYSCALLS_PATH / str(self.__context.os)
syscall_dir = SYSCALLS_PATH / str(self.__context.os).lower()

try:
fpath = syscall_dir / (self.syscall_filename + ".csv")
except ValueError as e:
popup(str(e), PopupType.Error, "No Syscall File Error")
error(f"No Syscall File Error: {e}")
return {}

self.__syscalls = {}
if fpath.exists():
with fpath.open("r") as fd:
for row in fd.readlines():
row = [x.strip() for x in row.strip().split(",")]
syscall_number = int(row[0])
syscall_name = row[1].lower()
self.__syscalls[syscall_name] = self.syscall_base + syscall_number
if not fpath.exists():
raise FileNotFoundError(fpath)

with fpath.open("r") as fd:
for row in fd.readlines():
row = [x.strip() for x in row.strip().split(",")]
syscall_number = int(row[0])
syscall_name = row[1].lower()
self.__syscalls[syscall_name] = self.syscall_base + syscall_number

return self.__syscalls

Expand Down Expand Up @@ -264,3 +272,118 @@ def is_sparc64(a: Architecture):

def is_ppc(a: Architecture):
return isinstance(a, PowerPC)


def format_address(addr: int, arch: Optional[Architecture] = None) -> str:
"""Format an address to string, aligned to the given architecture

Args:
addr (int): _description_
arch (Optional[Architecture], optional): _description_. Defaults to None.

Raises:
ValueError: _description_

Returns:
str: _description_
"""
if arch is None:
import cemu.core

if not cemu.core.context:
ptrsize = 8
else:
ptrsize = cemu.core.context.architecture.ptrsize
else:
ptrsize = arch.ptrsize

match ptrsize:
case 2:
return f"{addr:#04x}"
case 4:
return f"{addr:#08x}"
case 8:
return f"{addr:#016x}"
case _:
raise ValueError(f"Invalid pointer size value of {ptrsize}")


@dataclass
class Instruction:
address: int
mnemonic: str
operands: str
bytes: bytes

@property
def size(self):
return len(self.bytes)

@property
def end(self) -> int:
return self.address + self.size

def __str__(self):
return f'Instruction({self.address:#x}, "{self.mnemonic} {self.operands}")'


def disassemble(raw_data: bytes, count: int = -1, base: int = DISASSEMBLY_DEFAULT_BASE_ADDRESS) -> list[Instruction]:
"""Disassemble the code given as raw data, with the given architecture.

Args:
raw_data (bytes): the raw byte code to disassemble
arch (Architecture): the architecture to use for disassembling
count (int, optional): the maximum number of instruction to disassemble. Defaults to -1.
base (int, optional): the disassembled code base address. Defaults to DISASSEMBLY_DEFAULT_BASE_ADDRESS

Returns:
str: the text representation of the disassembled code
"""
assert cemu.core.context
arch = cemu.core.context.architecture
insns: list[Instruction] = []
for idx, ins in enumerate(arch.cs.disasm(raw_data, base)):
insn = Instruction(ins.address, ins.mnemonic, ins.op_str, ins.bytes)
insns.append(insn)
if idx == count:
break

dbg(f"{insns=}")
return insns


def disassemble_file(fpath: pathlib.Path) -> list[Instruction]:
return disassemble(fpath.read_bytes())


def assemble(code: str, base_address: int = DISASSEMBLY_DEFAULT_BASE_ADDRESS) -> list[Instruction]:
"""
Helper function to assemble code receive in parameter `asm_code` using Keystone.

@param code : assembly code in bytes (multiple instructions must be separated by ';')
@param base_address : (opt) the base address to use

@return a list of Instruction
"""
assert cemu.core.context
arch = cemu.core.context.architecture

#
# Compile the entire given code
#
bytecode, assembled_insn_count = arch.ks.asm(code, as_bytes=True, addr=base_address)
if not bytecode or assembled_insn_count == 0:
raise cemu.errors.AssemblyException("Not instruction compiled")

assert isinstance(bytecode, bytes)

#
# Decompile it and return the stuff
#
insns = disassemble(bytecode, base=base_address)
dbg(f"{insns=}")
return insns


def assemble_file(fpath: pathlib.Path) -> list[Instruction]:
return assemble(fpath.read_text())
Loading
Loading