├── tests ├── __init__.py ├── test_pointer.py ├── test_utils.py ├── test_ctypes.py └── test_dummy.py ├── python └── memflow │ ├── py.typed │ ├── dummy.pyi │ ├── memflow.pyi │ └── __init__.py ├── rust-toolchain.toml ├── .gitignore ├── .vscode └── settings.json ├── .devcontainer └── devcontainer.json ├── examples ├── native.py ├── unicorn_integration.py └── jupyter_tutorial.ipynb ├── Cargo.toml ├── pyproject.toml ├── .github └── workflows │ ├── publish.yml │ └── ci.yml ├── LICENSE ├── README.md ├── src ├── lib.rs ├── connector.rs ├── dummy.rs ├── inventory.rs ├── os.rs ├── process.rs └── internal.rs └── Cargo.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/memflow/py.typed: -------------------------------------------------------------------------------- 1 | partial 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .env 3 | /env 4 | /.pytest_cache 5 | /**/__pycache__ 6 | /**/*.pyd 7 | .ipynb_checkpoints -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "tests" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true 7 | } -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memflow-py-devcontainer", 3 | "image": "mcr.microsoft.com/devcontainers/universal:2", 4 | "features": { 5 | "ghcr.io/devcontainers/features/rust:1": {}, 6 | "ghcr.io/devcontainers/features/python:1": { 7 | "installJupyterlab": true 8 | } 9 | }, 10 | "postCreateCommand": "pip install maturin && pip install '.[dev]' && pip install venv && python -m venv venv" 11 | } 12 | -------------------------------------------------------------------------------- /tests/test_pointer.py: -------------------------------------------------------------------------------- 1 | from memflow import Pointer 2 | import pytest 3 | 4 | 5 | def test_init(): 6 | Pointer(0x5) 7 | with pytest.raises(ValueError): 8 | Pointer(-0x5) 9 | 10 | 11 | def test_ops(): 12 | # Add offset 13 | assert Pointer(0x3) + 5 == Pointer(0x8) 14 | # Subtract offset 15 | assert Pointer(0x3) - 2 == Pointer(0x1) 16 | 17 | 18 | def test_overflow(): 19 | with pytest.raises(ValueError): 20 | Pointer(0x3) - 5 21 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from memflow import * 2 | 3 | 4 | class POINT(Structure): 5 | _fields_ = [("x", c_uint32), ("y", c_float)] 6 | 7 | 8 | class TEST(Structure): 9 | _fields_ = [("one", (c_uint32 * 2)), ("two", c_int64), ("ptr", POINTER64(POINT))] 10 | 11 | 12 | def test_sizeof(): 13 | assert sizeof(TEST) == 0x18 14 | 15 | 16 | def test_struct_repr(): 17 | assert repr(POINT(1, 2)) == "POINT(x=1, y=2)" 18 | assert repr(TEST([1, 2], 3, 0x0)) == "TEST(one=[1, 2], two=3, ptr=0)" 19 | 20 | 21 | def test_struct_str(): 22 | assert str(POINT(1, 2)) == "x=1 y=2" 23 | assert str(TEST([1, 2], 3, 0x0)) == "one=[1, 2] two=3 ptr=0" 24 | -------------------------------------------------------------------------------- /tests/test_ctypes.py: -------------------------------------------------------------------------------- 1 | from memflow import Process, ProcessInfo, ProcessState, Inventory, Os, dummy 2 | from ctypes import * 3 | 4 | 5 | class POINT(Structure): 6 | _fields_ = [("x", c_uint32), ("y", c_float)] 7 | 8 | def __str__(self): 9 | return f"POINT = {self.x}, {self.y}" 10 | 11 | 12 | def test_basic(): 13 | proc = dummy.quick_process(4096, bytes([0x8])) 14 | proc_address = proc.info().address 15 | 16 | # Test writing new `TEST` structure. 17 | proc.write(proc_address, POINT, POINT(55, 3.14)) 18 | 19 | # Test reading a structure. 20 | test_works = proc.read(proc_address, POINT) 21 | assert test_works.x == 55 22 | -------------------------------------------------------------------------------- /examples/native.py: -------------------------------------------------------------------------------- 1 | from memflow import * 2 | import logging 3 | 4 | 5 | class COFFHeader(Structure): 6 | _fields_ = [ 7 | ("_pad0x0", c_uint8 * 6), 8 | ("sections", c_uint16), 9 | ("timestamp", c_uint32), 10 | ] 11 | 12 | 13 | # Setup logging 14 | FORMAT = "%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s" 15 | logging.basicConfig(format=FORMAT) 16 | logging.getLogger().setLevel(logging.INFO) 17 | 18 | inventory = Inventory() 19 | os = inventory.create_os("native") 20 | process = os.process_from_name("CalculatorApp.exe") 21 | module = process.module_by_name("CalculatorApp.dll") 22 | header = process.read(module.base + 0x40, COFFHeader) 23 | print(header) 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memflow-py" 3 | version = "0.2.0" 4 | authors = ["Dan Killinger "] 5 | edition = "2021" 6 | keywords = ["memflow", "python"] 7 | description = "Python package for memflow" 8 | repository = "https://github.com/memflow/memflow-py/" 9 | license = "MIT" 10 | 11 | [lib] 12 | name = "memflow" 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | pyo3 = { version = "0.20", features = [ 17 | "extension-module", 18 | "abi3-py37", 19 | "macros", 20 | ] } 21 | pyo3-log = "0.9" 22 | memflow = { version = "0.2", features = ["plugins", "dummy_mem"] } 23 | cglue = "0.2" 24 | thiserror = "1.0" 25 | indexmap = "2.1" 26 | 27 | [profile.release] 28 | lto = "fat" 29 | codegen-units = 1 30 | opt-level = 3 31 | -------------------------------------------------------------------------------- /python/memflow/dummy.pyi: -------------------------------------------------------------------------------- 1 | import memflow 2 | 3 | from typing import Optional, Type, Any, List 4 | 5 | def quick_process(virt_size: int, buffer: bytearray) -> memflow.Process: ... 6 | 7 | class DummyOs: 8 | def __init__(self, memory: DummyMemory) -> self: ... 9 | def retrieve_os(self) -> memflow.Os: ... 10 | def alloc_process(self, size: int) -> int: ... 11 | def alloc_process_with_module(self, size: int) -> int: ... 12 | def add_modules_for_process(self, pid: int, count: int, min_size: int): ... 13 | 14 | class DummyMemory: 15 | def __init__(self, size: int) -> self: ... 16 | def read(self, addr: int, type: Type[_CT]) -> Any: ... 17 | def read_ptr(self, ptr: Any) -> Any: ... 18 | def write(self, addr: int, type: Type[_CT], value: Any): ... 19 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "memflow" 3 | version = "0.2.0" 4 | authors = [{ name = "Dan Killinger", email = "git@dank.anonaddy.com" }] 5 | description = "Physical memory introspection framework" 6 | readme = "README.md" 7 | requires-python = ">=3.7" 8 | classifiers = [ 9 | "Programming Language :: Rust", 10 | "License :: OSI Approved :: MIT License", 11 | "Operating System :: OS Independent", 12 | "Topic :: System :: Operating System", 13 | "Typing :: Typed", 14 | ] 15 | 16 | [project.urls] 17 | "Homepage" = "https://github.com/memflow/memflow-py" 18 | "Bug Tracker" = "https://github.com/memflow/memflow-py/issues" 19 | 20 | [build-system] 21 | requires = ["maturin>=1.0,<2.0"] 22 | build-backend = "maturin" 23 | 24 | [tool.maturin] 25 | python-source = "python" 26 | 27 | [project.optional-dependencies] 28 | dev = ["pytest==7.1", "black==22.10"] 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: PyO3/maturin-action@v1 17 | with: 18 | manylinux: auto 19 | command: build 20 | args: --release -o dist -i 3.7 21 | - name: Upload wheels 22 | uses: actions/upload-artifact@v3 23 | with: 24 | name: wheels 25 | path: dist 26 | 27 | upload: 28 | runs-on: ubuntu-latest 29 | if: "startsWith(github.ref, 'refs/tags/')" 30 | needs: [ build ] 31 | steps: 32 | - uses: actions/download-artifact@v3 33 | with: 34 | name: wheels 35 | - name: Upload to PyPI 36 | uses: PyO3/maturin-action@v1 37 | env: 38 | MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 39 | with: 40 | command: upload 41 | args: --skip-existing * 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dan Killinger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memflow-py 2 | 3 | [Python] support for [Memflow], a physical memory introspection framework written in [Rust]. 4 | 5 | ## Installation 6 | 7 | 1. Install python package: `pip install memflow` 8 | 2. Install appropriate memflow components (see [memflowup]). 9 | 10 | ## Example 11 | 12 | ```py 13 | from memflow import * 14 | 15 | class COFFHeader(Structure): 16 | _fields_ = [ 17 | ("_pad0x0", c_byte * 6), 18 | ("sections", c_short), 19 | ("timestamp", c_uint32), 20 | ] 21 | 22 | inventory = Inventory() 23 | os = inventory.create_os("native") 24 | process = os.process_from_name("CalculatorApp.exe") 25 | module = process.module_by_name("CalculatorApp.dll") 26 | header = process.read(module.base + 0x40, COFFHeader) 27 | print(header) 28 | ``` 29 | 30 | ## Building from source 31 | 32 | ### Prerequisites 33 | 34 | - Rust ([Compilation support](https://github.com/memflow/memflow#compilation-support)) 35 | - Python (3.7 and up) 36 | - [python virtual environment](https://docs.python.org/3/tutorial/venv.html) 37 | 38 | ### Steps 39 | 40 | 1. Fetch repository: `git clone https://github.com/memflow/memflow-py` 41 | 2. Install maturin package: `pip install maturin` 42 | 3. Install dev packages: `pip install '.[dev]'` 43 | 4. Create virtualenv `virtualenv .` 44 | 5. Active virtualenv `source ./bin/activate` 45 | 6. Build wheels and install package: `maturin develop` 46 | 7. Repeat step 4 when you make changes to source. 47 | 7. Install pytest `pip install pytest` 48 | 8. Test your changes: `python -m pytest` 49 | 50 | For more information on building please see [Maturin]. 51 | 52 | [Memflow]: https://github.com/memflow/memflow 53 | [memflowup]: https://github.com/memflow/memflowup 54 | [Rust]: https://rust-lang.org/ 55 | [Python]: https://python.org/ 56 | [Maturin]: https://github.com/PyO3/maturin 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::array::TryFromSliceError; 2 | 3 | use internal::InternalDT; 4 | use pyo3::{exceptions::PyException, prelude::*}; 5 | use thiserror::Error; 6 | 7 | pub(crate) mod connector; 8 | pub(crate) mod dummy; 9 | pub(crate) mod internal; 10 | pub(crate) mod inventory; 11 | pub(crate) mod os; 12 | pub(crate) mod process; 13 | 14 | pub type Result = std::result::Result; 15 | 16 | #[derive(Error, Debug)] 17 | pub enum MemflowPyError { 18 | #[error(transparent)] 19 | Memflow(#[from] memflow::error::Error), 20 | #[error(transparent)] 21 | Python(#[from] PyErr), 22 | #[error("Python type `{0}` is not a valid type")] 23 | InvalidType(String), 24 | #[error("Python type found for `{0}`")] 25 | NoType(String), 26 | #[error(transparent)] 27 | ByteCast(#[from] TryFromSliceError), 28 | #[error("Python object missing attribute `{0}`")] 29 | MissingAttribute(String), 30 | #[error("The cglue object `{0}` is missing impl for `{1}`")] 31 | MissingCGlueImpl(String, String), 32 | #[error("The arch {0} is not valid")] 33 | InvalidArch(String), 34 | } 35 | 36 | impl From for PyErr { 37 | fn from(err: MemflowPyError) -> Self { 38 | match err { 39 | MemflowPyError::Python(e) => e, 40 | _ => PyException::new_err(err.to_string()), 41 | } 42 | } 43 | } 44 | 45 | #[pyfunction] 46 | fn sizeof(ty: PyObject) -> PyResult { 47 | let dt: InternalDT = ty.try_into()?; 48 | Ok(dt.size()) 49 | } 50 | 51 | #[pymodule] 52 | #[pyo3(name = "memflow")] 53 | fn memflow_py(_py: Python<'_>, m: &PyModule) -> PyResult<()> { 54 | pyo3_log::init(); 55 | dummy::register_dummy_module(_py, m)?; 56 | m.add_function(wrap_pyfunction!(sizeof, m)?)?; 57 | m.add_class::()?; 58 | m.add_class::()?; 59 | m.add_class::()?; 60 | m.add_class::()?; 61 | m.add_class::()?; 62 | m.add_class::()?; 63 | m.add_class::()?; 64 | m.add_class::()?; 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'src/**' 7 | - 'tests/**' 8 | - 'python/**' 9 | - '.github/**' 10 | pull_request: 11 | paths: 12 | - 'src/**' 13 | - 'tests/**' 14 | - 'python/**' 15 | - '.github/**' 16 | 17 | env: 18 | PACKAGE_NAME: memflow 19 | 20 | jobs: 21 | python: 22 | runs-on: ${{ matrix.os }} 23 | 24 | strategy: 25 | matrix: 26 | os: [ubuntu-latest, windows-latest] # TODO: Check macos compat 27 | python: ["3.7", "3.8", "3.9", "3.10"] 28 | target: [x64] # TODO: Check x86 compat 29 | 30 | steps: 31 | - uses: actions/checkout@v3 32 | - name: Set up Python ${{ matrix.python }} 33 | uses: actions/setup-python@v4 34 | id: python 35 | with: 36 | python-version: ${{ matrix.python }} 37 | architecture: ${{ matrix.target }} 38 | - uses: PyO3/maturin-action@v1 39 | with: 40 | target: ${{ matrix.target }} 41 | command: build 42 | args: --release -o dist --interpreter=${{ steps.python.outputs.python-path }} 43 | - name: Install built package 44 | run: pip install ${{ env.PACKAGE_NAME }} --no-index --find-links dist --force-reinstall 45 | - name: Install dev dependencies 46 | run: pip install '.[dev]' 47 | - name: Run tests 48 | run: pytest --verbose 49 | - name: black 50 | run: black --check tests/ python/ examples/ 51 | 52 | rust: 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v3 57 | - uses: actions/cache@v1 58 | with: 59 | path: $HOME/.cargo 60 | key: cargo-${{ hashFiles('Cargo.lock') }} 61 | - name: Install rust 62 | uses: dtolnay/rust-toolchain@master 63 | with: 64 | toolchain: stable 65 | components: rustfmt, clippy 66 | - name: Run clippy 67 | uses: actions-rs/clippy-check@v1 68 | with: 69 | token: ${{ secrets.GITHUB_TOKEN }} 70 | args: --all-features 71 | - name: Check format 72 | uses: actions-rs/cargo@v1 73 | with: 74 | command: fmt 75 | args: --all -- --check -------------------------------------------------------------------------------- /examples/unicorn_integration.py: -------------------------------------------------------------------------------- 1 | """ 2 | // example.exe 3 | 4 | int examplefn() { 5 | int a = 10; 6 | int b = 20; 7 | int c = 30; 8 | int d = a + b; 9 | int e = c - d; 10 | return e + d; 11 | } 12 | 13 | int main() { 14 | int result = examplefn(); 15 | printf("result = %d\n", result); 16 | 17 | system("PAUSE"); 18 | return 0; 19 | } 20 | """ 21 | from unicorn import * 22 | from unicorn.x86_const import * 23 | from memflow import * 24 | 25 | PAGE_SIZE = 1024 * 4 # 4 kb 26 | FUNCTION_OFFSET = 0x1000 27 | 28 | inventory = Inventory() 29 | os = inventory.create_os("native") 30 | process = os.process_from_name("example.exe") 31 | module = process.module_by_name("example.exe") 32 | 33 | 34 | def addr_page_aligned(addr): 35 | return addr - addr % PAGE_SIZE 36 | 37 | 38 | # callback for tracing invalid memory access (READ or WRITE) 39 | def hook_mem_invalid(uc, access, address, size, value, user_data): 40 | # We will map in memory by page 41 | page_addr = addr_page_aligned(address) 42 | print(">>> Mapping in memory page 0x%x" % (page_addr)) 43 | page_bytes = process.read(page_addr, c_byte * PAGE_SIZE) 44 | uc.mem_map(page_addr, PAGE_SIZE) 45 | uc.mem_write(page_addr, bytes(page_bytes)) 46 | return True 47 | 48 | 49 | # callback for tracing basic blocks 50 | def hook_block(uc, address, size, user_data): 51 | print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) 52 | 53 | 54 | # callback for tracing instructions 55 | def hook_code(uc, address, size, user_data): 56 | print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) 57 | 58 | 59 | mu = Uc(UC_ARCH_X86, UC_MODE_64) 60 | # Intercept invalid memory events 61 | mu.hook_add( 62 | UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, 63 | hook_mem_invalid, 64 | ) 65 | 66 | mu.hook_add(UC_HOOK_CODE, hook_code) 67 | 68 | # execute till print 69 | start = module.base + FUNCTION_OFFSET 70 | 71 | # map initial executable page 72 | mu.mem_map(addr_page_aligned(start), PAGE_SIZE) 73 | page_bytes = process.read(addr_page_aligned(start), c_ubyte * PAGE_SIZE) 74 | mu.mem_write( 75 | addr_page_aligned(start), 76 | bytes(page_bytes), 77 | ) 78 | 79 | mu.emu_start(start, start + 0x49) 80 | result = mu.reg_read(UC_X86_REG_EAX) 81 | print(f">>> result = {result}") 82 | -------------------------------------------------------------------------------- /src/connector.rs: -------------------------------------------------------------------------------- 1 | use memflow::{ 2 | prelude::{ConnectorInstanceArcBox, PhysicalMemory}, 3 | types::umem, 4 | }; 5 | use pyo3::{exceptions::PyException, prelude::*}; 6 | 7 | use crate::internal::InternalDT; 8 | 9 | #[derive(Clone)] 10 | #[pyclass(name = "Connector")] 11 | pub struct PyConnector(ConnectorInstanceArcBox<'static>); 12 | 13 | impl PyConnector { 14 | pub fn new(inst: ConnectorInstanceArcBox<'static>) -> Self { 15 | Self(inst) 16 | } 17 | } 18 | 19 | #[pymethods] 20 | impl PyConnector { 21 | #[getter] 22 | fn max_address(&mut self) -> umem { 23 | self.0.metadata().max_address.to_umem() 24 | } 25 | 26 | #[getter] 27 | fn real_size(&mut self) -> umem { 28 | self.0.metadata().real_size 29 | } 30 | 31 | #[getter] 32 | fn readonly(&mut self) -> bool { 33 | self.0.metadata().readonly 34 | } 35 | 36 | #[getter] 37 | fn ideal_batch_size(&mut self) -> u32 { 38 | self.0.metadata().ideal_batch_size 39 | } 40 | 41 | fn phys_read(&mut self, addr: umem, ty: PyObject) -> PyResult { 42 | let dt: InternalDT = ty.try_into()?; 43 | let mut raw: Vec = vec![0; dt.size()]; 44 | 45 | self.0 46 | .phys_read_into(addr.into(), raw.as_mut_slice()) 47 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 48 | 49 | Ok(dt.py_from_bytes(raw)?) 50 | } 51 | 52 | fn phys_read_ptr(&mut self, ptr_inst: PyObject) -> PyResult { 53 | let addr: umem = Python::with_gil(|py| ptr_inst.getattr(py, "addr")?.extract(py))?; 54 | let dt: InternalDT = Python::with_gil(|py| ptr_inst.getattr(py, "_type_")?.try_into())?; 55 | let mut raw: Vec = vec![0; dt.size()]; 56 | 57 | self.0 58 | .phys_read_into(addr.into(), raw.as_mut_slice()) 59 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 60 | 61 | Ok(dt.py_from_bytes(raw)?) 62 | } 63 | 64 | fn phys_write(&mut self, addr: umem, ty: PyObject, value: PyObject) -> PyResult<()> { 65 | let dt: InternalDT = ty.try_into()?; 66 | 67 | self.0 68 | .phys_write(addr.into(), dt.py_to_bytes(value)?.as_mut_slice()) 69 | .map_err(|e| PyException::new_err(format!("failed to write bytes {}", e)))?; 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | impl From> for PyConnector { 76 | fn from(inst: ConnectorInstanceArcBox<'static>) -> Self { 77 | Self(inst) 78 | } 79 | } 80 | 81 | impl From for ConnectorInstanceArcBox<'static> { 82 | fn from(value: PyConnector) -> Self { 83 | value.0 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/test_dummy.py: -------------------------------------------------------------------------------- 1 | from memflow import * 2 | import pytest 3 | 4 | 5 | class POINT(Structure): 6 | _fields_ = [("x", c_uint32), ("y", c_float)] 7 | 8 | def __str__(self): 9 | return f"POINT = {self.x}, {self.y}" 10 | 11 | 12 | class TEST(Structure): 13 | _fields_ = [("one", (c_uint32 * 2)), ("two", c_int64), ("ptr", POINTER64(POINT))] 14 | 15 | def __str__(self): 16 | return f"TEST = {self.one}, {self.two}, {self.ptr}" 17 | 18 | 19 | def test_basic(): 20 | proc = dummy.quick_process(4096, bytes([0x8])) 21 | proc_address = proc.info().address 22 | 23 | # Test writing new `TEST` structure. 24 | test_struct = TEST((1, 2), -2, POINTER64(POINT)(proc_address + 0x777)) 25 | proc.write(proc_address, TEST, test_struct) 26 | proc.write(proc_address + 0x777, POINT, POINT(55, 3.14)) 27 | 28 | # Test reading a structure. 29 | test_works = proc.read(proc_address, TEST) 30 | assert test_works.two == -2 31 | 32 | # Test reading through a pointer. 33 | point_works = proc.read_ptr(test_works.ptr) 34 | assert point_works.x == 55 35 | 36 | 37 | @pytest.mark.skip( 38 | reason="failing do to memory management escape on rust side (see issue #1)" 39 | ) 40 | def test_os_phys_rw(): 41 | my_os = dummy.DummyOs(dummy.DummyMemory(4096)).retrieve_os() 42 | 43 | # Test writing new `TEST` structure. 44 | test_struct = TEST((1, 2), 2, POINTER64(POINT)(0x777)) 45 | my_os.phys_write(0, TEST, test_struct) 46 | my_os.phys_write(0x777, POINT, POINT(55, 3.14)) 47 | 48 | # Test reading a structure. 49 | test_works = my_os.phys_read(0, TEST) 50 | assert test_works.two == 2 51 | 52 | # Test reading through a pointer. 53 | point_works = my_os.phys_read_ptr(test_works.ptr) 54 | assert point_works.x == 55 55 | 56 | 57 | class TEST_OFFSETS(Structure): 58 | _fields_ = [("one", (c_uint32 * 2)), ("two", c_int64)] 59 | _offsets_ = [(0x8, "two_offset", c_int64)] 60 | 61 | def __str__(self): 62 | return f"TEST_OFFSETS = {self.one}, {self.two}" 63 | 64 | 65 | def test_offsets(): 66 | mem = dummy.DummyMemory(4096) 67 | 68 | # Test writing new `TEST` structure. 69 | test_struct = TEST_OFFSETS((1, 2), 2, two_offset=2) 70 | mem.write(0, TEST_OFFSETS, test_struct) 71 | 72 | # Test reading a structure with offsets. 73 | test_works = mem.read(0, TEST_OFFSETS) 74 | assert test_works.two_offset == 2 75 | 76 | 77 | class NESTED_OFFSET_TEST(Structure): 78 | _fields_ = [("inner", TEST_OFFSETS)] 79 | 80 | 81 | # TODO: Test `_anonymous_` make sure it can unnest a struct field into struct 82 | def test_nested_offsets(): 83 | mem = dummy.DummyMemory(4096) 84 | 85 | # Test writing new `TEST` structure. 86 | test_struct = TEST_OFFSETS((1, 2), 2, two_offset=2) 87 | mem.write(0, TEST_OFFSETS, test_struct) 88 | 89 | # Test reading a nested structure with offsets. 90 | test_works = mem.read(0, NESTED_OFFSET_TEST).inner 91 | assert test_works.two_offset == 2 92 | 93 | 94 | def test_string(): 95 | proc = dummy.quick_process(4096, bytes([0x8])) 96 | proc_address = proc.info().address 97 | 98 | # Test writing a string using `bytes` 99 | proc.write(proc_address, c_char * 8, bytes("it works", "utf-8")) 100 | # Test reading a char null terminated string. 101 | test_works = proc.read_char_string(proc_address) 102 | assert test_works == "it works" 103 | 104 | 105 | def test_struct_array(): 106 | proc = dummy.quick_process(4096, bytes([0x8])) 107 | proc_address = proc.info().address 108 | 109 | # Test writing an array of structures. 110 | proc.write(proc_address, POINT * 3, [POINT(1, 2), POINT(3, 4), POINT(5, 6)]) 111 | # Test reading an array of structures. 112 | test_works = proc.read(proc_address, POINT * 3) 113 | assert test_works[0] == POINT(1, 2) 114 | -------------------------------------------------------------------------------- /src/dummy.rs: -------------------------------------------------------------------------------- 1 | // Provide dummy api for testing from python. 2 | 3 | use memflow::{ 4 | dummy::{DummyMemory, DummyOs}, 5 | prelude::{IntoProcessInstance, Os, OsInstance, PhysicalMemory, Pid}, 6 | types::umem, 7 | }; 8 | use pyo3::{exceptions::PyException, prelude::*}; 9 | // Used for trait_obj 10 | use cglue::arc::CArc; 11 | use cglue::*; 12 | 13 | use crate::{internal::InternalDT, os::PyOs, process::PyProcess, MemflowPyError}; 14 | 15 | #[pymodule] 16 | pub fn register_dummy_module(_py: Python<'_>, parent_module: &PyModule) -> PyResult<()> { 17 | let child_module = PyModule::new(_py, "dummy")?; 18 | child_module.add_class::()?; 19 | child_module.add_class::()?; 20 | child_module.add_function(wrap_pyfunction!(quick_process, child_module)?)?; 21 | parent_module.add_submodule(child_module)?; 22 | Ok(()) 23 | } 24 | 25 | #[pyfunction] 26 | fn quick_process(virt_size: usize, buffer: &[u8]) -> PyProcess { 27 | PyProcess::new(group_obj!( 28 | (DummyOs::quick_process(virt_size, buffer), CArc::default()) as IntoProcessInstance 29 | )) 30 | } 31 | 32 | #[derive(Clone)] 33 | #[pyclass(name = "DummyOs")] 34 | pub struct PyDummyOs(DummyOs); 35 | 36 | #[pymethods] 37 | impl PyDummyOs { 38 | #[new] 39 | fn new(memory: PyDummyMemory) -> Self { 40 | DummyOs::new(memory.into()).into() 41 | } 42 | 43 | fn retrieve_os(&mut self) -> PyOs { 44 | group_obj!((self.0.clone(), CArc::default()) as OsInstance).into() 45 | } 46 | 47 | fn alloc_process(&mut self, size: usize) -> Pid { 48 | self.0.alloc_process(size, &[]) 49 | } 50 | 51 | fn alloc_process_with_module(&mut self, size: usize) -> Pid { 52 | self.0.alloc_process_with_module(size, &[]) 53 | } 54 | 55 | fn add_modules_for_process(&mut self, pid: Pid, count: usize, min_size: usize) -> PyResult<()> { 56 | self.0 57 | .process_by_pid(pid) 58 | .map_err(MemflowPyError::Memflow)? 59 | .proc 60 | .add_modules(count, min_size); 61 | 62 | Ok(()) 63 | } 64 | } 65 | 66 | impl From for PyDummyOs { 67 | fn from(dummy_os: DummyOs) -> Self { 68 | Self(dummy_os) 69 | } 70 | } 71 | 72 | impl From for DummyOs { 73 | fn from(py_dummy_os: PyDummyOs) -> Self { 74 | py_dummy_os.0 75 | } 76 | } 77 | 78 | #[derive(Clone)] 79 | #[pyclass(name = "DummyMemory")] 80 | pub struct PyDummyMemory(DummyMemory); 81 | 82 | #[pymethods] 83 | impl PyDummyMemory { 84 | #[new] 85 | fn new(size: usize) -> Self { 86 | DummyMemory::new(size).into() 87 | } 88 | 89 | fn read(&mut self, addr: umem, ty: PyObject) -> PyResult { 90 | let dt: InternalDT = ty.try_into()?; 91 | let mut raw: Vec = vec![0; dt.size()]; 92 | 93 | self.0 94 | .phys_read_into(addr.into(), raw.as_mut_slice()) 95 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 96 | 97 | Ok(dt.py_from_bytes(raw)?) 98 | } 99 | 100 | fn read_ptr(&mut self, ptr_inst: PyObject) -> PyResult { 101 | let addr: umem = Python::with_gil(|py| ptr_inst.getattr(py, "addr")?.extract(py))?; 102 | let dt: InternalDT = Python::with_gil(|py| ptr_inst.getattr(py, "_type_")?.try_into())?; 103 | let mut raw: Vec = vec![0; dt.size()]; 104 | 105 | self.0 106 | .phys_read_into(addr.into(), raw.as_mut_slice()) 107 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 108 | 109 | Ok(dt.py_from_bytes(raw)?) 110 | } 111 | 112 | fn write(&mut self, addr: umem, ty: PyObject, value: PyObject) -> PyResult<()> { 113 | let dt: InternalDT = ty.try_into()?; 114 | 115 | self.0 116 | .phys_write(addr.into(), dt.py_to_bytes(value)?.as_mut_slice()) 117 | .map_err(|e| PyException::new_err(format!("failed to write bytes {}", e)))?; 118 | 119 | Ok(()) 120 | } 121 | } 122 | 123 | impl From for PyDummyMemory { 124 | fn from(dm: DummyMemory) -> Self { 125 | Self(dm) 126 | } 127 | } 128 | 129 | impl From for DummyMemory { 130 | fn from(py_dm: PyDummyMemory) -> Self { 131 | py_dm.0 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /python/memflow/memflow.pyi: -------------------------------------------------------------------------------- 1 | import memflow.dummy 2 | 3 | from typing import Optional, Type, Any, List 4 | 5 | def sizeof(type: Type[_CT]) -> int: ... 6 | 7 | class Inventory: 8 | def __init__(self, path: Optional[str]) -> self: ... 9 | def add_dir(self, path: str, filter: Optional[str]): ... 10 | def available_os(self) -> List[str]: ... 11 | def available_connectors(self) -> List[str]: ... 12 | def connector_help(self, name: str) -> str: ... 13 | def os_help(self, name: str) -> str: ... 14 | def connector_target_list(self, name: str) -> List[TargetInfo]: ... 15 | def create_connector( 16 | self, name: str, input: Optional[Connector], args: Optional[str] 17 | ) -> Connector: ... 18 | def create_os(self, name: str, input: Optional[Os], args: Optional[str]) -> Os: ... 19 | 20 | class TargetInfo: 21 | @property 22 | def name(self) -> str: ... 23 | 24 | class Connector: 25 | @property 26 | def max_address(self) -> int: ... 27 | @property 28 | def real_size(self) -> int: ... 29 | @property 30 | def readonly(self) -> bool: ... 31 | @property 32 | def ideal_batch_size(self) -> int: ... 33 | def phys_read(self, addr: int, type: Type[_CT]) -> Any: ... 34 | def phys_read_ptr(self, ptr: Any) -> Any: ... 35 | def phys_write(self, addr: int, type: Type[_CT], value: Any): ... 36 | 37 | class Os: 38 | @property 39 | def arch(self) -> str: ... 40 | @property 41 | def base(self) -> int: ... 42 | @property 43 | def size(self) -> int: ... 44 | def process_info_list(self) -> List[ProcessInfo]: ... 45 | def process_from_name(self, name: str) -> Process: ... 46 | def process_from_pid(self, pid: int) -> Process: ... 47 | def process_from_info(self, info: ProcessInfo) -> Process: ... 48 | def process_from_addr(self, addr: int) -> Process: ... 49 | def module_info_list(self) -> List[ModuleInfo]: ... 50 | def module_from_name(self, name: str) -> ModuleInfo: ... 51 | def read(self, addr: int, type: Type[_CT]) -> Any: ... 52 | def read_ptr(self, ptr: Any) -> Any: ... 53 | def write(self, addr: int, type: Type[_CT], value: Any): ... 54 | def phys_read(self, addr: int, type: Type[_CT]) -> Any: ... 55 | def phys_read_ptr(self, ptr: Any) -> Any: ... 56 | def phys_write(self, addr: int, type: Type[_CT], value: Any): ... 57 | 58 | class Process: 59 | def read(self, addr: int, type: Type[_CT]) -> Any: ... 60 | def read_ptr(self, ptr: Any) -> Any: ... 61 | def read_char_string(self, addr: int, max_bytes: Optional[int]) -> str: ... 62 | def read_wchar_string(self, addr: int, max_bytes: Optional[int]) -> bytes: ... 63 | def write(self, addr: int, type: Type[_CT], value: Any): ... 64 | def module_info_list(self) -> List[ModuleInfo]: ... 65 | def module_by_name(self, name: str) -> ModuleInfo: ... 66 | def info(self) -> ProcessInfo: ... 67 | 68 | class ProcessInfo: 69 | def __init__( 70 | self, 71 | address: int, 72 | pid: int, 73 | state: ProcessState, 74 | name: str, 75 | path: str, 76 | command_line: str, 77 | sys_arch: ArchitectureIdent, 78 | proc_arch: ArchitectureIdent, 79 | ) -> self: ... 80 | @property 81 | def address(self) -> int: ... 82 | @property 83 | def name(self) -> str: ... 84 | @property 85 | def pid(self) -> int: ... 86 | @property 87 | def state(self) -> ProcessState: ... 88 | @property 89 | def path(self) -> str: ... 90 | @property 91 | def command_line(self) -> str: ... 92 | @property 93 | def sys_arch(self) -> ArchitectureIdent: ... 94 | @property 95 | def proc_arch(self) -> ArchitectureIdent: ... 96 | 97 | class ModuleInfo: 98 | def __init__( 99 | self, 100 | name: str, 101 | address: int, 102 | base: int, 103 | size: int, 104 | path: str, 105 | process_addr: int, 106 | arch: ArchitectureIdent, 107 | ) -> self: ... 108 | @property 109 | def address(self) -> int: ... 110 | @property 111 | def name(self) -> str: ... 112 | @property 113 | def base(self) -> int: ... 114 | @property 115 | def size(self) -> int: ... 116 | @property 117 | def path(self) -> str: ... 118 | @property 119 | def parent_process(self) -> int: ... 120 | @property 121 | def arch(self) -> ArchitectureIdent: ... 122 | 123 | class ProcessState: 124 | def __init__(self, alive: bool, exit_code: Optional[int]) -> self: ... 125 | def is_alive(self) -> bool: ... 126 | def is_dead(self) -> bool: ... 127 | def is_unknown(self) -> bool: ... 128 | 129 | class ArchitectureIdent: 130 | def __init__( 131 | self, arch: str, page_size: Optional[int], address_extensions: Optional[bool] 132 | ) -> self: ... 133 | def is_alive(self) -> bool: ... 134 | def is_dead(self) -> bool: ... 135 | def is_unknown(self) -> bool: ... 136 | 137 | class Pointer: 138 | def __init__(self, addr: int) -> self: ... 139 | def is_null(self) -> bool: ... 140 | -------------------------------------------------------------------------------- /src/inventory.rs: -------------------------------------------------------------------------------- 1 | use memflow::prelude::{ConnectorArgs, Inventory, OsArgs, TargetInfo}; 2 | use pyo3::prelude::*; 3 | 4 | use crate::{connector::PyConnector, os::PyOs, MemflowPyError}; 5 | 6 | #[pyclass(name = "Inventory")] 7 | pub struct PyInventory(Inventory); 8 | 9 | #[pymethods] 10 | impl PyInventory { 11 | /// Creates a new inventory of plugins from the provided path. 12 | /// The path has to be a valid directory or the function will fail with an `Error::IO` error. 13 | /// 14 | /// If no path is provided the Inventory will query PATH, and an additional set of of directories (standard unix ones, if unix, 15 | /// and "HOME/.local/lib" on all OSes) for "memflow" directory, and if there is one, then 16 | /// search for libraries in there. 17 | #[new] 18 | fn new(path: Option<&str>) -> PyResult { 19 | let inventory = match path { 20 | Some(path) => Inventory::scan_path(path).map_err(MemflowPyError::Memflow)?, 21 | None => Inventory::scan(), 22 | }; 23 | 24 | Ok(Self(inventory)) 25 | } 26 | 27 | /// Adds a library directory to the inventory 28 | /// 29 | /// This function optionally applies additional filter to only scan potentially wanted files 30 | /// 31 | /// # Safety 32 | /// 33 | /// Same as previous functions - compiler can not guarantee the safety of 34 | /// third party library implementations. 35 | fn add_dir(&mut self, path: &str, filter: Option<&str>) -> PyResult<()> { 36 | match filter { 37 | Some(filter) => self 38 | .0 39 | .add_dir_filtered(path.into(), filter) 40 | .map_err(MemflowPyError::Memflow)?, 41 | None => self 42 | .0 43 | .add_dir(path.into()) 44 | .map_err(MemflowPyError::Memflow)?, 45 | }; 46 | 47 | Ok(()) 48 | } 49 | 50 | /// Returns the names of all currently available connectors that can be used. 51 | fn available_connectors(&self) -> Vec { 52 | self.0.available_connectors() 53 | } 54 | 55 | /// Returns the names of all currently available os plugins that can be used. 56 | fn available_os(&self) -> Vec { 57 | self.0.available_os() 58 | } 59 | 60 | /// Returns the help string of the given Connector. 61 | /// 62 | /// This function returns an error in case the Connector was not found or does not implement the help feature. 63 | fn connector_help(&self, name: &str) -> PyResult { 64 | Ok(self 65 | .0 66 | .connector_help(name) 67 | .map_err(MemflowPyError::Memflow)?) 68 | } 69 | 70 | /// Returns the help string of the given Os Plugin. 71 | /// 72 | /// This function returns an error in case the Os Plugin was not found or does not implement the help feature. 73 | fn os_help(&self, name: &str) -> PyResult { 74 | Ok(self.0.os_help(name).map_err(MemflowPyError::Memflow)?) 75 | } 76 | 77 | /// Returns a list of all available targets of the connector. 78 | /// 79 | /// This function returns an error in case the connector does not implement this feature. 80 | fn connector_target_list(&self, name: &str) -> PyResult> { 81 | Ok(self 82 | .0 83 | .connector_target_list(name) 84 | .map_err(MemflowPyError::Memflow)? 85 | .into_iter() 86 | .map(PyTargetInfo::from) 87 | .collect()) 88 | } 89 | 90 | // TODO: 91 | // Creates a new Connector / OS builder. 92 | // pub fn builder(&self) -> BuilderEmpty 93 | 94 | /// Tries to create a new instance for the library with the given name. 95 | /// The instance will be initialized with the args provided to this call. 96 | /// 97 | /// In case no library could be found this will throw an `Error::Library`. 98 | /// 99 | /// # Safety 100 | /// 101 | /// This function assumes all libraries were loaded with appropriate safety 102 | /// checks in place. This function is safe, but can crash if previous checks 103 | /// fail. 104 | fn create_connector( 105 | &self, 106 | name: &str, 107 | input: Option, 108 | args: Option<&str>, 109 | ) -> PyResult { 110 | Ok(PyConnector::new( 111 | self.0 112 | .create_connector( 113 | name, 114 | input.map(|o| o.into()), 115 | args.and_then(|a| str::parse::(a).ok()) 116 | .as_ref(), 117 | ) 118 | .map_err(MemflowPyError::Memflow)?, 119 | )) 120 | } 121 | 122 | /// Create OS instance 123 | /// 124 | /// This is the primary way of building a OS instance. 125 | /// 126 | /// # Arguments 127 | /// 128 | /// * `name` - name of the target OS 129 | /// * `input` - connector to be passed to the OS 130 | /// * `args` - arguments to be passed to the OS 131 | fn create_os( 132 | &self, 133 | name: &str, 134 | input: Option, 135 | args: Option<&str>, 136 | ) -> PyResult { 137 | Ok(self 138 | .0 139 | .create_os( 140 | name, 141 | input.map(|c| c.into()), 142 | args.and_then(|a| str::parse::(a).ok()).as_ref(), 143 | ) 144 | .map_err(MemflowPyError::Memflow)? 145 | .into()) 146 | } 147 | 148 | // TODO: 149 | // Sets the maximum logging level in all plugins and updates the 150 | // internal [`PluginLogger`] in each plugin instance. 151 | // pub fn set_max_log_level(&self, level: LevelFilter) 152 | } 153 | 154 | #[derive(Clone)] 155 | #[pyclass(name = "TargetInfo")] 156 | pub struct PyTargetInfo(TargetInfo); 157 | 158 | #[pymethods] 159 | impl PyTargetInfo { 160 | #[getter] 161 | fn name(&self) -> String { 162 | self.0.name.to_string() 163 | } 164 | 165 | fn __str__(&self) -> String { 166 | format!("{:?}", self.0) 167 | } 168 | 169 | fn __repr__(&self) -> String { 170 | format!("{:?}", self.0) 171 | } 172 | } 173 | 174 | impl From for PyTargetInfo { 175 | fn from(ti: TargetInfo) -> Self { 176 | Self(ti) 177 | } 178 | } 179 | 180 | impl From for TargetInfo { 181 | fn from(py_info: PyTargetInfo) -> Self { 182 | py_info.0 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/os.rs: -------------------------------------------------------------------------------- 1 | use memflow::{ 2 | os::{process::Pid, Os}, 3 | prelude::{MemoryView, OsInstanceArcBox, PhysicalMemory}, 4 | types::umem, 5 | }; 6 | use pyo3::{exceptions::PyException, prelude::*}; 7 | use std::cell::RefCell; 8 | 9 | use crate::{ 10 | internal::InternalDT, 11 | process::{PyModuleInfo, PyProcess, PyProcessInfo}, 12 | MemflowPyError, 13 | }; 14 | 15 | #[derive(Clone)] 16 | #[pyclass(name = "Os")] 17 | pub struct PyOs(RefCell>); 18 | 19 | #[pymethods] 20 | impl PyOs { 21 | #[getter] 22 | fn arch(&mut self) -> String { 23 | self.0.borrow_mut().info().arch.to_string() 24 | } 25 | 26 | #[getter] 27 | fn base(&mut self) -> umem { 28 | self.0.borrow_mut().info().base.to_umem() 29 | } 30 | 31 | #[getter] 32 | fn size(&mut self) -> u64 { 33 | self.0.borrow_mut().info().size 34 | } 35 | 36 | pub fn process_info_list(&mut self) -> PyResult> { 37 | Ok(self 38 | .0 39 | .borrow_mut() 40 | .process_info_list() 41 | .map_err(MemflowPyError::Memflow)? 42 | .into_iter() 43 | .map(PyProcessInfo::from) 44 | .collect()) 45 | } 46 | 47 | pub fn process_from_info(&mut self, info: PyProcessInfo) -> PyResult { 48 | let t = self.0.borrow_mut().clone(); 49 | Ok(PyProcess::new(t.into_process_by_info(info.into()).unwrap())) 50 | } 51 | 52 | pub fn process_from_addr(&mut self, addr: umem) -> PyResult { 53 | let t = self.0.borrow_mut().clone(); 54 | Ok(PyProcess::new( 55 | t.into_process_by_address(addr.into()).unwrap(), 56 | )) 57 | } 58 | 59 | pub fn process_from_pid(&mut self, pid: Pid) -> PyResult { 60 | let t = self.0.borrow_mut().clone(); 61 | Ok(PyProcess::new(t.into_process_by_pid(pid).unwrap())) 62 | } 63 | 64 | pub fn process_from_name(&mut self, name: &str) -> PyResult { 65 | let t = self.0.borrow_mut().clone(); 66 | Ok(PyProcess::new(t.into_process_by_name(name).unwrap())) 67 | } 68 | 69 | fn module_info_list(&mut self) -> PyResult> { 70 | Ok(self 71 | .0 72 | .borrow_mut() 73 | .module_list() 74 | .map_err(MemflowPyError::Memflow)? 75 | .into_iter() 76 | .map(PyModuleInfo::from) 77 | .collect()) 78 | } 79 | 80 | fn module_from_name(&mut self, name: &str) -> PyResult { 81 | Ok(self 82 | .0 83 | .borrow_mut() 84 | .module_by_name(name) 85 | .map_err(MemflowPyError::Memflow)? 86 | .into()) 87 | } 88 | 89 | fn read(&mut self, addr: umem, ty: PyObject) -> PyResult { 90 | let dt: InternalDT = ty.try_into()?; 91 | 92 | let bytes = self 93 | .0 94 | .borrow_mut() 95 | .as_mut_impl_memoryview() 96 | .ok_or_else(|| { 97 | MemflowPyError::MissingCGlueImpl("Os".to_owned(), "MemoryView".to_owned()) 98 | })? 99 | .read_raw(addr.into(), dt.size()) 100 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 101 | 102 | Ok(dt.py_from_bytes(bytes)?) 103 | } 104 | 105 | fn read_ptr(&mut self, ptr_inst: PyObject) -> PyResult { 106 | let addr: umem = Python::with_gil(|py| ptr_inst.getattr(py, "addr")?.extract(py))?; 107 | let dt: InternalDT = Python::with_gil(|py| ptr_inst.getattr(py, "_type_")?.try_into())?; 108 | 109 | let bytes = self 110 | .0 111 | .borrow_mut() 112 | .as_mut_impl_memoryview() 113 | .ok_or_else(|| { 114 | MemflowPyError::MissingCGlueImpl("Os".to_owned(), "MemoryView".to_owned()) 115 | })? 116 | .read_raw(addr.into(), dt.size()) 117 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 118 | 119 | Ok(dt.py_from_bytes(bytes)?) 120 | } 121 | 122 | fn write(&mut self, addr: umem, ty: PyObject, value: PyObject) -> PyResult<()> { 123 | let dt: InternalDT = ty.try_into()?; 124 | 125 | self.0 126 | .borrow_mut() 127 | .as_mut_impl_memoryview() 128 | .ok_or_else(|| { 129 | MemflowPyError::MissingCGlueImpl("Os".to_owned(), "MemoryView".to_owned()) 130 | })? 131 | .write_raw(addr.into(), &dt.py_to_bytes(value)?) 132 | .map_err(|e| PyException::new_err(format!("failed to write bytes {}", e)))?; 133 | 134 | Ok(()) 135 | } 136 | 137 | fn phys_read(&mut self, addr: umem, ty: PyObject) -> PyResult { 138 | let dt: InternalDT = ty.try_into()?; 139 | let mut raw: Vec = vec![0; dt.size()]; 140 | 141 | self.0 142 | .borrow_mut() 143 | .as_mut_impl_physicalmemory() 144 | .ok_or_else(|| { 145 | MemflowPyError::MissingCGlueImpl("Os".to_owned(), "PhysicalMemory".to_owned()) 146 | })? 147 | .phys_read_into(addr.into(), raw.as_mut_slice()) 148 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 149 | 150 | Ok(dt.py_from_bytes(raw)?) 151 | } 152 | 153 | fn phys_read_ptr(&mut self, ptr_inst: PyObject) -> PyResult { 154 | let addr: umem = Python::with_gil(|py| ptr_inst.getattr(py, "addr")?.extract(py))?; 155 | let dt: InternalDT = Python::with_gil(|py| ptr_inst.getattr(py, "_type_")?.try_into())?; 156 | let mut raw: Vec = vec![0; dt.size()]; 157 | 158 | self.0 159 | .borrow_mut() 160 | .as_mut_impl_physicalmemory() 161 | .ok_or_else(|| { 162 | MemflowPyError::MissingCGlueImpl("Os".to_owned(), "PhysicalMemory".to_owned()) 163 | })? 164 | .phys_read_into(addr.into(), raw.as_mut_slice()) 165 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 166 | 167 | Ok(dt.py_from_bytes(raw)?) 168 | } 169 | 170 | fn phys_write(&mut self, addr: umem, ty: PyObject, value: PyObject) -> PyResult<()> { 171 | let dt: InternalDT = ty.try_into()?; 172 | 173 | self.0 174 | .borrow_mut() 175 | .as_mut_impl_physicalmemory() 176 | .ok_or_else(|| { 177 | MemflowPyError::MissingCGlueImpl("Os".to_owned(), "PhysicalMemory".to_owned()) 178 | })? 179 | .phys_write(addr.into(), dt.py_to_bytes(value)?.as_mut_slice()) 180 | .map_err(|e| PyException::new_err(format!("failed to write bytes {}", e)))?; 181 | 182 | Ok(()) 183 | } 184 | } 185 | 186 | impl From> for PyOs { 187 | fn from(inst: OsInstanceArcBox<'static>) -> Self { 188 | Self(RefCell::new(inst)) 189 | } 190 | } 191 | 192 | impl From for OsInstanceArcBox<'static> { 193 | fn from(value: PyOs) -> Self { 194 | value.0.into_inner() 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /python/memflow/__init__.py: -------------------------------------------------------------------------------- 1 | from .memflow import * 2 | 3 | 4 | class CDataTypeMeta(type): 5 | def __mul__(self, length): 6 | return ARRAY(self, length) 7 | 8 | 9 | # TODO: Move to rust 10 | class Structure(object, metaclass=CDataTypeMeta): 11 | def __new__(cls, *args, **kwargs): 12 | # self._endianness_ = "what to put here?" 13 | # unpack *args to kwargs if not already present 14 | for idx, val in enumerate(args): 15 | field_name = cls._fields_[idx][0] 16 | if not kwargs.get(field_name): 17 | kwargs[field_name] = val 18 | # Set field and offset attributes on the class 19 | for field_name, field_val in kwargs.items(): 20 | setattr(cls, field_name, field_val) 21 | return super(Structure, cls).__new__(cls) 22 | 23 | def __eq__(self, other): 24 | if self.__class__ == other.__class__: 25 | return self.__dict__ == other.__dict__ 26 | else: 27 | raise TypeError("Comparing object is not of the same structure type.") 28 | 29 | def __str__(self): 30 | fields_strs = [] 31 | append_field = lambda name: fields_strs.append(f"{name}={getattr(self, name)}") 32 | for field in self._fields_: 33 | field_name = field[0] 34 | # Skip "private" fields (i.e. _pad_0x0) 35 | if field_name.startswith("_"): 36 | continue 37 | append_field(field_name) 38 | if hasattr(self, "_offsets_"): 39 | for offset_field in self._offsets_: 40 | field_name = offset_field[1] 41 | # Skip "private" fields (i.e. _pad_0x0) 42 | if field_name.startswith("_"): 43 | continue 44 | append_field(field_name) 45 | return "{}".format(" ".join(fields_strs)) 46 | 47 | def __repr__(self): 48 | fields_strs = [] 49 | append_field = lambda name: fields_strs.append(f"{name}={getattr(self, name)}") 50 | for field in self._fields_: 51 | append_field(field[0]) 52 | if hasattr(self, "_offsets_"): 53 | for offset_field in self._offsets_: 54 | append_field(offset_field[1]) 55 | return "{}({})".format(self.__class__.__name__, ", ".join(fields_strs)) 56 | 57 | 58 | # TODO: Move to rust 59 | class Array: 60 | def __init__(self, *args): 61 | self._vals_ = args 62 | 63 | def __getitem__(self, key): 64 | return self._vals_[key] 65 | 66 | def __len__(self): 67 | return self._length_ 68 | 69 | def __str__(self): 70 | return repr(self) 71 | 72 | def __repr__(self): 73 | return "ARRAY({}, {}){}".format( 74 | self._type_.__name__, len(self), self._vals_.__repr__() 75 | ) 76 | 77 | 78 | # Cache for memflow array types. 79 | mf_arr_types = dict() 80 | 81 | 82 | def ARRAY(target_type, len): 83 | global mf_arr_types 84 | if len not in mf_arr_types: 85 | mf_arr_types[len] = dict() 86 | if target_type not in mf_arr_types[len]: 87 | arr_type = type( 88 | f"{target_type.__name__}_MF_Array_{len}", 89 | (Array,), 90 | dict(_type_=target_type, _length_=len), 91 | ) 92 | mf_arr_types[len][target_type] = arr_type 93 | return mf_arr_types[len][target_type] 94 | 95 | 96 | class CDataType(object, metaclass=CDataTypeMeta): 97 | # Preserve ctypes shorthand type 98 | _type_ = "" 99 | 100 | 101 | # Fill in c data types 102 | class c_short(CDataType): 103 | _type_ = "h" 104 | 105 | 106 | class c_ushort(CDataType): 107 | _type_ = "H" 108 | 109 | 110 | class c_long(CDataType): 111 | _type_ = "l" 112 | 113 | 114 | class c_ulong(CDataType): 115 | _type_ = "L" 116 | 117 | 118 | class c_int(CDataType): 119 | _type_ = "i" 120 | 121 | 122 | class c_uint(CDataType): 123 | _type_ = "I" 124 | 125 | 126 | class c_float(CDataType): 127 | _type_ = "f" 128 | 129 | 130 | class c_double(CDataType): 131 | _type_ = "d" 132 | 133 | 134 | class c_longdouble(CDataType): 135 | _type_ = "g" 136 | 137 | 138 | class c_longlong(CDataType): 139 | _type_ = "q" 140 | 141 | 142 | class c_ulonglong(CDataType): 143 | _type_ = "Q" 144 | 145 | 146 | class c_ubyte(CDataType): 147 | _type_ = "B" 148 | 149 | 150 | class c_byte(CDataType): 151 | _type_ = "b" 152 | 153 | 154 | class c_char(CDataType): 155 | _type_ = "c" 156 | 157 | 158 | class c_bool(CDataType): 159 | _type_ = "?" 160 | 161 | 162 | class c_wchar(CDataType): 163 | _type_ = "u" 164 | 165 | 166 | # Fill in specifically-sized types 167 | c_int8 = c_byte 168 | c_uint8 = c_ubyte 169 | for kind in [c_short, c_int, c_long, c_longlong]: 170 | if sizeof(kind) == 2: 171 | c_int16 = kind 172 | elif sizeof(kind) == 4: 173 | c_int32 = kind 174 | elif sizeof(kind) == 8: 175 | c_int64 = kind 176 | for kind in [c_ushort, c_uint, c_ulong, c_ulonglong]: 177 | if sizeof(kind) == 2: 178 | c_uint16 = kind 179 | elif sizeof(kind) == 4: 180 | c_uint32 = kind 181 | elif sizeof(kind) == 8: 182 | c_uint64 = kind 183 | del kind 184 | # Corresponds to memflow `umem` type, guaranteed to hold a whole address of introspection target. 185 | umem = c_uint64 186 | 187 | 188 | class Pointer(Structure): 189 | _fields_ = [("addr", umem)] 190 | 191 | def __init__(self, addr): 192 | if addr < 0: 193 | raise ValueError("addr should be a positive number") 194 | self.addr = addr 195 | 196 | def is_null(self): 197 | return self.addr != 0 198 | 199 | def __add__(self, other): 200 | if other < 0: 201 | raise ValueError("other should be a positive number") 202 | return Pointer(self.addr + other) 203 | 204 | def __sub__(self, other): 205 | if other < 0: 206 | raise ValueError("other should be a positive number") 207 | return Pointer(self.addr - other) 208 | 209 | def __bool__(self): 210 | return self.is_null() 211 | 212 | def __eq__(self, other): 213 | if isinstance(other, Pointer): 214 | return self.addr == other.addr 215 | return self.addr == other 216 | 217 | def __ne__(self, other): 218 | if isinstance(other, Pointer): 219 | return self.addr != other.addr 220 | return self.addr != other 221 | 222 | def __str__(self): 223 | return f"{self._type_.__name__} @ {hex(self.addr)}" 224 | 225 | def __repr__(self): 226 | return f"{self._type_.__name__}({hex(self.addr)})" 227 | 228 | 229 | # Cache for memflow pointer types. 230 | mf_lp_types = dict() 231 | 232 | 233 | def POINTER(target_type, byteness): 234 | global mf_lp_types 235 | if byteness not in mf_lp_types: 236 | mf_lp_types[byteness] = dict() 237 | if target_type not in mf_lp_types[byteness]: 238 | ptr_type = type( 239 | f"MF_LP_{target_type.__name__}", 240 | (Pointer,), 241 | dict( 242 | _type_=target_type, 243 | _byteness_=byteness, 244 | ), 245 | ) 246 | mf_lp_types[byteness][target_type] = ptr_type 247 | return mf_lp_types[byteness][target_type] 248 | 249 | 250 | def POINTER64(target_type): 251 | return POINTER(target_type, 8) 252 | 253 | 254 | def POINTER32(target_type): 255 | return POINTER(target_type, 4) 256 | -------------------------------------------------------------------------------- /examples/jupyter_tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "3cc861d1", 7 | "metadata": {}, 8 | "source": [ 9 | "First we will be setting up all imports and set up basic logging:" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "a38e057e-eecf-445d-9d18-bb8888625de1", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from memflow import *\n", 20 | "import logging\n", 21 | "\n", 22 | "FORMAT = \"%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s\"\n", 23 | "logging.basicConfig(format=FORMAT)\n", 24 | "logging.getLogger().setLevel(logging.INFO)" 25 | ] 26 | }, 27 | { 28 | "attachments": {}, 29 | "cell_type": "markdown", 30 | "id": "3ad5b916", 31 | "metadata": {}, 32 | "source": [ 33 | "The first step towards memflow is then creating an Inventory. The Inventory contains a list of all connectors found on the System:" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "id": "454c4dcc", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "inventory = Inventory()" 44 | ] 45 | }, 46 | { 47 | "attachments": {}, 48 | "cell_type": "markdown", 49 | "id": "bf27f6ba", 50 | "metadata": {}, 51 | "source": [ 52 | "The next step is creating an actual connector. Memflow connectors are used to acquire physical memory of a device. Examples of connectors are: kvm, qemu, pcileech, coredump, ...\n", 53 | "\n", 54 | "The arguments are provided as triplet, they use the following form:\n", 55 | "\n", 56 | "```\n", 57 | "{connector}:{target}:{arg1},{arg2},{arg3}\n", 58 | "```\n", 59 | "\n", 60 | "Where `target` is the name of the target (in case the connector supports multiple targets).\n", 61 | "Where `arg1`, `arg2` and `arg3` use a `key=value` format like `device=FPGA`\n", 62 | "\n", 63 | "Here we are loading the `kvm` connector and letting it connect to a virtual machine with the name `win11` as the target." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "id": "b7b091f3", 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "connector = inventory.create_connector(name=\"kvm\", args=\":win11:\")" 74 | ] 75 | }, 76 | { 77 | "attachments": {}, 78 | "cell_type": "markdown", 79 | "id": "bb246aa8", 80 | "metadata": {}, 81 | "source": [ 82 | "Without the `target` argument the kvm connector will just pick the first virtual machine it finds. It is also possible on some connectors to retrieve a list of all available targets (whereas the resulting `name` is the name of the target):" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "id": "760b6687", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "inventory.connector_target_list(\"qemu\")" 93 | ] 94 | }, 95 | { 96 | "attachments": {}, 97 | "cell_type": "markdown", 98 | "id": "150e13f0", 99 | "metadata": {}, 100 | "source": [ 101 | "It is also possible to retrieve a Help-Text for Plugins, this is especially useful when writing CLI applications:" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 19, 107 | "id": "7c2d9f98", 108 | "metadata": {}, 109 | "outputs": [ 110 | { 111 | "data": { 112 | "text/plain": [ 113 | "'The `qemu` connector implements a memflow plugin interface\\nfor QEMU on top of the Process Filesystem on Linux.\\n\\nThis connector requires access to the qemu process via the linux procfs.\\nThis means any process which loads this connector requires\\nto have at least ptrace permissions set.\\n\\nThe `target` argument specifies the target qemu virtual machine.\\nThe qemu virtual machine name can be specified when starting qemu with the -name flag.\\n\\nAvailable arguments are:\\nmap_base: override of VM memory base\\nmap_size: override of VM memory size'" 114 | ] 115 | }, 116 | "execution_count": 19, 117 | "metadata": {}, 118 | "output_type": "execute_result" 119 | } 120 | ], 121 | "source": [ 122 | "inventory.connector_help(\"qemu\")" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 20, 128 | "id": "e7f09308", 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "name": "stderr", 133 | "output_type": "stream", 134 | "text": [ 135 | "ERROR memflow.error 2022-12-18 21:00:09,824 error.rs:31 connector: not supported (Os-Plugin `win32` does not support help text.)\n" 136 | ] 137 | }, 138 | { 139 | "ename": "Exception", 140 | "evalue": "connector: not supported", 141 | "output_type": "error", 142 | "traceback": [ 143 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 144 | "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", 145 | "Cell \u001b[0;32mIn[20], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m inventory\u001b[39m.\u001b[39;49mos_help(\u001b[39m\"\u001b[39;49m\u001b[39mwin32\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n", 146 | "\u001b[0;31mException\u001b[0m: connector: not supported" 147 | ] 148 | } 149 | ], 150 | "source": [ 151 | "inventory.os_help(\"win32\")" 152 | ] 153 | }, 154 | { 155 | "attachments": {}, 156 | "cell_type": "markdown", 157 | "id": "2c510e26", 158 | "metadata": {}, 159 | "source": [ 160 | "The previously created connector can now be utilized to initialize an Os. In the given example we try to find Windows running in memory in the KVM Virtual Machine." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "id": "397ab78a-3faa-428a-93b8-d4b152bcbee2", 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "os = inventory.create_os(name=\"win32\", input=connector)" 171 | ] 172 | }, 173 | { 174 | "attachments": {}, 175 | "cell_type": "markdown", 176 | "id": "eeda98ad", 177 | "metadata": {}, 178 | "source": [ 179 | "You can now access drivers and processes on the target. In the windows (and memflow) world drivers are just modules of the root process (in the case of Windows all drivers are modules of the ntoskrnl.exe kernel process). The following example shows how to list all running drivers:" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": null, 185 | "id": "27987be4", 186 | "metadata": {}, 187 | "outputs": [], 188 | "source": [ 189 | "drivers = os.module_info_list()\n", 190 | "print(drivers)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "id": "88aa478d-d24c-46d0-b419-3310663834fd", 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "kernel = os # TODO:" 201 | ] 202 | }, 203 | { 204 | "attachments": {}, 205 | "cell_type": "markdown", 206 | "id": "3a2fc7e9", 207 | "metadata": {}, 208 | "source": [ 209 | "To access the memory of a driver or process you have to open the corresponding process:" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "id": "0dc364fa", 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [ 219 | "process = os.process_from_name(\"explorer.exe\")" 220 | ] 221 | }, 222 | { 223 | "attachments": {}, 224 | "cell_type": "markdown", 225 | "id": "d0ef57bf", 226 | "metadata": {}, 227 | "source": [ 228 | "A Process also features the same functions for retrieving modules:" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "id": "686addaa-ea2e-4291-94b7-832b99b1e344", 235 | "metadata": {}, 236 | "outputs": [], 237 | "source": [ 238 | "modules = process.module_info_list()\n", 239 | "print(modules)" 240 | ] 241 | }, 242 | { 243 | "attachments": {}, 244 | "cell_type": "markdown", 245 | "id": "9194330f", 246 | "metadata": {}, 247 | "source": [ 248 | "It is also possible to get a module by it's name:" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "id": "37fe5bdc-6d6f-42e5-bf21-a88f11971eaf", 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [ 258 | "module = process.module_from_name(\"Explorer.EXE\")" 259 | ] 260 | }, 261 | { 262 | "attachments": {}, 263 | "cell_type": "markdown", 264 | "id": "b612a8d1", 265 | "metadata": {}, 266 | "source": [ 267 | "Finally we are able to read Data from the process/module. In the following example we read parts of the COFF Header from the PE Header of the primary module:" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "id": "b67080f8-d20b-4b77-963b-0f4ee274ede2", 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "# Define COFF Header struct and read it from the module:\n", 278 | "class COFFHeader(Structure):\n", 279 | " _fields_ = [\n", 280 | " (\"_pad0x0\", c_uint8 * 6),\n", 281 | " (\"sections\", c_uint16),\n", 282 | " (\"timestamp\", c_uint32),\n", 283 | " ]\n", 284 | "\n", 285 | "header = process.read(module.base + 0x40, COFFHeader)\n", 286 | "print(header)" 287 | ] 288 | } 289 | ], 290 | "metadata": { 291 | "kernelspec": { 292 | "display_name": "Python 3 (ipykernel)", 293 | "language": "python", 294 | "name": "python3" 295 | }, 296 | "language_info": { 297 | "codemirror_mode": { 298 | "name": "ipython", 299 | "version": 3 300 | }, 301 | "file_extension": ".py", 302 | "mimetype": "text/x-python", 303 | "name": "python", 304 | "nbconvert_exporter": "python", 305 | "pygments_lexer": "ipython3", 306 | "version": "3.10.8" 307 | }, 308 | "vscode": { 309 | "interpreter": { 310 | "hash": "767d51c1340bd893661ea55ea3124f6de3c7a262a8b4abca0554b478b1e2ff90" 311 | } 312 | } 313 | }, 314 | "nbformat": 4, 315 | "nbformat_minor": 5 316 | } 317 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | use crate::{internal::InternalDT, MemflowPyError}; 2 | use memflow::{ 3 | prelude::{ 4 | ArchitectureIdent, IntoProcessInstanceArcBox, MemoryView, ModuleInfo, PartialError, 5 | Process, ProcessInfo, ProcessState, 6 | }, 7 | types::{umem, Address}, 8 | }; 9 | use pyo3::{exceptions::PyException, prelude::*}; 10 | 11 | #[derive(Clone)] 12 | #[pyclass(name = "Process")] 13 | pub struct PyProcess(IntoProcessInstanceArcBox<'static>); 14 | 15 | impl PyProcess { 16 | pub fn new(inst: IntoProcessInstanceArcBox<'static>) -> Self { 17 | Self(inst) 18 | } 19 | } 20 | 21 | #[pymethods] 22 | impl PyProcess { 23 | fn read(&mut self, addr: umem, ty: PyObject) -> PyResult { 24 | let dt: InternalDT = ty.try_into()?; 25 | 26 | let bytes = self 27 | .0 28 | .read_raw(addr.into(), dt.size()) 29 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 30 | 31 | Ok(dt.py_from_bytes(bytes)?) 32 | } 33 | 34 | fn read_char_string(&mut self, addr: umem, max_bytes: Option) -> PyResult { 35 | let str = self 36 | .0 37 | .read_char_string_n(addr.into(), max_bytes.unwrap_or(4096)) 38 | .map_err(|e| { 39 | PyException::new_err(format!("failed to read variable length string {}", e)) 40 | })?; 41 | 42 | Ok(str) 43 | } 44 | 45 | fn read_wchar_string(&mut self, addr: umem, max_bytes: Option) -> PyResult> { 46 | let mut read_wchar_string_n = 47 | |addr: Address, n: usize| -> Result, PartialError> { 48 | let mut buf = vec![0; std::cmp::min(32, n)]; 49 | let mut last_n = 0; 50 | loop { 51 | let (_, right) = buf.split_at_mut(last_n); 52 | memflow::prelude::PartialResultExt::data_part( 53 | self.0.read_raw_into(addr + last_n, right), 54 | )?; 55 | if let Some((n, _)) = right.iter().enumerate().find(|(_, c)| **c == 0_u8) { 56 | buf.truncate(last_n + n); 57 | return Ok(buf 58 | .chunks_exact(2) 59 | .into_iter() 60 | .map(|a| u16::from_ne_bytes([a[0], a[1]])) 61 | .collect()); 62 | } 63 | if buf.len() >= n { 64 | break; 65 | } 66 | last_n = buf.len(); 67 | buf.extend((0..buf.len()).map(|_| 0)); 68 | } 69 | Err(PartialError::Error(memflow::prelude::Error( 70 | memflow::prelude::ErrorOrigin::VirtualMemory, 71 | memflow::prelude::ErrorKind::OutOfBounds, 72 | ))) 73 | }; 74 | 75 | let wide_str_buf: Vec = read_wchar_string_n(addr.into(), max_bytes.unwrap_or(4096)) 76 | .map_err(|e| { 77 | PyException::new_err(format!("failed to read variable length wide string {}", e)) 78 | })?; 79 | 80 | Ok(wide_str_buf) 81 | } 82 | 83 | fn read_ptr(&mut self, ptr_inst: PyObject) -> PyResult { 84 | let addr: umem = Python::with_gil(|py| ptr_inst.getattr(py, "addr")?.extract(py))?; 85 | let dt: InternalDT = Python::with_gil(|py| ptr_inst.getattr(py, "_type_")?.try_into())?; 86 | 87 | let bytes = self 88 | .0 89 | .read_raw(addr.into(), dt.size()) 90 | .map_err(|e| PyException::new_err(format!("failed to read bytes {}", e)))?; 91 | 92 | Ok(dt.py_from_bytes(bytes)?) 93 | } 94 | 95 | fn write(&mut self, addr: umem, ty: PyObject, value: PyObject) -> PyResult<()> { 96 | let dt: InternalDT = ty.try_into()?; 97 | 98 | self.0 99 | .write_raw(addr.into(), &dt.py_to_bytes(value)?) 100 | .map_err(|e| PyException::new_err(format!("failed to write bytes {}", e)))?; 101 | 102 | Ok(()) 103 | } 104 | 105 | fn set_dtb(&mut self, dtb1: umem, dtb2: umem) -> PyResult<()> { 106 | self.0 107 | .set_dtb(dtb1.into(), dtb2.into()) 108 | .map_err(|e| PyException::new_err(format!("failed to set dtb {}", e))) 109 | } 110 | 111 | fn module_info_list(&mut self) -> PyResult> { 112 | Ok(self 113 | .0 114 | .module_list() 115 | .map_err(MemflowPyError::Memflow)? 116 | .into_iter() 117 | .map(PyModuleInfo::from) 118 | .collect()) 119 | } 120 | 121 | fn module_by_name(&mut self, name: &str) -> PyResult { 122 | Ok(self 123 | .0 124 | .module_by_name(name) 125 | .map_err(MemflowPyError::Memflow)? 126 | .into()) 127 | } 128 | 129 | fn info(&self) -> PyProcessInfo { 130 | self.0.info().clone().into() 131 | } 132 | 133 | fn __str__(&self) -> String { 134 | self.info().__str__() 135 | } 136 | } 137 | 138 | #[derive(Clone)] 139 | #[pyclass(name = "ProcessInfo")] 140 | pub struct PyProcessInfo(ProcessInfo); 141 | 142 | #[pymethods] 143 | impl PyProcessInfo { 144 | #[new] 145 | fn new( 146 | address: umem, 147 | pid: u32, 148 | state: PyProcessState, 149 | name: &str, 150 | path: &str, 151 | command_line: &str, 152 | sys_arch: PyArchitectureIdent, 153 | proc_arch: PyArchitectureIdent, 154 | dtb1: umem, 155 | dtb2: umem, 156 | ) -> Self { 157 | Self(ProcessInfo { 158 | address: address.into(), 159 | pid, 160 | state: state.into(), 161 | name: name.into(), 162 | path: path.into(), 163 | command_line: command_line.into(), 164 | sys_arch: sys_arch.into(), 165 | proc_arch: proc_arch.into(), 166 | dtb1: dtb1.into(), 167 | dtb2: dtb2.into(), 168 | }) 169 | } 170 | 171 | #[getter] 172 | fn address(&self) -> umem { 173 | self.0.address.to_umem() 174 | } 175 | 176 | #[getter] 177 | fn name(&self) -> String { 178 | self.0.name.to_string() 179 | } 180 | 181 | #[getter] 182 | fn pid(&self) -> u32 { 183 | self.0.pid 184 | } 185 | 186 | #[getter] 187 | fn state(&self) -> PyProcessState { 188 | self.0.state.clone().into() 189 | } 190 | 191 | #[getter] 192 | fn path(&self) -> String { 193 | self.0.path.to_string() 194 | } 195 | 196 | #[getter] 197 | fn command_line(&self) -> String { 198 | self.0.command_line.to_string() 199 | } 200 | 201 | #[getter] 202 | fn sys_arch(&self) -> PyArchitectureIdent { 203 | self.0.sys_arch.into() 204 | } 205 | 206 | #[getter] 207 | fn proc_arch(&self) -> PyArchitectureIdent { 208 | self.0.proc_arch.into() 209 | } 210 | 211 | #[getter] 212 | fn dtb1(&self) -> umem { 213 | self.0.dtb1.to_umem() 214 | } 215 | 216 | #[getter] 217 | fn dtb2(&self) -> umem { 218 | self.0.dtb2.to_umem() 219 | } 220 | 221 | fn __repr__(&self) -> String { 222 | format!( 223 | r#"ProcessInfo(address={:#04x}, pid={}, state={}, name="{}", path="{}", command_line="{}", sys_arch={}, proc_arch={}, dtb1={}, dtb2={})"#, 224 | self.address(), 225 | self.pid(), 226 | self.state().__repr__(), 227 | self.name(), 228 | self.path(), 229 | self.command_line(), 230 | self.sys_arch().__repr__(), 231 | self.proc_arch().__repr__(), 232 | self.dtb1(), 233 | self.dtb2() 234 | ) 235 | } 236 | 237 | fn __str__(&self) -> String { 238 | format!("{} ({}) @ {:#04x}", self.name(), self.pid(), self.address()) 239 | } 240 | } 241 | 242 | impl From for PyProcessInfo { 243 | fn from(pi: ProcessInfo) -> Self { 244 | Self(pi) 245 | } 246 | } 247 | 248 | impl From for ProcessInfo { 249 | fn from(py_info: PyProcessInfo) -> Self { 250 | py_info.0 251 | } 252 | } 253 | 254 | #[derive(Clone)] 255 | #[pyclass(name = "ModuleInfo")] 256 | pub struct PyModuleInfo(ModuleInfo); 257 | 258 | #[pymethods] 259 | impl PyModuleInfo { 260 | #[new] 261 | fn new( 262 | name: &str, 263 | address: i32, 264 | base: umem, 265 | size: u64, 266 | path: &str, 267 | process_addr: umem, 268 | arch: PyArchitectureIdent, 269 | ) -> Self { 270 | Self(ModuleInfo { 271 | address: address.into(), 272 | name: name.into(), 273 | base: base.into(), 274 | size, 275 | path: path.into(), 276 | parent_process: process_addr.into(), 277 | arch: arch.into(), 278 | }) 279 | } 280 | 281 | /// Returns the address of the module header. 282 | /// 283 | /// # Remarks 284 | /// 285 | /// On Windows this will be the address where the [`PEB`](https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb) entry is stored. 286 | #[getter] 287 | fn address(&self) -> umem { 288 | self.0.address.to_umem() 289 | } 290 | 291 | #[getter] 292 | fn name(&self) -> String { 293 | self.0.name.to_string() 294 | } 295 | 296 | #[getter] 297 | fn base(&self) -> umem { 298 | self.0.base.to_umem() 299 | } 300 | 301 | #[getter] 302 | fn size(&self) -> u64 { 303 | self.0.size 304 | } 305 | 306 | #[getter] 307 | fn path(&self) -> String { 308 | self.0.path.to_string() 309 | } 310 | 311 | #[getter] 312 | fn parent_process(&self) -> umem { 313 | self.0.parent_process.to_umem() 314 | } 315 | 316 | #[getter] 317 | fn arch(&self) -> PyArchitectureIdent { 318 | self.0.arch.into() 319 | } 320 | 321 | fn __repr__(&self) -> String { 322 | format!( 323 | r#"ModuleInfo(address={:#04x}, name="{}", base={:#04x}, size={:#04x}, path="{}", parent_process={}, arch={})"#, 324 | self.address(), 325 | self.name(), 326 | self.base(), 327 | self.size(), 328 | self.path(), 329 | self.parent_process(), 330 | self.arch().__repr__() 331 | ) 332 | } 333 | 334 | fn __str__(&self) -> String { 335 | format!("{} @ {:#04x}", self.name(), self.base()) 336 | } 337 | } 338 | 339 | impl From for PyModuleInfo { 340 | fn from(mi: ModuleInfo) -> Self { 341 | Self(mi) 342 | } 343 | } 344 | 345 | impl From for ModuleInfo { 346 | fn from(py_info: PyModuleInfo) -> Self { 347 | py_info.0 348 | } 349 | } 350 | 351 | #[derive(Clone)] 352 | #[pyclass(name = "ArchitectureIdent")] 353 | pub struct PyArchitectureIdent(ArchitectureIdent); 354 | 355 | #[pymethods] 356 | impl PyArchitectureIdent { 357 | #[new] 358 | fn new( 359 | arch: &str, 360 | page_size: Option, 361 | address_extensions: Option, 362 | ) -> PyResult { 363 | let ident = match arch { 364 | "X86_64" => ArchitectureIdent::X86(64, address_extensions.unwrap_or_default()), 365 | "X86" => ArchitectureIdent::X86(32, address_extensions.unwrap_or_default()), 366 | "AArch64" => { 367 | ArchitectureIdent::AArch64(page_size.unwrap_or_else(|| memflow::types::size::kb(4))) 368 | } 369 | "Unknown" => { 370 | ArchitectureIdent::Unknown(page_size.unwrap_or_else(|| memflow::types::size::kb(4))) 371 | } 372 | _ => Err(MemflowPyError::InvalidArch(arch.to_string()))?, 373 | }; 374 | 375 | Ok(Self(ident)) 376 | } 377 | 378 | fn __repr__(&self) -> String { 379 | match self.0 { 380 | ArchitectureIdent::Unknown(page_size) => { 381 | format!(r#"ArchitectureIdent("Unknown", page_size={})"#, page_size) 382 | } 383 | ArchitectureIdent::X86(bitness, address_extensions) => match bitness { 384 | 64 if address_extensions => { 385 | r#"ArchitectureIdent("X86", address_extensions=True)"#.to_owned() 386 | } 387 | 32 if address_extensions => { 388 | r#"ArchitectureIdent("X86", address_extensions=True)"#.to_owned() 389 | } 390 | 64 => r#"ArchitectureIdent("X86_64")"#.to_owned(), 391 | 32 => r#"ArchitectureIdent("X86")"#.to_owned(), 392 | _ => unreachable!("bitness should only be 32bit or 64bit"), 393 | }, 394 | ArchitectureIdent::AArch64(page_size) => { 395 | format!( 396 | r#"ArchitectureIdent("AArch64", page_size={:#04x})"#, 397 | page_size 398 | ) 399 | } 400 | } 401 | } 402 | } 403 | 404 | impl From for PyArchitectureIdent { 405 | fn from(ai: ArchitectureIdent) -> Self { 406 | Self(ai) 407 | } 408 | } 409 | 410 | impl From for ArchitectureIdent { 411 | fn from(py_ident: PyArchitectureIdent) -> Self { 412 | py_ident.0 413 | } 414 | } 415 | 416 | #[derive(Clone)] 417 | #[pyclass(name = "ProcessState")] 418 | pub struct PyProcessState(ProcessState); 419 | 420 | #[pymethods] 421 | impl PyProcessState { 422 | #[new] 423 | fn new(alive: bool, exit_code: Option) -> Self { 424 | let state = match alive { 425 | true => ProcessState::Alive, 426 | false if exit_code.is_some() => ProcessState::Dead(exit_code.unwrap()), 427 | _ => ProcessState::Unknown, 428 | }; 429 | 430 | Self(state) 431 | } 432 | 433 | fn is_alive(&self) -> bool { 434 | self.0.is_alive() 435 | } 436 | 437 | fn is_dead(&self) -> bool { 438 | self.0.is_dead() 439 | } 440 | 441 | fn is_unknown(&self) -> bool { 442 | self.0.is_unknown() 443 | } 444 | 445 | fn __repr__(&self) -> String { 446 | match self.0 { 447 | ProcessState::Unknown => "ProcessState(alive=False)".to_owned(), 448 | ProcessState::Alive => "ProcessState(alive=True)".to_owned(), 449 | ProcessState::Dead(exit_code) => format!("ProcessState(alive=false, {})", exit_code), 450 | } 451 | } 452 | 453 | fn __str__(&self) -> String { 454 | self.__repr__() 455 | } 456 | } 457 | 458 | impl From for PyProcessState { 459 | fn from(ps: ProcessState) -> Self { 460 | Self(ps) 461 | } 462 | } 463 | 464 | impl From for ProcessState { 465 | fn from(py_state: PyProcessState) -> Self { 466 | py_state.0 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/internal.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::*; 2 | use std::mem::size_of; 3 | 4 | use indexmap::IndexMap; 5 | use memflow::types::umem; 6 | use pyo3::prelude::*; 7 | use pyo3::types::{PyDict, PyTuple}; 8 | 9 | use crate::MemflowPyError; 10 | 11 | pub type DTMap = IndexMap; 12 | 13 | /// Please stick to explicit widths, no c_int nonsense! 14 | #[derive(Clone, Debug)] 15 | pub enum InternalDT { 16 | /// Represents the C signed char datatype, and interprets the value as small integer. 17 | Byte, 18 | /// Represents the C unsigned char datatype, it interprets the value as small integer. 19 | UByte, 20 | /// Represents the C char datatype, and interprets the value as a single character. 21 | Char, 22 | /// Represents the C wchar_t datatype, and interprets the value as a single character unicode string. 23 | WideChar, 24 | /// Represents the C double datatype. 25 | Double, 26 | /// Represents the C long double datatype. On platforms where sizeof(long double) == sizeof(double) it is an alias to c_double. 27 | /// For more info see: https://github.com/rust-lang/rust-bindgen/issues/1549 28 | LongDouble, 29 | /// Represents the C float datatype. 30 | Float, 31 | /// Represents the C signed short datatype. no overflow checking is done. 32 | Short, 33 | /// Represents the C unsigned short datatype. no overflow checking is done. 34 | UShort, 35 | /// Represents the C signed int datatype. no overflow checking is done. On platforms where sizeof(int) == sizeof(long) it is an alias to c_long. 36 | Int, 37 | /// Represents the C unsigned int datatype. no overflow checking is done. On platforms where sizeof(int) == sizeof(long) it is an alias for c_ulong. 38 | UInt, 39 | /// Represents the C signed long datatype. 40 | Long, 41 | /// Represents the C unsigned long datatype. 42 | ULong, 43 | /// Represents the C signed long long datatype. 44 | LongLong, 45 | /// Represents the C unsigned long long datatype. 46 | ULongLong, 47 | /// Native pointer type, backed by `MF_Pointer`. 48 | Pointer(PyObject, usize), 49 | // Backed by the ctypes (ctype * size) syntax. 50 | Array(PyObject, Box, u32), 51 | /// Any python class with a ctypes _fields_ attribute. 52 | Structure(PyObject, DTMap), 53 | } 54 | 55 | impl InternalDT { 56 | pub fn py_from_bytes(&self, bytes: Vec) -> crate::Result { 57 | Python::with_gil(|py| match self { 58 | InternalDT::Byte => Ok(i8::from_le_bytes(bytes[..].try_into()?).to_object(py)), 59 | InternalDT::UByte => Ok(u8::from_le_bytes(bytes[..].try_into()?).to_object(py)), 60 | InternalDT::Char => Ok(c_char::from_le_bytes(bytes[..].try_into()?).to_object(py)), 61 | InternalDT::WideChar => Ok(u16::from_le_bytes(bytes[..].try_into()?).to_object(py)), 62 | InternalDT::Double => Ok(c_double::from_le_bytes(bytes[..].try_into()?).to_object(py)), 63 | InternalDT::LongDouble => todo!(), 64 | InternalDT::Float => Ok(c_float::from_le_bytes(bytes[..].try_into()?).to_object(py)), 65 | InternalDT::Short => Ok(c_short::from_le_bytes(bytes[..].try_into()?).to_object(py)), 66 | InternalDT::UShort => Ok(c_ushort::from_le_bytes(bytes[..].try_into()?).to_object(py)), 67 | InternalDT::Int => Ok(c_int::from_le_bytes(bytes[..].try_into()?).to_object(py)), 68 | InternalDT::UInt => Ok(c_uint::from_le_bytes(bytes[..].try_into()?).to_object(py)), 69 | InternalDT::Long => Ok(c_long::from_le_bytes(bytes[..].try_into()?).to_object(py)), 70 | InternalDT::ULong => Ok(c_ulong::from_le_bytes(bytes[..].try_into()?).to_object(py)), 71 | InternalDT::LongLong => { 72 | Ok(c_longlong::from_le_bytes(bytes[..].try_into()?).to_object(py)) 73 | } 74 | InternalDT::ULongLong => { 75 | Ok(c_ulonglong::from_le_bytes(bytes[..].try_into()?).to_object(py)) 76 | } 77 | InternalDT::Pointer(class, _) => { 78 | Ok(class.call1(py, (umem::from_le_bytes(bytes[..self.size()].try_into()?),))?) 79 | } 80 | InternalDT::Array(class, dt, _) => Ok(class.call1( 81 | py, 82 | PyTuple::new( 83 | py, 84 | bytes 85 | .chunks(dt.size()) 86 | .into_iter() 87 | .map(|w| dt.py_from_bytes(w.to_vec()).unwrap()), 88 | ), 89 | )?), 90 | InternalDT::Structure(class, dts) => { 91 | let dict = PyDict::new(py); 92 | dts.into_iter() 93 | .try_for_each::<_, crate::Result<()>>(|(name, (offset, dt))| { 94 | let start = *offset; 95 | let size = dt.size(); 96 | let val = dt.py_from_bytes(bytes[start..(start + size)].to_vec())?; 97 | dict.set_item(name.as_str(), val)?; 98 | Ok(()) 99 | })?; 100 | // Create instance passing fields through kwargs, easy to override for usecases such as field punting. 101 | let class_inst = class.call(py, (), Some(dict))?; 102 | Ok(class_inst) 103 | } 104 | }) 105 | } 106 | 107 | pub fn py_to_bytes(&self, obj: PyObject) -> crate::Result> { 108 | Python::with_gil(|py| match self { 109 | InternalDT::Byte => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 110 | InternalDT::UByte => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 111 | InternalDT::Char => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 112 | // OS widechar encoding. 113 | InternalDT::WideChar => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 114 | InternalDT::Double => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 115 | InternalDT::LongDouble => todo!(), 116 | InternalDT::Float => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 117 | InternalDT::Short => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 118 | InternalDT::UShort => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 119 | InternalDT::Int => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 120 | InternalDT::UInt => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 121 | InternalDT::Long => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 122 | InternalDT::ULong => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 123 | InternalDT::LongLong => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 124 | InternalDT::ULongLong => Ok(obj.extract::(py)?.to_le_bytes().to_vec()), 125 | InternalDT::Pointer(_, _) => Ok(obj 126 | .getattr(py, "addr")? 127 | .extract::(py)? 128 | .to_le_bytes()[..self.size()] 129 | .to_vec()), 130 | InternalDT::Array(_, dt, len) => { 131 | let mut bytes = Vec::new(); 132 | for i in 0..*len { 133 | let item_obj = obj.call_method1(py, "__getitem__", (i,))?; 134 | bytes.append(&mut dt.py_to_bytes(item_obj)?); 135 | } 136 | Ok(bytes) 137 | } 138 | // NOTE: The passed object is not checked to be type of structure. 139 | InternalDT::Structure(_, dts) => { 140 | let mut bytes = Vec::new(); 141 | bytes.resize(self.size(), 0); 142 | dts.into_iter() 143 | .try_for_each::<_, crate::Result<()>>(|(name, (offset, dt))| { 144 | if let Ok(val_obj) = obj.getattr(py, name.as_str()) { 145 | bytes.splice(offset..&(offset + dt.size()), dt.py_to_bytes(val_obj)?); 146 | Ok(()) 147 | } else { 148 | Err(MemflowPyError::MissingAttribute(name.to_owned())) 149 | } 150 | })?; 151 | Ok(bytes) 152 | } 153 | }) 154 | } 155 | 156 | pub fn size(&self) -> usize { 157 | match self { 158 | InternalDT::Byte => size_of::(), 159 | InternalDT::UByte => size_of::(), 160 | InternalDT::Char => size_of::(), 161 | InternalDT::WideChar => size_of::() * 2, 162 | InternalDT::Short => size_of::(), 163 | InternalDT::UShort => size_of::(), 164 | InternalDT::Double => size_of::(), 165 | InternalDT::LongDouble => size_of::() * 2, 166 | InternalDT::Float => size_of::(), 167 | InternalDT::Int => size_of::(), 168 | InternalDT::UInt => size_of::(), 169 | InternalDT::Long => size_of::(), 170 | InternalDT::ULong => size_of::(), 171 | InternalDT::LongLong => size_of::(), 172 | InternalDT::ULongLong => size_of::(), 173 | InternalDT::Pointer(_, byteness) => *byteness as usize, 174 | InternalDT::Array(_, dt, len) => dt.size() * (*len as usize), 175 | InternalDT::Structure(_, dts) => { 176 | let (_, max_dt) = dts 177 | .iter() 178 | .max_by(|(_, x), (_, y)| (x.0 + x.1.size()).cmp(&(y.0 + y.1.size()))) 179 | .unwrap(); 180 | // Offset + dt size 181 | max_dt.0 + max_dt.1.size() 182 | } 183 | } 184 | } 185 | } 186 | 187 | impl TryFrom for InternalDT { 188 | type Error = MemflowPyError; 189 | 190 | fn try_from(value: PyObject) -> Result { 191 | let base_name: String = Python::with_gil(|py| { 192 | let base_obj: PyObject = value.getattr(py, "__base__")?.extract(py)?; 193 | base_obj.getattr(py, "__name__")?.extract(py) 194 | })?; 195 | 196 | // NOTE: While we do try to follow ctypes there is no guarantee that it will work. 197 | match base_name.as_str() { 198 | "CDataType" | "_SimpleCData" => { 199 | // Type identifier originates from ctypes (see: cpython/Lib/ctypes/__init__.py) 200 | let type_ident: String = 201 | Python::with_gil(|py| value.getattr(py, "_type_")?.extract(py))?; 202 | let dt = match type_ident.as_str() { 203 | "b" => Self::Byte, 204 | "B" | "?" => Self::UByte, 205 | "c" => Self::Char, 206 | "u" => Self::WideChar, 207 | "z" | "Z" => { 208 | unimplemented!("please use `read_char_string` and `read_wchar_string`") 209 | } 210 | "d" => Self::Double, 211 | "g" => Self::LongDouble, 212 | "f" => Self::Float, 213 | "h" => Self::Short, 214 | "H" => Self::UShort, 215 | "i" => Self::Int, 216 | "I" => Self::UInt, 217 | "l" => Self::Long, 218 | "L" => Self::ULong, 219 | "q" => Self::LongLong, 220 | "Q" => Self::ULongLong, 221 | name => unreachable!("unknown type identifier `{}`", name), 222 | }; 223 | Ok(dt) 224 | } 225 | "Pointer" => { 226 | let byteness: usize = 227 | Python::with_gil(|py| match value.getattr(py, "_byteness_") { 228 | Ok(val) => val.extract(py), 229 | // If we are passed a pointer with no set byteness we assume the pointer to be local system width. 230 | Err(_) => Ok(size_of::()), 231 | })?; 232 | Ok(Self::Pointer(value, byteness)) 233 | } 234 | "Array" => { 235 | let (len, ty_obj) = Python::with_gil::<_, crate::Result<(u32, PyObject)>>(|py| { 236 | Ok(( 237 | value.getattr(py, "_length_")?.extract(py)?, 238 | value.getattr(py, "_type_")?.extract(py)?, 239 | )) 240 | })?; 241 | Ok(InternalDT::Array(value, Box::new(ty_obj.try_into()?), len)) 242 | } 243 | "Structure" => { 244 | let fields = Python::with_gil(|py| { 245 | value 246 | .getattr(py, "_fields_")? 247 | .extract::>>(py) 248 | })?; 249 | 250 | // TODO: Clean this up with a zip iter (offset, field_tuple) 251 | let mut current_offset = 0_usize; 252 | let mut dt_fields = fields 253 | .into_iter() 254 | .map(|field| { 255 | let mut it = field.into_iter(); 256 | let field_offset = current_offset; 257 | let field_name = it.next().unwrap().to_string(); 258 | let field_type: InternalDT = it 259 | .next() 260 | .ok_or_else(|| MemflowPyError::NoType(field_name.clone()))? 261 | .try_into()?; 262 | current_offset += field_type.size(); 263 | Ok((field_name, (field_offset, field_type))) 264 | }) 265 | .collect::>()?; 266 | 267 | // TODO: Clean this up 268 | if let Some(offset_fields) = 269 | Python::with_gil::<_, Result, MemflowPyError>>(|py| { 270 | if let Ok(offsets_attr) = value.getattr(py, "_offsets_") { 271 | let offsets_obj = offsets_attr.extract::>>(py)?; 272 | 273 | let offset_fields = offsets_obj 274 | .into_iter() 275 | .map(|field| { 276 | let mut it = field.into_iter(); 277 | let field_offset: usize = it.next().unwrap().extract(py)?; 278 | let field_name = it.next().unwrap().to_string(); 279 | let field_type: InternalDT = it 280 | .next() 281 | .ok_or_else(|| MemflowPyError::NoType(field_name.clone()))? 282 | .try_into()?; 283 | Ok((field_name, (field_offset, field_type))) 284 | }) 285 | .collect::>()?; 286 | 287 | Ok(Some(offset_fields)) 288 | } else { 289 | Ok(None) 290 | } 291 | })? 292 | { 293 | dt_fields.extend(offset_fields); 294 | } 295 | 296 | Ok(Self::Structure(value, dt_fields)) 297 | } 298 | _ => Err(MemflowPyError::InvalidType(base_name)), 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "abi_stable" 7 | version = "0.10.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "3c8f0d5c8e09e8b591f6db9f56b006c9b9e7a44064acb274411ae51aaaa1364d" 10 | dependencies = [ 11 | "abi_stable_derive", 12 | "abi_stable_shared", 13 | "core_extensions", 14 | "crossbeam-channel", 15 | "generational-arena", 16 | "libloading 0.7.4", 17 | "lock_api", 18 | "parking_lot 0.11.2", 19 | "paste", 20 | "repr_offset", 21 | "rustc_version 0.4.0", 22 | "serde", 23 | "serde_derive", 24 | "serde_json", 25 | ] 26 | 27 | [[package]] 28 | name = "abi_stable_derive" 29 | version = "0.10.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "3b9810ef8debee5544010d92dac0b88b32853cd5bd5ca3298243c99e931da0f0" 32 | dependencies = [ 33 | "abi_stable_shared", 34 | "as_derive_utils", 35 | "core_extensions", 36 | "proc-macro2", 37 | "quote", 38 | "rustc_version 0.2.3", 39 | "syn 1.0.109", 40 | "typed-arena", 41 | ] 42 | 43 | [[package]] 44 | name = "abi_stable_shared" 45 | version = "0.10.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "ece9da89066e018d908b48755e17cdd08a46ce222f84b1f226e837f836f84a5f" 48 | dependencies = [ 49 | "core_extensions", 50 | ] 51 | 52 | [[package]] 53 | name = "ahash" 54 | version = "0.8.6" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" 57 | dependencies = [ 58 | "cfg-if 1.0.0", 59 | "once_cell", 60 | "version_check", 61 | "zerocopy", 62 | ] 63 | 64 | [[package]] 65 | name = "allocator-api2" 66 | version = "0.2.16" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" 69 | 70 | [[package]] 71 | name = "arc-swap" 72 | version = "1.6.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" 75 | 76 | [[package]] 77 | name = "as_derive_utils" 78 | version = "0.10.3" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "e0a26fa495cefb8c86d9ce183b3f39ad11678e54fb3c20e6b7dbd87770811d9b" 81 | dependencies = [ 82 | "core_extensions", 83 | "proc-macro2", 84 | "quote", 85 | "syn 1.0.109", 86 | ] 87 | 88 | [[package]] 89 | name = "autocfg" 90 | version = "1.1.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 93 | 94 | [[package]] 95 | name = "bit_field" 96 | version = "0.10.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 99 | 100 | [[package]] 101 | name = "bitflags" 102 | version = "1.3.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 105 | 106 | [[package]] 107 | name = "bumpalo" 108 | version = "3.12.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 111 | 112 | [[package]] 113 | name = "cfg-if" 114 | version = "0.1.10" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 117 | 118 | [[package]] 119 | name = "cfg-if" 120 | version = "1.0.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 123 | 124 | [[package]] 125 | name = "cglue" 126 | version = "0.2.12" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "0781c9468d801a834bed4252f3fd3a536b4f82199bca9f099d713e3732b860a7" 129 | dependencies = [ 130 | "abi_stable", 131 | "cglue-macro", 132 | "no-std-compat", 133 | "rustc_version 0.4.0", 134 | "serde", 135 | ] 136 | 137 | [[package]] 138 | name = "cglue-gen" 139 | version = "0.2.7" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "f713559a7e76bef29ef6eafb64d05fca776a177bb369be02bbea342dc42ffce8" 142 | dependencies = [ 143 | "itertools 0.10.5", 144 | "lazy_static", 145 | "proc-macro-crate 1.1.3", 146 | "proc-macro2", 147 | "quote", 148 | "syn 1.0.109", 149 | ] 150 | 151 | [[package]] 152 | name = "cglue-macro" 153 | version = "0.2.3" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cf28b8c74a050973c9972edfad2f32f404bc1ff6725e37d003e7d55504dec1cf" 156 | dependencies = [ 157 | "cglue-gen", 158 | "proc-macro2", 159 | "quote", 160 | "syn 1.0.109", 161 | ] 162 | 163 | [[package]] 164 | name = "coarsetime" 165 | version = "0.1.23" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" 168 | dependencies = [ 169 | "libc", 170 | "once_cell", 171 | "wasi", 172 | "wasm-bindgen", 173 | ] 174 | 175 | [[package]] 176 | name = "core_extensions" 177 | version = "1.5.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "92c71dc07c9721607e7a16108336048ee978c3a8b129294534272e8bac96c0ee" 180 | dependencies = [ 181 | "core_extensions_proc_macros", 182 | ] 183 | 184 | [[package]] 185 | name = "core_extensions_proc_macros" 186 | version = "1.5.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "69f3b219d28b6e3b4ac87bc1fc522e0803ab22e055da177bff0068c4150c61a6" 189 | 190 | [[package]] 191 | name = "crossbeam-channel" 192 | version = "0.5.7" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" 195 | dependencies = [ 196 | "cfg-if 1.0.0", 197 | "crossbeam-utils", 198 | ] 199 | 200 | [[package]] 201 | name = "crossbeam-utils" 202 | version = "0.8.15" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 205 | dependencies = [ 206 | "cfg-if 1.0.0", 207 | ] 208 | 209 | [[package]] 210 | name = "darling" 211 | version = "0.20.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" 214 | dependencies = [ 215 | "darling_core", 216 | "darling_macro", 217 | ] 218 | 219 | [[package]] 220 | name = "darling_core" 221 | version = "0.20.3" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" 224 | dependencies = [ 225 | "fnv", 226 | "ident_case", 227 | "proc-macro2", 228 | "quote", 229 | "strsim", 230 | "syn 2.0.41", 231 | ] 232 | 233 | [[package]] 234 | name = "darling_macro" 235 | version = "0.20.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" 238 | dependencies = [ 239 | "darling_core", 240 | "quote", 241 | "syn 2.0.41", 242 | ] 243 | 244 | [[package]] 245 | name = "dataview" 246 | version = "0.1.2" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "47a802a2cad0ff4dfc4f3110da174b7a6928c315cae523e88638cfb72941b4d5" 249 | 250 | [[package]] 251 | name = "dataview" 252 | version = "1.0.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "50eb3a329e19d78c3a3dfa4ec5a51ecb84fa3a20c06edad04be25356018218f9" 255 | 256 | [[package]] 257 | name = "dirs" 258 | version = "5.0.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 261 | dependencies = [ 262 | "dirs-sys", 263 | ] 264 | 265 | [[package]] 266 | name = "dirs-sys" 267 | version = "0.4.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 270 | dependencies = [ 271 | "libc", 272 | "option-ext", 273 | "redox_users", 274 | "windows-sys 0.48.0", 275 | ] 276 | 277 | [[package]] 278 | name = "either" 279 | version = "1.8.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 282 | 283 | [[package]] 284 | name = "equivalent" 285 | version = "1.0.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 288 | 289 | [[package]] 290 | name = "fixed-slice-vec" 291 | version = "0.10.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "1bb23c599a9ff5b981529099902fe5de8d55ecc8c1f451542da17b8d2d65326e" 294 | 295 | [[package]] 296 | name = "fnv" 297 | version = "1.0.7" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 300 | 301 | [[package]] 302 | name = "generational-arena" 303 | version = "0.2.8" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" 306 | dependencies = [ 307 | "cfg-if 0.1.10", 308 | ] 309 | 310 | [[package]] 311 | name = "getrandom" 312 | version = "0.2.8" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 315 | dependencies = [ 316 | "cfg-if 1.0.0", 317 | "libc", 318 | "wasi", 319 | ] 320 | 321 | [[package]] 322 | name = "goblin" 323 | version = "0.7.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134" 326 | dependencies = [ 327 | "log", 328 | "plain", 329 | "scroll", 330 | ] 331 | 332 | [[package]] 333 | name = "hashbrown" 334 | version = "0.14.3" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 337 | dependencies = [ 338 | "ahash", 339 | "allocator-api2", 340 | ] 341 | 342 | [[package]] 343 | name = "heck" 344 | version = "0.4.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 347 | 348 | [[package]] 349 | name = "ident_case" 350 | version = "1.0.1" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 353 | 354 | [[package]] 355 | name = "indexmap" 356 | version = "2.1.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 359 | dependencies = [ 360 | "equivalent", 361 | "hashbrown", 362 | ] 363 | 364 | [[package]] 365 | name = "indoc" 366 | version = "2.0.4" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" 369 | 370 | [[package]] 371 | name = "instant" 372 | version = "0.1.12" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 375 | dependencies = [ 376 | "cfg-if 1.0.0", 377 | ] 378 | 379 | [[package]] 380 | name = "itertools" 381 | version = "0.10.5" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 384 | dependencies = [ 385 | "either", 386 | ] 387 | 388 | [[package]] 389 | name = "itertools" 390 | version = "0.12.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" 393 | dependencies = [ 394 | "either", 395 | ] 396 | 397 | [[package]] 398 | name = "itoa" 399 | version = "1.0.6" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 402 | 403 | [[package]] 404 | name = "lazy_static" 405 | version = "1.4.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 408 | 409 | [[package]] 410 | name = "libc" 411 | version = "0.2.139" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 414 | 415 | [[package]] 416 | name = "libloading" 417 | version = "0.7.4" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 420 | dependencies = [ 421 | "cfg-if 1.0.0", 422 | "winapi", 423 | ] 424 | 425 | [[package]] 426 | name = "libloading" 427 | version = "0.8.1" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" 430 | dependencies = [ 431 | "cfg-if 1.0.0", 432 | "windows-sys 0.48.0", 433 | ] 434 | 435 | [[package]] 436 | name = "lock_api" 437 | version = "0.4.9" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 440 | dependencies = [ 441 | "autocfg", 442 | "scopeguard", 443 | ] 444 | 445 | [[package]] 446 | name = "log" 447 | version = "0.4.17" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 450 | dependencies = [ 451 | "cfg-if 1.0.0", 452 | ] 453 | 454 | [[package]] 455 | name = "memchr" 456 | version = "2.5.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 459 | 460 | [[package]] 461 | name = "memflow" 462 | version = "0.2.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "5e4ab23adc6b68aa85d9d2d43c04f31c51b895acb323f807ff95ed8d844639b1" 465 | dependencies = [ 466 | "abi_stable", 467 | "bitflags", 468 | "bumpalo", 469 | "cglue", 470 | "coarsetime", 471 | "dataview 1.0.1", 472 | "dirs", 473 | "fixed-slice-vec", 474 | "goblin", 475 | "hashbrown", 476 | "itertools 0.12.0", 477 | "libloading 0.8.1", 478 | "log", 479 | "memflow-derive", 480 | "memmap", 481 | "no-std-compat", 482 | "once_cell", 483 | "pelite", 484 | "rand", 485 | "rand_xorshift", 486 | "rangemap", 487 | "serde", 488 | "smallvec", 489 | "toml 0.8.2", 490 | "x86_64", 491 | ] 492 | 493 | [[package]] 494 | name = "memflow-derive" 495 | version = "0.2.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "d766f6681f968c92eb0359fc4bc99039ebe2568df4bb884c7cb7b16023e94d32" 498 | dependencies = [ 499 | "darling", 500 | "proc-macro-crate 2.0.1", 501 | "proc-macro2", 502 | "quote", 503 | "syn 2.0.41", 504 | ] 505 | 506 | [[package]] 507 | name = "memflow-py" 508 | version = "0.2.0" 509 | dependencies = [ 510 | "cglue", 511 | "indexmap", 512 | "memflow", 513 | "pyo3", 514 | "pyo3-log", 515 | "thiserror", 516 | ] 517 | 518 | [[package]] 519 | name = "memmap" 520 | version = "0.7.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 523 | dependencies = [ 524 | "libc", 525 | "winapi", 526 | ] 527 | 528 | [[package]] 529 | name = "memoffset" 530 | version = "0.9.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 533 | dependencies = [ 534 | "autocfg", 535 | ] 536 | 537 | [[package]] 538 | name = "no-std-compat" 539 | version = "0.4.1" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" 542 | 543 | [[package]] 544 | name = "once_cell" 545 | version = "1.19.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 548 | 549 | [[package]] 550 | name = "option-ext" 551 | version = "0.2.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 554 | 555 | [[package]] 556 | name = "parking_lot" 557 | version = "0.11.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 560 | dependencies = [ 561 | "instant", 562 | "lock_api", 563 | "parking_lot_core 0.8.6", 564 | ] 565 | 566 | [[package]] 567 | name = "parking_lot" 568 | version = "0.12.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 571 | dependencies = [ 572 | "lock_api", 573 | "parking_lot_core 0.9.7", 574 | ] 575 | 576 | [[package]] 577 | name = "parking_lot_core" 578 | version = "0.8.6" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 581 | dependencies = [ 582 | "cfg-if 1.0.0", 583 | "instant", 584 | "libc", 585 | "redox_syscall", 586 | "smallvec", 587 | "winapi", 588 | ] 589 | 590 | [[package]] 591 | name = "parking_lot_core" 592 | version = "0.9.7" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 595 | dependencies = [ 596 | "cfg-if 1.0.0", 597 | "libc", 598 | "redox_syscall", 599 | "smallvec", 600 | "windows-sys 0.45.0", 601 | ] 602 | 603 | [[package]] 604 | name = "paste" 605 | version = "1.0.12" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" 608 | 609 | [[package]] 610 | name = "pelite" 611 | version = "0.9.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "8c270b1a0c279bbcb4cff3d2294121731c48ee68f0435d26cf71018a853cb890" 614 | dependencies = [ 615 | "dataview 0.1.2", 616 | "no-std-compat", 617 | "pelite-macros", 618 | ] 619 | 620 | [[package]] 621 | name = "pelite-macros" 622 | version = "0.1.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "7a7cf3f8ecebb0f4895f4892a8be0a0dc81b498f9d56735cb769dc31bf00815b" 625 | 626 | [[package]] 627 | name = "plain" 628 | version = "0.2.3" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 631 | 632 | [[package]] 633 | name = "ppv-lite86" 634 | version = "0.2.17" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 637 | 638 | [[package]] 639 | name = "proc-macro-crate" 640 | version = "1.1.3" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" 643 | dependencies = [ 644 | "thiserror", 645 | "toml 0.5.11", 646 | ] 647 | 648 | [[package]] 649 | name = "proc-macro-crate" 650 | version = "2.0.1" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" 653 | dependencies = [ 654 | "toml_datetime", 655 | "toml_edit", 656 | ] 657 | 658 | [[package]] 659 | name = "proc-macro2" 660 | version = "1.0.70" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" 663 | dependencies = [ 664 | "unicode-ident", 665 | ] 666 | 667 | [[package]] 668 | name = "pyo3" 669 | version = "0.20.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" 672 | dependencies = [ 673 | "cfg-if 1.0.0", 674 | "indoc", 675 | "libc", 676 | "memoffset", 677 | "parking_lot 0.12.1", 678 | "pyo3-build-config", 679 | "pyo3-ffi", 680 | "pyo3-macros", 681 | "unindent", 682 | ] 683 | 684 | [[package]] 685 | name = "pyo3-build-config" 686 | version = "0.20.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" 689 | dependencies = [ 690 | "once_cell", 691 | "target-lexicon", 692 | ] 693 | 694 | [[package]] 695 | name = "pyo3-ffi" 696 | version = "0.20.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" 699 | dependencies = [ 700 | "libc", 701 | "pyo3-build-config", 702 | ] 703 | 704 | [[package]] 705 | name = "pyo3-log" 706 | version = "0.9.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd" 709 | dependencies = [ 710 | "arc-swap", 711 | "log", 712 | "pyo3", 713 | ] 714 | 715 | [[package]] 716 | name = "pyo3-macros" 717 | version = "0.20.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" 720 | dependencies = [ 721 | "proc-macro2", 722 | "pyo3-macros-backend", 723 | "quote", 724 | "syn 2.0.41", 725 | ] 726 | 727 | [[package]] 728 | name = "pyo3-macros-backend" 729 | version = "0.20.0" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" 732 | dependencies = [ 733 | "heck", 734 | "proc-macro2", 735 | "quote", 736 | "syn 2.0.41", 737 | ] 738 | 739 | [[package]] 740 | name = "quote" 741 | version = "1.0.33" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 744 | dependencies = [ 745 | "proc-macro2", 746 | ] 747 | 748 | [[package]] 749 | name = "rand" 750 | version = "0.8.5" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 753 | dependencies = [ 754 | "libc", 755 | "rand_chacha", 756 | "rand_core", 757 | ] 758 | 759 | [[package]] 760 | name = "rand_chacha" 761 | version = "0.3.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 764 | dependencies = [ 765 | "ppv-lite86", 766 | "rand_core", 767 | ] 768 | 769 | [[package]] 770 | name = "rand_core" 771 | version = "0.6.4" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 774 | dependencies = [ 775 | "getrandom", 776 | ] 777 | 778 | [[package]] 779 | name = "rand_xorshift" 780 | version = "0.3.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" 783 | dependencies = [ 784 | "rand_core", 785 | ] 786 | 787 | [[package]] 788 | name = "rangemap" 789 | version = "1.3.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "8b9283c6b06096b47afc7109834fdedab891175bb5241ee5d4f7d2546549f263" 792 | 793 | [[package]] 794 | name = "redox_syscall" 795 | version = "0.2.16" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 798 | dependencies = [ 799 | "bitflags", 800 | ] 801 | 802 | [[package]] 803 | name = "redox_users" 804 | version = "0.4.3" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 807 | dependencies = [ 808 | "getrandom", 809 | "redox_syscall", 810 | "thiserror", 811 | ] 812 | 813 | [[package]] 814 | name = "repr_offset" 815 | version = "0.2.2" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "fb1070755bd29dffc19d0971cab794e607839ba2ef4b69a9e6fbc8733c1b72ea" 818 | dependencies = [ 819 | "tstr", 820 | ] 821 | 822 | [[package]] 823 | name = "rustc_version" 824 | version = "0.2.3" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 827 | dependencies = [ 828 | "semver 0.9.0", 829 | ] 830 | 831 | [[package]] 832 | name = "rustc_version" 833 | version = "0.4.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 836 | dependencies = [ 837 | "semver 1.0.16", 838 | ] 839 | 840 | [[package]] 841 | name = "rustversion" 842 | version = "1.0.12" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" 845 | 846 | [[package]] 847 | name = "ryu" 848 | version = "1.0.13" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 851 | 852 | [[package]] 853 | name = "scopeguard" 854 | version = "1.1.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 857 | 858 | [[package]] 859 | name = "scroll" 860 | version = "0.11.0" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" 863 | dependencies = [ 864 | "scroll_derive", 865 | ] 866 | 867 | [[package]] 868 | name = "scroll_derive" 869 | version = "0.11.0" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" 872 | dependencies = [ 873 | "proc-macro2", 874 | "quote", 875 | "syn 1.0.109", 876 | ] 877 | 878 | [[package]] 879 | name = "semver" 880 | version = "0.9.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 883 | dependencies = [ 884 | "semver-parser", 885 | ] 886 | 887 | [[package]] 888 | name = "semver" 889 | version = "1.0.16" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" 892 | 893 | [[package]] 894 | name = "semver-parser" 895 | version = "0.7.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 898 | 899 | [[package]] 900 | name = "serde" 901 | version = "1.0.152" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 904 | dependencies = [ 905 | "serde_derive", 906 | ] 907 | 908 | [[package]] 909 | name = "serde_derive" 910 | version = "1.0.152" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 913 | dependencies = [ 914 | "proc-macro2", 915 | "quote", 916 | "syn 1.0.109", 917 | ] 918 | 919 | [[package]] 920 | name = "serde_json" 921 | version = "1.0.94" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" 924 | dependencies = [ 925 | "itoa", 926 | "ryu", 927 | "serde", 928 | ] 929 | 930 | [[package]] 931 | name = "serde_spanned" 932 | version = "0.6.4" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" 935 | dependencies = [ 936 | "serde", 937 | ] 938 | 939 | [[package]] 940 | name = "smallvec" 941 | version = "1.10.0" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 944 | 945 | [[package]] 946 | name = "strsim" 947 | version = "0.10.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 950 | 951 | [[package]] 952 | name = "syn" 953 | version = "1.0.109" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 956 | dependencies = [ 957 | "proc-macro2", 958 | "quote", 959 | "unicode-ident", 960 | ] 961 | 962 | [[package]] 963 | name = "syn" 964 | version = "2.0.41" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" 967 | dependencies = [ 968 | "proc-macro2", 969 | "quote", 970 | "unicode-ident", 971 | ] 972 | 973 | [[package]] 974 | name = "target-lexicon" 975 | version = "0.12.6" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" 978 | 979 | [[package]] 980 | name = "thiserror" 981 | version = "1.0.39" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" 984 | dependencies = [ 985 | "thiserror-impl", 986 | ] 987 | 988 | [[package]] 989 | name = "thiserror-impl" 990 | version = "1.0.39" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" 993 | dependencies = [ 994 | "proc-macro2", 995 | "quote", 996 | "syn 1.0.109", 997 | ] 998 | 999 | [[package]] 1000 | name = "toml" 1001 | version = "0.5.11" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1004 | dependencies = [ 1005 | "serde", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "toml" 1010 | version = "0.8.2" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" 1013 | dependencies = [ 1014 | "serde", 1015 | "serde_spanned", 1016 | "toml_datetime", 1017 | "toml_edit", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "toml_datetime" 1022 | version = "0.6.3" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 1025 | dependencies = [ 1026 | "serde", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "toml_edit" 1031 | version = "0.20.2" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" 1034 | dependencies = [ 1035 | "indexmap", 1036 | "serde", 1037 | "serde_spanned", 1038 | "toml_datetime", 1039 | "winnow", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "tstr" 1044 | version = "0.2.3" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "cca3264971090dec0feef3b455a3c178f02762f7550cf4592991ac64b3be2d7e" 1047 | dependencies = [ 1048 | "tstr_proc_macros", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "tstr_proc_macros" 1053 | version = "0.2.2" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "e78122066b0cb818b8afd08f7ed22f7fdbc3e90815035726f0840d0d26c0747a" 1056 | 1057 | [[package]] 1058 | name = "typed-arena" 1059 | version = "2.0.2" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" 1062 | 1063 | [[package]] 1064 | name = "unicode-ident" 1065 | version = "1.0.8" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1068 | 1069 | [[package]] 1070 | name = "unindent" 1071 | version = "0.2.3" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 1074 | 1075 | [[package]] 1076 | name = "version_check" 1077 | version = "0.9.4" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1080 | 1081 | [[package]] 1082 | name = "volatile" 1083 | version = "0.4.6" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" 1086 | 1087 | [[package]] 1088 | name = "wasi" 1089 | version = "0.11.0+wasi-snapshot-preview1" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1092 | 1093 | [[package]] 1094 | name = "wasm-bindgen" 1095 | version = "0.2.81" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" 1098 | dependencies = [ 1099 | "cfg-if 1.0.0", 1100 | "wasm-bindgen-macro", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "wasm-bindgen-backend" 1105 | version = "0.2.81" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" 1108 | dependencies = [ 1109 | "bumpalo", 1110 | "lazy_static", 1111 | "log", 1112 | "proc-macro2", 1113 | "quote", 1114 | "syn 1.0.109", 1115 | "wasm-bindgen-shared", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "wasm-bindgen-macro" 1120 | version = "0.2.81" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" 1123 | dependencies = [ 1124 | "quote", 1125 | "wasm-bindgen-macro-support", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "wasm-bindgen-macro-support" 1130 | version = "0.2.81" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" 1133 | dependencies = [ 1134 | "proc-macro2", 1135 | "quote", 1136 | "syn 1.0.109", 1137 | "wasm-bindgen-backend", 1138 | "wasm-bindgen-shared", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "wasm-bindgen-shared" 1143 | version = "0.2.81" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" 1146 | 1147 | [[package]] 1148 | name = "winapi" 1149 | version = "0.3.9" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1152 | dependencies = [ 1153 | "winapi-i686-pc-windows-gnu", 1154 | "winapi-x86_64-pc-windows-gnu", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "winapi-i686-pc-windows-gnu" 1159 | version = "0.4.0" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1162 | 1163 | [[package]] 1164 | name = "winapi-x86_64-pc-windows-gnu" 1165 | version = "0.4.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1168 | 1169 | [[package]] 1170 | name = "windows-sys" 1171 | version = "0.45.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1174 | dependencies = [ 1175 | "windows-targets 0.42.1", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "windows-sys" 1180 | version = "0.48.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1183 | dependencies = [ 1184 | "windows-targets 0.48.5", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "windows-targets" 1189 | version = "0.42.1" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 1192 | dependencies = [ 1193 | "windows_aarch64_gnullvm 0.42.1", 1194 | "windows_aarch64_msvc 0.42.1", 1195 | "windows_i686_gnu 0.42.1", 1196 | "windows_i686_msvc 0.42.1", 1197 | "windows_x86_64_gnu 0.42.1", 1198 | "windows_x86_64_gnullvm 0.42.1", 1199 | "windows_x86_64_msvc 0.42.1", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "windows-targets" 1204 | version = "0.48.5" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1207 | dependencies = [ 1208 | "windows_aarch64_gnullvm 0.48.5", 1209 | "windows_aarch64_msvc 0.48.5", 1210 | "windows_i686_gnu 0.48.5", 1211 | "windows_i686_msvc 0.48.5", 1212 | "windows_x86_64_gnu 0.48.5", 1213 | "windows_x86_64_gnullvm 0.48.5", 1214 | "windows_x86_64_msvc 0.48.5", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "windows_aarch64_gnullvm" 1219 | version = "0.42.1" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1222 | 1223 | [[package]] 1224 | name = "windows_aarch64_gnullvm" 1225 | version = "0.48.5" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1228 | 1229 | [[package]] 1230 | name = "windows_aarch64_msvc" 1231 | version = "0.42.1" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1234 | 1235 | [[package]] 1236 | name = "windows_aarch64_msvc" 1237 | version = "0.48.5" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1240 | 1241 | [[package]] 1242 | name = "windows_i686_gnu" 1243 | version = "0.42.1" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1246 | 1247 | [[package]] 1248 | name = "windows_i686_gnu" 1249 | version = "0.48.5" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1252 | 1253 | [[package]] 1254 | name = "windows_i686_msvc" 1255 | version = "0.42.1" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1258 | 1259 | [[package]] 1260 | name = "windows_i686_msvc" 1261 | version = "0.48.5" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1264 | 1265 | [[package]] 1266 | name = "windows_x86_64_gnu" 1267 | version = "0.42.1" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1270 | 1271 | [[package]] 1272 | name = "windows_x86_64_gnu" 1273 | version = "0.48.5" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1276 | 1277 | [[package]] 1278 | name = "windows_x86_64_gnullvm" 1279 | version = "0.42.1" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1282 | 1283 | [[package]] 1284 | name = "windows_x86_64_gnullvm" 1285 | version = "0.48.5" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1288 | 1289 | [[package]] 1290 | name = "windows_x86_64_msvc" 1291 | version = "0.42.1" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1294 | 1295 | [[package]] 1296 | name = "windows_x86_64_msvc" 1297 | version = "0.48.5" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1300 | 1301 | [[package]] 1302 | name = "winnow" 1303 | version = "0.5.28" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" 1306 | dependencies = [ 1307 | "memchr", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "x86_64" 1312 | version = "0.14.10" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "100555a863c0092238c2e0e814c1096c1e5cf066a309c696a87e907b5f8c5d69" 1315 | dependencies = [ 1316 | "bit_field", 1317 | "bitflags", 1318 | "rustversion", 1319 | "volatile", 1320 | ] 1321 | 1322 | [[package]] 1323 | name = "zerocopy" 1324 | version = "0.7.31" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" 1327 | dependencies = [ 1328 | "zerocopy-derive", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "zerocopy-derive" 1333 | version = "0.7.31" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" 1336 | dependencies = [ 1337 | "proc-macro2", 1338 | "quote", 1339 | "syn 2.0.41", 1340 | ] 1341 | --------------------------------------------------------------------------------