Skip to content

Commit

Permalink
Fix hexdump Qt view crash (#103)
Browse files Browse the repository at this point in the history
## Description

This PR fixes a crash happening with the Qt memory viewer, when invoking the `hexdump` function with a bytearray (instead of expected `bytes` type).
This was fixed by also allowing bytearray as input for `hexdump`.

This PR also add a minor additional description for contributing in the readme
  • Loading branch information
hugsy authored Aug 5, 2024
1 parent 5812e05 commit 019a0c5
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 43 deletions.
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ This should produce a GUI similar to this:
### In the terminal

```bash
python -m cemu cli
python -m cemu --cli
```

From where you'll end up in a REPL, allowing you to build and run the emulation environment.
Expand All @@ -99,11 +99,28 @@ $ python

Then use the provided API to build and run your environment.

## Contribution ##
## Contribution

`cemu` was created and maintained by myself, [`@_hugsy_`](https://twitter.com/_hugsy_), but kept fresh thanks to [all the contributors](https://github.com/hugsy/cemu/graphs/contributors).

[ ![contributors-img](https://contrib.rocks/image?repo=hugsy/cemu) ](https://github.com/hugsy/cemu/graphs/contributors)

If you just like the tool, consider dropping on Discord (or Twitter or other) a simple *"thanks"*, it is always very appreciated.
To make contributions, the easiest way to get started is by using [`rye`](https://rye-up.sh) to get everything setup:

```bash
curl -sSf https://rye.astral.sh/get | bash # opt
rye sync
```

Before submitting a Pull Request, ensure that both linting and formatting of your new code comply with the project's standards. This can be achieved easily as such:

```bash
rye lint
rye fmt
```

Note that any non-compliance will make CI validation fail, therefore preventing your code being merged.


But if you just like the tool as a user, consider dropping on Discord (or Twitter or other) a simple *"thanks"*, it is always very appreciated.
And if you can, consider [sponsoring me](https://github.com/hugsy/sponsors) - it really helps dedicating time and resources to the projects!
6 changes: 2 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,8 @@ allow-direct-references = true
packages = ["src/cemu"]

[project.scripts]
cemu = "cemu.__main__:main"

[project.gui-scripts]
cemu = "cemu.__main__:main"
cemu-cli = "cemu.__main__:main_cli"
cemu-gui = "cemu.__main__:main_gui"

[project.urls]
"Homepage" = "https://github.com/hugsy/cemu"
Expand Down
20 changes: 19 additions & 1 deletion src/cemu/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import cemu.core
import cemu.log

THIS_FILE = pathlib.Path(__file__).absolute()


def setup_remote_debug(port: int = cemu.const.DEBUG_DEBUGPY_PORT):
assert cemu.const.DEBUG
Expand Down Expand Up @@ -42,9 +44,25 @@ def main(argv: list[str]):
cemu.core.CemuGui()


def main_gui(debug: bool = False):
args = [
str(THIS_FILE),
]
if debug:
args.append("--debug")
main(args)


def main_cli(debug: bool = False):
args = [str(THIS_FILE), "--cli"]
if debug:
args.append("--debug")
main(args)


if __name__ == "__main__":
import sys

path = pathlib.Path(__file__).absolute().parent.parent
path = THIS_FILE.parent.parent
sys.path.append(str(path))
main(sys.argv)
46 changes: 24 additions & 22 deletions src/cemu/exports.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os
import tempfile
import pathlib
from typing import Any, List

from lief import PE
from lief import PE # type: ignore

from .arch import Architecture, is_x86_32, is_x86_64
from .memory import MemoryPermission, MemorySection
Expand All @@ -12,24 +12,25 @@
def parse_as_lief_pe_permission(perm: MemoryPermission, extra: Any = None) -> int:
res = 0
if perm.r:
res |= PE.SECTION_CHARACTERISTICS.MEM_READ.value
res |= PE.Section.CHARACTERISTICS.MEM_READ.value
if perm.w:
res |= PE.SECTION_CHARACTERISTICS.MEM_WRITE.value
res |= PE.Section.CHARACTERISTICS.MEM_WRITE.value
if perm.x:
res |= PE.SECTION_CHARACTERISTICS.MEM_EXECUTE.value
res |= PE.Section.CHARACTERISTICS.MEM_EXECUTE.value

if extra:
if extra.lower() == "code":
res |= PE.SECTION_CHARACTERISTICS.CNT_CODE.value
if extra.lower() == "idata":
res |= PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA.value
if extra.lower() == "udata":
res |= PE.SECTION_CHARACTERISTICS.CNT_UNINITIALIZED_DATA.value
match extra.lower():
case "code":
res |= PE.Section.CHARACTERISTICS.CNT_CODE.value
case "idata":
res |= PE.Section.CHARACTERISTICS.CNT_INITIALIZED_DATA.value
case "udata":
res |= PE.Section.CHARACTERISTICS.CNT_UNINITIALIZED_DATA.value

return res


def build_pe_executable(text: bytes, memory_layout: List[MemorySection], arch: Architecture) -> str:
def build_pe_executable(text: bytes, memory_layout: List[MemorySection], arch: Architecture) -> pathlib.Path:
"""
Uses LIEF to build a standalone binary.
Expand All @@ -43,10 +44,10 @@ def build_pe_executable(text: bytes, memory_layout: List[MemorySection], arch: A

if is_x64:
basename = "cemu-pe-amd64-{:s}".format(cemu.utils.generate_random_string(5))
pe = PE.Binary(basename, PE.PE_TYPE.PE32_PLUS)
pe = PE.Binary(PE.PE_TYPE.PE32_PLUS)
else:
basename = "cemu-pe-i386-{:s}".format(cemu.utils.generate_random_string(5))
pe = PE.Binary(basename, PE.PE_TYPE.PE32)
pe = PE.Binary(PE.PE_TYPE.PE32)

# adding sections
sections = {}
Expand Down Expand Up @@ -82,12 +83,12 @@ def build_pe_executable(text: bytes, memory_layout: List[MemorySection], arch: A
reladdr += size

# fixing pe header
pe.header.add_characteristic(PE.HEADER_CHARACTERISTICS.EXECUTABLE_IMAGE)
pe.header.add_characteristic(PE.HEADER_CHARACTERISTICS.DEBUG_STRIPPED)
pe.header.add_characteristic(PE.Header.CHARACTERISTICS.EXECUTABLE_IMAGE)
pe.header.add_characteristic(PE.Header.CHARACTERISTICS.DEBUG_STRIPPED)
if is_x64:
pe.header.add_characteristic(PE.HEADER_CHARACTERISTICS.LARGE_ADDRESS_AWARE)
pe.header.add_characteristic(PE.Header.CHARACTERISTICS.LARGE_ADDRESS_AWARE)
else:
pe.header.add_characteristic(PE.HEADER_CHARACTERISTICS.CHARA_32BIT_MACHINE)
pe.header.add_characteristic(PE.Header.CHARACTERISTICS.NEED_32BIT_MACHINE)

# fixing pe optional header
pe.optional_header.addressof_entrypoint = sections["text"].virtual_address
Expand All @@ -97,16 +98,17 @@ def build_pe_executable(text: bytes, memory_layout: List[MemorySection], arch: A
pe.optional_header.minor_subsystem_version = 0x02
pe.optional_header.major_linker_version = 0x02
pe.optional_header.minor_linker_version = 0x1E
pe.optional_header.remove(PE.DLL_CHARACTERISTICS.NX_COMPAT)
pe.optional_header.add(PE.DLL_CHARACTERISTICS.NO_SEH)
pe.optional_header.remove(PE.OptionalHeader.DLL_CHARACTERISTICS.NX_COMPAT)
pe.optional_header.add(PE.OptionalHeader.DLL_CHARACTERISTICS.NO_SEH)
# pe.add_library("ntdll.dll")

# building exe to disk
outfile = f"{tempfile.gettempdir()}{os.path.sep:s}{basename:s}.exe"
outfile = pathlib.Path(tempfile.gettempdir()) / f"{basename:s}.exe"
builder = PE.Builder(pe)
builder.build_imports(True)
builder.build()
builder.write(outfile)
builder.write(str(outfile.absolute()))
assert outfile.exists()
return outfile


Expand Down
2 changes: 1 addition & 1 deletion src/cemu/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import cemu.const

loggers = set()
loggers: set[Callable] = set()


def register_sink(cb: Callable):
Expand Down
3 changes: 1 addition & 2 deletions src/cemu/plugins/scratchboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@

class ScratchboardWidget(QDockWidget):
def __init__(self, parent: CEmuWindow, *args, **kwargs):
super(ScratchboardWidget, self).__init__("Scratchboard", parent)
self.parent = parent
super().__init__("Scratchboard", parent)
self.title = "Scratchboard"
QVBoxLayout()
self.__editor = QTextEdit()
Expand Down
8 changes: 4 additions & 4 deletions src/cemu/ui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ def LoadExtraPlugins(self) -> int:
if not module:
continue

m = module.register(self)
if not m:
plugin_widget: Optional[QDockWidget] = module.register(self)
if not plugin_widget:
error(f"The registration of '{path}' failed")
continue

self.__dockable_widgets.append(m)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, m)
self.__dockable_widgets.append(plugin_widget)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, plugin_widget)
ok(f"Loaded plugin '{path}'")
nb_added += 1
return nb_added
Expand Down
4 changes: 2 additions & 2 deletions src/cemu/ui/memory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

import unicorn
from PyQt6.QtGui import QFont
Expand Down Expand Up @@ -79,7 +79,7 @@ def updateEditor(self) -> None:
self.editor.setText("VM not running")
return

addr: int
addr: Optional[int] = None
msg = "Displaying "

value = self.address.text()
Expand Down
8 changes: 4 additions & 4 deletions src/cemu/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import random
import string
from typing import Optional, TYPE_CHECKING
from typing import Optional, TYPE_CHECKING, Union

if TYPE_CHECKING:
import cemu.arch
Expand All @@ -14,7 +14,7 @@


def hexdump(
source: bytes,
source: Union[bytes, bytearray],
alignment: int = 0x10,
separator: str = ".",
show_raw: bool = False,
Expand All @@ -34,8 +34,8 @@ def hexdump(
"""
import cemu.arch

if not isinstance(source, bytes):
raise ValueError("source must be of type `bytes`")
if not (isinstance(source, bytes) or isinstance(source, bytearray)):
raise ValueError("source must be of type `bytes` or `bytearray`")

if len(separator) != 1:
raise ValueError("separator must be a single character")
Expand Down

0 comments on commit 019a0c5

Please sign in to comment.